package json

import "core:mem"
import "core:math"
import "core:reflect"
import "core:strconv"
import "core:strings"
import "core:runtime"

Unmarshal_Data_Error :: enum {
	Invalid_Data,
	Invalid_Parameter,
	Non_Pointer_Parameter,
	Multiple_Use_Field,
}

Unsupported_Type_Error :: struct {
	id:    typeid,
	token: Token,
}

Unmarshal_Error :: union {
	Error,
	Unmarshal_Data_Error,
	Unsupported_Type_Error,
}

unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error {
	v := v
	if v == nil || v.id == nil {
		return .Invalid_Parameter
	}
	v = reflect.any_base(v)
	ti := type_info_of(v.id)
	if !reflect.is_pointer(ti) || ti.id == rawptr {
		return .Non_Pointer_Parameter
	}
	PARSE_INTEGERS :: true
	
	if !is_valid(data, spec, PARSE_INTEGERS) {
		return .Invalid_Data
	}
	p := make_parser(data, spec, PARSE_INTEGERS, allocator)
	
	data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
	if v.data == nil {
		return .Invalid_Parameter
	}
	
	context.allocator = p.allocator
	
	if p.spec == .MJSON {
		#partial switch p.curr_token.kind {
		case .Ident, .String:
			return unmarshal_object(&p, data, .EOF)
		}
	}

	return unmarshal_value(&p, data)
}


