package pers.fw.tplugin.view;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import com.jetbrains.php.lang.psi.elements.ClassReference;
import com.jetbrains.php.lang.psi.elements.FunctionReference;
import com.jetbrains.php.lang.psi.elements.MethodReference;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import pers.fw.tplugin.view.dict.TemplatePath;

import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TemplateUtil {
    public static Set<String> RENDER_METHODS = new HashSet<String>() {{
        add("make");
        add("of");
    }};

    @NotNull
    public static Set<VirtualFile> resolveTemplateName(@NotNull Project project, @NotNull String templateName) {
        Set<String> templateNames = new HashSet<>();

        int i = templateName.indexOf("::");
        String ns = null;
        if (i > 0) {
            ns = templateName.substring(0, i);
            templateName = templateName.substring(i + 2, templateName.length());
        }

        String pointName = templateName.replace(".", "/");

        // find template files by extensions
//        templateNames.add(pointName.concat(".blade.php"));

        templateNames.add(ViewCollector.curModule + "/" + pointName.concat(".html"));
        templateNames.add(pointName.concat(".html"));

        Set<VirtualFile> templateFiles = new HashSet<>();
        for (TemplatePath templatePath : ViewCollector.getPaths(project)) {
            // we have a namespace given; ignore all other paths
            String namespace = templatePath.getNamespace();
            if ((ns == null && namespace != null) || ns != null && !ns.equals(namespace)) {
                continue;
            }

            VirtualFile viewDir = templatePath.getRelativePath(project);
            if (viewDir == null) {
                continue;
            }
            for (String templateRelative : templateNames) {
                VirtualFile viewsDir = VfsUtil.findRelativeFile(templateRelative, viewDir);
                if (viewsDir != null) {
                    templateFiles.add(viewsDir);
                }
            }
        }
        return templateFiles;
    }

    @NotNull
    public static Collection<String> resolveTemplateName(@NotNull PsiFile psiFile) {
        return resolveTemplateName(psiFile.getProject(), psiFile.getVirtualFile());
    }

    @NotNull
    public static Collection<String> resolveTemplateName(@NotNull Project project, @NotNull VirtualFile virtualFile) {
        Set<String> templateNames = new HashSet<>();

        for (TemplatePath templatePath : ViewCollector.getPaths(project)) {
            VirtualFile viewDir = templatePath.getRelativePath(project);
            if (viewDir == null) {
                continue;
            }

            String relativePath = VfsUtil.getRelativePath(virtualFile, viewDir);
            if (relativePath != null) {
                relativePath = stripTemplateExtensions(relativePath);

                if (templatePath.getNamespace() != null && StringUtils.isNotBlank(templatePath.getNamespace())) {
                    templateNames.add(templatePath.getNamespace() + "::" + relativePath.replace("/", "."));
                } else {
                    templateNames.add(relativePath.replace("/", "."));
                }
            }
        }

        return templateNames;
    }

    @NotNull
    public static Set<VirtualFile> resolveTemplateDirectory(@NotNull Project project, @NotNull String directory) {
        int i = directory.indexOf("::");
        String ns = null;
        if (i > 0) {
            ns = directory.substring(0, i);
            directory = directory.substring(i + 2, directory.length());
        }

        directory = directory.replace(".", "/");

        Set<VirtualFile> templateFiles = new HashSet<>();
        for (TemplatePath templatePath : ViewCollector.getPaths(project)) {
            // we have a namespace given; ignore all other paths
            String namespace = templatePath.getNamespace();
            if ((ns == null && namespace != null) || ns != null && !ns.equals(namespace)) {
                continue;
            }

            VirtualFile viewDir = templatePath.getRelativePath(project);
            if (viewDir == null) {
                continue;
            }

            VirtualFile viewsDir = VfsUtil.findRelativeFile(directory, viewDir);
            if (viewsDir != null) {
                templateFiles.add(viewsDir);
            }
        }

        return templateFiles;
    }


    /**
     * Try to find directory or file navigation for template name
     * <p>
     * "foo.bar" => "foo", "bar"
     */
    @NotNull
    public static Collection<VirtualFile> resolveTemplate(@NotNull Project project, @NotNull String templateName, int offset) {
        Set<VirtualFile> files = new HashSet<>();

        // try to find a path pattern on current offset after path normalization
        if (offset > 0 && offset < templateName.length()) {
            String templateNameWithCaret = normalizeTemplate(new StringBuilder(templateName).insert(offset, '\u0182').toString()).replace("/", ".");
            offset = templateNameWithCaret.indexOf('\u0182');

            int i = StringUtils.strip(templateNameWithCaret.replace(String.valueOf('\u0182'), ""), "/").indexOf(".", offset);
            if (i > 0) {
                files.addAll(resolveTemplateDirectory(project, templateName.substring(0, i)));
            }
        }

        // full filepath fallback: "foo/foo<caret>.blade.php"
        if (files.size() == 0) {
            files.addAll(resolveTemplateName(project, templateName));
        }

        return files;
    }

    /**
     * Normalize template path
     */
    @NotNull
    public static String normalizeTemplate(@NotNull String templateName) {
        return templateName
                .replace("\\", "/")
                .replaceAll("/+", "/");
    }


    /**
     * "'foobar'"
     * "'foobar', []"
     */
    @Nullable
    public static String getParameterFromParameterDirective(@NotNull String content) {
        Matcher matcher = Pattern.compile("^\\s*['|\"]([^'\"]+)['|\"]").matcher(content);

        if (matcher.find()) {
            return StringUtil.trim(matcher.group(1));
        }

        return null;
    }


    /**
     * Strip template extension for given template name
     * <p>
     * "foo_blade.blade.php" => "foo_blade"
     * "foo_blade.html.twig" => "foo_blade"
     * "foo_blade.php" => "foo_blade"
     */
    @NotNull
    public static String stripTemplateExtensions(@NotNull String filename) {
        if (filename.endsWith(".blade.php")) {
            filename = filename.substring(0, filename.length() - ".blade.php".length());
        } else if (filename.endsWith(".html.twig")) {
            filename = filename.substring(0, filename.length() - ".html.twig".length());
        } else if (filename.endsWith(".php")) {
            filename = filename.substring(0, filename.length() - ".php".length());
        }

        return filename;
    }

    private static class MyViewRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
        private final Collection<Pair<String, PsiElement>> views;

        private MyViewRecursiveElementWalkingVisitor(Collection<Pair<String, PsiElement>> views) {
            this.views = views;
        }

        @Override
        public void visitElement(PsiElement element) {

            if (element instanceof MethodReference) {
                visitMethodReference((MethodReference) element);
            }

            if (element instanceof FunctionReference) {
                visitFunctionReference((FunctionReference) element);
            }

            super.visitElement(element);
        }

        private void visitFunctionReference(FunctionReference functionReference) {

            if (!"view".equals(functionReference.getName())) {
                return;
            }

            PsiElement[] parameters = functionReference.getParameters();

            if (parameters.length < 1 || !(parameters[0] instanceof StringLiteralExpression)) {
                return;
            }

            String contents = ((StringLiteralExpression) parameters[0]).getContents();
            if (StringUtils.isBlank(contents)) {
                return;
            }

            views.add(Pair.create(contents, parameters[0]));
        }

        private void visitMethodReference(MethodReference methodReference) {

            String methodName = methodReference.getName();
            if (!RENDER_METHODS.contains(methodName)) {
                return;
            }

            PsiElement classReference = methodReference.getFirstChild();
            if (!(classReference instanceof ClassReference)) {
                return;
            }

            if (!"View".equals(((ClassReference) classReference).getName())) {
                return;
            }

            PsiElement[] parameters = methodReference.getParameters();
            if (parameters.length == 0 || !(parameters[0] instanceof StringLiteralExpression)) {
                return;
            }

            String contents = ((StringLiteralExpression) parameters[0]).getContents();
            if (StringUtils.isBlank(contents)) {
                return;
            }

            views.add(Pair.create(contents, parameters[0]));
        }
    }

    public static File recursionMatch(File root, String file) {
        if (root == null) return null;
        File[] files = root.listFiles();
        if (files != null)
            for (File f : files) {
                if (f.isFile()) {
                    String curPath = f.getAbsolutePath().toLowerCase();
                    curPath = curPath.replace("\\", "/");
                    curPath=curPath.substring(0,curPath.lastIndexOf("."));
                    if (curPath.equals(file.toLowerCase())) {
                        return f;
                    }
                } else if (f.isDirectory()) {
                    File res=recursionMatch(f, file);
                    if(res!=null)
                        return res;
                }
            }
        return null;
    }
}