import * as ts from "typescript";
import { LanguageHost } from "./LanguageHost";
import { OneFileRenameLocations } from "./OneFileRenameLocations";
import * as plus from "./tsPlusService";
import * as fs from "fs";

export class RenameService {
    public languageService: ts.LanguageService;
    public compilerHost!: ts.CompilerHost;
    public languageServiceHost!: LanguageHost;

    public options: ts.CompilerOptions;
    public debugMode: boolean;

    //用于重命名时计算字母表取值位置
    public times: number = 0;
    //是否更改源文件
    //TODO DEBUG 似乎缓存更改过的源文件可以提高增量编译模式的访问效率,但更改文件后位置会有变化，记录数组待调整
    public ifWriteFile: boolean;

    public propsRecorder: Map<string, number> = new Map();
    public compileChildren: boolean = false;
    public firstTime = true;

    constructor(files: string[], optionsPath: string, debugMode: boolean, ifWriteFile = false) {
        //TODO 此处可在重命名时再开启相应Host
        //如果有更多自定义编译要求，也可把各种功能组合派生，编译作为单独类独立出来
        this.options = this.getCompilerOptions(optionsPath);
        this.debugMode = debugMode;
        this.languageServiceHost = new LanguageHost(files, this.options);
        this.languageService = ts.createLanguageService(this.languageServiceHost);
        this.ifWriteFile = ifWriteFile;
    }



    public renameProperty(node: ts.Node, context: ts.TransformationContext, checker: ts.TypeChecker) {
        /**
        
         //真正的访问每个节点的函数，也就是ts.Vistor类型
         const visitor = (node: ts.Node) => {
             if (ts.isClassDeclaration(node)) {
                 let props = node.members;
                 let privateNumber = 0;
                 this.resetTimes(this.getParentPropsNum(node, checker));
                 for (let i = 0; i < props.length; i++) {
                     //由于构造函数没有name，所以跳过了对其的解读
                     //同时跳过的还有多写分号SemicolonClassElement
                     //私有属性末位是$的情况，可能是由于用户命名产生的，也可能是增量编译中改变继承关系时，重编译已改名的父类产生的
                     //需要注意的是，在增量编译中，除非重编译该文件，否则已改名的Identifier是没有symbol的
                     if (ts.isConstructorDeclaration(props[i])
                         || ts.isSemicolonClassElement(props[i])
                         || !this.checkUserProperty(props[i].name as ts.Identifier)) {
                         continue;
                     }
                     let symbol = checker.getSymbolAtLocation(props[i].name);
                     let tages = symbol.getJsDocTags();
                     if (this.ifCompress(tages)
                         && this.ifPrivate(props[i])) {
                         let fileName = node.getSourceFile().fileName;
                         privateNumber++;
                         //getStart()只能获取没有改动之前的原始位置，所以需要计算偏移
                         //getText()此处之所以暂存原先的名字，是因为改变名字以后就不能再读取了
                         let previousPosition = props[i].name.getStart();
                         let previousName = props[i].name.getText();
                         let offset = this.computeProducedOffset(fileName, previousPosition);
                         //只要通过位移找到定义时的属性，语言服务就可以找到内存中该属性的使用位置，并在后续判断中，为相关节点改名
                         let locations = this.languageService.findRenameLocations(fileName, previousPosition + offset, false, false);
                         if (locations) {
                             //DEBUG 改变了生成节点的position，防止异步函数处理流程中，error检测节点pos和end报错，似乎对后续编译没有影响？
                             //之所以没改PropertyAccessExpression节点中新生成节点的位置，是因为error只使用定义位置进行判断
                             let newName = this.nameFomate(previousName);
                             props[i].name = ts.createIdentifier(newName);
                             props[i].name.pos = previousPosition;
                             props[i].name.end = previousPosition + newName.length;
                             if (this.ifWriteFile) {
                                 this.changeSourceFile(locations, newName);
                             }
                         }
                     } else {
                         continue;
                     }
                 }
                 //如果node是父类，node私有属性数目发生变化，其子类也应该重新编译，从而防止命名重复
                 if (this.propsRecorder.get(node.name.getText()) != privateNumber) {
                     this.compileChildren = true;
                 }
                 this.propsRecorder.set(node.name.getText(), privateNumber)
             } else if (ts.isPropertyAccessExpression(node)) {
                 let variety = node.expression;
                 let symbol = checker.getSymbolAtLocation(node);
                 if (symbol
                     && symbol.valueDeclaration
                     && this.ifPrivate(symbol.valueDeclaration)) {
 
                     let propName = symbol.valueDeclaration['name'].text as string;
                     //当属性访问先于属性定义被编译时,会检查名称中是否含有$，如果没有，那程序可能出问题了
                     //在只改变私有属性时，应该不会出现这种情况
                     if (this.ifCompress(symbol.getJsDocTags())) {
                         if (propName.indexOf("$") < 0) {
                             let newName = this.nameFomate(propName);
                             node = ts.createPropertyAccess(variety, ts.createIdentifier(newName));
                         } else {
                             let props = ts.createIdentifier(propName as string);
                             node = ts.createPropertyAccess(variety, props);
                         }
                     }
                 }
             }
             return ts.visitEachChild(node, visitor, context);
         }
         return visitor;
 
          */
    }

