// #module_parameters(RUN_TESTS := false);

Vm :: struct {
   reports: [..]Report;

   parser_allocator: Allocator;
   toplevel:         [..]*Node;
   previous:         Token;
   filename:         string = "(unnamed file)";
   source:           string;
   offset:           int;

   interp_allocator: Allocator;
   global:           *Interp_Scope;
}

#load "parser.jai";
#load "resolver.jai";
#load "interp.jai";

#scope_module;

Report :: struct {
   message: string;
   offset:  int = -1;
   error:   bool;
   extras:  Extra = .show_entire_line;

   Extra :: enum_flags {
      hide_location;
      show_entire_line;
   }
}

report :: (vm: *Vm, message := "", args: ..Any) -> *Report {
   r: *Report;

   if vm.reports.count != 0 {
      last := *vm.reports[vm.reports.count - 1];
      if last.offset == vm.offset {
         r = last;
      }
      else {
         r = array.append(*vm.reports);
      }
   }
   else {
      r = array.append(*vm.reports);
   }

   r.message = basic.tprint(message, ..args);
   r.offset  = vm.offset;
   return r;
}

an_error_was_reported :: (vm: Vm) -> bool {
   if vm.reports.count == 0 return false;

   reported_error := false;
   for < vm.reports {
      if it.error reported_error = true;

      line   := -1;
      column := -1;

      if !(it.extras & .hide_location) {
         for 0..vm.source.count - 1 {
            if vm.source[it] == "\n" {
               line  += 1;
               column = 1;
            }
            else {
               column += 1;
            }
         }

         basic.print("%:%,% ", vm.filename, line, column);
      }

      basic.print("%\n", it.message);

      if it.extras & .show_entire_line {
         i := it.offset;
         start := i;
         while i >= 0 {
            if vm.source[i] == "\n" {
               start = i + 1;
               break;
            }

            i -= 1;
         }

         end := it.offset;

         i = it.offset;
         while i < vm.source.count {
            if vm.source[i] == "\n" {
               end = i;
               break;
            }

            i += 1;
         }

         line := string.{ data = vm.source.data + start, count = end - start };
         basic.print("\t%\n", strings.trim(line));
      }
   }

   return reported_error;
}

// exported to the entire module since we want these everywhere

mem   :: #import "jc/memory";
array :: #import "jc/array";
kv    :: #import "jc/kv";

basic   :: #import "Basic"; // @future
strings :: #import "String"; // @future

#scope_file;

#run {
   vm: Vm;

   ok := parse_string(*vm, #string END
      fn add(l, r) do return l + r end
      fn sub(l, r) do return l - r end
      fn mul(l, r) do return l * r end
      fn div(l, r) do return l / r end

      var x = 21.0
      var y = 22.0
      var z = x + y

      x = x + 1.0 / 2.0
      print x

      x = add(x, div(1.0, 2.0))
      print x

      print x == x
      print x == y
      print x == z

      assert x == y
      assert x != z

      // def (
      //    Add = poly[T] proc(x T, y T) T do return x + y end
      //    Sub = poly[T] proc(x T, y T) T do return x - y end
      //    Mul = poly[T] proc(x T, y T) T do return x * y end
      //    Div = poly[T] proc(x T, y T) T do return x / y end
      // )

      // def (
      //    Addi = Add[int]
      //    Addf = Add[float]
      //    Subi = Sub[int]
      //    Subf = Sub[float]
      //    Muli = Mul[int]
      //    Mulf = Mul[float]
      //    Divi = Div[int]
      //    Divf = Div[float]
      // )

      // def Foo = struct {
      //    x int = 1
      //    y int = 2
      //    z int = 3
      // }

      // def Value = union {
      //    i int
      //    f float
      //    b bool
      // }

      // def Kind = enum {
      //    a
      //    b
      //    c
      //    d
      // }

      // var foo  = Foo{ x = 10, y = 20, z = 30 }
      // var val  = Value{ f = 3.14 }
      // var kind = Kind.a
   END);

   if an_error_was_reported(vm) return;

   resolve_everything(*vm);
   // generate_bytecode(*vm);
   // interp_bytecode(*vm);
}

// #if RUN_TESTS {
//    test :: #import "jc/test";
// }
