import * as ts from "typescript";
import * as order from "./sortService"

/* tsPlus中自定义的编译选项 */
let accessorOptimization: boolean;
let defines: ts.MapLike<any>;
let emitReflection: boolean;
let noEmitJs: boolean;
let reorderFiles: boolean;

defines = {
    "debug": true,
    "age": 10
}

export function transformPlus(checker: ts.TypeChecker) {

    return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {

        function onSubstituteNode(hint: ts.EmitHint, node: ts.Node) {
            node = previousOnSubstituteNode(hint, node);
            if (ts.isIdentifier(node) && isDefinedConstant(node)) {
                let nodeText = node.getText();
                return ts.createIdentifier(compilerDefines[nodeText]);
            }
            return node;
        }

        function isDefinedConstant(node: ts.Identifier): boolean {
            //会出现有的类名节点pos小于0的情况，可能是由于该节点改动过，但应该没有，具体情况不得而知
            if (node.pos < 0 || node.text.indexOf("$") > -1) {
                return false;
            }
            let nodeText = node.getText();
            if (compilerDefines[nodeText] === undefined) {
                return false;
            }
            if (!node.parent) {
                return false
            }
            if (node.parent.kind === ts.SyntaxKind.VariableDeclaration && (<ts.VariableDeclaration>node.parent).name === node) {
                return false;
            }
            if (node.parent.kind === ts.SyntaxKind.BinaryExpression) {
                let parent = <ts.BinaryExpression>node.parent;
                if (parent.left === node && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
                    return false;
                }
            }

            let symbol = checker.getSymbolAtLocation(node);
            if (!symbol || !symbol.declarations) {
                return false;
            }
            let declaration = symbol.declarations[0];
            if (!declaration) {
                return false;
            }
            if (declaration.kind !== ts.SyntaxKind.VariableDeclaration) {
                return false;
            }
            let statement = declaration.parent.parent;
            return (statement.parent.kind === ts.SyntaxKind.SourceFile);
        }

        function getCompilerDefines(defines: ts.MapLike<any>): ts.MapLike<string> {
            if (!defines) {
                return null;
            }
            let compilerDefines: ts.MapLike<string> = {};
            let keys = Object.keys(defines);
            for (let key of keys) {
                let value = defines[key];
                let type = typeof value;
                switch (type) {
                    case "boolean":
                    case "number":
                        compilerDefines[key] = value.toString();
                        break;
                    case "string":
                        compilerDefines[key] = "\"" + value + "\"";
                        break;
                }
            }
            if (Object.keys(compilerDefines).length == 0) {
                return null;
            }
            return compilerDefines;
        }

        let compilerOptions = context.getCompilerOptions();

        const compilerDefines = getCompilerDefines(defines);
        const previousOnSubstituteNode = context.onSubstituteNode;
        if (compilerDefines) {
            context.onSubstituteNode = onSubstituteNode;
            context.enableSubstitution(ts.SyntaxKind.Identifier);
        }
        return (sourceFile: ts.SourceFile) => ts.visitNode(sourceFile, transformTypeScriptPlus(context, checker))
    }
}



