Json_Value :: struct {
   kind: enum {
      none;
      bool;
      number;
      string;
      array;
      object;
   };

   using value: union {
      b: bool;
      n: float64;
      s: string;

      array: [..]Json_Value;

      object: struct {
         keys:   [..]string;
         values: [..]Json_Value;
      }
   }
}

json_serialize :: (value: $T) -> Json_Value {
   json_serialize_with_info :: (info: *Type_Info, value: *void) -> Json_Value {
      if value == null {
         return .{ kind = .none };
      }

      if info.type == {
         case .BOOL;
            return .{ kind = .bool, b = value.(*bool).* };

         case .INTEGER;
            i_info := info.(*Type_Info_Integer);
            if i_info.signed {
               if i_info.runtime_size == {
                  case 1; return .{ kind = .number, n = (value.( *s8).*).(float64) };
                  case 2; return .{ kind = .number, n = (value.(*s16).*).(float64) };
                  case 4; return .{ kind = .number, n = (value.(*s32).*).(float64) };
                  case 8; return .{ kind = .number, n = (value.(*s64).*).(float64) };
                  case; basic.assert(false, "unknown float size: %", info.runtime_size);
               }
            }
            else {
               if i_info.runtime_size == {
                  case 1; return .{ kind = .number, n = (value.( *u8).*).(float64) };
                  case 2; return .{ kind = .number, n = (value.(*u16).*).(float64) };
                  case 4; return .{ kind = .number, n = (value.(*u32).*).(float64) };
                  case 8; return .{ kind = .number, n = (value.(*u64).*).(float64) };
                  case; basic.assert(false, "unknown float size: %", info.runtime_size);
               }
            }

         case .FLOAT;
            if info.runtime_size == {
               case 4; return .{ kind = .number, n = (value.(*float32).*).(float64) };
               case 8; return .{ kind = .number, n = (value.(*float64).*) };
               case; basic.assert(false, "unknown float size: %", info.runtime_size);
            }

         case .STRING;
            return .{ kind = .string, s = (value.(*string)).* };

         case .STRUCT;
            json_obj: Json_Value;
            json_obj.kind = .object;

            obj  := *json_obj.object;
            vptr := value.(*u8);

            s_info := info.(*Type_Info_Struct);
            for s_info.members if it.offset_in_bytes >= 0 {
               name := get_json_member_name(it);
               basic.array_add(*obj.keys, name);

               field_value := vptr + it.offset_in_bytes;
               basic.array_add(*obj.values, json_serialize_with_info(it.type, field_value));
            }

            return json_obj;

         case .ARRAY;
            json_array: Json_Value;
            json_array.kind = .array;

            arr  := *json_array.array;
            vptr := value.(*u8);

            a_info  := info.(*Type_Info_Array);
            el_info := a_info.element_type;

            if a_info.array_type == {
               case .FIXED;
                  for 0..a_info.array_count - 1 {
                     el_ptr := vptr + (it * el_info.runtime_size);
                     basic.array_add(arr, json_serialize_with_info(el_info, el_ptr));
                  }

               case .VIEW; #through;
               case .RESIZABLE;
                  data := vptr.(*[]u8);
                  for 0..data.count - 1 {
                     el_ptr := data.data + (it * el_info.runtime_size);
                     basic.array_add(arr, json_serialize_with_info(el_info, el_ptr));
                  }
            }

            return json_array;
      }

      basic.assert(false, "unhandled type in marshal: %", info.type);
      return .{};
   }

   return json_serialize_with_info(T.(*Type_Info), *value);
}

json_deserialize :: (data: string) -> (Json_Value, bool) {
   p := Parser.{
      data      = data.([]u8),
      allocator = context.allocator,
   };

   // @todo(judah): logging
   return parse_value(*p), p.error.count == 0;
}

