/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2023 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 java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import javax.swing.JComponent;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.actions.QuitAction;
import org.jboss.installer.common.InstallerDialogs;
import org.jboss.installer.common.UiResources;
import org.jboss.installer.core.DefaultActionExecutor;
import org.jboss.installer.core.DialogManager;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.Navigator;
import org.jboss.installer.core.Screen;
import org.jboss.installer.core.ScreenManager;
import org.jboss.installer.navigation.NavigationState;
import org.jboss.installer.panels.frame.ContentPanel;
import org.jboss.installer.panels.frame.NavigationPanel;
import org.jboss.installer.panels.frame.ProgressPanel;
import org.wildfly.channel.ArtifactCoordinate;
import org.wildfly.channel.ChannelMetadataCoordinate;
import org.wildfly.prospero.api.exceptions.ArtifactResolutionException;
import org.wildfly.prospero.api.exceptions.UnresolvedChannelMetadataException;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import java.awt.HeadlessException;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class InstallerFrame extends JFrame implements DialogManager {
    public static final String SYSTEM_ERROR_NO_LOG_KEY = "generic.error.system_error.no_log";
    public static final String SYSTEM_ERROR_KEY = "generic.error.system_error";
    private final LanguageUtils langUtils;
    private final NavigationState navState;
    private final ScreenManager screenManager;
    private final Navigator navigator;
    private QuitAction quitAction;
    private final ProgressPanel progressPanel;
    private ContentPanel contentPanel;

    public InstallerFrame(LanguageUtils langUtils, InstallationData installationData) throws HeadlessException {
        super(Installer.getInstallerFrameTitle());
        this.navState = new NavigationState();
        this.langUtils = langUtils;
        this.screenManager = new ScreenManager(navState, this.langUtils);
        this.progressPanel = new ProgressPanel();
        this.progressPanel.setName("ProgressPanel");
        screenManager.addScreenStateListener(progressPanel);
        this.navigator = new Navigator(screenManager, navState, this, this::updateScreen,
                new DefaultActionExecutor(this), installationData);
    }

    public void createWindow() {
        this.quitAction = new QuitAction(this.langUtils, navigator, screenManager, this);
        // handle shutdown in QuitAction rather than in Swing
        this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                quitAction.actionPerformed();
            }
        });

        this.setIconImages(FrameImages.icons);

        createUI();
        this.setSize(900, 600);

        // Enforces a minimum window size no matter the platform by adding a component listener that blocks resizing under the minimum dimensions.
        // Also keeps focused components (if any) in view if the frame gets resized.
        this.setMinimumSize(new Dimension(900, 600));
        this.addComponentListener(new ComponentAdapter(){
            public void componentResized(ComponentEvent e){
                final Dimension d= InstallerFrame.this.getSize();
                final Dimension minD=InstallerFrame.this.getMinimumSize();
                if(d.width<minD.width)
                    d.width=minD.width;
                if(d.height<minD.height)
                    d.height=minD.height;
                InstallerFrame.this.setSize(d);
                Component focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
                if (focusedComponent instanceof JComponent){
                    Rectangle bounds = focusedComponent.getBounds();
                    ((JComponent) focusedComponent.getParent()).scrollRectToVisible(bounds);
                }
            }
        });
        this.setResizable(true);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private void createUI(){
        final JPanel headerPanel = buildHeader();
        headerPanel.setName("HeaderPanel");
        this.contentPanel = new ContentPanel();
        this.contentPanel.setName("ContentPanel");
        final NavigationPanel navigationPanel = new NavigationPanel(navigator, navState, langUtils, quitAction);
        navigationPanel.setName("NavigationPanel");

        final JPanel centerPanel = buildCenterPanel(contentPanel, navigationPanel);
        final JPanel installerPanel = buildMainPanel(headerPanel, progressPanel, centerPanel);

        this.getContentPane().add(installerPanel, BorderLayout.CENTER);
        this.getRootPane().setDefaultButton(navigationPanel.getNextButton());

        this.navigator.startNavigation();
        this.revalidate();
        this.repaint();
    }

    private JPanel buildMainPanel(JPanel headerPanel, JPanel progressPanel, JPanel centerPanel) {
        final JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(headerPanel, BorderLayout.NORTH);
        final JScrollPane scrollPane = new JScrollPane(progressPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBorder(new EmptyBorder(0,0,0,0));
        mainPanel.add(scrollPane, BorderLayout.WEST);
        mainPanel.add(centerPanel, BorderLayout.CENTER);
        return mainPanel;
    }

    private JPanel buildCenterPanel(JPanel contentPanel, JPanel navigationPanel) {
        final JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new BorderLayout());

        centerPanel.add(contentPanel, BorderLayout.CENTER);
        centerPanel.add(navigationPanel, BorderLayout.SOUTH);

        return centerPanel;
    }

    private JPanel buildHeader() {
        final JPanel headerPanel = new JPanel();
        final BoxLayout mgr = new BoxLayout(headerPanel, BoxLayout.X_AXIS);
        headerPanel.setLayout(mgr);
        final JLabel label = new JLabel();
        label.setIcon(new ImageIcon(Installer.class.getResource("/img/heading.png")));
        label.setAlignmentX(Component.LEFT_ALIGNMENT);
        headerPanel.add(label);
        headerPanel.setBackground(UiResources.HEADING_PANEL_BACKGROUND);
        return headerPanel;
    }

    private void updateScreen(Screen screen) {
        contentPanel.displayScreen(screen);
    }

    @Override
    public void systemError(Exception e) {
        Path log = dumpException(e);
        if (log != null) {
            InstallerDialogs.showErrorMessage(langUtils.getString(SYSTEM_ERROR_KEY, e.getMessage(), log.toAbsolutePath().toString()),
                    langUtils, this);
        } else {
            InstallerDialogs.showErrorMessage(langUtils.getString(SYSTEM_ERROR_KEY, e.getMessage()), langUtils, this);
        }
    }

    @Override
    public int validationWarning(String text) {
        return InstallerDialogs.showWarningMessage(text, langUtils, this);
    }

    @Override
    public int validationWarning(List<String> messages) {
        return InstallerDialogs.showWarningMessage(messages, langUtils, this);
    }

    @Override
    public void actionError(String text) {
        InstallerDialogs.showErrorMessage(text, langUtils, this);
    }

    @Override
    public void actionError(ActionException e) {
        StringBuilder text = new StringBuilder();
        if (e.getCause() instanceof ArtifactResolutionException) {
            final Set<ArtifactCoordinate> missingArtifacts = ((ArtifactResolutionException) e.getCause()).getMissingArtifacts();
            final String gavs = missingArtifacts.stream()
                    .map(c -> c.getGroupId() + ":" + c.getArtifactId() + ":" + c.getExtension() + ":" + c.getVersion())
                    .collect(Collectors.joining("\n  "));
            text.append(e.getMessage()).append("\n  ").append(gavs);
        } else if (e.getCause() instanceof UnresolvedChannelMetadataException) {
            final Set<ChannelMetadataCoordinate> missingArtifacts = ((UnresolvedChannelMetadataException) e.getCause()).getMissingArtifacts();
            final String gavs = missingArtifacts.stream()
                    .map(c -> c.getGroupId() + ":" + c.getArtifactId() + ":" + c.getExtension() + ":" + c.getVersion())
                    .collect(Collectors.joining("\n  "));
            text.append(e.getMessage()).append("\n  ").append(gavs);
        } else {
            // unexpected error, print the thread stack
            e.printStackTrace();
            text.append(e.getMessage());
        }

        InstallerDialogs.showErrorMessage(text.toString().trim(), langUtils, this);
    }

    @Override
    public void validationError(String text) {
        InstallerDialogs.showErrorMessage(text, langUtils, this);
    }

    private Path dumpException(Exception e) {
        try {
            Path log = Files.createTempFile("eap-installer", ".log");
            try(final PrintWriter pw = new PrintWriter(log.toFile())) {
                e.printStackTrace(pw);
            }
            return log;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return null;
    }
}