function transformTypeScriptPlus(context: ts.TransformationContext, checker: ts.TypeChecker) {
    let compilerOptions = context.getCompilerOptions();
    //便于测试，强行为真
    compilerOptions.emitReflection = true;
    const typeChecker = checker;
    return transformSourceFile;

    function transformSourceFile(node1: ts.Node): ts.Node {
        const node = node1 as ts.SourceFile;
        if (!compilerOptions.emitReflection) {
            return node;
        }
        let visited = ts.updateSourceFileNode(node, ts.visitNodes(node.statements, visitStatement));
        ts.addEmitHelpers(visited, context.readEmitHelpers());
        return visited;
    }

    function visitStatement(node: ts.Node): ts.VisitResult<ts.Node> {
        if (order.hasModifier(node as ts.Statement, ts.SyntaxKind.DeclareKeyword)) {
            return node;
        }
        if (node.kind === ts.SyntaxKind.ClassDeclaration) {
            return visitClassDeclaration(<ts.ClassDeclaration>node);
        }
        if (node.kind === ts.SyntaxKind.ModuleDeclaration) {
            return visitModule(<ts.NamespaceDeclaration>node);
        }
        return node;
    }

    function visitModule(node: ts.NamespaceDeclaration): ts.NamespaceDeclaration {
        if (node.body.kind === ts.SyntaxKind.ModuleDeclaration) {
            return updateModuleDeclaration(node, visitModule(<ts.NamespaceDeclaration>node.body));
        }
        if (node.body.kind === ts.SyntaxKind.ModuleBlock) {
            const body = updateModuleBlock(node.body, ts.visitNodes(
                (<ts.ModuleBlock>node.body).statements, visitStatement));
            return updateModuleDeclaration(node, body);
        }
        return node;
    }

    function updateModuleDeclaration(node: ts.NamespaceDeclaration, body: ts.ModuleBody) {
        if (node.body !== body) {
            let updated = ts.getMutableClone(node);
            updated.body = <ts.ModuleBlock>body;
            return ts.updateModuleDeclaration(node, updated.decorators, updated.modifiers, updated.name, updated.body) as ts.NamespaceDeclaration;
        }
        return node
    }

    function updateModuleBlock(node: ts.ModuleBlock, statements: ts.NodeArray<ts.Statement>) {
        if (node.statements !== statements) {
            let updated = ts.getMutableClone(node);
            updated.statements = ts.createNodeArray(statements);
            return ts.updateModuleBlock(node, updated.statements);
        }
        return node;
    }

    function visitClassDeclaration(node: ts.ClassDeclaration): ts.VisitResult<ts.Statement> {
        const classStatement = ts.getMutableClone(node);
        const statements: ts.Statement[] = [classStatement];
        let interfaceMap: any = {};
        getImplementedInterfaces(node, interfaceMap);
        let interfaces: string[] = Object.keys(interfaceMap);

        let fullClassName = getModuleName(node) + "." + node.name.getText();

        let namedNode = node as TypedNameNode;
        namedNode.typeNames = interfaces;

        const expression = createReflectHelper(context, namedNode, fullClassName, interfaces);
        let range = ts.getSourceMapRange(namedNode);
        ts.setSourceMapRange(expression, range);

        const statement = ts.createStatement(expression);
        range.pos = -1;
        ts.setSourceMapRange(statement, range);
        statements.push(statement);

        return statements;
    }

    function getImplementedInterfaces(node: ts.Node, result: any) {
        let superInterfaces: ts.NodeArray<ts.HeritageClause> = null;
        if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
            superInterfaces = node.heritageClauses;
        }

        if (superInterfaces) {
            superInterfaces.forEach(superInterface => {
                let types = superInterface.types;
                for (let i = 0; i < types.length; i++) {
                    let symbol = typeChecker.getSymbolAtLocation(types[i].expression);
                    if (symbol.valueDeclaration) {
                        if (ts.isClassDeclaration(symbol.valueDeclaration)) {
                            getImplementedInterfaces(symbol.valueDeclaration, result);
                        } else if (ts.isInterfaceDeclaration(symbol.valueDeclaration)) {
                            result[getModuleName(superInterface) + "." + symbol.valueDeclaration.getText()] = true;
                        }
                    }
                    //如果只有declaration，而没有valueDeclaration那么我认为就是接口
                    else if (symbol.declarations) {
                        result[getModuleName(superInterface) + "." + symbol.name] = true;
                    }
                }
            });
        }
    }

    function getModuleName(node: ts.Node): string {
        if (ts.isModuleDeclaration(node)) {
            let preNamespace = "";
            if (node.parent && !ts.isSourceFile(node.parent)) {
                preNamespace = getModuleName(node.parent);
            }
            if (preNamespace != "") {
                return preNamespace + "." + node.name.getText();
            } else {
                return node.name.getText();
            }
        } else {
            if (node.parent && !ts.isSourceFile(node.parent)) {
                return getModuleName(node.parent);
            } else {
                return "";
            }
        }
    }
}

const reflectHelper: ts.EmitHelper = {
    name: "typescript:reflect",
    scoped: false,
    priority: 0,
    text: `
        var __reflect = (this && this.__reflect) || function (p, c, t) {
            p.__class__ = c, t ? t.push(c) : t = [c], p.__types__ = p.__types__ ? t.concat(p.__types__) : t;
        };`
};

function createReflectHelper(context: ts.TransformationContext, node: ts.ClassDeclaration, fullClassName: string, interfaces: string[]) {
    context.requestEmitHelper(reflectHelper);
    let argumentsArray: ts.Expression[] = [
        ts.createPropertyAccess(node.name, ts.createIdentifier("prototype")),
        ts.createLiteral(fullClassName)
    ];
    if (interfaces.length) {
        let elements: ts.Expression[] = [];
        for (let value of interfaces) {
            elements.push(ts.createLiteral(value));
        }
        argumentsArray.push(ts.createArrayLiteral(elements));
    }
    //DEBUG 由于API改动，我不知道这里直接赋值一个identifier会不会出问题，但暂时没找到（肯定有的）代替方法 
    let helperName = ts.createIdentifier("__reflect");
    helperName.pos = node.end + 1;
    helperName.end = helperName.pos + "__reflect".length;
    return ts.createCall(
        ts.createIdentifier("__reflect"),
        /*typeArguments*/ undefined,
        argumentsArray
    );
}


interface TypedNameNode extends ts.ClassDeclaration {
    typeNames: string[]
}