    //以下两种编译模式未来可能需要更新或合并，或者重构一个编译模式的模块
    //普通编译模式
    public compiling(ifRename: boolean): ts.EmitResult {

        console.log(this.options)
        let emitResult: ts.EmitResult

        this.compilerHost = ts.createCompilerHost(this.options);
        let program = ts.createProgram(this.languageServiceHost.getScriptFileNames(), this.options, this.compilerHost);
        if (ifRename) {
            let checker = program.getTypeChecker();
            emitResult = program.emit(undefined, undefined, undefined, undefined, {
                before: [
                    plus.transformPlus(checker),
                    // this.transformAll(checker)
                ]
            });
        } else {
            emitResult = program.emit();
        }

        return emitResult;
    }

    //增量编译模式
    public compilingWithWatch(ifRename: boolean): any {
        if (!ts.sys.watchFile || !ts.sys.watchDirectory) {
            return null;
        }

        let watchCompilerHost = ts.createWatchCompilerHost(this.languageServiceHost.fileNames, this.options, ts.sys);
        const compileUsingBuilder = watchCompilerHost.createProgram;
        watchCompilerHost.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics) => {
            this.languageServiceHost = new LanguageHost(rootNames as string[], this.options);
            this.languageService = ts.createLanguageService(this.languageServiceHost);
            return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics);
        };
        if (ifRename) {
            watchCompilerHost.afterProgramCreate = builderProgram => {
                let checker = builderProgram.getProgram().getTypeChecker();
                builderProgram.emit(undefined, undefined, undefined, undefined, {
                    before: [
                        plus.transformPlus(checker),
                        // this.transformAll(checker)
                    ]
                })
                if (this.compileChildren) {
                    if (!this.firstTime) {
                        this.compiling(ifRename);
                    }
                    this.firstTime = false;
                    this.compileChildren = false;
                }
            };
        } else {
            watchCompilerHost.afterProgramCreate = builderProgram => {
                builderProgram.emit();
            };
        }

        watchCompilerHost.options = this.options;
        ts.createWatchProgram(watchCompilerHost);
    }

    // public transformAll(checker: ts.TypeChecker) {
    //     return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
    //         return (sourceFile: ts.SourceFile) => ts.visitNode(sourceFile, this.renameProperty(sourceFile, context, checker))
    //     }
    // }



    //调用语言服务host对源文件进行更改
    public applyEdits(fileName: string, edits: any[]): number {
        let runningOffset = 0;
        for (let i = 0; i < edits.length; i++) {
            let edit = edits[i];
            let offsetStart = edit.span.start;
            let offsetEnd = offsetStart + edit.span.length;
            this.languageServiceHost.scripts.get(fileName)
            const scriptInfo = this.languageServiceHost.scripts.get(fileName);
            if (scriptInfo) {
                scriptInfo.editContent(offsetStart, offsetEnd, edit.newText);
            }
            let editDelta = edit.newText.length - edit.span.length;
            runningOffset += editDelta;
            //fs.writeFileSync(fileName, this.languageServiceHost.scripts.get(fileName).content);
            // 由于该变动的影响，文件内其他变动的位置也要相应改变
            for (let j = i + 1; j < edits.length; j++) {
                if (edits[j].span.start >= edits[i].span.start) {
                    edits[j].span.start += editDelta;
                }
            }
        }
        return runningOffset;
    }

    //查看是否为私有属性
    public ifPrivate(node: ts.Node): boolean {
        let res = false;
        if (node.modifiers) {
            let modifiers = node.modifiers;
            modifiers.forEach((modify) => {
                if (modify.kind == ts.SyntaxKind.PrivateKeyword) {
                    res = true;
                }
            });
        }
        return res;
    }

    //查看是否有重命名标记
    public ifCompress(tags: ts.JSDocTagInfo[]) {
        let ifCompress = true;
        const uncompressTag = "uncompress";
        tags.forEach((tag) => {
            if (tag.name == uncompressTag) {
                ifCompress = false;
            }
        })
        return ifCompress;
    }

    //检查用户代码类属性中，是否有以$结尾的名称，如果有，则跳过对该属性的处理
    public checkUserProperty(nameNode: ts.Identifier) {
        let name = nameNode.text;
        if (name[name.length - 1] == "$") {
            return false
        } else {
            return true;
        }
    }
    //获取父类私有属性个数，用于初始化计数器
    public getParentPropsNum(node: ts.ClassDeclaration, checker: ts.TypeChecker): number {
        let res = 0;
        let heritageClause = node.heritageClauses;
        if (heritageClause) {
            for (let i = 0; i < heritageClause.length; i++) {
                if (heritageClause[i].token == ts.SyntaxKind.ExtendsKeyword) {
                    heritageClause[i].types.forEach((father) => {
                        res = this.propsRecorder.get(father.getText()) as any as number;
                    })
                }
            }
        }
        return res;
    }

    //计算该文件，当前类当前位置声明的属性由于之前的重命名，产生的位移偏差
    //计算方法：如果变动在该属性原位置之前，则累计
    public computeProducedOffset(fileName: string, previousPosition: number) {
        let offset = 0;
        if (this.ifWriteFile) {
            const scriptInfo = this.languageServiceHost.scripts.get(fileName);
            if (scriptInfo) {
                let changes = scriptInfo.editRanges;
                changes.forEach((change) => {
                    if (previousPosition > change.textChangeRange.span.start) {
                        offset += change.textChangeRange.newLength - change.textChangeRange.span.length;
                    }
                });
            }

        }
        return offset;
    }

    //读取编译配置文件,此处可以为用户提供更改接口或常量
    public getCompilerOptions(optionsPath: string) {
        let jsonString = fs.readFileSync(optionsPath, "utf-8");
        let configFile = ts.parseConfigFileTextToJson(optionsPath, jsonString);
        let options: ts.CompilerOptions = configFile.config as ts.CompilerOptions;
        return options;
    }

    //将命名改变写入原来的ts源文件
    public changeSourceFile(locations: ts.RenameLocation[], newName: string) {
        let eachFileEdits = new Map();
        for (let k = 0; k < locations.length; k++) {
            if (!eachFileEdits.has(locations[k].fileName)) {
                eachFileEdits.set(locations[k].fileName, new OneFileRenameLocations(locations[k].fileName));
            }

            eachFileEdits.get(locations[k].fileName).edits.push({
                span: locations[k].textSpan,
                newText: newName
            });
        }
        eachFileEdits.forEach(function (edit) {
            this.applyEdits(edit.filename, edit.edits);
        }, this);
    }

    //此处格式可以提供更改接口
    public nameFomate(previousName: string): string {
        const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        let secondName = '';
        for (let k = 0; k <= Math.floor(this.times / alphabet.length); k++) {
            secondName += alphabet[this.times % (alphabet.length - 1)];
        }
        this.times++;
        if (this.debugMode) {
            return previousName + "_" + secondName + "$";
        } else {
            return secondName + "$";
        }
    }

    public resetRecorder() {
        this.propsRecorder.clear();
    }

    public resetTimes(start: number) {
        this.times = start;
    }
}