json_deserialize :: ($T: Type, data: string) -> (T, bool) {
   json_deserialize_with_info :: (info: *Type_Info, json: Json_Value, ptr: *void) -> bool {
      if json.kind == {
         case .none; return true;

         case .bool;
            basic.assert(info.type == .BOOL, "cannot deserialize % into %", json.b, info.type); // @errors
            ptr.(*bool).* = json.b;

         case .number;
            if info.type == {
               case .INTEGER;
                  i_info := info.(*Type_Info_Integer);
                  if i_info.signed {
                     // @todo(judah): bounds check to make sure this is safe
                     if i_info.runtime_size == {
                        case 1; ptr.(*s8 ).* = json.n.(s8 );
                        case 2; ptr.(*s16).* = json.n.(s16);
                        case 4; ptr.(*s32).* = json.n.(s32);
                        case 8; ptr.(*s64).* = json.n.(s64);
                        case; basic.assert(false, "unhandled int size % in deserialize", i_info.runtime_size);
                     }
                  }
                  else {
                     if i_info.runtime_size == {
                        case 1; ptr.(*u8 ).* = json.n.(u8 );
                        case 2; ptr.(*u16).* = json.n.(u16);
                        case 4; ptr.(*u32).* = json.n.(u32);
                        case 8; ptr.(*u64).* = json.n.(u64);
                        case; basic.assert(false, "unhandled uint size % in deserialize", i_info.runtime_size);
                     }
                  }

               case .FLOAT;
                  if info.runtime_size == {
                     case 4; ptr.(*float32).* = json.n.(float32);
                     case 8; ptr.(*float64).* = json.n.(float64);
                     case; basic.assert(false, "unhandled float size % in deserialize", info.runtime_size);
                  }
            }

            return true;

         case .string;
            basic.assert(info.type == .STRING, "cannot deserialize '%' into %", json.s, info.type); // @errors
            sptr := ptr.(*string);
            sptr.data  = basic.copy_string(json.s).data;
            sptr.count = json.s.count;
            return true;

         case .object;
            basic.assert(info.type == .STRUCT, "cannot deserialize json object into %", info.type); // @errors

            vptr   := ptr.(*u8);
            s_info := info.(*Type_Info_Struct);
            for mem: s_info.members {
               found := false;
               name  := get_json_member_name(mem);

               value: Json_Value;
               for json.object.keys if it == name { // @todo(judah): handle custom names in notes
                  found = true;
                  value = json.object.values[it_index];
                  break;
               }

               if !found continue;

               field_ptr := vptr + mem.offset_in_bytes;
               if !json_deserialize_with_info(mem.type, value, field_ptr) {
                  return false;
               }
            }

            return true;

         case .array;
            basic.assert(info.type == .ARRAY, "cannot deserialize json array into %", info.type); // @errors

            vptr    := ptr.(*u8);
            a_info  := info.(*Type_Info_Array);
            el_info := a_info.element_type;

            if a_info.array_type == {
               case .FIXED;
                  basic.assert(json.array.count <= a_info.array_count);

                  for 0..a_info.array_count - 1 {
                     el_ptr := vptr + (it * el_info.runtime_size);
                     value  := json.array[it];
                     if !json_deserialize_with_info(el_info, value, el_ptr) {
                        return false;
                     }
                  }

               case .VIEW; #through;
               case .RESIZABLE;
                  data := basic.alloc(json.array.count * el_info.runtime_size).(*u8);
                  if json.array.count == 0 {
                     return true;
                  }

                  // @todo(judah): verify the json array is homogenous
                  for json.array {
                     el_ptr := data + (it_index * el_info.runtime_size);
                     if !json_deserialize_with_info(el_info, it, el_ptr) {
                        return false;
                     }
                  }

                  if a_info.array_type == .VIEW {
                     out := ptr.(*[]u8);
                     out.data  = data;
                     out.count = json.array.count;
                  }
                  else {
                     out := ptr.(*[..]u8);
                     out.data      = data;
                     out.count     = json.array.count;
                     out.allocated = json.array.count;
                  }

                  return true;
            }

            return true;
      }

      basic.assert(false, "unhandled json value: %", json.kind);
      return false;
   }

   json, ok := json_deserialize(data);
   if !ok return zero_of(T), ok;

   out: T;
   ok = json_deserialize_with_info(T.(*Type_Info), json, *out);
   return out, ok;
}

