package edu.sc.seis.launch4j;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.util.regex.Pattern;

public class CreateLaunch4jXMLTask extends DefaultTask {
    private final String projectName = getProject().getName();
    @OutputFile
    public File getXmlOutFile() {
        return ((Launch4jPluginExtension) getProject().getExtensions().getByName(Launch4jPlugin.LAUNCH4J_CONFIGURATION_NAME)).getXmlOutFileForProject(getProject());
    }

    @TaskAction
    public void writeXmlConfig() throws ParserConfigurationException, TransformerException {
        Launch4jPluginExtension cfg = (Launch4jPluginExtension) getProject().getExtensions().getByName("launch4j");

        File file = getXmlOutFile();
        file.getParentFile().mkdirs();

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document doc = docBuilder.newDocument();

        Element root = doc.createElement("launch4jConfig");
        doc.appendChild(root);

        Element child;

        textElement(doc, root, "dontWrapJar", cfg.getDontWrapJar());
        textElement(doc, root, "headerType", cfg.getHeaderType());
        textElement(doc, root, "jar", new File(cfg.getJar()).getAbsolutePath());
        textElement(doc, root, "outfile", new File(cfg.getOutfile()).getAbsolutePath());
        textElement(doc, root, "errTitle", cfg.getErrTitle());
        textElement(doc, root, "cmdLine", cfg.getCmdLine());
        textElement(doc, root, "chdir", cfg.getChdir());
        textElement(doc, root, "priority", cfg.getPriority());
        textElement(doc, root, "downloadUrl", cfg.getDownloadUrl());
        textElement(doc, root, "supportUrl", cfg.getSupportUrl());
        textElement(doc, root, "customProcName", cfg.getCustomProcName());
        textElement(doc, root, "stayAlive", cfg.getStayAlive());
        textElement(doc, root, "manifest", cfg.getManifest());
        textElement(doc, root, "icon", cfg.getIcon());

        /*
        child = doc.createElement("classPath");
        {
            root.appendChild(child);
            textElement(doc, child, "icon", cfg.getIcon());
        }
        */

        child = doc.createElement("versionInfo");
        {
            String originalFilename = ensureLength(46, new File(cfg.getOutfile()).getName());
            if (!originalFilename.endsWith(".exe")) originalFilename += ".exe";

            textElement(doc, child, "fileVersion", ensureLength(20, parseDotVersion(cfg.getVersion())));    //Max 20
            textElement(doc, child, "txtFileVersion", ensureLength(50, cfg.getVersion()));                     //Max 50
            textElement(doc, child, "fileDescription", ensureLength(150, projectName));               //Max 150
            textElement(doc, child, "copyright", ensureLength(20, cfg.getCopyright()));                   //Max 150
            textElement(doc, child, "productVersion", ensureLength(20, parseDotVersion(cfg.getVersion())));    //Max 20
            textElement(doc, child, "txtProductVersion", ensureLength(50, cfg.getVersion()));                     //Max 50
            textElement(doc, child, "productName", ensureLength(150, projectName));               //Max 150
            textElement(doc, child, "internalName", ensureLength(150, projectName));               //Max 50, Must NOT end in .exe
            textElement(doc, child, "originalFilename", originalFilename);                                         //Max 50, Must end in .exe
        }

        child = doc.createElement("jre");
        {
            root.appendChild(child);
            textElement(doc, child, "path", cfg.getBundledJrePath());
            textElement(doc, child, "minVersion", cfg.getJreMinVersion());
            textElement(doc, child, "maxVersion", cfg.getJreMaxVersion());
            textElement(doc, child, "opt", cfg.getOpt());
            textElement(doc, child, "initialHeapSize", cfg.getInitialHeapSize());
            textElement(doc, child, "initialHeapPercent", cfg.getInitialHeapPercent());
            textElement(doc, child, "maxHeapSize", cfg.getMaxHeapSize());
            textElement(doc, child, "maxHeapPercent", cfg.getMaxHeapPercent());
        }

        if (cfg.getMessagesStartupError() != null ||
                cfg.getMessagesBundledJreError() != null ||
                cfg.getMessagesJreVersionError() != null ||
                cfg.getMessagesLauncherError() != null) {
            child = doc.createElement("messages");
            {
                root.appendChild(child);
                textElement(doc, child, "startupErr", cfg.getMessagesStartupError());
                textElement(doc, child, "bundledJreErr", cfg.getMessagesBundledJreError());
                textElement(doc, child, "jreVersionErr", cfg.getMessagesJreVersionError());
                textElement(doc, child, "launcherErr", cfg.getMessagesLauncherError());
            }
        }

        if (cfg.getMutexName() != null || cfg.getWindowTitle() != null) {
            child = doc.createElement("singleInstance");
            {
                root.appendChild(child);
                textElement(doc, child, "mutexName", cfg.getMutexName());
                textElement(doc, child, "windowTitle", cfg.getWindowTitle());
            }
        }

        // now for writing it...

        // write the content into xml file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(file);
        //StreamResult result = new StreamResult(System.out);

        transformer.transform(source, result);
    }

    private String ensureLength(int maxLen, String value) {
        if (value.length() > maxLen) return value.substring(0, maxLen - 1);
        return value;
    }

    private void textElement(Document doc, Element parent, String name, Integer val) {
        if (val == null) return;
        textElement(doc, parent, name, val.toString());
    }

    private void textElement(Document doc, Element parent, String name, boolean val) {
        textElement(doc, parent, name, val ? "true" : "false");
    }

    private void textElement(Document doc, Element parent, String name, String val) {
        if (val == null || name == null || val.isEmpty())
            return;

        Element node = doc.createElement(name);

        node.appendChild(doc.createTextNode(val));

        if (parent != null)
            parent.appendChild(node);
    }

    private final Pattern VERSION1 = Pattern.compile("\\d+(\\.\\d+){3}");
    private final Pattern VERSION2 = Pattern.compile("\\d+(\\.\\d+){0,2}");

    /**
     * launch4j fileVersion and productVersion are required to be x.y.z.w format, no text like beta or
     * SNAPSHOT. I think this is a windows thing. So we check the version, and if it is only dots and
     * numbers, we use it. If not we use 0.0.0.1
     */
    private String parseDotVersion(String version) {
        if (VERSION1.matcher(version).matches()) {
            return version;
        } else if (VERSION2.matcher(version).matches()) {
            String s = version + ".0";
            while (VERSION2.matcher(s).matches()) {
                s += ".0";
            }
            return s;
        } else {
            return "0.0.0.1";
        }
    }

}
