const fs = require('fs');
const types = require("@babel/types");
const parser = require("@babel/parser");
const template = require("@babel/template").default;
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;


//js混淆代码读取
process.argv.length > 2 ? encodeFile = process.argv[2] : encodeFile = "./main.js";  //默认的js文件
// process.argv.length > 2 ? encodeFile = process.argv[2] : encodeFile = "./decodeResult22.js";  //默认的js文件
process.argv.length > 3 ? decodeFile = process.argv[3] : decodeFile = encodeFile.replace(".js", "") + "_ok.js";

//将源代码解析为AST
let sourceCode = fs.readFileSync(encodeFile, { encoding: "utf-8" });
let ast = parser.parse(sourceCode);
console.time("处理完毕，耗时");


const keyToLiteral = {
	MemberExpression:
	{
		exit({ node }) {
			const prop = node.property;
			if (!node.computed && types.isIdentifier(prop)) {
				node.property = types.StringLiteral(prop.name);
				node.computed = true;
			}
		}
	},
	ObjectProperty:
	{
		exit({ node }) {
			const key = node.key;
			if (!node.computed && types.isIdentifier(key)) {
				node.key = types.StringLiteral(key.name);
				return;
			}
			if (node.computed && types.isStringLiteral(key)) {
				node.computed = false;
			}
		}
	},
}

traverse(ast, keyToLiteral);


const standardLoop =
{
	"ForStatement|WhileStatement|ForInStatement|ForOfStatement"({ node }) {
		if (!types.isBlockStatement(node.body)) {
			node.body = types.BlockStatement([node.body]);
		}
	},
	IfStatement(path) {
		const consequent = path.get("consequent");
		const alternate = path.get("alternate");
		if (!consequent.isBlockStatement()) {
			consequent.replaceWith(types.BlockStatement([consequent.node]));
		}
		if (alternate.node !== null && !alternate.isBlockStatement()) {
			alternate.replaceWith(types.BlockStatement([alternate.node]));
		}
	},
}

traverse(ast, standardLoop);


const DeclaratorToDeclaration =
{
	VariableDeclaration(path) {
		let { parentPath, node } = path;
		if (!parentPath.isBlock()) {
			return;
		}
		let { declarations, kind } = node;

		if (declarations.length == 1) {
			return;
		}

		let newNodes = [];

		for (const varNode of declarations) {
			let newDeclartionNode = types.VariableDeclaration(kind, [varNode]);
			newNodes.push(newDeclartionNode);
		}

		path.replaceWithMultiple(newNodes);

	},
}

traverse(ast, DeclaratorToDeclaration);



function SequenceOfStatement(path) {
	let { scope, parentPath, node } = path;
	if (parentPath.parentPath.isLabeledStatement()) {//标签节点无法往前插入。
		return;
	}
	let expressions = node.expressions;
	if (parentPath.isReturnStatement({ "argument": node }) ||
		parentPath.isThrowStatement({ "argument": node })) {
		parentPath.node.argument = expressions.pop();
	}
	else if (parentPath.isIfStatement({ "test": node }) ||
		parentPath.isWhileStatement({ "test": node })) {
		parentPath.node.test = expressions.pop();
	}
	else if (parentPath.isForStatement({ "init": node })) {
		parentPath.node.init = expressions.pop();
	}
	else if (parentPath.isForInStatement({ "right": node }) ||
		parentPath.isForOfStatement({ "right": node })) {
		parentPath.node.right = expressions.pop();
	}
	else if (parentPath.isSwitchStatement({ "discriminant": node })) {
		parentPath.node.discriminant = expressions.pop();
	}

	else if (parentPath.isExpressionStatement({ "expression": node })) {
		parentPath.node.expression = expressions.pop();
	}
	else {
		return;
	}

	for (let expression of expressions) {
		parentPath.insertBefore(types.ExpressionStatement(expression = expression));
	}
}


