/*
 * 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;

import com.formdev.flatlaf.FlatLightLaf;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.auto.AutomaticInstallation;
import org.jboss.installer.auto.AutomaticInstallationParsingException;
import org.jboss.installer.auto.InstallationDataSerializer;
import org.jboss.installer.common.FontResources;
import org.jboss.installer.common.UiResources;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.InstallationFailedException;
import org.jboss.installer.core.InstallerRuntimeException;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.LoggerUtils;
import org.jboss.installer.core.MavenRepositoryLoader;
import org.jboss.installer.core.MnemonicUtils;
import org.jboss.installer.core.VersionGetter;
import org.jboss.installer.core.i18n.DefaultLanguageUtils;
import org.jboss.installer.dialogs.FileChooserWithMnemonicsUI;
import org.jboss.installer.dialogs.LanguageDialog;
import org.jboss.installer.dialogs.OptionPaneWithMnemonicsUI;
import org.jboss.logmanager.LogManager;
import org.xml.sax.SAXParseException;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.UIDefaults;
import javax.swing.UIManager;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class Installer {
    public static final String ERROR_TITLE_KEY = "generic.error.title";
    public static final String EAP_VERSION_PROPERTY_NAME = "eap.release.version";
    public static final String EAP_VERSION = new VersionGetter().getVersionFromPomProperties(EAP_VERSION_PROPERTY_NAME);
    public static final String INSTALLER_FRAME_TITLE_KEY = "installer_frame.title";
    public static final String CONSOLE_ERROR_AUTOMATIC_INSTALLER_FAIL = "console.error.automatic_installer_fail";
    public static final String CONSOLE_ERROR_AUTOMATIC_INSTALLER_INVALID_SCRIPT = "console.error.automatic_installer_invalid_script";
    private static final int INSTALLER_SPLASH_DURATION = Integer.getInteger("installer.override.splash_duration", 3000);
    public static final String INSTALLER_OVERRIDE_FPL = "installer.override.fpl";
    public static final String INSTALLER_OVERRIDE_MANIFEST = "installer.override.manifest";
    private static String installerFrameTitle;

    public static void main(String[] args) {
        enableJBossLogManager(Arrays.stream(args).filter(s->s.equals("-X")).findFirst().isPresent());

        ArgumentsParser.Arguments arguments = null;
        final ArgumentsParser argumentsParser = new ArgumentsParser();
        try {
            String[] filteredArgs = Arrays.stream(args).filter(s->!s.equals("-X")).collect(Collectors.toList()).toArray(new String[]{});
            arguments = argumentsParser.parse(filteredArgs);
        } catch (ArgumentsParsingException e) {
            argumentsParser.printUsage(e.getMessage());
            System.exit(-1);
        }

        if (arguments.isAutomated()) {
            runAutomatedInstaller(arguments);
        } else {
            runGuiInstaller(arguments);
        }
    }

    public static String getInstallerFrameTitle() {
        if (installerFrameTitle == null) {
            // load default title in english if it wasn't set yet
            installerFrameTitle = DefaultLanguageUtils.builder()
                    .setLocale(Locale.ENGLISH)
                    .build().getString(INSTALLER_FRAME_TITLE_KEY, EAP_VERSION);
        }
        return installerFrameTitle;
    }

    private static void runGuiInstaller(ArgumentsParser.Arguments arguments) {
        // make sure we are starting with clean slate
        MnemonicUtils.resetGlobal();

        // setup GUI
        FlatLightLaf.setup();
        installerSplash();
        UIManager.put("Panel.background", Color.WHITE);
        UIManager.put("OptionPane.background", Color.WHITE);
        UIManager.put("EditorPane.background", Color.WHITE);
        UIManager.put("TextArea.inactiveBackground", UiResources.inactiveTextField);
        UIManager.put("TextField.inactiveBackground", UiResources.inactiveTextField);
        UIManager.put("Component.hideMnemonics", false);
        UIManager.put("OptionPaneUI", OptionPaneWithMnemonicsUI.class.getName());
        UIManager.put("FileChooserUI", FileChooserWithMnemonicsUI.class.getName());
        // prevent ENTER from triggering focused buttons (reserve ENTER for default buttons like Next)
        UIManager.put("Button.focusInputMap", new UIDefaults.LazyInputMap( new Object[] {
                "SPACE", "pressed",
                "released SPACE", "released"
        }));

        LanguageDialog languagePicker = showLanguagePicker();

        if (languagePicker.isCanceled()) {
            return;
        }

        LanguageUtils langUtils = DefaultLanguageUtils.builder()
                .setLocale(languagePicker.getSelectedLocale())
                .build();
        FontResources.setLocale(languagePicker.getSelectedLocale());
        FileChooserWithMnemonicsUI.setLangUtils(langUtils);
        installerFrameTitle = langUtils.getString(INSTALLER_FRAME_TITLE_KEY, EAP_VERSION);

        final InstallationData installationData = new InstallationData();
        final Map<String, URL> defaultRepositories = MavenRepositoryLoader.getDefaultRepositories(arguments.getOfflinePath());
        installationData.setDefaultMavenRepositories(defaultRepositories);

        // run GUI installer
        final InstallerFrame installerFrame = new InstallerFrame(langUtils, installationData);
        try {
            installerFrame.createWindow();
        } catch (InstallerRuntimeException e) {
            installerFrame.systemError(e);
            System.exit(-1);
        }
    }

    private static void runAutomatedInstaller(ArgumentsParser.Arguments arguments) {
        final LanguageUtils langUtils = new DefaultLanguageUtils.Builder().setLocale(Locale.getDefault()).build();
        try {
            final URL scriptPath = toUrl(arguments.getAutomatedScript(), langUtils);
            final Optional<URL> variablesPath = arguments.getVariables().map(input -> toUrl(input, langUtils));

            final InstallationData installationData;
            try (final InstallationDataSerializer serializer = new InstallationDataSerializer(langUtils)) {
                installationData = serializer.deserialize(scriptPath, variablesPath);
            }

            if (arguments.isOffline()) {
                // override the previous repository
                try {
                    installationData.setMavenRepositories(Collections.singletonMap("offline-repo",
                            arguments.getOfflinePath().get().toUri().toURL()));
                } catch (MalformedURLException e) {
                    // that should not happen, the file was verified to be valid
                    throw new RuntimeException(e);
                }
            }

            new AutomaticInstallation(langUtils).performInstallation(installationData);
        } catch (AutomaticInstallationParsingException | ActionException | InstallationFailedException |
                 InstallerRuntimeException | IOException e) {
            System.out.println(langUtils.getString(CONSOLE_ERROR_AUTOMATIC_INSTALLER_FAIL));
            System.out.println(e.getMessage());
            System.exit(-1);
        } catch (SAXParseException e) {
            System.out.println(langUtils.getString(CONSOLE_ERROR_AUTOMATIC_INSTALLER_INVALID_SCRIPT));
            System.out.println(e.getMessage());
            System.exit(-1);
        }
    }

    private static URL toUrl(String input, LanguageUtils langUtils) {
        try {
            return new URL(input);
        } catch (MalformedURLException e) {
            try {
                return Path.of(input).toAbsolutePath().toUri().toURL();
            } catch (InvalidPathException ex) {
                throw new InstallerRuntimeException(langUtils.getString("console.error.automatic_installer_file_illegal", input), e);
            } catch (MalformedURLException ex) {
                throw new InstallerRuntimeException("Unable to convert the file path to URL", e);
            }
        }
    }

    private static void enableJBossLogManager(boolean enableDebug) {
        if (System.getProperty("java.util.logging.manager") == null) {
            System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager");
        }

        if (enableDebug) {
            final InputStream is = Installer.class.getClassLoader().getResourceAsStream("debug_logging.properties");
            try {
                LogManager.getLogManager().readConfiguration(is);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static LanguageDialog showLanguagePicker() {
        LanguageDialog languagePicker = new LanguageDialog();
        languagePicker.display();
        return languagePicker;
    }

    private static void installerSplash(){

        //Show splash screen
        ImageIcon splash;
        URL spUrl = Installer.class.getResource("/img/splash.png");
        if (spUrl != null) {
            splash = new ImageIcon(spUrl);
            JWindow frame = new JWindow();
            JLabel splashLabel = new JLabel(splash);
            frame.add(splashLabel);
            frame.setSize(splash.getIconWidth(), splash.getIconHeight());
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
            if (INSTALLER_SPLASH_DURATION > 0) {
                try {
                    Thread.sleep(INSTALLER_SPLASH_DURATION);
                } catch (InterruptedException e) {
                    LoggerUtils.systemLog.error("Unable to load splash screen", e);
                }
            }
            frame.setVisible(false);
            frame.dispose();
        }

    }
}
