module otya.smilebasic.node;
import otya.smilebasic.type;
import otya.smilebasic.token;
import std.container;

enum NodeType
{
    Node,
    Expression,
    Constant,
    BinaryOperator,
    Variable,
    CallFunction,
    VoidExpression,
    UnaryOperator,
    IndexExpressions,

    Statements,
    FunctionBody,
    Assign,
    CallFunctionStatement,
    Print,
    Label,
    Goto,
    If,
    For,
    Gosub,
    Return,
    End,
    Break,
    Continue,
    Var,
    DefineVariable,
    DefineArray,
    ArrayAssign,
    DefineFunction,
    While,
    Inc,
    Data,
    Read,
    Restore,
    On,
    Input,
    RepeatUntil,
    Option,
    Swap,
    AssignRef,
    VarRef,
    XOn,
    XOff,
    Exec,
    Use,
    Linput,
    CallSprite,
    CallBG,
    StopStatement,
}
abstract class Node
{
    NodeType type;
    //式は複数行に渡って描けないのでStatementに置くべき
    SourceLocation location;
}
abstract class Expression : Node
{
}
class Constant : Expression
{
    TokenValue value;
    this(TokenValue v, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Constant;
        this.value = v;
    }
}
class BinaryOperator : Expression
{
    Expression item1;
    TokenType operator;
    Expression item2;
    this(BinaryOperator bop, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.BinaryOperator;
        this.item1 = bop.item1;
        this.operator = bop.operator;
        this.item2 = bop.item2;
    }
    this(Expression i1, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.BinaryOperator;
        this.item1 = i1;
    }
    this(Expression i1, TokenType o, Expression i2, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.BinaryOperator;
        this.item1 = i1;
        this.operator = o;
        this.item2 = i2;
    }
}
class UnaryOperator : Expression
{
    TokenType operator;
    Expression item;
    this(TokenType o, Expression i, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.UnaryOperator;
        this.operator = o;
        this.item = i;
    }
}
class Variable : Expression
{
    wstring name;
    this(wstring n, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Variable;
        this.name = n;
    }
}
class CallFunction : Expression
{
    wstring name;
    Expression[] args;
    this(wstring n, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.CallFunction;
        this.name = n;
        args = new Expression[0];
    }
    void addArg(Expression arg)
    {
        args ~= arg;
    }
}
class VoidExpression : Expression
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.VoidExpression;
    }
}
abstract class Statement : Node
{
    static Statement NOP = null;
}
class Statements : Statement
{
    Statement[] statements;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Statements;
        statements = new Statement[0];
    }
    void addStatement(Statement statement)
    {
        statements ~= statement;
    }
}

enum PrintArgumentType
{
    Expression,//exp
    Line,
    Tab,//,
}
/*
*;は基本無視
*/
struct PrintArgument
{
    PrintArgumentType type;
    Expression expression;
    this(PrintArgumentType type)
    {
        this.type = type;
        this.expression = null;
    }
    this(PrintArgumentType type, Expression expression)
    {
        this.type = type;
        this.expression = expression;
    }
}
/*
*PRINTは関数ではないしmkIIと違って必ず:がいる
*関数と違ってFUNC(,,,)のような記述はエラー
*と言っても3.2で上の記述もエラーになりうる
*/

