/**
 * 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.postinstall.task;

import com.google.auto.service.AutoService;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.auto.AutomaticInstallationParsingException;
import org.jboss.installer.core.FileUtils;
import org.jboss.installer.core.FlatListPostInstallConfig;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.LoggerUtils;
import org.jboss.installer.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.SimplePostInstallTask;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.task.jsf.MyFacesJsfLibrarySetup;
import org.jboss.installer.postinstall.task.jsf.MojarraJsfLibrarySetup;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static org.jboss.installer.core.LoggerUtils.taskLog;

@AutoService(PostInstallTask.class)
public class JsfLibraryTask implements SimplePostInstallTask {

    private LanguageUtils langUtils;

    @Override
    public boolean applyToInstallation(InstallationData data, TaskPrinter printer) {
        printer.print("tasks.jsf_module.started");

        final Config config = data.getConfig(Config.class);
        final Path baseFolder = data.getTargetFolder();

        boolean res;
        if (config.getJsfProject() == Config.JsfProject.Mojarra) {
            try {
                res = new MojarraJsfLibrarySetup(printer).perform(config, baseFolder);
                printer.print("tasks.jsf_module.finished");
                return res;
            } catch (IOException e) {
                taskLog.error(e.getMessage(), e);
                printer.print("tasks.jsf_module.failed");
                printer.print(e);
                return false;
            }
        } else {
            try {
                installMyFacesLibrary(printer, config, baseFolder);
                return true;
            } catch (ActionException e) {
                taskLog.error(e.getMessage(), e);
                printer.print("tasks.jsf_module.failed");
                printer.print(e);
                return false;
            }
        }
    }

    private void installMyFacesLibrary(TaskPrinter printer, Config config, Path baseFolder) throws ActionException {
        final MyFacesJsfLibrarySetup myFacesJsfLibrarySetup = new MyFacesJsfLibrarySetup(baseFolder, langUtils);
        final List<String> repositoryUrls = new ArrayList<>();
        String manifestCoordinates = config.getManifestCoordinates();
        // if new_channel
        if (manifestCoordinates == null) {
            // create channel manifest
            printer.print("tasks.jsf_module.manifest.create", config.getLocalChannelRepositoryPath());
            LoggerUtils.taskLog.debug(String.format("Creating MyFaces manifest repository at %s", config.getLocalChannelRepositoryPath()));
            manifestCoordinates = myFacesJsfLibrarySetup.createChannelManifest(config.getJsfVersion(), config.getLocalChannelRepositoryPath());

            // add local repository location to the channel repositories
            LoggerUtils.taskLog.debug(String.format("Adding manifest repository %s to the channel repositories",
                    FileUtils.asUrl(config.getLocalChannelRepositoryPath())));
            repositoryUrls.add(FileUtils.asUrl(config.getLocalChannelRepositoryPath()).toExternalForm());

        }

        for (URL repository : config.getRemoteMavenRepositoryUrls()) {
            LoggerUtils.taskLog.debug(String.format("Adding remote repository %s to the channel repositories",
                    repository.toExternalForm()));
            repositoryUrls.add(repository.toExternalForm());
        }
        // add channel definition
        printer.print("tasks.jsf_module.channel.register");
        LoggerUtils.taskLog.debug(String.format("Registering MyFaces channel with manifest %s", manifestCoordinates));
        myFacesJsfLibrarySetup.registerChannel(repositoryUrls, manifestCoordinates);

        // provision the feature
        printer.print("tasks.jsf_module.provision.starting");
        LoggerUtils.taskLog.debug("Installing MyFaces feature pack");
        myFacesJsfLibrarySetup.installFeaturePack(printer);
        LoggerUtils.taskLog.debug("MyFaces feature pack installed");
    }

    @Override
    public String getName() {
        return "jsf_config.task.name";
    }

    @Override
    public String getSerializationName() {
        return "add-jsf-module";
    }

    @Override
    public void setLanguageUtils(LanguageUtils langUtils) {
        this.langUtils = langUtils;
    }

    @Override
    public Class<? extends InstallationData.PostInstallConfig> getConfigClass() {
        return Config.class;
    }

    public static class Config extends FlatListPostInstallConfig {

        public enum JsfProject {
            Mojarra,
            MyFaces;

            public String fileName() {
                return toString().toLowerCase(Locale.ROOT);
            }
        }
        private boolean makeDefault;
        private String implJarPath;
        private JsfProject jsfProject;
        private String jsfVersion;
        private String localChannelRepositoryPath;
        private List<URL> remoteMavenRepositoryUrls = Collections.emptyList();
        private String manifestCoordinates;

        // not persisted
        private String resolvedImplJarPath;

        public boolean isMakeDefault() {
            return makeDefault;
        }

        public void setMakeDefault(boolean makeDefault) {
            this.makeDefault = makeDefault;
        }

        public String getImplJarPath() {
            return implJarPath;
        }

        public void setImplJarPath(String implJarPath) {
            this.implJarPath = implJarPath;
        }

        public JsfProject getJsfProject() {
            return jsfProject;
        }

        public void setJsfProject(JsfProject jsfProject) {
            this.jsfProject = jsfProject;
        }

        public String getJsfVersion() {
            return jsfVersion;
        }

        public void setJsfVersion(String jsfVersion) {
            this.jsfVersion = jsfVersion;
        }

        public String getResolvedImplJarPath() {
            return resolvedImplJarPath;
        }

        public void setResolvedImplJarPath(String resolvedImplJarPath) {
            this.resolvedImplJarPath = resolvedImplJarPath;
        }

        public String getLocalChannelRepositoryPath() {
            return localChannelRepositoryPath;
        }

        public void setLocalChannelRepositoryPath(String localChannelRepositoryPath) {
            this.localChannelRepositoryPath = localChannelRepositoryPath;
        }

        public List<URL> getRemoteMavenRepositoryUrls() {
            return remoteMavenRepositoryUrls;
        }

        public void setRemoteMavenRepositoryUrl(URL remoteMavenRepositoryUrls) {
            this.remoteMavenRepositoryUrls = List.of(remoteMavenRepositoryUrls);
        }

        public void setRemoteMavenRepositoryUrls(List<URL> remoteMavenRepositoryUrls) {
            this.remoteMavenRepositoryUrls = remoteMavenRepositoryUrls;
        }

        public String getManifestCoordinates() {
            return manifestCoordinates;
        }

        public void setManifestCoordinates(String manifestCoordinates) {
            this.manifestCoordinates = manifestCoordinates;
        }

        @Override
        protected Map<String, String> listAttributes() {
            final HashMap<String, String> attrs = new HashMap<>();
            attrs.put("makeDefault", makeDefault + "");
            attrs.put("implJarPath", implJarPath);
            attrs.put("jsfProject", jsfProject.toString());
            attrs.put("jsfVersion", jsfVersion);
            if (remoteMavenRepositoryUrls != null) {
                for (int i = 0; i < remoteMavenRepositoryUrls.size(); i++) {
                    attrs.put("remoteRepositoryUrls-"+i, remoteMavenRepositoryUrls.get(i).toExternalForm());
                }
            }
            attrs.put("localChannelPath", localChannelRepositoryPath);
            attrs.put("manifestCoordinates", manifestCoordinates);
            return attrs;
        }

        @Override
        protected void acceptAttributes(Map<String, String> attributes, BiFunction<String, String, String> variableResolver) throws AutomaticInstallationParsingException {
            makeDefault = Boolean.parseBoolean(attributes.getOrDefault("makeDefault", "false"));
            implJarPath = attributes.get("implJarPath");
            jsfProject = JsfProject.valueOf(attributes.get("jsfProject"));
            jsfVersion = attributes.get("jsfVersion");
            remoteMavenRepositoryUrls = getListOfRepositories(attributes);
            localChannelRepositoryPath = attributes.get("localChannelPath");
            manifestCoordinates = attributes.get("manifestCoordinates");
        }

        private List<URL> getListOfRepositories(Map<String, String> attributes) {
            return attributes.entrySet().stream()
                    .filter(entry -> entry.getKey().startsWith("remoteRepositoryUrls"))
                    .map(Map.Entry::getValue)
                    .map(FileUtils::asUrl)
                    .collect(Collectors.toList());
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Config config = (Config) o;
            return makeDefault == config.makeDefault && Objects.equals(implJarPath, config.implJarPath) && jsfProject == config.jsfProject && Objects.equals(jsfVersion, config.jsfVersion) && Objects.equals(localChannelRepositoryPath, config.localChannelRepositoryPath) && Objects.equals(remoteMavenRepositoryUrls, config.remoteMavenRepositoryUrls) && Objects.equals(manifestCoordinates, config.manifestCoordinates) && Objects.equals(resolvedImplJarPath, config.resolvedImplJarPath);
        }

        @Override
        public int hashCode() {
            return Objects.hash(makeDefault, implJarPath, jsfProject, jsfVersion, localChannelRepositoryPath, remoteMavenRepositoryUrls, manifestCoordinates, resolvedImplJarPath);
        }

        @Override
        public String toString() {
            return "Config{" +
                    "makeDefault=" + makeDefault +
                    ", implJarPath='" + implJarPath + '\'' +
                    ", jsfProject=" + jsfProject +
                    ", jsfVersion='" + jsfVersion + '\'' +
                    ", localChannelRepositoryPath='" + localChannelRepositoryPath + '\'' +
                    ", remoteMavenRepositoryUrls=" + remoteMavenRepositoryUrls +
                    ", manifestCoordinates='" + manifestCoordinates + '\'' +
                    ", resolvedImplJarPath='" + resolvedImplJarPath + '\'' +
                    '}';
        }
    }
}
