/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.core;

import org.jboss.installer.navigation.ScreenNavState;
import org.jboss.installer.screens.AdminCreationScreen;
import org.jboss.installer.screens.ComponentSelectionScreen;
import org.jboss.installer.screens.ConfigureRuntimeEnvironmentScreen;
import org.jboss.installer.screens.DatasourceScreen;
import org.jboss.installer.screens.DomainDefaultPortConfigurationScreen;
import org.jboss.installer.screens.DomainFullHaPortConfigurationScreen;
import org.jboss.installer.screens.DomainFullPortConfigurationScreen;
import org.jboss.installer.screens.DomainHaPortConfigurationScreen;
import org.jboss.installer.screens.DomainHostPortConfigurationScreen;
import org.jboss.installer.screens.InstallationCompleteScreen;
import org.jboss.installer.screens.InstallationOverviewScreen;
import org.jboss.installer.screens.InstallationScreen;
import org.jboss.installer.screens.JDBCDriverScreen;
import org.jboss.installer.screens.JSFInstallScreen;
import org.jboss.installer.screens.JSSESecurityScreen;
import org.jboss.installer.screens.LDAPConnectionScreen;
import org.jboss.installer.screens.LDAPSecurityRealmScreen;
import org.jboss.installer.screens.LicenseScreen;
import org.jboss.installer.screens.LoggingConfigurationScreen;
import org.jboss.installer.screens.CredentialStoreScreen;
import org.jboss.installer.screens.PortConfigurationScreen;
import org.jboss.installer.screens.ProcessingScreen;
import org.jboss.installer.screens.QuickstartsMavenRepositorySetupScreen;
import org.jboss.installer.screens.QuickstartsScreen;
import org.jboss.installer.screens.SSLSecurityScreen;
import org.jboss.installer.screens.StandaloneFullHaPortConfigurationScreen;
import org.jboss.installer.screens.StandaloneFullPortConfigurationScreen;
import org.jboss.installer.screens.StandaloneHaPortConfigurationScreen;
import org.jboss.installer.screens.StandalonePortConfigurationScreen;
import org.jboss.installer.screens.SummaryScreen;
import org.jboss.installer.screens.TargetScreen;
import org.jboss.installer.screens.domain.SecurityDomainScreen;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class ScreenManager {

    public static final boolean INACTIVE = false;
    public static final boolean ACTIVE = true;
    private List<Screen> screens = new ArrayList<>();
    private int currentScreen = 0;
    private Map<String, Screen> screenNameMap = new HashMap<>();
    private Set<Consumer<ScreensState>> listeners = new HashSet<>();

    public ScreenManager(ScreenNavState navState, LanguageUtils langUtils) {
        initScreens(navState, langUtils);
    }

    ScreenManager(List<Screen> screens, Map<String, Screen> screenNameMap) {
        for (Screen screen : screens) {
            addScreen(screen);
        }
        this.screenNameMap = screenNameMap;
        this.screens = screens;
    }

    private void initScreens(ScreenNavState navState, LanguageUtils langUtils) {
        addScreen(new LicenseScreen(navState, langUtils));
        addScreen(new TargetScreen(langUtils));
        addScreen(new ComponentSelectionScreen(langUtils));
        addScreen(new AdminCreationScreen(langUtils));
        addScreen(new InstallationOverviewScreen(langUtils));
        addScreen(new InstallationScreen(langUtils));
        final ConfigureRuntimeEnvironmentScreen runtimeConfigScreen = new ConfigureRuntimeEnvironmentScreen(langUtils);
        final QuickstartsScreen quickstartsScreen = new QuickstartsScreen(runtimeConfigScreen, langUtils, INACTIVE);
        addScreen(runtimeConfigScreen);
        addScreen(new CredentialStoreScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new SSLSecurityScreen(runtimeConfigScreen,langUtils, INACTIVE));
        addScreen(new JSSESecurityScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new LDAPConnectionScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new LDAPSecurityRealmScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new SecurityDomainScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(quickstartsScreen);
        addScreen(new QuickstartsMavenRepositorySetupScreen(quickstartsScreen, langUtils, INACTIVE));
        addScreen(new PortConfigurationScreen(runtimeConfigScreen, langUtils, INACTIVE));
        final PortConfigurationScreen portConfigurationScreen = new PortConfigurationScreen(runtimeConfigScreen, langUtils, INACTIVE);
        addScreen(portConfigurationScreen);
        addScreen(new StandalonePortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new StandaloneHaPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new StandaloneFullPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new StandaloneFullHaPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new DomainHostPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new DomainDefaultPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new DomainHaPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new DomainFullPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new DomainFullHaPortConfigurationScreen(portConfigurationScreen, langUtils, INACTIVE));
        addScreen(new LoggingConfigurationScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new JSFInstallScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new JDBCDriverScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new DatasourceScreen(runtimeConfigScreen, langUtils, INACTIVE));
        addScreen(new SummaryScreen(langUtils));
        addScreen(new ProcessingScreen(langUtils));
        addScreen(new InstallationCompleteScreen(langUtils));
    }

    private void addScreen(Screen screen) {
        screens.add(screen);
        screenNameMap.put(screen.getName(), screen);
    }

    public Screen first() {
        currentScreen = 0;
        return screens.get(currentScreen);
    }

    public Screen current() {
        return screens.get(currentScreen);
    }

    public Screen next() {
        Screen screen = screens.get(++currentScreen);
        while (!screen.isActive()) {
            if (!hasNext()) {
                throw new InstallerRuntimeException("Could not get next screen, because it is either deactivated or it does not exist");
            }
            screen = screens.get(++currentScreen);
        }
        notifyListeners();
        return screen;
    }

    public Screen previous() {
        Screen screen = screens.get(--currentScreen);
        while (!screen.isActive()) {
            if (!hasPrevious()) {
                throw new InstallerRuntimeException("Could not get previous screen, because it is either deactivated or it does not exist");
            }
            screen = screens.get(--currentScreen);
        }
        notifyListeners();
        return screen;
    }

    public boolean hasNext() {
        return currentScreen < (screens.size() -1);
    }

    public boolean hasPrevious() {
        return currentScreen > 0;
    }

    public void setScreenActivity(String screenName, boolean isActive) {
        doSetScreenActivity(screenName, isActive);
        notifyListeners();
    }

    private void doSetScreenActivity(String screenName, boolean isActive) {
        Screen screen = screenNameMap.get(screenName);
        if (screen == null) {
            throw new InstallerRuntimeException("Trying to set activity of a screen that does not exist");
        }
        screen.setActive(isActive);
    }

    public void setScreensActive(List<String> screenNames) {
        for (String screenName : screenNames) {
            doSetScreenActivity(screenName, ACTIVE);
        }
        notifyListeners();
    }

    public void setScreensInactive(List<String> screenNames) {
        for (String screenName : screenNames) {
            doSetScreenActivity(screenName, INACTIVE);
        }
        notifyListeners();
    }

    public void addScreenStateListener(Consumer<ScreensState> screenStateListener) {
        listeners.add(screenStateListener);
        screenStateListener.accept(new ScreensState(activeScreens(), current()));
    }

    private void notifyListeners() {
        for (Consumer<ScreensState> listener : listeners) {
            listener.accept(new ScreensState(activeScreens(), current()));
        }
    }

    private List<Screen> activeScreens() {
        return screens.stream().filter(Screen::isActive).collect(Collectors.toList());
    }

    public static class ScreensState {
        private List<Screen> visibleScreens;
        private Screen currentScreen;

        public ScreensState(List<Screen> visibleScreens, Screen currentScreen) {
            this.visibleScreens = visibleScreens;
            this.currentScreen = currentScreen;
        }

        public List<Screen> getVisibleScreens() {
            return visibleScreens;
        }

        public Screen getCurrentScreen() {
            return currentScreen;
        }
    }
}