unmarshal :: proc(data: []byte, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error {
	return unmarshal_any(data, ptr, spec, allocator)
}

unmarshal_string :: proc(data: string, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error {
	return unmarshal_any(transmute([]byte)data, ptr, spec, allocator)
}


@(private)
assign_bool :: proc(val: any, b: bool) -> bool {
	v := reflect.any_core(val)
	switch dst in &v {
	case bool: dst = bool(b)
	case b8:   dst = b8  (b)
	case b16:  dst = b16 (b)
	case b32:  dst = b32 (b)
	case b64:  dst = b64 (b)
	case: return false
	}
	return true
}
@(private)
assign_int :: proc(val: any, i: $T) -> bool {
	v := reflect.any_core(val)
	switch dst in &v {
	case i8:      dst = i8     (i)
	case i16:     dst = i16    (i)
	case i16le:   dst = i16le  (i)
	case i16be:   dst = i16be  (i)
	case i32:     dst = i32    (i)
	case i32le:   dst = i32le  (i)
	case i32be:   dst = i32be  (i)
	case i64:     dst = i64    (i)
	case i64le:   dst = i64le  (i)
	case i64be:   dst = i64be  (i)
	case i128:    dst = i128   (i)
	case i128le:  dst = i128le (i)
	case i128be:  dst = i128be (i)
	case u8:      dst = u8     (i)
	case u16:     dst = u16    (i)
	case u16le:   dst = u16le  (i)
	case u16be:   dst = u16be  (i)
	case u32:     dst = u32    (i)
	case u32le:   dst = u32le  (i)
	case u32be:   dst = u32be  (i)
	case u64:     dst = u64    (i)
	case u64le:   dst = u64le  (i)
	case u64be:   dst = u64be  (i)
	case u128:    dst = u128   (i)
	case u128le:  dst = u128le (i)
	case u128be:  dst = u128be (i)
	case int:     dst = int    (i)
	case uint:    dst = uint   (i)
	case uintptr: dst = uintptr(i)
	case: return false
	}
	return true
}
@(private)
assign_float :: proc(val: any, f: $T) -> bool {
	v := reflect.any_core(val)
	switch dst in &v {
	case f16:     dst = f16  (f)
	case f16le:   dst = f16le(f)
	case f16be:   dst = f16be(f)
	case f32:     dst = f32  (f)
	case f32le:   dst = f32le(f)
	case f32be:   dst = f32be(f)
	case f64:     dst = f64  (f)
	case f64le:   dst = f64le(f)
	case f64be:   dst = f64be(f)
	
	case complex32:  dst = complex(f16(f), 0)
	case complex64:  dst = complex(f32(f), 0)
	case complex128: dst = complex(f64(f), 0)
	
	case quaternion64:  dst = quaternion(f16(f), 0, 0, 0)
	case quaternion128: dst = quaternion(f32(f), 0, 0, 0)
	case quaternion256: dst = quaternion(f64(f), 0, 0, 0)
	
	case: return false
	}
	return true
}


@(private)
unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool {
	val := val
	switch dst in &val {
	case string:
		dst = str
		return true
	case cstring:  
		if str == "" {
			dst = strings.clone_to_cstring("", p.allocator)
		} else {
			// NOTE: This is valid because 'clone_string' appends a NUL terminator
			dst = cstring(raw_data(str)) 
		}
		return true
	}
	
	#partial switch variant in ti.variant {
	case reflect.Type_Info_Enum:
		for name, i in variant.names {
			if name == str {
				assign_int(val, variant.values[i])
				return true
			}
		}
		// TODO(bill): should this be an error or not?
		return true
		
	case reflect.Type_Info_Integer:
		i := strconv.parse_i128(str) or_return
		if assign_int(val, i) {
			return true
		}
		if assign_float(val, i) {
			return true
		}
	case reflect.Type_Info_Float:
		f := strconv.parse_f64(str) or_return
		if assign_int(val, f) {
			return true
		}
		if assign_float(val, f) {
			return true
		}
	}
	
	return false
}


@(private)
unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
	UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token}
	token := p.curr_token
	
	v := v
	ti := reflect.type_info_base(type_info_of(v.id))
	// NOTE: If it's a union with only one variant, then treat it as that variant
	if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 && token.kind != .Null {
		variant := u.variants[0]
		v.id = variant.id
		ti = reflect.type_info_base(variant)
		if !(u.maybe && reflect.is_pointer(variant)) {
			tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
			assign_int(tag, 1)
		}
	}
	
	
	#partial switch token.kind {
	case .Null:
		mem.zero(v.data, ti.size)
		advance_token(p)
		return
	case .False, .True:
		advance_token(p)
		if assign_bool(v, token.kind == .True) {
			return
		}
		return UNSUPPORTED_TYPE

	case .Integer:
		advance_token(p)
		i, _ := strconv.parse_i128(token.text)
		if assign_int(v, i) {
			return
		}
		if assign_float(v, i) {
			return
		}
		return UNSUPPORTED_TYPE
	case .Float:
		advance_token(p)
		f, _ := strconv.parse_f64(token.text)
		if assign_float(v, f) {
			return
		}
		if i, fract := math.modf(f); fract == 0 {
			if assign_int(v, i) {
				return
			}
			if assign_float(v, i) {
				return
			}
		}
		return UNSUPPORTED_TYPE
		
	case .Ident:
		advance_token(p)
		if p.spec == .MJSON {
			if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) {
				return nil
			}
		}
		return UNSUPPORTED_TYPE
		
	case .String:
		advance_token(p)
		str := unquote_string(token, p.spec, p.allocator) or_return
		if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) {
			return nil
		}
		delete(str, p.allocator)
		return UNSUPPORTED_TYPE


	case .Open_Brace:
		return unmarshal_object(p, v, .Close_Brace)

	case .Open_Bracket:
		return unmarshal_array(p, v)

	case:
		if p.spec != .JSON {
			#partial switch token.kind {
			case .Infinity:
				advance_token(p)
				f: f64 = 0h7ff0000000000000
				if token.text[0] == '-' {
					f = 0hfff0000000000000
				}
				if assign_float(v, f) {
					return
				}
				return UNSUPPORTED_TYPE
			case .NaN:
				advance_token(p)
				f: f64 = 0h7ff7ffffffffffff
				if token.text[0] == '-' {
					f = 0hfff7ffffffffffff
				}
				if assign_float(v, f) {
					return
				}
				return UNSUPPORTED_TYPE
			}
		}
	}

	advance_token(p)
	return UNSUPPORTED_TYPE
}


