Interp :: struct {
   allocator: Allocator;

   toplevel: []*Node;
   global:   *Interp_Scope;
}

Interp_Scope :: struct {
   parent:   *Interp_Scope;
   bindings: kv.Kv(string, *Interp_Value);
}

Interp_Value :: struct {
   kind: Kind;
   union {
      b: bool;
      i: s64;
      u: u64;
      f: float64;
      s: string;
      p: *void;
      proc: *Node_Procedure;
      val:  *Interp_Value;
   }

   Kind :: enum {
      none;
      nil;
      bool;
      int;
      float;
      string;
      pointer;
      procedure;
      value;
   }
}

init :: (i: *Interp, allocator: Allocator) {
   value_nil = make_interp_value(i, .nil);

   value_true   = make_interp_value(i, .bool);
   value_true.b = true;

   value_false   = make_interp_value(i, .bool);
   value_false.b = false;

   i.global = make_scope(null,, allocator = allocator);
}

interp_program :: (i: *Interp) {
   for i.toplevel {
      interp_statement(i, it, i.global);
   }
}

interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
   if stmt.kind == {
      case .variable;
         var  := stmt.(*Node_Var);
         sym  := var.symbol;
         basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring symbol '%'", sym.str); // @errors

         value := value_nil;
         if var.value_expr != null {
            value = interp_expression(i, var.value_expr, scope);
            basic.assert(value != null); // @errors
         }

         kv.set(*scope.bindings, sym.str, value);

      case .assign;
         assign := stmt.(*Node_Assign);

         src := interp_expression(i, assign.src, scope);
         basic.assert(src != null); // @errors

         dst := interp_lvalue(i, assign.dst, scope);
         basic.assert(dst != null); // @errors
         basic.assert(dst.kind == .value); // @errors

         // @todo: typechecking
         basic.assert(assign.op.kind == .equal, "unexpected assign op: %", assign.op.kind);
         dst.val.* = src.*;

      case .procedure;
         proc := stmt.(*Node_Procedure);
         sym  := proc.symbol;
         basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring procedure '%'", sym.str);

         value := make_interp_value(i, .procedure);
         value.proc = proc;
         kv.set(*scope.bindings, sym.str, value);

      case .print;
         print := stmt.(*Node_Print);
         expr  := interp_expression(i, print.expr, scope);
         if expr == null return;

         if expr.kind == {
            case .none; // do nothing
            case .nil;    basic.print("nil");
            case .bool;   basic.print("%", expr.b);
            case .int;    basic.print("%", expr.i);
            case .float;  basic.print("%", expr.f);
            case .string; basic.print("%", expr.s);
            case; basic.assert(false, "unhandled interp value kind: %", expr.kind);
         }

         basic.print("\n");

      case .assert;
         assert := stmt.(*Node_Assert);
         expr   := interp_expression(i, assert.expr, scope);
         basic.assert(expr != null, "runtime assertion failed");
         basic.assert(expr.kind == .bool, "non-boolean expression given to assert");
         basic.assert(expr.b, "runtime assertion failed");

      case;
         interp_expression(i, stmt, scope);
         // basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors
   }
}

interp_lvalue :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value {
   if expr.kind == {
      case .symbol;
         sym := expr.(*Node_Symbol);
         lval := find_symbol(scope, sym.str);
         oval := make_interp_value(i, .value);
         oval.val = lval;
         return oval;

      case;
         basic.assert(false, "unable to get lvalue from %", expr.kind);
   }

   return null;
}

