// From http://www.redblobgames.com/grids/hexagons/
// Copyright 2015 Red Blob Games <redblobgames@gmail.com>
// License: Apache v2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
//
// Lua output

using Ast;
import Output.*;

class OutputLua extends Output {
    function new() {}

    
    override function Expr(e:AExpr) {
        return switch (e) {
            case AName(name):
                if (name == "PI") { name = "math.pi"; }
                '$name';
            case AArrayIndex(a, i): '${Expr(a)}[1+${Expr(i)}]';
            case ANew(TArray(_), arguments): '{${[for (a in arguments) Expr(a)].join(", ")}}';
            case ANew(type, arguments):
                var typeName = '${Type(type)}';
                if (typeName == "FractionalHex") { typeName = "Hex"; }
                '$typeName(${[for (a in arguments) Expr(a)].join(", ")})';
            case AFunctionCall("$error", [expr]): 'error(${Expr(expr)})';
            case AFunctionCall("$array_length", [expr]): '#${Expr(expr)}';
            case AFunctionCall("#negate", [expr]): '-${Expr(expr)}';
            case AFunctionCall("#not", [expr]): 'not ${Expr(expr)}';
                // NOTE: Lua 5.3 has an // operator and we could use it here:
                // case AFunctionCall("Int", [AFunctionCall("/", [a, b])]): '${Expr(a)} // ${Expr(b)}';
            case AFunctionCall("round", [expr]): 'math.floor (0.5 + ${Expr(expr)})';
            case AFunctionCall("Float", [expr]): Expr(expr);
            case AFunctionCall(name, arguments):
                if ((name == ">" || name == "==" || name == "!=" || name == "+" || name == "-" || name == "*" || name == "/" || name == "and") && arguments.length == 2) {
                    if (name == "!=") { name = "~="; }
                    '${Expr(arguments[0])} $name ${Expr(arguments[1])}';
                    // NOTE:
                    // * Lua 5.1 doesn't have bitwise and
                    // * Lua 5.2 has bitwise and as bit32.band(a, b)
                    // * Lua 5.3 has an integer type, and bitwise and is a & b
                } else {
                    if (name == "Int") { name = "math.floor"; }
                    if (name == "&") { name = "bitand"; }
                    if (name == "sqrt" || name == "cos" || name == "sin" || name == "abs" || name == "max" || name == "min") { name = "math." + name; }
                    '$name(${[for (a in arguments) Expr(a)].join(", ")})';
                }
            case AMethodCall(name, "push", [argument]):
                'table.insert($name, ${Expr(argument)})';
            case AMethodCall(name, method, arguments):
                '$name.$method(${[for (a in arguments) Expr(a)].join(", ")})';
            case _: super.Expr(e);
        };
    }


    function Stmt(level:Int, e:AExpr) {
        return switch (e) {
            case ABlock(exprs): [for (e in exprs) Stmt(level, e)].join("");
            case AFor(name, AFunctionCall("#range", [begin, end]), body):
                var endExpr = switch (end) {
                    case AFunctionCall("+", [end, AInt(1)]): '${Expr(end)}';
                    case AInt(num): '${num-1}';
                    default: '${Expr(end)} - 1';
                }
                '${indent(level)}for $name = ${Expr(begin)}, $endExpr do\n${Stmt(1+level, body)}${indent(level)}end\n';
            case AFor(name, range, body): '${indent(level)}for $name in ${Expr(range)} do\n${Stmt(1+level, body)}${indent(level)}end\n';
            case AIf(test, ifTrue, ABlock([])): '${indent(level)}if ${Expr(test)} then\n${Stmt(1+level, ifTrue)}${indent(level)}end\n';
            case AIf(test, ifTrue, ifFalse): '${indent(level)}if ${Expr(test)} then\n${Stmt(1+level, ifTrue)}${indent(level)}else\n${Stmt(1+level, ifFalse)}${indent(level)}end\n';
            case AFunctionCall("set!", [AName(name), expr]): '${indent(level)}$name = ${Expr(expr)}\n';
            case AFunctionCall("set!", [AArrayIndex(AName(name), index), expr]): '${indent(level)}$name[1+${Expr(index)}] = ${Expr(expr)}\n';
            case AVar(name, _, init): '${indent(level)}local $name = ${Expr(init)}\n';
            case AReturn(expr): '${indent(level)}return ${Expr(expr)}\n';
            case _: '${indent(level)}${Expr(e)}\n';
        };
    }

    
    // Statements (block, for, if, var) will return spaces + ... + "\n"
    // Decls (fn, static var)
    function Decl(decl:DExpr) {
        return switch(decl) {
            case DMethod(name, type, params, body): 'function $name (${[for (p in params) p.name].join(", ")})\n${Stmt(1, body)}end\n\n';
            case DStaticVar(name, type, init): '$name = ${Expr(init)}\n';
            case DField(name, type): ''; // These are handled elsewhere
            case DPrecondition(expr, msg): ''; // These are handled elsewhere
            case _: '#unknown';
        };
    }

    
    function output(classname:String, decls:Array<DExpr>) {
        var output = [for (decl in decls) Decl(decl)].join("");
        var fields = collectFields(decls);
        if (fields.length > 0) {
            var preconditions = collectPreconditions(decls);
            var preconditionTest = (preconditions != null)? '${indent(1)}assert(not (${Expr(preconditions.test)}), ${Expr(preconditions.message)})\n' : '';
            var fieldNames:Array<String> = [for (f in fields) f.name];
            var parameters:String = [for (f in fieldNames) '${f}'].join(", ");
            var init:String = [for (f in fieldNames) '${f} = ${f}'].join(", ");
            output = '\n\nfunction ${classname}(${parameters})\n${preconditionTest}${indent(1)}return {${init}}\nend\n\n' + output;
        }
        return output;
    }

    
    public static function main() {
        var output = new OutputLua();
        
        Sys.println('-- Generated code -- CC0 -- No Rights Reserved -- http://www.redblobgames.com/grids/hexagons/
function bitand(a, b)
    -- For lua >= 5.3 use this:
    -- return a & b
    -- For lua 5.2 and luau use this:
    return bit32.band(a, b)
end
');
        
        Output.outputAll(output.output, true);

        Sys.println('

-- Tests

function complain (name)
    print("FAIL ", name)
end
');
            
        Output.outputTests(output.output);

        Sys.println('
test_all()
');
        
    }
}