@(private)
unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_location) -> Token {
	prev := p.curr_token
	err := expect_token(p, kind)
	assert(err == nil, "unmarshal_expect_token")
	return prev
}


@(private)
unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unmarshal_Error) {
	UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token}
	
	if end_token == .Close_Brace {
		assert(expect_token(p, .Open_Brace) == nil)
	}

	v := v
	v = reflect.any_base(v)
	ti := type_info_of(v.id)
	
	#partial switch t in ti.variant {
	case reflect.Type_Info_Struct:
		if t.is_raw_union {
			return UNSUPPORTED_TYPE
		}
	
		struct_loop: for p.curr_token.kind != end_token {
			key, _ := parse_object_key(p, p.allocator)
			defer delete(key, p.allocator)
			
			unmarshal_expect_token(p, .Colon)						
			
			fields := reflect.struct_fields_zipped(ti.id)
			
			field_used := make([]bool, len(fields), context.temp_allocator)
			
			use_field_idx := -1
			
			for field, field_idx in fields {
				tag_value := string(reflect.struct_tag_get(field.tag, "json"))
				if key == tag_value {
					use_field_idx = field_idx
					break
				}
			}
			
			if use_field_idx < 0 {
				for field, field_idx in fields {
					if key == field.name {
						use_field_idx = field_idx
						break
					}
				}
			}
			
			if use_field_idx >= 0 {
				if field_used[use_field_idx] {
					return .Multiple_Use_Field
				}
				field_used[use_field_idx] = true
				offset := fields[use_field_idx].offset
				type := fields[use_field_idx].type
				name := fields[use_field_idx].name
				
				field_ptr := rawptr(uintptr(v.data) + offset)
				field := any{field_ptr, type.id}
				unmarshal_value(p, field) or_return
					
				if parse_comma(p) {
					break struct_loop
				}
				continue struct_loop
			}
			
			return Unsupported_Type_Error{v.id, p.curr_token}
		}
		
	case reflect.Type_Info_Map:
		if !reflect.is_string(t.key) {
			return UNSUPPORTED_TYPE
		}
		raw_map := (^mem.Raw_Map)(v.data)
		if raw_map.entries.allocator.procedure == nil {
			raw_map.entries.allocator = p.allocator
		}
		
		header := runtime.__get_map_header_runtime(raw_map, t)
		
		elem_backing := bytes_make(t.value.size, t.value.align, p.allocator) or_return
		defer delete(elem_backing, p.allocator)
		
		map_backing_value := any{raw_data(elem_backing), t.value.id}
		
		map_loop: for p.curr_token.kind != end_token {
			key, _ := parse_object_key(p, p.allocator)
			unmarshal_expect_token(p, .Colon)
			
			
			mem.zero_slice(elem_backing)
			if err := unmarshal_value(p, map_backing_value); err != nil {
				delete(key, p.allocator)
				return err
			}
			
			hash := runtime.Map_Hash {
				hash = runtime.default_hasher_string(&key, 0),
				key_ptr = &key,
			}
			
			key_cstr: cstring
			if reflect.is_cstring(t.key) {
				key_cstr = cstring(raw_data(key))
				hash.key_ptr = &key_cstr
			}
			
			set_ptr := runtime.__dynamic_map_set(header, hash, map_backing_value.data)
			if set_ptr == nil {
				delete(key, p.allocator)
			} 
			
			if parse_comma(p) {
				break map_loop
			}
		}
		
	case reflect.Type_Info_Enumerated_Array:
		index_type := reflect.type_info_base(t.index)
		enum_type := index_type.variant.(reflect.Type_Info_Enum)
	
		enumerated_array_loop: for p.curr_token.kind != end_token {
			key, _ := parse_object_key(p, p.allocator)
			unmarshal_expect_token(p, .Colon)
			defer delete(key, p.allocator)

			index := -1
			for name, i in enum_type.names {
				if key == name {
					index = int(enum_type.values[i] - t.min_value)
					break
				}
			}
			if index < 0 || index >= t.count {
				return UNSUPPORTED_TYPE
			}
						
			index_ptr := rawptr(uintptr(v.data) + uintptr(index*t.elem_size))
			index_any := any{index_ptr, t.elem.id}
			
			unmarshal_value(p, index_any) or_return
			
			if parse_comma(p) {
				break enumerated_array_loop
			}
		}

		return nil
	case:
		return UNSUPPORTED_TYPE
	}
	
	if end_token == .Close_Brace {
		assert(expect_token(p, .Close_Brace) == nil)
	}
	return
}


