/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.auto;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.jboss.installer.ProgressBar;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.actions.impl.InstallEap;
import org.jboss.installer.core.DownloadUtils;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.InstallationFailedException;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.ValidationResult;
import org.jboss.installer.dialogs.downloader.DownloadHandler;
import org.jboss.installer.dialogs.downloader.DownloadManager;
import org.jboss.installer.postinstall.PostInstallTaskRunner;
import org.jboss.installer.postinstall.server.DomainServer;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.postinstall.task.JDBCDriverTask;
import org.jboss.installer.postinstall.task.JsfLibraryTask;
import org.jboss.installer.validators.WritableFolderValidator;
import org.wildfly.prospero.api.ProvisioningProgressEvent;

import java.net.URL;
import java.util.List;

import static org.jboss.installer.validators.WritableFolderValidator.EMPTY_DIRECTORY_DISABLED;

public class AutomaticInstallation {

    public static final String INSTALLATION_PATH = "target_screen.title";
    public static final String MAVEN_REPOSITORIES = "automated_installation.maven.repos";
    public static final String INSTALLATION_COMPLETED = "installation_complete.title";
    public static final String STARTING_CONFIG_TASKS = "automated_installation.config.task";
    public static final String SELECTED_COMPONENT = "automated_installation.selection.component";
    public static final String CONFIG_TASKS_COMPLETED = "automated_installation.config.task.completed";
    public static final String AUTOMATED_INSTALLATION = "automated_installation.title";

    private final ProgressBar progressBars = new ProgressBar();
    private final LanguageUtils langUtils;

    public AutomaticInstallation(LanguageUtils langUtils) {
        this.langUtils = langUtils;
    }

    public void performInstallation(InstallationData installationData) throws ActionException, InstallationFailedException {

        PropertyChangeListener galleonProgressListener = evt -> {
            final ProvisioningProgressEvent.EventType eventType = ProvisioningProgressEvent.EventType.valueOf(evt.getPropertyName());
            if (eventType == ProvisioningProgressEvent.EventType.UPDATE) {
                final InstallEap.Progress progress = (InstallEap.Progress) evt.getNewValue();

                final InstallEap.Stage stage = progress.getCurrent();
                if (stage == InstallEap.Stage.EXTRA_CONFIG) {
                    // special handling as EXTRA_CONFIG doesn't emit start event
                    progressBars.start(langUtils.getString(stage.getKey()), true);
                } else {
                    progressBars.update(progress.getProgress());
                }
            } else if (eventType == ProvisioningProgressEvent.EventType.COMPLETED) {
                progressBars.complete();
            } else if (eventType == ProvisioningProgressEvent.EventType.STARTING) {
                InstallEap.Stage stage = (InstallEap.Stage) evt.getNewValue();
                if (stage == InstallEap.Stage.CLEANUP || stage == InstallEap.Stage.EXTRA_CONFIG) {
                    progressBars.start(langUtils.getString(stage.getKey()), true);
                } else {
                    progressBars.start(langUtils.getString(stage.getKey()), false);
                }
            }
        };

        PropertyChangeListener changeListenerForPostInstallation = evt -> {
            if (evt.getPropertyName().equals(PostInstallTaskRunner.POST_INSTALL_TASK_NAME_PROPERTY)) {
                System.out.printf("%s%n", evt.getNewValue().toString());
            }
        };
        System.out.printf("[ %s ]%n", langUtils.getString(AUTOMATED_INSTALLATION));

        final ValidationResult result = new WritableFolderValidator("target_screen", langUtils).validate(installationData.getTargetFolder(), EMPTY_DIRECTORY_DISABLED);
        if (result.getResult() == ValidationResult.Result.ERROR) {
            throw new InstallationFailedException(result.getMessage(), null);
        }


        System.out.printf("%s:%n", langUtils.getString(SELECTED_COMPONENT));
        for (String packageName : installationData.getSelectedPackages()) {
            System.out.printf("%s%n", langUtils.getString(packageName));
        }
        System.out.printf("%s: %s%n", langUtils.getString(INSTALLATION_PATH), installationData.getTargetFolder().toString());
        System.out.printf("%s:%n", langUtils.getString(MAVEN_REPOSITORIES));
        for (URL mavenRepo : installationData.getMavenRepositories().values()) {

            System.out.printf(" * %s %n", mavenRepo.toExternalForm());
        }

        new InstallEap(installationData.getTargetFolder(), galleonProgressListener, installationData.getExcludedPackages(),
                installationData.getMavenRepositories(), langUtils)
                .perform(new PropertyChangeSupport(this));
        System.out.printf("[ %s ]%n", langUtils.getString(INSTALLATION_COMPLETED));

        // download potential remote files
        handleDownloadableResources(installationData);

        try (StandaloneServer standaloneServer = new StandaloneServer(installationData.getTargetFolder());
             DomainServer domainServer = new DomainServer(installationData.getTargetFolder())) {
            final PostInstallTaskRunner taskRunner = new PostInstallTaskRunner(standaloneServer, domainServer, langUtils, changeListenerForPostInstallation);
            System.out.printf("[ %s ]%n", langUtils.getString(STARTING_CONFIG_TASKS));
            taskRunner.execute(installationData, new PropertyChangeSupport(this));
            System.out.printf("[ %s ]%n", langUtils.getString(CONFIG_TASKS_COMPLETED));
        }
    }

    private void handleDownloadableResources(InstallationData installationData) {
        final JDBCDriverTask.Config dbDriverConfig = installationData.getConfig(JDBCDriverTask.Config.class);
        final JsfLibraryTask.Config jsfConfig = installationData.getConfig(JsfLibraryTask.Config.class);
        if (dbDriverConfig != null || jsfConfig != null) {
            final DownloadManager downloadManager = new DownloadManager();
            final DownloadUtils downloadUtils = new DownloadUtils(downloadManager,
                    (downloads, dm) -> {
                        final CliDownloadProgress progressHandler = new CliDownloadProgress(langUtils);
                        return new DownloadUtils.DownloadUI() {
                            @Override
                            public boolean display() {
                                return true;
                            }

                            @Override
                            public DownloadHandler getDownloadHandler() {
                                return progressHandler;
                            }
                        };
                    },
                    (download) ->System.out.println(langUtils.getString("generic.download.unreachable_file", download.getUrl().toString())));

            // resolve driver jars and set them in the config
            if (dbDriverConfig != null) {
                dbDriverConfig.setResolvedJarList(downloadUtils.downloadAll(dbDriverConfig.getJarList()));
                try {
                    downloadManager.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            // resolve jsf jars and set them in the config
            if (jsfConfig != null && jsfConfig.getJsfProject() == JsfLibraryTask.Config.JsfProject.Mojarra) {
                final List<String> downloaded = downloadUtils.downloadAll(List.of(jsfConfig.getImplJarPath()));
                jsfConfig.setResolvedImplJarPath(downloaded.get(0));
                try {
                    downloadManager.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
