import { parse } from './jass';
import ast from './ast';

function recompileOperator(operator) {
    if (operator === 'and') {
        return '&&';
    } else if (operator === 'or') {
        return '||';
    } else if (operator === 'not') {
        return '!';
    } else {
        // The other operators work the same in Jass and JS.
        return operator;
    }
}

function recompileNode(node, info) {
    let result = '';

    if (node instanceof ast.Program) {
        result += `${node.blocks.map((block) => recompileNode(block, info)).join('\n')}\n`;
    } else if (node instanceof ast.Globals) {
        result += `${node.variables.map((variable) => recompileNode(variable, info)).join('\n')}\n`;
    } else if (node instanceof ast.FunctionDefinition) {
        info[node.name] = { type: 'function' };

        result += `function ${node.name}(${node.args.map((arg) => recompileNode(arg, info)).join(', ')}) {\n${node.body.map((statement) => recompileNode(statement, info)).join('\n')}\n}\n`;
    } else if (node instanceof ast.FunctionArgument) {
        result += node.name;
    } else if (node instanceof ast.IfThenElse) {
        result += `if (${recompileNode(node.condition, info)}) {\n${node.thenActions.map((action) => recompileNode(action, info)).join('\n')}\n}`;

        if (node.elseActions.length) {
            result += ` else {\n${node.elseActions.map((action) => recompileNode(action, info)).join('\n')}\n}`;
        }
    } else if (node instanceof ast.SetVariable) {
        result += `${node.name} = ${recompileNode(node.value, info)}`;
    } else if (node instanceof ast.SetVariableArray) {
        result += `${node.name}[${recompileNode(node.index, info)}] = ${recompileNode(node.value, info)}`;
    } else if (node instanceof ast.Literal) {
        if (node.type === 'string') {
            result += `"${node.value}"`;
        } else {
            result += node.value;
        }
    } else if (node instanceof ast.GetVariable) {
        result += node.name;
    } else if (node instanceof ast.GetVariableArray) {
        result += `${node.name}[${recompileNode(node.index, info)}]`;
    } else if (node instanceof ast.Return) {
        result += `return ${recompileNode(node.value, info)}`;
    } else if (node instanceof ast.Infix) {
        result += `${recompileNode(node.lhs, info)} ${recompileOperator(node.operator)} ${recompileNode(node.rhs, info)}`;
    } else if (node instanceof ast.VariableDefinition) {
        if (node.isLocal) {
            result += 'let ';
        }
    
        result += node.name;
    
        if (node.isArray) {
            result += ' = []';
        } else if (node.value !== null) {
            result += ' = ' + recompileNode(node.value, info);
        }
    } else if (node instanceof ast.Loop) {
        result += `while (true) {\n${node.actions.map((action) => recompileNode(action, info)).join('\n')}\n}`;
    } else if (node instanceof ast.ExitWhen) {
        result += `if (${recompileNode(node.condition, info)}) { break }`;
    } else if (node instanceof ast.FunctionCall) {
        let funcInfo = info[node.name];

        if (!funcInfo) {
            console.warn(`Can't resolve function "${node.name}"`);
        } else if (funcInfo.type === 'native') {
            // Prepend native calls with a natives object that will be supplied at run-time.
            result += 'natives.';
        }

        result += `${node.name}(${node.args.map((arg) => recompileNode(arg, info)).join(', ')})`;
    } else if (node instanceof ast.FunctionReference) {
        result += node.name;
    
    } else if (node instanceof ast.Unary) {
        result += `${recompileOperator(node.operator)}${recompileNode(node.value, info)}`;
    } else if (node instanceof ast.Type) {
        // ?
    } else if (node instanceof ast.Native) {
        info[node.name] = { type: 'native' };
    } else if (node instanceof ast.FunctionCallStatement) {
        result += recompileNode(node.functionCall, info);
    }

    return result;
}

export default function recompile(jassCode) {
    let info = {};

    return { jsCode: recompileNode(parse(jassCode), info), info} ;
}