@(private)
unmarshal_count_array :: proc(p: ^Parser) -> (length: uintptr) {
	p_backup := p^
	p.allocator = mem.nil_allocator()
	unmarshal_expect_token(p, .Open_Bracket)
	array_length_loop: for p.curr_token.kind != .Close_Bracket {
		_, _ = parse_value(p)
		length += 1
		
		if parse_comma(p) {
			break
		}
	}
	p^ = p_backup
	return
}

@(private)
unmarshal_array :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
	assign_array :: proc(p: ^Parser, base: rawptr, elem: ^reflect.Type_Info, length: uintptr) -> Unmarshal_Error {
		unmarshal_expect_token(p, .Open_Bracket)
		
		for idx: uintptr = 0; p.curr_token.kind != .Close_Bracket; idx += 1 {
			assert(idx < length)
			
			elem_ptr := rawptr(uintptr(base) + idx*uintptr(elem.size))
			elem := any{elem_ptr, elem.id}
			
			unmarshal_value(p, elem) or_return
			
			if parse_comma(p) {
				break
			}	
		}
		
		unmarshal_expect_token(p, .Close_Bracket)
		
		
		return nil
	}

	UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token}
	
	ti := reflect.type_info_base(type_info_of(v.id))
	
	length := unmarshal_count_array(p)
	
	#partial switch t in ti.variant {
	case reflect.Type_Info_Slice:	
		raw := (^mem.Raw_Slice)(v.data)
		data := bytes_make(t.elem.size * int(length), t.elem.align, p.allocator) or_return
		raw.data = raw_data(data)
		raw.len = int(length)
			
		return assign_array(p, raw.data, t.elem, length)
		
	case reflect.Type_Info_Dynamic_Array:
		raw := (^mem.Raw_Dynamic_Array)(v.data)
		data := bytes_make(t.elem.size * int(length), t.elem.align, p.allocator) or_return
		raw.data = raw_data(data)
		raw.len = int(length)
		raw.cap = int(length)
		raw.allocator = p.allocator
		
		return assign_array(p, raw.data, t.elem, length)
		
	case reflect.Type_Info_Array:
		// NOTE(bill): Allow lengths which are less than the dst array
		if int(length) > t.count {
			return UNSUPPORTED_TYPE
		}
		
		return assign_array(p, v.data, t.elem, length)
		
	case reflect.Type_Info_Enumerated_Array:
		// NOTE(bill): Allow lengths which are less than the dst array
		if int(length) > t.count {
			return UNSUPPORTED_TYPE
		}
		
		return assign_array(p, v.data, t.elem, length)
		
	case reflect.Type_Info_Complex:
		// NOTE(bill): Allow lengths which are less than the dst array
		if int(length) > 2 {
			return UNSUPPORTED_TYPE
		}
	
		switch ti.id {
		case complex32:  return assign_array(p, v.data, type_info_of(f16), 2)
		case complex64:  return assign_array(p, v.data, type_info_of(f32), 2)
		case complex128: return assign_array(p, v.data, type_info_of(f64), 2)
		}
		
		return UNSUPPORTED_TYPE
		
	}
		
	return UNSUPPORTED_TYPE
}
