/* BdfServer - Copyright (c) 2019-2022 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.tools.configuration;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.conf.BdfServerDirs;
import fr.exemole.bdfserver.conf.ConfConstants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Consumer;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.ini.IniParser;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.logging.MultiMessageHandler;
import net.mapeadores.util.text.AccoladeArgument;
import net.mapeadores.util.text.AccoladePattern;
import net.mapeadores.util.text.RelativePath;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.text.ValueResolver;
import net.mapeadores.util.xml.DOMUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**
 *
 * @author Vincent Calame
 */
public final class PathConfigurationBuilder {

    private final static String SEVERE = "severe";
    private final static String WARNING = "warning";
    private final static String INFO = "info";
    private final static String TARGET_PREFIX = "target.";
    private final static RelativePath PUB_PATH = RelativePath.build("pub");
    private final BdfServer bdfServer;
    private final SortedMap<String, File> targetMap = new TreeMap<String, File>();
    private final MultiMessageHandler messageHandler;
    private File publicDirectory;
    private String publicUrlRoot;

    private PathConfigurationBuilder(BdfServer bdfServer, MultiMessageHandler messageHandler) {
        this.bdfServer = bdfServer;
        this.messageHandler = messageHandler;
    }

    private PathConfiguration toPathConfiguration() {
        if ((publicDirectory == null) || (!publicDirectory.exists())) {
            publicDirectory = bdfServer.getOutputStorage().getFile(PUB_PATH);
            publicDirectory.mkdirs();
            publicUrlRoot = null;
        }
        if (publicUrlRoot == null) {
            publicUrlRoot = "pub/";
        }
        return new InternalPathConfiguration(publicDirectory, publicUrlRoot, targetMap);
    }

    public static PathConfiguration build(BdfServer bdfServer) {
        return build(bdfServer, null);
    }

    public static PathConfiguration build(BdfServer bdfServer, @Nullable MultiMessageHandler messageHandler) {
        if (messageHandler == null) {
            messageHandler = LogUtils.NULL_MULTIMESSAGEHANDLER;
        }
        PathConfigurationBuilder builder = new PathConfigurationBuilder(bdfServer, messageHandler);
        builder.readOldOutputConfiguration();
        builder.readPathsIni();
        return builder.toPathConfiguration();
    }

    private void readOldOutputConfiguration() {
        File oldXml = bdfServer.getBdfServerDirs().getSubPath(ConfConstants.VAR_DATA, "bdfdata/conf/output-configuration.xml");
        if (oldXml.exists()) {
            messageHandler.setCurrentSource(oldXml.getPath());
            messageHandler.addMessage(INFO, "_ info.configuration.oldouputconfiguration");
            Document document = DOMUtils.readDocument(oldXml);
            DOMUtils.readChildren(document.getDocumentElement(), new OldOutputConfigurationConsumer());
        }
    }