class Print : Statement
{
    PrintArgument[] args;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Print;
        args = new PrintArgument[0];
    }
    void addArgument(Expression expression)
    {
        args ~= PrintArgument(PrintArgumentType.Expression, expression);
    }
    void addTab()
    {
        args ~= PrintArgument(PrintArgumentType.Tab);
    }
    void addLine()
    {
        args ~= PrintArgument(PrintArgumentType.Line);
    }
}
class Assign : Statement
{
    wstring name;
    Expression expression;
    this(wstring name, Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Assign;
        this.name = name;
        this.expression = expr;
    }
}
class AssignRef : Statement
{
    Expression left;
    Expression expression;
    this(Expression left, Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.AssignRef;
        this.left = left;
        this.expression = expr;
    }
}
class Label : Statement
{
    wstring label;
    this(wstring name, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Label;
        this.label = name;
    }
}
class Goto : Statement
{
    wstring label;
    this(wstring name, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Goto;
        this.label = name;
    }
    Expression labelexpr;
    this(Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Goto;
        this.labelexpr = expr;
    }
}
class If : Statement
{
    import std.typecons;
    Expression condition;
    Statements then;
    Statements else_;
    Tuple!(Statements, Expression)[] elseif;
    this(Expression condition, Statements t, Statements e, Tuple!(Statements, Expression)[] elif, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.If;
        this.condition = condition;
        this.then = t;
        this.else_ = e;
        this.elseif = elif;
    }
    bool hasElse()
    {
        return !(else_ is null) && else_.statements.length != 0;
    }
    bool hasElseif()
    {
        return !(elseif is null) && elseif.length != 0;
    }
}
class For : Statement
{
    Assign initExpression;
    Expression toExpression;
    Expression stepExpression;
    Statements statements;
    this(Assign assign, Expression toExpression, Expression stepExpression, Statements statements, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.For;
        this.initExpression = assign;
        this.toExpression = toExpression;
        this.stepExpression = stepExpression;
        this.statements = statements;
    }
    this(Assign assign, Expression toExpression, Statements statements, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.For;
        this.initExpression = assign;
        this.toExpression = toExpression;
        this.stepExpression = null;
        this.statements = statements;
    }
}
class Gosub : Statement
{
    wstring label;
    this(wstring name, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Gosub;
        this.label = name;
    }
    Expression labelexpr;
    this(Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Gosub;
        this.labelexpr = expr;
    }
}
class Return : Statement
{
    Expression expression;
    this(Expression expression, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Return;
        this.expression = expression;
    }
}
class End : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.End;
    }
}
class Break : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Break;
    }
}
class Continue : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Continue;
    }
}
class Var : Statement
{
    Statement[] define;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Var;
        this.define = new Statement[0];
    }
    void addDefineVar(DefineVariable dv)
    {
        this.define ~= dv;
    }
    void addDefineArray(DefineArray da)
    {
        this.define ~= da;
    }
}
class DefineVariable : Statement
{
    wstring name;
    Expression expression;
    this(wstring name, Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.DefineVariable;
        this.name = name;
        this.expression = expr;
    }
}
class DefineArray : Statement
{
    wstring name;
    IndexExpressions dim;
    this(wstring name, IndexExpressions dim, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.DefineArray;
        this.name = name;
        this.dim = dim;
    }
}
class IndexExpressions : Expression
{
    Expression[] expressions;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.IndexExpressions;
        this.expressions = new Expression[0];
    }
    void addExpression(Expression expr)
    {
        this.expressions ~= expr;
    }
}
class ArrayAssign : Statement
{
    wstring name;
    IndexExpressions indexExpression;
    Expression assignExpression;
    this(wstring name, IndexExpressions expr, Expression assign, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.ArrayAssign;
        this.name = name;
        this.indexExpression = expr;
        this.assignExpression = assign;
    }
}
class DefineFunction : Statement
{
    wstring[] arguments;
    wstring[] outArguments;
    wstring name;
    Statements functionBody;
    bool returnExpr;
    bool isCommon;
    this(wstring name, bool returnExpr, bool isCommon, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.DefineFunction;
        this.name = name;
        this.returnExpr = returnExpr;
        this.isCommon = isCommon;
    }
    this(wstring name, bool isCommon, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.DefineFunction;
        this.name = name;
        this.returnExpr = false;
        this.isCommon = isCommon;
    }
    void addArgument(wstring name)
    {
        this.arguments ~= name;
    }
    void addOutArgument(wstring name)
    {
        this.outArguments ~= name;
    }
}
class CallFunctionStatement : Statement
{
    wstring name;
    Expression[] args;
    Expression[] outVariable;
    this(wstring n, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.CallFunctionStatement;
        this.name = n;
        args = new Expression[0];
    }
    void addArg(Expression arg)
    {
        args ~= arg;
    }
    void addOut(Expression var)
    {
        outVariable ~= var;
    }
}
class While : Statement
{
    Expression condExpression;
    Statements statements;
    this(Expression condExpression, Statements statements, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.While;
        this.condExpression = condExpression;
        this.statements = statements;
    }
}
class Inc : Statement
{
    Expression name;
    Expression expression;
    this(Expression name, Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Inc;
        this.name = name;
        this.expression = expr;
    }
}
class Data : Statement
{
    TokenValue[] data;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Data;
        this.data = new TokenValue[0];
    }
    void addData(TokenValue v)
    {
        data ~= v;
    }
}
class Read : Statement
{
    Expression[] variables;
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Read;
    }
    void addVariable(Expression lvalue)
    {
        variables ~= lvalue;
    }
}
class Restore : Statement
{
    Expression label;
    this(Expression label, SourceLocation loc)
    {
        super.location = loc;
        this.label = label;
        this.type = NodeType.Restore;
    }
}
class On : Statement
{
    Expression condition;
    bool isGosub;
    wstring[] labels;
    this(Expression expr, bool isgosub, SourceLocation loc)
    {
        super.location = loc;
        this.condition = expr;
        this.isGosub = isgosub;
        this.labels = new wstring[0];
        this.type = NodeType.On;
    }
    void addLabel(wstring label)
    {
        this.labels ~= label;
    }
}
class Input : Statement
{
    Expression message;
    bool question;
    Expression[] variables;
    this(Expression message, bool question, SourceLocation loc)
    {
        super.location = loc;
        this.message = message;
        this.question = question;
        this.variables = new Expression[0];
        this.type = NodeType.Input;
    }
    void addVariable(Expression lvalue)
    {
        variables ~= lvalue;
    }
}

