package com.taobao.weex.utils;

import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.xml.*;
import com.taobao.weex.lint.Attribute;
import com.taobao.weex.lint.DirectiveLint;
import com.taobao.weex.lint.WeexTag;

import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by moxun on 16/10/13.
 */
public class WeexFileUtil {

    private static String currentFileName = "";
    private static JSObjectLiteralExpression cachedExportsStatement;
    private static Map<String, String> vars = new ConcurrentHashMap<String, String>();
    private static Set<String> functions = new HashSet<String>();

    public static boolean isOnWeexFile(PsiElement element) {
        if (element.getContainingFile() != null) {
            return element.getContainingFile().getName().toLowerCase().endsWith(".we");
        } else {
            return false;
        }
    }

    public static String getValueType(XmlAttributeValue value) {
        if (value.getContext() != null && value.getContext() instanceof XmlAttribute) {
            String attrName = ((XmlAttribute) value.getContext()).getName();
            if (value.getContext().getContext() != null && value.getContext().getContext() instanceof XmlTag) {
                String tagName = ((XmlTag) value.getContext().getContext()).getName();
                WeexTag tag = DirectiveLint.getWeexTag(tagName);
                if (tag != null) {
                    Attribute attribute = tag.getAttribute(attrName);
                    if (attribute != null) {
                        return attribute.valueType;
                    }
                }
            }
        }
        return "var";
    }

    public static String getJSPropertyType(JSProperty jsProperty) {
        JSType t = jsProperty.getType();
        String typeString = "var";
        if (t == null) {
            if (jsProperty.getValue() instanceof JSObjectLiteralExpression) {
                typeString = "Object";
            }
        } else {
            typeString = t.getResolvedTypeText();
        }
        return typeString;
    }

    public static boolean hasSameType(XmlAttributeValue value, JSProperty property) {
        String valueType = getValueType(value);
        String JsType = getJSPropertyType(property);
        return hasSameType(valueType, JsType);
    }

    public static boolean hasSameType(String value, String property) {
        if (value.toLowerCase().equals(property.toLowerCase())) {
            return true;
        }
        if (value.equals("var")) {
            return true;
        }
        return false;
    }

    private static void ensureFile(PsiElement element) {
        String path = String.valueOf(System.currentTimeMillis());
        if (element != null
                && element.getContainingFile() != null
                && element.getContainingFile().getVirtualFile() != null) {
            path = element.getContainingFile().getVirtualFile().getPath();
        }
        if (!currentFileName.equals(path)) {
            cachedExportsStatement = null;
            vars.clear();
            functions.clear();
            currentFileName = path;
        }
    }