to_string :: (value: Json_Value, indent := 0) -> string {
   b: basic.String_Builder;

   add_indents :: (b: *basic.String_Builder, level: int) #expand {
      if level != 0 {
         for 0..level basic.append(b, " ");
      }
   }

   if value.kind == {
      case .none;
         basic.append(*b, "null");

      case .bool;
         if value.b {
            basic.append(*b, "true");
         }
         else {
            basic.append(*b, "false");
         }

      case .number;
         basic.print_to_builder(*b, "%", value.n);

      case .string;
         basic.print_to_builder(*b, "\"%\"", value.s);

      case .array;
         basic.append(*b, "[\n");

         for value.array {
            add_indents(*b, indent + 2);
            basic.append(*b, to_string(it, indent + 2));
            if it_index < value.array.count {
               basic.append(*b, ",\n");
            }
         }

         add_indents(*b, indent);
         basic.append(*b, "]");

      case .object;
         basic.append(*b, "{\n");

         for value.object.keys {
            add_indents(*b, indent + 2);
            v := value.object.values[it_index];
            basic.print_to_builder(*b, "\"%\": %,\n", it, to_string(v, indent + 2));
         }

         add_indents(*b, indent);
         basic.append(*b, "}");
   }

   return basic.builder_to_string(*b);
}


#scope_file;

get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
   name := member.name;
   for member.notes if strings.contains(it, "json:") {
      name = .{ data = it.data + 5, count = it.count - 5 };
      name = strings.replace(name, "\\_", " ");
      break;
   }

   return name;
}

Parser :: struct {
   allocator: Allocator;
   data:      []u8;
   error:     string;
   offset:    uint;
   line:      uint;
}

Token :: struct {
   kind: enum {
      invalid;
      eof;
      true_;
      false_;
      null_;
      number;
      str;

      colon    :: #char ":";
      comma    :: #char ",";
      l_brace  :: #char "{";
      r_brace  :: #char "}";
      l_square :: #char "[";
      r_square :: #char "]";
   };

   str: string;
}