class RepeatUntil : Statement
{
    Expression condExpression;
    Statements statements;
    this(Expression condExpression, Statements statements, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.RepeatUntil;
        this.condExpression = condExpression;
        this.statements = statements;
    }
}

class Option : Statement
{
    wstring argument;
    this(wstring arg, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Option;
        argument = arg;
    }
}

class Swap : Statement
{
    Expression item1;
    Expression item2;
    this(Expression item1, Expression item2, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Swap;
        this.item1 = item1;
        this.item2 = item2;
    }
}
class VarRef : Expression
{
    Expression expression;
    this(Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.VarRef;
        this.expression = expr;
    }
}
class XOn : Statement
{
    wstring func;
    this(wstring func, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.XOn;
        this.func = func;
    }
}
class XOff : Statement
{
    wstring func;
    this(wstring func, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.XOff;
        this.func = func;
    }
}
class Exec : Statement
{
    Expression expression;
    this(Expression e, SourceLocation loc)
    {
        super.location = loc;
        expression = e;
        this.type = NodeType.Exec;
    }
}
class Use : Statement
{
    Expression expression;
    this(Expression e, SourceLocation loc)
    {
        super.location = loc;
        expression = e;
        this.type = NodeType.Use;
    }
}
class Linput : Statement
{
    bool hasGuide;
    Expression guide;
    Expression expression;
    this(Expression guide, Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Linput;
        this.hasGuide = true;
        this.guide = guide;
        this.expression = expr;
    }
    this(Expression expr, SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.Linput;
        this.hasGuide = false;
        this.expression = expr;
    }
}
class CallSprite : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.CallSprite;
    }
}
class CallBG : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.CallBG;
    }
}
class StopStatement : Statement
{
    this(SourceLocation loc)
    {
        super.location = loc;
        this.type = NodeType.StopStatement;
    }
}