    public static JSObjectLiteralExpression getExportsStatement(PsiElement anyElementOnWeexScript) {
        ensureFile(anyElementOnWeexScript);

        if (isValid(cachedExportsStatement)) {
            return cachedExportsStatement;
        }

        //WELCOME TO HELL!!!
        PsiFile file = anyElementOnWeexScript.getContainingFile();
        if (file instanceof XmlFile) {
            XmlDocument document = ((XmlFile) file).getDocument();
            if (document != null) {
                for (PsiElement e : document.getChildren()) {
                    if (e instanceof XmlTag) {
                        if ("script".equals(((XmlTag) e).getName())) {
                            for (PsiElement e1 : e.getChildren()) {
                                if (e1 instanceof JSEmbeddedContent) {
                                    for (PsiElement e2 : e1.getChildren()) {
                                        if (e2 instanceof JSExpressionStatement) {
                                            for (PsiElement e3 : e2.getChildren()) {
                                                if (e3 instanceof JSAssignmentExpression) {
                                                    PsiElement[] children = e3.getChildren();
                                                    if (children.length == 2) {
                                                        if (children[0].getText().equals("module.exports")) {
                                                            if (children[1] instanceof JSObjectLiteralExpression) {
                                                                cachedExportsStatement = (JSObjectLiteralExpression) children[1];
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return cachedExportsStatement;
    }

    private static boolean isValid(JSObjectLiteralExpression expression) {
        if (expression == null) {
            return false;
        }
        try {
            PsiFile file = expression.getContainingFile();
            if (file == null) {
                return false;
            }
        } catch (PsiInvalidElementAccessException e) {
            return false;
        }
        JSProperty data = expression.findProperty("data");
        if (data == null || data.getValue() == null) {
            return false;
        }
        return true;
    }

    public static Map<String, String> getAllVarNames(PsiElement any) {
        ensureFile(any);
        getVarDeclaration(any, String.valueOf(System.currentTimeMillis()));
        return vars;
    }

    public static Set<String> getAllFunctionNames(PsiElement any) {
        ensureFile(any);
        getFunctionDeclaration(any, String.valueOf(System.currentTimeMillis()));
        return functions;
    }

    public static JSProperty getVarDeclaration(PsiElement anyElementOnWeexScript, String valueName) {
        valueName = CodeUtil.getVarNameFromMustache(valueName);
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnWeexScript);
        vars.clear();
        JSProperty ret = null;
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return null;
                }
            } catch (PsiInvalidElementAccessException e) {
                return null;
            }
            JSProperty data = exports.findProperty("data");
            if (data == null || data.getValue() == null) {
                return null;
            }
            for (PsiElement pe : data.getValue().getChildren()) {
                if (pe instanceof JSProperty) {
                    String varName = ((JSProperty) pe).getName();
                    String varValue = getJSPropertyType((JSProperty) pe);
                    if (varName != null && varValue != null) {
                        vars.put(varName, varValue);
                    }
                    if (valueName.equals(varName)) {
                        ret = (JSProperty) pe;
                    }
                }

            }
        }
        return ret;
    }

    public static JSFunctionExpression getFunctionDeclaration(PsiElement anyElementOnWeexScript, String valueName) {
        valueName = CodeUtil.getFunctionNameFromMustache(valueName);
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnWeexScript);
        functions.clear();
        JSFunctionExpression ret = null;
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return null;
                }
            } catch (PsiInvalidElementAccessException e) {
                return null;
            }
            JSProperty data = exports.findProperty("methods");
            if (data != null && data.getValue() != null) {
                for (PsiElement e : data.getValue().getChildren()) {
                    if (e instanceof JSProperty) {
                        for (PsiElement e1 : e.getChildren()) {
                            if (e1 instanceof JSFunctionExpression) {
                                functions.add(((JSFunctionExpression) e1).getName());
                                if (valueName.equals(((JSFunctionExpression) e1).getName())) {
                                    ret = (JSFunctionExpression) e1;
                                }
                            }
                        }
                    }
                }
            }
        }
        return ret;
    }

    public static int getExportsEndOffset(PsiElement anyElementOnWeexScript, String name) {
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnWeexScript);
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return -1;
                }
            } catch (PsiInvalidElementAccessException e) {
                return -1;
            }
            JSProperty data = exports.findProperty(name);
            if (data == null || data.getValue() == null) {
                return -1;
            }
            return data.getValue().getTextRange().getEndOffset() - 1;
        }
        return -1;
    }

    public static boolean isMustacheValue(String value) {
        if (value == null) {
            return false;
        }
        return Pattern.compile("\\{\\{.+?\\}\\}").matcher(value).matches();
    }

    public static boolean containsMustacheValue(String value) {
        if (value == null) {
            return false;
        }
        return Pattern.compile(".*\\{\\{.+?\\}\\}.*").matcher(value).matches();
    }

    public static Map<String, TextRange> getVars(String src) {
        Map<String, TextRange> results = new IdentityHashMap<String, TextRange>();
        Pattern p = Pattern.compile("\\{\\{.+?\\}\\}");
        Matcher m = p.matcher(src);
        while (m.find()) {
            String g = m.group().replaceAll("\\{+", "").replaceAll("\\}+", "").trim();
            TextRange textRange = new TextRange(m.start(), m.end());
            results.put(g, textRange);
        }
        return results;
    }
}