    private void readPathsIni() {
        BdfServerDirs dirs = bdfServer.getBdfServerDirs();
        File pathsIni = dirs.getSubPath(ConfConstants.ETC_CONFIG, "paths.ini");
        File defaultPathsIni = dirs.getSubPath(ConfConstants.ETC_CONFIG_DEFAULT, "paths.ini");
        Map<String, String> map = new HashMap<String, String>();
        if ((defaultPathsIni.exists()) && (!defaultPathsIni.equals(pathsIni))) {
            messageHandler.setCurrentSource(defaultPathsIni.getPath());
            try (InputStream is = new FileInputStream(defaultPathsIni)) {
                IniParser.parseIni(is, map);
            } catch (IOException ioe) {
                messageHandler.addMessage(SEVERE, "_ error.exception.io", ioe.getLocalizedMessage());
            }
        }
        if (pathsIni.exists()) {
            messageHandler.setCurrentSource(pathsIni.getPath());
            try (InputStream is = new FileInputStream(pathsIni)) {
                IniParser.parseIni(is, map);
            } catch (IOException ioe) {
                messageHandler.addMessage(SEVERE, "_ error.exception.io", ioe.getLocalizedMessage());
            }
        }
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            String value = checkValue(entry.getValue());
            if (key.startsWith(TARGET_PREFIX)) {
                String targetName = key.substring(TARGET_PREFIX.length());
                if (StringUtils.isTechnicalName(targetName, true)) {
                    try {
                        File targetDir = ConfigurationUtils.toDirectory(value);
                        targetMap.put(targetName, targetDir);
                    } catch (FileNotFoundException fnne) {
                        messageHandler.addMessage(SEVERE, "_ error.unknown.directory", value);
                    }
                } else {
                    messageHandler.addMessage(SEVERE, "_ error.wrong.parametername", key);
                }
            } else {
                switch (key) {
                    case "public_path":
                        try {
                        publicDirectory = ConfigurationUtils.toDirectory(value);
                    } catch (FileNotFoundException fnne) {
                        messageHandler.addMessage(SEVERE, "_ error.unknown.directory", value);
                    }
                    break;
                    case "public_url":
                        try {
                        publicUrlRoot = ConfigurationUtils.checkURL(value);
                    } catch (MalformedURLException mue) {
                        messageHandler.addMessage(SEVERE, "_ error.wrong.url", value);
                    }
                    break;
                    default:
                        messageHandler.addMessage(WARNING, "_ error.unknown.parametername", key);
                        break;
                }
            }
        }
    }

    private String checkValue(String value) {
        try {
            AccoladePattern accoladePattern = new AccoladePattern(value);
            return accoladePattern.format(new InternalValueResolver());
        } catch (ParseException pe) {
            return value;
        }
    }


    private class InternalValueResolver implements ValueResolver {

        private InternalValueResolver() {

        }

        @Override
        public String getValue(AccoladeArgument accoladeArgument) {
            String name = accoladeArgument.getName();
            if (name.equals("name")) {
                return bdfServer.getBdfServerDirs().getName();
            } else if (name.startsWith("custom.")) {
                File dir = bdfServer.getBdfServerDirs().getDir(name);
                if (dir != null) {
                    return dir.getPath();
                }
            }
            messageHandler.addMessage(name, "_ error.unknown.pattern", name);
            return "__" + name + "__";
        }

    }


    private class OldOutputConfigurationConsumer implements Consumer<Element> {

        private OldOutputConfigurationConsumer() {
        }

        @Override
        public void accept(Element element) {
            String tagName = element.getTagName();
            if (tagName.equals("path")) {
                String name = element.getAttribute("name");
                if (name.equals("output._pub")) {
                    String publicValue = element.getAttribute("public");
                    String systemValue = element.getAttribute("system");
                    if (publicValue.length() > 0) {
                        try {
                            publicUrlRoot = ConfigurationUtils.checkURL(publicValue);
                        } catch (MalformedURLException mue) {
                        }
                    }
                    if (systemValue.length() > 0) {
                        try {
                            publicDirectory = ConfigurationUtils.toDirectory(systemValue);
                        } catch (FileNotFoundException fnne) {
                        }
                    }
                } else if (name.startsWith("root.")) {
                    String rootName = name.substring(5);
                    String systemValue = element.getAttribute("system");
                    if (systemValue.length() > 0) {
                        try {
                            File dir = ConfigurationUtils.toDirectory(systemValue);
                            if (dir != null) {
                                targetMap.put(rootName, dir);
                            }
                        } catch (FileNotFoundException fnne) {
                        }
                    }
                }

            }
        }

    }


    private static class InternalPathConfiguration implements PathConfiguration {

        private final File publicDirectory;
        private final String publicUrlRoot;
        private final SortedMap<String, File> targetMap;

        private InternalPathConfiguration(File publicDirectory, String publicUrlRoot, SortedMap<String, File> targetMap) {
            this.publicDirectory = publicDirectory;
            this.publicUrlRoot = publicUrlRoot;
            this.targetMap = targetMap;
        }

        @Override
        public String getPublicUrl(String path) {
            return publicUrlRoot + path;
        }

        @Override
        public File getTargetDirectory(String name) {
            if (name.equals(PathConfiguration.PUBLIC_TARGET)) {
                return publicDirectory;
            }
            return targetMap.get(name);
        }

        @Override
        public Set<String> getTargetNameSet() {
            return Collections.unmodifiableSet(targetMap.keySet());
        }

    }

}
