package com.cedricziel.idea.typo3.util;

import com.cedricziel.idea.typo3.domain.TYPO3ExtensionDefinition;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import com.jetbrains.php.lang.PhpFileType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class ExtensionFileGenerationUtil {

    /**
     * @param templateFile Name of the generated file
     * @param context      Template Context variables
     */
    public static String readTemplateToString(@NotNull String templateFile, @NotNull Map<String, String> context) {
        String template = getFileTemplateContent("/fileTemplates/" + templateFile);
        if (template == null) {
            // empty template file, this should never happen.
            return "";
        }

        for (Map.Entry<String, String> entry : context.entrySet()) {
            String variableValue = entry.getValue();
            if (variableValue == null) {
                variableValue = "";
            }

            template = template.replace("{{ " + entry.getKey() + " }}", variableValue);
        }

        return template;
    }

    /**
     * @param templateFile        Name of the generated file
     * @param destinationPath     Relative path to the target file system entry
     * @param extensionDefinition Extension definition containing all relevant metadata
     * @param context             Template Context variables
     * @param project             Project in context
     */
    public static PsiElement fromTemplate(@NotNull String templateFile, @NotNull String destinationPath, @NotNull String destinationFileName, @NotNull TYPO3ExtensionDefinition extensionDefinition, @NotNull Map<String, String> context, Project project) {
        String template = readTemplateToString(templateFile, context);

        VirtualFile targetDirectory = getOrCreateDestinationPath(extensionDefinition.getRootDirectory(), destinationPath);

        LanguageFileType fileType = FileTypes.PLAIN_TEXT;
        if (templateFile.endsWith(".php")) {
            fileType = PhpFileType.INSTANCE;
        }

        PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText(destinationFileName, fileType, template);
        CodeStyleManager.getInstance(project).reformat(fileFromText);
        return PsiDirectoryFactory
                .getInstance(project)
                .createDirectory(targetDirectory)
                .add(fileFromText);
    }

    /**
     * @param templateFile           Name of the generated file
     * @param destinationPath        Relative path to the target file system entry
     * @param extensionRootDirectory Extension definition containing all relevant metadata
     * @param context                Template Context variables
     * @param project                Project in context
     */
    public static PsiElement fromTemplate(@NotNull String templateFile, @NotNull String destinationPath, @NotNull String destinationFileName, @NotNull PsiDirectory extensionRootDirectory, @NotNull Map<String, String> context, Project project) {
        String template = readTemplateToString(templateFile, context);

        VirtualFile targetDirectory = getOrCreateDestinationPath(extensionRootDirectory.getVirtualFile(), destinationPath);

        LanguageFileType fileType = FileTypes.PLAIN_TEXT;
        if (templateFile.endsWith(".php")) {
            fileType = PhpFileType.INSTANCE;
        }

        PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText(destinationFileName, fileType, template);
        CodeStyleManager.getInstance(project).reformat(fileFromText);
        return PsiDirectoryFactory
                .getInstance(project)
                .createDirectory(targetDirectory)
                .add(fileFromText);
    }

    /**
     * Appends a string to a new or existing file
     *
     * @param templateString      The string to append. Variables with '{{ varname }}' syntax are substituted from context
     * @param destinationPath     Relative path inside the extension directory folder
     * @param destinationFileName The file name
     * @param extensionDefinition The containing extension
     * @param context             Template variable map
     * @param project             Current project
     * @return The created or modified file
     */
    public static VirtualFile appendOrCreate(@NotNull String templateString, @NotNull String destinationPath, @NotNull String destinationFileName, @NotNull TYPO3ExtensionDefinition extensionDefinition, @NotNull Map<String, String> context, Project project) {
        for (Map.Entry<String, String> entry : context.entrySet()) {
            templateString = templateString.replace("{{ " + entry.getKey() + " }}", entry.getValue());
        }

        VirtualFile targetDirectory = getOrCreateDestinationPath(extensionDefinition.getRootDirectory(), destinationPath);
        if (targetDirectory == null) {
            return null;
        }

        VirtualFile[] children = targetDirectory.getChildren();
        VirtualFile targetFile = null;
        for (VirtualFile file : children) {
            if (!file.isDirectory() && file.getName().equals(destinationFileName)) {
                targetFile = file;
            }
        }

        if (targetFile == null) {
            PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText(destinationFileName, FileTypes.PLAIN_TEXT, templateString);

            CodeStyleManager.getInstance(project).reformat(fileFromText);
            PsiDirectoryFactory
                    .getInstance(project)
                    .createDirectory(targetDirectory)
                    .add(fileFromText);

            return fileFromText.getVirtualFile();
        }

        PsiFile file = PsiManager.getInstance(project).findFile(targetFile);
        if (file == null) {
            return null;
        }

        PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
        Document document = documentManager.getDocument(file);
        if (document == null) {
            return null;
        }

        String content = document.getText();
        StringBuilder contentSb = new StringBuilder(content);
        contentSb
                .append("\n")
                .append(templateString);
        document.setText(contentSb);

        return file.getVirtualFile();
    }

    private static VirtualFile getOrCreateDestinationPath(VirtualFile rootDirectory, String destinationPath) {
        try {
            return VfsUtil.createDirectoryIfMissing(rootDirectory, destinationPath);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Nullable
    private static String getFileTemplateContent(@NotNull String filename) {
        try {
            return StreamUtil
                    .readText(ExtensionFileGenerationUtil.class.getResourceAsStream(filename), "UTF-8")
                    .replace("\r\n", "\n");
        } catch (IOException e) {
            return null;
        }
    }

    public static boolean extensionHasFile(TYPO3ExtensionDefinition extensionDefinition, String filename) {

        List<VirtualFile> childFiles = VfsUtil.collectChildrenRecursively(extensionDefinition.getRootDirectory());
        for (VirtualFile file : childFiles) {
            if (file.getPath().contains(filename)) {
                return true;
            }
        }

        return false;
    }
}