get_next_token :: (p: *Parser) -> Token {
   skip_whitespace(p);

   if at_end(p) {
      return .{ kind = .eof };
   }

   c := p.data[p.offset];
   if c == {
      case #char ":"; #through;
      case #char ","; #through;
      case #char "{"; #through;
      case #char "}"; #through;
      case #char "["; #through;
      case #char "]";
         p.offset += 1;
         return .{ kind = xx c };
   }

   // Comments
   if c == #char "/" && p.offset + 1 < p.data.count.(uint) && p.data[p.offset+1] == #char "/" {
      capture_while(p, c => c != #char "\n");
      return get_next_token(p);
   }

   // Strings
   // @todo(judah): handle escaped quotes
   if c == #char "\"" {
      p.offset += 1; // skip "
      str := capture_while(p, s => s != #char "\"");
      p.offset += 1; // skip "
      return .{ kind = .str, str = str };
   }

   // Numbers
   if (c >= #char "0" && c <= #char "9") || c == #char "." {
      number := capture_while(p, n => (n >= #char "0" && n <= #char "9") || n == #char ".");
      return .{ kind = .number, str = number };
   }

   // true, false, null
   if (c >= #char "a" && c <= #char "z") {
      keyword := capture_while(p, n => (n >= #char "a" && n <= #char "z"));
      if keyword == {
         case "true";
            return .{ kind = .true_ };
         case "false";
            return .{ kind = .false_ };
         case "null";
            return .{ kind = .null_ };
         case;
            return .{ kind = .invalid, str = keyword };
      }
   }

   return .{ kind = .eof };
}

peek_token :: (p: *Parser) -> Token {
   copy := p.*; // @todo(judah): this is bad?
   return get_next_token(*copy);
}

expect :: (p: *Parser, kinds: ..type_of(Token.kind)) -> (bool, Token) {
   tok := get_next_token(p);
   for kinds if it == tok.kind {
      return true, tok;
   }

   return false, tok;
}

parse_value :: (p: *Parser) -> Json_Value {
   to_string :: (c: u8) -> string #expand {
      str: string;
      str.data  = *c;
      str.count = 1;
      return str;
   }

   ok, tok := expect(p, .l_brace, .l_square, .str, .number, .true_, .false_, .null_);
   if !ok {
      set_error(p, "expected value, found '%'", tok.str);
      return .{};
   }

   if tok.kind == {
      case .number;
         number := tok.str;

         // @todo(judah): parse_float works a bit weird
         f, ok := strings.parse_float(*number);
         if !ok {
            set_error(p, "malformed number '%'", tok.str);
            return .{};
         }

         return .{ kind = .number, n = f };

      case .true_; #through;
      case .false_;
         return .{ kind = .bool, b = tok.kind == .true_ };

      case .null_;
         return .{ kind = .none };

      case .str;
         return .{ kind = .string, s = tok.str };

      case .l_brace;
         object: Json_Value;
         object.kind = .object;

         obj := *object.object;
         obj.keys.allocator   = p.allocator;
         obj.values.allocator = p.allocator;

         while !at_end(p) {
            end := peek_token(p);
            if end.kind == .r_brace break;

            key_ok, key := expect(p, .str);
            if !key_ok {
               set_error(p, "expected key for object");
               return .{};
            }

            if !expect(p, .colon) {
               set_error(p, "missing ':' after key");
               return .{};
            }

            value := parse_value(p);
            if p.error.count != 0 {
               return .{};
            }

            basic.array_add(*obj.keys, key.str);
            basic.array_add(*obj.values, value);

            end = peek_token(p);
            if end.kind == .r_brace break;

            if !expect(p, .comma) {
               set_error(p, "missing ',' after value");
               return .{};
            }
         }

         if !expect(p, .r_brace) {
            set_error(p, "missing '}' at end of object");
            return .{};
         }

         return object;

      case .l_square;
         array: Json_Value;
         array.kind = .array;
         array.array.allocator = p.allocator;

         while !at_end(p) {
            end := peek_token(p);
            if end.kind == .r_square break;

            value := parse_value(p);
            if p.error.count != 0 {
               return .{};
            }

            basic.array_add(*array.array, value);

            end = peek_token(p);
            if end.kind == .r_square break;

            if !expect(p, .comma) {
               set_error(p, "missing ',' after value");
               return .{};
            }
         }

         if !expect(p, .r_square) {
            set_error(p, "missing ']' at end of array");
            return .{};
         }

         return array;
   }

   return .{};
}

set_error :: (p: *Parser, message: string, args: ..Any) {
   p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args));
}

skip_whitespace :: (p: *Parser) {
   while !at_end(p) {
      c := p.data[p.offset];
      if c == {
         case #char " ";  #through;
         case #char "\t"; #through;
         case #char "\r";
            p.offset += 1;

         case #char "\n";
            p.offset += 1;
            p.line   += 1;

         case;
            break;
      }
   }
}

capture_while :: (p: *Parser, proc: (u8) -> bool) -> string {
   basic.assert(p.offset < p.data.count.(uint));

   capture := p.data;
   capture.data += p.offset;
   capture.count = 0;

   while !at_end(p) {
      c := p.data[p.offset];
      if !proc(c) break;
      p.offset += 1;
   }

   capture.count = ((p.data.data + p.offset) - capture.data).(sint);
   return capture.(string);
}

at_end :: inline (p: *Parser) -> bool {
   return p.offset >= p.data.count.(uint);
}

basic   :: #import "Basic";
strings :: #import "String";

#import,file "../base.jai";