function SequenceOfExpression(path) {
	let { scope, parentPath, node, parent } = path;
	let ancestorPath = parentPath.parentPath;
	let expressions = node.expressions;
	if (parentPath.isConditionalExpression({ "test": node }) &&
		ancestorPath.isExpressionStatement({ "expression": parent })) {
		parentPath.node.test = expressions.pop();
	}
	else if (parentPath.isVariableDeclarator({ "init": node }) &&
		ancestorPath.parentPath.isBlock()) {
		parentPath.node.init = expressions.pop();
	}
	else if (parentPath.isAssignmentExpression({ "right": node }) &&
		ancestorPath.isExpressionStatement({ "expression": parent })) {
		parentPath.node.right = expressions.pop();
	}
	else if ((parentPath.isCallExpression({ "callee": node }) ||
		parentPath.isNewExpression({ "callee": node })) &&
		ancestorPath.isExpressionStatement({ "expression": parent })) {
		parentPath.node.callee = expressions.pop();
	}
	else {
		return;
	}

	for (let expression of expressions) {
		ancestorPath.insertBefore(types.ExpressionStatement(expression = expression));
	}
}



const resolveSequence =
{
	SequenceExpression:
	{//对同一节点遍历多个方法
		exit: [SequenceOfStatement, SequenceOfExpression]
	}
}

traverse(ast, resolveSequence);


const removeDeadCode = {
	"IfStatement|ConditionalExpression"(path) {
		let { consequent, alternate } = path.node;
		let testPath = path.get('test');
		const evaluateTest = testPath.evaluateTruthy();
		if (evaluateTest === true) {
			if (types.isBlockStatement(consequent)) {
				consequent = consequent.body;
			}
			path.replaceWithMultiple(consequent);
			return;
		}
		if (evaluateTest === false) {
			if (alternate != null) {
				if (types.isBlockStatement(alternate)) {
					alternate = alternate.body;
				}
				path.replaceWithMultiple(alternate);
			}
			else {
				path.remove();
			}
		}
	},
	"LogicalExpression"(path) {
		let { left, operator, right } = path.node;

		let leftPath = path.get('left');

		const evaluateLeft = leftPath.evaluateTruthy();

		if ((operator == "||" && evaluateLeft == true) ||
			(operator == "&&" && evaluateLeft == false)) {
			path.replaceWith(left);
			return;
		}
		if ((operator == "||" && evaluateLeft == false) ||
			(operator == "&&" && evaluateLeft == true)) {
			path.replaceWith(right);
		}
	},
	"EmptyStatement|DebuggerStatement"(path) {
		path.remove();
	},
}

traverse(ast, removeDeadCode);  //PS：因为有赋值语句和定义语句同时存在，因此该插件可能需要运行多次才能删除干净。


const simplifyLiteral = {
	NumericLiteral({ node }) {
		if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
			node.extra = undefined;
		}
	},
	StringLiteral({ node }) {
		if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
			node.extra = undefined;
		}
	},
}


traverse(ast, simplifyLiteral);

function isNodePure(node, scope) {
    if (types.isLiteral(node)) {
        return true;
    }
	return false
    if (types.isUnaryExpression(node)) {
        return isNodePure(node.argument, scope)
    }

    if (types.isIdentifier(node)) {//处理 var c = String;
        if (scope && scope.isPure(node, true)) {
            return true;
        }

        if (typeof this[node.name] != 'undefined') {
            return true;
        }

        return false;
    }

    if (types.isMemberExpression(node)) {//处理 var d = String.fromCharCode;

        let {object, property, computed} = node;

        if (computed && !isNodePure(property, scope)) {
            return false;
        }
        if (isNodePure(object, scope)) {
            return true;
        }

        if (types.isIdentifier(object)) {

            let name = object.name;

            if (typeof this[name] != 'undefined' && name != 'window') {//注意object为window时，可能会还原出错
                return true;
            }

            return false;
        }
        if (types.isMemberExpression(object)) {
            return isNodePure(object, scope);
        }

        return false;
    }

    if (types.isBinary(node) && scope) {
        return isNodePure(node.left, scope) && isNodePure(node.right, scope);
    }

    return false;
}