interp_expression :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value {
   if expr.kind == {
      case .procedure_call;
         call := expr.(*Node_Procedure_Call);
         args := call.all_arguments;

         // @temp
         sym  := call.call_expr.(*Node_Symbol);
         basic.assert(sym.kind == .symbol, "%", sym.kind);

         value := find_symbol(scope, sym.str);
         basic.assert(value != null, "procedure didn't exists '%'", sym.str);
         basic.assert(value.kind == .procedure, "attempt to call non procedure '%'", sym.str);

         result := value_nil;
         proc   := value.proc;
         basic.assert(proc.arguments.count == args.count, "argument mismatch. expected %, given %", proc.arguments.count, args.count); // @errors

         proc_scope := make_scope(scope);
         for proc.arguments {
            kv.set(*proc_scope.bindings, it.symbol.str, interp_expression(i, args[it_index], scope));
         }

         for expr: proc.block.body {
            if expr.kind == .return_ {
               ret := expr.(*Node_Return);
               if ret.values.count != 0 {
                  result = interp_expression(i, ret.values[0], proc_scope);
               }

               break;
            }
            else {
               interp_statement(i, expr, proc_scope);
            }
         }

         return result;

      case .unary;
         do_unop :: (code: Code) #expand {
            if rhs.kind == {
               case .int;
                  right := rhs.i;
                  res.i = #insert,scope() code;
               case .float;
                  right := rhs.f;
                  res.f = #insert,scope() code;
               case;
                  basic.assert(false, "cannot use unary operator '%' on values of type '%'", un.op, rhs.kind);
            }
         }

         un  := expr.(*Node_Unary);
         rhs := interp_expression(i, un.right, scope);
         res := make_interp_value(i, rhs.kind);
         if un.op.kind == {
            case .plus;  do_unop(#code ifx right < 0 then -right else right);
            case .minus; do_unop(#code -right);
            case; basic.assert(false, "unhandled unary operator '%'", un.op.str); // @errors
         }

         return res;

      case .binary;
         bin := expr.(*Node_Binary);
         lhs := interp_expression(i, bin.left, scope);
         rhs := interp_expression(i, bin.right, scope);
         basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors

         do_binop :: (code: Code) #expand {
            if lhs.kind == {
               case .int;
                  left  := lhs.i;
                  right := rhs.i;
                  res.i = #insert,scope() code;
               case .float;
                  left  := lhs.f;
                  right := rhs.f;
                  res.f = #insert,scope() code;
               case;
                  basic.assert(false, "cannot use binary operator '%' on values of type '%'", bin.op, lhs.kind);
            }
         }

         res := make_interp_value(i, lhs.kind);
         if bin.op.kind == {
            case .plus;  do_binop(#code left + right);
            case .minus; do_binop(#code left - right);
            case .star;  do_binop(#code left * right);

            case .f_slash;
               basic.assert(rhs.i != 0, "divide by zero"); // @errors
               do_binop(#code left / right);

            case .percent;
               basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind);
               res.i = lhs.i % rhs.i;

            // @todo: typechecking
            case .equal_equal;
               res.kind = .bool;
               res.b = lhs.i == rhs.i;
            case .bang_equal;
               res.kind = .bool;
               res.b = lhs.i != rhs.i;
            case .less;
               res.kind = .bool;
               res.b = lhs.i < rhs.i;
            case .less_equal;
               res.kind = .bool;
               res.b = lhs.i <= rhs.i;
            case .more;
               res.kind = .bool;
               res.b = lhs.i > rhs.i;
            case .more_equal;
               res.kind = .bool;
               res.b = lhs.i >= rhs.i;

            case; basic.assert(false, "unhandled binary operator '%'", bin.op.str);
         }

         return res;

      case .symbol;
         sym := expr.(*Node_Symbol);

         value := find_symbol(scope, sym.str);
         basic.assert(value != null, "use of undeclared symbol '%'", sym.str); // @errors
         return value;

      case .literal;
         lit := expr.(*Node_Literal);
         if lit.value_kind == {
            case .int;
               value := make_interp_value(i, .int);
               value.i = lit.i;
               return value;

            case .float;
               value := make_interp_value(i, .float);
               value.f = lit.f;
               return value;

            case; basic.assert(false, "unhandled literal kind: %", lit.value_kind); // @errors
         }

      case; basic.assert(false, "unhandled node kind: %", expr.kind); // @errors
   }

   return null;
}

#scope_file;

value_nil:   *Interp_Value;
value_true:  *Interp_Value;
value_false: *Interp_Value;

find_symbol :: (scope: *Interp_Scope, symbol: string, all_the_way_up := true) -> *Interp_Value {
   value, ok := kv.get(*scope.bindings, symbol);
   if !ok && (all_the_way_up && scope.parent != null) {
      return find_symbol(scope.parent, symbol, true);
   }

   return value;
}

make_scope :: (parent: *Interp_Scope) -> *Interp_Scope {
   scope := mem.request_memory(Interp_Scope);
   scope.parent = parent;
   kv.init(*scope.bindings, context.allocator);
   return scope;
}

make_interp_value :: (i: *Interp, kind: Interp_Value.Kind) -> *Interp_Value {
   value := mem.request_memory(Interp_Value,, allocator = i.allocator);
   value.kind = kind;
   return value;
}
