package io.github.manusant.ss;

import io.github.manusant.ss.ui.UiTemplates;
import io.github.manusant.ss.conf.Theme;
import com.typesafe.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author manusant
 */
public class SwaggerHammer {

    private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerHammer.class);

    public void prepareUi(final Config config, Swagger swagger) throws IOException {
        LOGGER.debug("Spark-Swagger: Start compiling Swagger UI");

        String uiFolder = SwaggerHammer.getUiFolder(config.getString("spark-swagger.basePath"));

        // 1 - Extract UI/Templates folder to a temporary folder
        extractUi(uiFolder);

        // 2 - Decorate index.html according to configurations
        String newIndex = decorateIndex(config);

        // 3 - Save new Index to UI folder
        saveFile(uiFolder, "index.html", newIndex);

        // 4 - Parse Swagger definitions and save it to UI folder
        SwaggerParser.parseJs(swagger, uiFolder + "swagger-spec.js");
        SwaggerParser.parseYaml(swagger, uiFolder + "doc.yaml");
        SwaggerParser.parseJson(swagger, uiFolder + "doc.json");

        // 5 - Apply theme according to configurations
        applyTheme(uiFolder, config);
    }

    private void extractUi(String uiFolder) throws IOException {
        extractUiFolder(uiFolder);
        extractTemplatesFolder(uiFolder);
        LOGGER.debug("Spark-Swagger: UI resources and templates successfully extracted");
    }

    private void extractUiFolder(String uiFolder) throws IOException {

        String dir = "ui";
        List<String> uiFiles = listFiles(dir)
                .stream()
                .map(filePath -> filePath.substring(filePath.indexOf(dir) + dir.length() + 1).trim())
                .filter(fileName -> !fileName.contains("/") && !fileName.isEmpty())
                .collect(Collectors.toList());

        for (String uiFileName : uiFiles) {
            InputStream uiFile = SparkSwagger.class.getClassLoader().getResourceAsStream(dir + "/" + uiFileName);
            File file = new File(uiFolder + uiFileName);
            if (file.exists()) {
                file.delete();
            }
            file.createNewFile();
            Files.copy(uiFile, file.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
        }
    }

    private void extractTemplatesFolder(String uiFolder) throws IOException {

        File templatesFolder = new File(uiFolder + "templates/");
        if (!templatesFolder.exists()) {
            templatesFolder.mkdir();
        }

        String dir = "ui/templates";
        List<String> templateFiles = listFiles(dir)
                .stream()
                .map(filePath -> filePath.substring(filePath.indexOf(dir) + dir.length() + 1).trim())
                .filter(fileName -> !fileName.contains("/") && !fileName.isEmpty())
                .collect(Collectors.toList());

        for (String templateFileName : templateFiles) {
            InputStream templateFile = SparkSwagger.class.getClassLoader().getResourceAsStream(dir + "/" + templateFileName);
            File file = new File(uiFolder + "templates/" + templateFileName);
            if (file.exists()) {
                file.delete();
            }
            file.createNewFile();
            Files.copy(templateFile, file.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
        }
    }

    private List<String> listFiles(String prefix) throws IOException {
        List<String> uiFiles = new ArrayList<>();

        CodeSource src = SparkSwagger.class.getProtectionDomain().getCodeSource();
        if (src != null) {
            URL jar = src.getLocation();
            ZipInputStream zip = new ZipInputStream(jar.openStream());
            while (true) {
                ZipEntry e = zip.getNextEntry();
                if (e == null)
                    break;
                String name = e.getName();
                if (name.startsWith(prefix)) {
                    uiFiles.add(name);
                }
            }
        }
        return uiFiles;
    }

    private void applyTheme(String uiFolder, final Config config) throws IOException {
        LOGGER.debug("Spark-Swagger: Start applying configured CSS Theme");
        String themeName = config.getString("spark-swagger.theme");
        Theme theme = Theme.fromValue(themeName);

        String themeCss = readFile(uiFolder, "templates/" + theme.getValue() + ".css", StandardCharsets.UTF_8);
        saveFile(uiFolder, "swagger-ui.css", themeCss);
        LOGGER.debug("Spark-Swagger: CSS Theme successfully applied");
    }

    private String readFile(String uiFolder, String name, Charset encoding) throws IOException {
        byte[] encoded = Files.readAllBytes(Paths.get(uiFolder + name));
        return new String(encoded, encoding);
    }

    private void saveFile(String uiFolder, String fileName, String content) throws IOException {
        File file = new File(uiFolder + fileName);
        file.delete();

        FileWriter f2 = new FileWriter(file, false);
        f2.write(content);
        f2.close();
        LOGGER.debug("Spark-Swagger: Swagger UI file " + fileName + " successfully saved");
    }

    public static String getUiFolder(String basePath) {
        return System.getProperty("java.io.tmpdir") + "/swagger-ui" + (basePath.startsWith("/") ? "" : "/") + basePath + (basePath.endsWith("/") ? "" : "/");
    }

    public static String getSwaggerUiFolder() {
        return System.getProperty("java.io.tmpdir") + "/swagger-ui/";
    }

    public static void createDir(final String path) {
        File uiFolder = new File(path);
        if (!uiFolder.exists()) {
            uiFolder.mkdir();
        }
    }

    private String decorateIndex(final Config config) {
        LOGGER.debug("Spark-Swagger: Start decorating index.html according to ui configurations");
        String indexTemplate = UiTemplates.indexTemplate();
        int scriptStartIndex = indexTemplate.indexOf("window.onload");
        int scriptEndIndex = indexTemplate.indexOf("</script>", scriptStartIndex);
        String currentScript = indexTemplate.substring(scriptStartIndex, scriptEndIndex);

        String scriptTemplate = UiTemplates.scriptTemplate();
        scriptTemplate = setStringProperty(scriptTemplate, "docExpansion", config.getString("spark-swagger.docExpansion"), "list");
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "deepLinking", config.getString("spark-swagger.deepLinking"), false);
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "displayOperationId", config.getString("spark-swagger.displayOperationId"), false);
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "defaultModelsExpandDepth", config.getString("spark-swagger.defaultModelsExpandDepth"), 1);
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "defaultModelExpandDepth", config.getString("spark-swagger.defaultModelExpandDepth"), 1);
        scriptTemplate = setStringProperty(scriptTemplate, "defaultModelRendering", config.getString("spark-swagger.defaultModelRendering"), "example");
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "displayRequestDuration", config.getString("spark-swagger.displayRequestDuration"), false);
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "filter", config.getString("spark-swagger.filter"), false);
        scriptTemplate = setStringProperty(scriptTemplate, "operationsSorter", config.getString("spark-swagger.operationsSorter"), "alpha");
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "showExtensions", config.getString("spark-swagger.showExtensions"), false);
        scriptTemplate = setPrimitiveProperty(scriptTemplate, "showCommonExtensions", config.getString("spark-swagger.showCommonExtensions"), false);
        scriptTemplate = setStringProperty(scriptTemplate, "tagsSorter", config.getString("spark-swagger.tagsSorter"), "alpha");
        LOGGER.debug("Spark-Swagger: index.html successfully decorated");
        return indexTemplate.replace(currentScript, scriptTemplate);
    }

    private String setStringProperty(String scriptTemplate, String propertyName, String value, String defaultValue) {
        if (value != null) {
            scriptTemplate = replaceProperty(scriptTemplate, propertyName, value.toLowerCase(), true);
        } else {
            scriptTemplate = replaceProperty(scriptTemplate, propertyName, defaultValue, true);
        }
        return scriptTemplate;
    }

    private String setPrimitiveProperty(String scriptTemplate, String propertyName, Object value, Object defaultValue) {
        if (value != null) {
            scriptTemplate = replaceProperty(scriptTemplate, propertyName, value.toString(), false);
        } else {
            scriptTemplate = replaceProperty(scriptTemplate, propertyName, defaultValue.toString(), false);
        }
        return scriptTemplate;
    }

    private String replaceProperty(String script, String property, String newValue, boolean isString) {
        if (newValue == null) newValue = "null";
        if (property != null) {
            int propertyIndex = script.indexOf(property);

            if (propertyIndex != -1 && script.indexOf(":", propertyIndex) != -1) {
                String propertyValue = script.substring(script.indexOf(":", propertyIndex) + 1, script.indexOf(",", propertyIndex)).trim();

                if (isString && !newValue.equals("null")) {
                    if (newValue.isEmpty()) {
                        script = replace(script, property, propertyValue, "null");
                    } else {
                        script = replace(script, property, propertyValue, "'" + newValue + "'");
                    }
                } else {
                    script = replace(script, property, propertyValue, newValue);
                }
            }
        }
        return script;
    }

    private String replace(String script, String property, String oldValue, String newValue) {
        String oldConf = property + ": " + oldValue;
        String newConf = property + ": " + newValue;
        return script.replace(oldConf, newConf);
    }
}