traverse(ast, {
    VariableDeclaration(path) {
        let {node} = path;
        let {declarations} = node;
        let res = [];
        // console.log(path.parentPath.type)
        if (types.isForStatement(path.parentPath)) return
        if (!declarations || declarations.length == 1) return
        for (let i = 0; i < declarations.length; i++) {
            let declaration = declarations[i];
            res.push(types.VariableDeclaration('var', [declaration]))
        }
        path.replaceWithMultiple(res)
        path.skip()

    }
})
traverse(ast, {
    VariableDeclaration(path) {
        let {node} = path;
        let {declarations} = node;
        let res = [];
        // console.log(path.parentPath.type)
        if (types.isForStatement(path.parentPath)) return
        if (!declarations || declarations.length == 1) return
        for (let i = 0; i < declarations.length; i++) {
            let declaration = declarations[i];
            res.push(types.VariableDeclaration('var', [declaration]))
        }
        path.replaceWithMultiple(res)
        path.skip()

    }
})


const restoreVarDeclarator = {

    VariableDeclarator(path) {
        let scope = path.scope;
        let {id, init} = path.node;
        if (!init && (!types.isNumericLiteral(init) || !types.isStringLiteral(init))) return
        if (id.name === 'c' && init.value === 0) return
        if (!types.isIdentifier(id) || !isNodePure(init, scope)) {
            return;
        }

        const binding = scope.getBinding(id.name);
        try {
            var {
                constant, referencePaths, constantViolations
            } = binding;  //变量的定义一定会有binding.

        } catch (e) {
            return;
        }


        if (constantViolations.length > 1) {
            return;
        }
		if (init.name === 'i')return
        if (constant || constantViolations[0] == path) {
            for (let referPath of referencePaths) {
                // console.log(init.value)
                // console.log(id.name, referPath.type, generator(referPath.container).code,  generator(init).code)
                referPath.replaceWith(init);
            }
            // console.log(path.toString())
            // path.remove();//没有被引用，或者替换完成，可直接删除
        }
    },
}
traverse(ast, restoreVarDeclarator)

//还原object
function isBaseLiteral(node) {
    if (types.isLiteral(node)) {
        return true;
    }
    if (types.isUnaryExpression(node, {operator: "-"}) ||
        types.isUnaryExpression(node, {operator: "+"})) {
        return isBaseLiteral(node.argument);
    }

    return false;
}

const decodeValueOfObject =
{//当一个object里面的value全部为字面量时的还原，没有考虑单个key重新赋值的情况。
    VariableDeclarator(path) {
        let { node, scope } = path;
        const { id, init } = node;
        if (!types.isObjectExpression(init)) return;
		console.log(id.name)
        let properties = init.properties;
        if (properties.length == 0 || !properties.every(property => isBaseLiteral(property.value)))
            return;

        let binding = scope.getBinding(id.name);
		if (!binding)return;
        let { constant, referencePaths } = binding;
        if (!constant) return;

        let newMap = new Map();
        for (const property of properties) {
            let { key, value } = property;
            newMap.set(key.value, value);
        }
		console.log(id.name, properties.length, properties[properties.length-1].key.value)
		// console.log(properties[properties.length-1])
		// console.log(generator(node).code)
        let canBeRemoved = true;
        for (const referPath of referencePaths) {
            let { parentPath } = referPath;
            if (!parentPath.isMemberExpression()) {
                canBeRemoved = false;
                return;
            }

            let AncestorPath = parentPath.parentPath;

            if (AncestorPath.isAssignmentExpression({"left":parentPath.node}))
            {
            	  canBeRemoved = false;
                return;
            }
            if (AncestorPath.isUpdateExpression() && ['++','--'].includes(AncestorPath.node.operator))
            {
            	  canBeRemoved = false;
                return;
            }

            let curKey = parentPath.node.property.value;
            if (!newMap.has(curKey)) {
                canBeRemoved = false;
                break;
            }
            parentPath.replaceWith(newMap.get(curKey));
        }
        canBeRemoved && path.remove();
        newMap.clear();
    },
}


traverse(ast, decodeValueOfObject);


console.timeEnd("处理完毕，耗时");
let { code } = generator(ast, opts = {
	"compact": false,  // 是否压缩代码
	"comments": false,  // 是否保留注释
	"jsescOption": { "minimal": true },  //Unicode转义
});

fs.writeFile(decodeFile, code, (err) => { });