
// Parse a JSON string into the given Type.
// All members of Type that are not present in the JSON are kept at their default values.
// All fields in the JSON that have no corresponding member in Type are ignored by default
// but you can pass ignore_unknown = false to fail instead.
json_parse_string :: (content: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
	result: T;
	if !content then return false, result;

	info := type_info(T);
	remainder, success := parse_value(content, cast(*u8)*result, info, ignore_unknown, "", rename=rename);
	if !success		return false, result;

	remainder = trim_left(remainder, WHITESPACE_CHARS);
	if remainder.count {
		log_error("Unexpected trailing characters: %", remainder);
		return false, result;
	}

	return true, result;
}

json_parse_file :: (filename: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
	file_data, success := read_entire_file(filename);
	result: T;
	if !success		{
		log_error("Could not read file: %", filename);
		return false, result;
	}
	defer free(file_data);

	if (context.log_level >= .VERBOSE) {
		log("Read file: %", success);
	}
	success, result = json_parse_string(file_data, T, ignore_unknown, rename=rename);
	return success, result;
}

json_write_native :: (builder: *String_Builder, data: *void, info: *Type_Info, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note, level := 0) {
	if info.type == {
		case .BOOL;
			append(builder, ifx (.*)(cast(*bool) data) "true" else "false");
		case .INTEGER; #through;
		case .FLOAT;
			any_val: Any;
			any_val.type = info;
			any_val.value_pointer = data;
			print_item_to_builder(builder, any_val);
		case .ENUM;
			any_val: Any;
			any_val.type = info;
			any_val.value_pointer = data;

			append(builder, #char "\"");
			print_item_to_builder(builder, any_val);
			append(builder, #char "\"");
		case .STRING;
			json_append_escaped(builder, (.*)(cast(*string) data));
		case .ARRAY;
			info_array := cast(*Type_Info_Array) info;
			element_size := info_array.element_type.runtime_size;
			assert(element_size != -1);

			stride := element_size;
			array_data := data;
			array_count := info_array.array_count;
			if info_array.array_count == -1 {
				array_count = (.*) cast(*s64) data;

				array_dest: **void = data + 8;
				array_data = (.*) array_dest;
			}

			append(builder, "[");
			if array_data {
				if indent_char.count {
					append(builder, "\n");
					for 0..level	append(builder, indent_char);
				}
				for 0..array_count-1 {
					json_write_native(builder, array_data, info_array.element_type, indent_char, ignore, rename, level + 1);
					if it != array_count - 1		append(builder, ",");
					array_data += stride;
				}
			}
			if indent_char.count {
				append(builder, "\n");
				for 0..level-1	append(builder, indent_char);
			}
			append(builder, "]");
		case .STRUCT;
			struct_info := cast(*Type_Info_Struct) info;
			if is_generic_json_value(info) {
				value := cast(*JSON_Value) data;
				json_write_json_value(builder, (.*)value, indent_char, level);
			} else {
				append(builder, #char "{");
				first := true;
				json_write_native_members(builder, data, struct_info.members, indent_char, ignore, rename, level, *first);
				if indent_char.count {
					append(builder, "\n");
					for 0..level-1	append(builder, indent_char);
				}
				append(builder, "}");
			}
		case .POINTER;
			ptr_info := cast(*Type_Info_Pointer) info;
			ptr := (.*) cast(**void) data;
			if ptr {
				json_write_native(builder, ptr, ptr_info.pointer_to, indent_char, ignore, rename, level);
			} else {
				append(builder, "null");
			}
		case;
			assert(false, "Unsupported type: %", info.type);
	}
}

#scope_file

json_write_native_members :: (builder: *String_Builder, data: *void, members: [] Type_Info_Struct_Member, indent_char := "\t", ignore := ignore_by_note, rename: Rename_Proc, level := 0, first: *bool) {
	for * member: members {
		if member.flags & .CONSTANT     continue;
		if ignore(member)               continue;
		if (member.type.type == .STRUCT && member.flags & .USING) {
			info := cast(*Type_Info_Struct) member.type;
			json_write_native_members(builder, data + member.offset_in_bytes, info.members, indent_char, ignore, rename, level, first);
		} else {
			if !(.*)first	append(builder, ",");
			(.*)first = false;

			if indent_char.count {
				append(builder, "\n");
				for 0..level	append(builder, indent_char);
			}

			renamed_name := rename(member);
			name := ifx renamed_name.count > 0	renamed_name else member.name;

			json_append_escaped(builder, name);
			append(builder, ": ");
			json_write_native(builder, data + member.offset_in_bytes, member.type, indent_char, ignore, rename, level + 1);
		}
	}
}

is_generic_json_value_or_pointer_to_it :: (info: *Type_Info) -> bool {
	value_info := info;
	if info.type == .POINTER {
		pointer_info := cast(*Type_Info_Pointer) info;
		value_info = pointer_info.pointer_to;
	}

	return is_generic_json_value(info);
}

is_generic_json_value :: (info: *Type_Info) -> bool {
	return info == type_info(JSON_Value);
}

parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, field_name: string, rename: Rename_Proc) -> remainder: string, success: bool {
	remainder := trim_left(to_parse, WHITESPACE_CHARS);
	success := true;

	prepare_slot :: (expected_type: Type_Info_Tag, info: *Type_Info, slot: *u8, to_parse: string) -> *u8, success: bool, is_generic: bool, info: *Type_Info {
		value_info := info;
		if info.type == .POINTER {
			pointer_info := cast(*Type_Info_Pointer) info;
			value_info = pointer_info.pointer_to;
		}

        if info.type == .ENUM {
            info_enum := cast(*Type_Info_Enum)info;
            value_info = info_enum.internal_type;
        }

		is_generic := is_generic_json_value(value_info);

		if !is_generic && value_info.type != expected_type {
			teaser := to_parse;
			if teaser.count > 50	teaser.count = 50;
            builder: String_Builder;
            print_type_to_builder(*builder, info);
            type_name := builder_to_string(*builder,, temp);
			log_error("Cannot parse % value into type \"%\". Remaining input is: %…", expected_type, type_name, teaser);
			return null, false, false, value_info;
		}

		if info.type == .POINTER {
			value_slot := alloc(value_info.runtime_size);
			initializer: (*void) #no_context;
			if value_info.type == .STRUCT {
				struct_info := cast(*Type_Info_Struct) value_info;
				initializer = struct_info.initializer;
			}
			if initializer {
				initializer(value_slot);
			} else {
				memset(value_slot, 0, value_info.runtime_size);
			}
			(.*)cast(**u8)slot = value_slot;
			return value_slot, true, is_generic, value_info;
		} else {
			return slot, true, is_generic, value_info;
		}
	}

	is_generic: bool;
	if remainder[0] == {
		case #char "n";
			remainder, success = expect_and_slice(remainder, "null");
			if !success		return remainder, false;
			if slot {
				if info.type == .POINTER {
                    (.*)cast(**void) slot = null;
                } else {
                    builder: String_Builder;
                    print_type_to_builder(*builder, info);
                    type_name := builder_to_string(*builder,, temp);
					log_error("Got NULL value for non-pointer type \"%\" of field \"%\". Keeping default value instead.", type_name, field_name);
				}
			}
			return remainder, true;
		case #char "t";
			remainder, success = expect_and_slice(remainder, "true");
			if !success		return remainder, false;
			if slot {
				value_slot: *u8;
				value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
				if success {
					if is_generic {
						json_set(cast(*JSON_Value)value_slot, true);
					} else {
						(.*)cast(*bool)value_slot = true;
					}
				}
			}
		case #char "f";
			remainder, success = expect_and_slice(remainder, "false");
			if !success		return remainder, false;
			if slot {
				value_slot: *u8;
				value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
				if success {
					if is_generic {
						json_set(cast(*JSON_Value)value_slot, false);
					} else {
						(.*)cast(*bool)value_slot = false;
					}
				}
			}
		case #char "\"";
			if slot && info && info.type == .ENUM {
				info_enum := cast(*Type_Info_Enum)info;
				value_slot: *u8;
				value_slot, success, is_generic = prepare_slot(.INTEGER, info_enum.internal_type, slot, to_parse);
				remainder, success = parse_enum_string(remainder, value_slot, info_enum);
			} else {
				value: string;
				value, remainder, success = parse_string(remainder);
				stored := false;
				defer if !stored	free(value);
				if success && slot {
					value_slot: *u8;
					value_slot, success, is_generic = prepare_slot(.STRING, info, slot, to_parse);
					if success {
						if is_generic {
							json_set(cast(*JSON_Value)value_slot, value);
						} else {
							(.*)cast(*string)value_slot = value;
						}
						stored = true;
					}
				}
			}
		case #char "[";
			value_slot: *u8;
			value_info: *Type_Info;
			if slot {
				value_slot, success, is_generic, value_info = prepare_slot(.ARRAY, info, slot, to_parse);
			}
			if success {
				if is_generic {
					value: [] JSON_Value;
					value, remainder, success = parse_array(remainder);
					json_set(cast(*JSON_Value)value_slot, value);
				} else {
					remainder, success = parse_array(remainder, value_slot, cast(*Type_Info_Array) value_info, ignore_unknown, rename=rename);
				}
			}
		case #char "{";
			value_slot: *u8;
			value_info: *Type_Info;
			if slot {
				value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse);
			}
			if success {
				if is_generic {
					value := New(JSON_Object);
					(.*)value, remainder, success = parse_object(remainder);
					json_set(cast(*JSON_Value)value_slot, value);
				} else {
					remainder, success = parse_object(remainder, value_slot, cast(*Type_Info_Struct) value_info, ignore_unknown, rename=rename);
				}
			}
		case;
			if slot == null || info.type == .FLOAT || is_generic_json_value_or_pointer_to_it(info) {
				float_value: float64;
				float_value, success, remainder = string_to_float64(remainder);
				if success && slot {
					value_slot: *u8;
					value_info: *Type_Info;
					value_slot, success, is_generic, value_info = prepare_slot(.FLOAT, info, slot, to_parse);
					if success {
						if is_generic {
							json_set(cast(*JSON_Value)value_slot, float_value);
						} else {
							if value_info.runtime_size == 4 {
								((.*) cast(*float) slot) = cast(float) float_value;
							} else {
								assert(value_info.runtime_size == 8);
								((.*) cast(*float64) slot) = float_value;
							}
						}
					}
				}
			} else {
                if slot {
                    value_slot: *u8;
                    value_info: *Type_Info;
                    value_slot, success, is_generic, value_info = prepare_slot(.INTEGER, info, slot, to_parse);
                    if success {
                        if is_generic {
                            int_value: s64;
                            int_value, success, remainder = string_to_int(remainder, T = s64);
                            if success {
                                json_set(cast(*JSON_Value)value_slot, int_value);
                            } else {
                                log_error("Could not parse \"%\" as an integer.", to_parse);
                            }
                        } else {
                            info_int := cast(*Type_Info_Integer) value_info;
                            success, remainder = parse_and_write_integer(info_int, value_slot, to_parse);
                        }
                    }
                } else {
                    int_value: s64;
                    int_value, success, remainder = string_to_int(remainder, T = s64);
                    if !success {
                        log_error("Could not parse \"%\" as an integer.", to_parse);
                    }
                }
			}
	}

	return remainder, success;
}

parse_enum_string :: (str: string, slot: *u8, info_enum: *Type_Info_Enum) -> remainder: string, success: bool {
	value, remainder, success := parse_string(str);
	defer free(value);
    if !success return remainder, false;

    // Parse by members' names
    normalize_enum_value :: inline (name: string) -> string #expand {
        normalized := trim(name);
        if normalized.count > info_enum.name.count && starts_with(normalized, info_enum.name) && normalized[info_enum.name.count] == #char "." {
            normalized = slice(normalized, info_enum.name.count+1, normalized.count-info_enum.name.count-1);
        } else if starts_with(normalized, ".") {
            normalized = slice(normalized, 1, normalized.count-1);
        }
        return normalized;
    }

    int_info := info_enum.internal_type;
    int_value: s64;
    if info_enum.enum_type_flags & .FLAGS {
        values := split(value, "|",, temp);

        for v: values {
            name := normalize_enum_value(v);
            found_name := false;
            for info_enum.names {
                if name == it {
                    found_name = true;
                    int_value |= info_enum.values[it_index];
                    break;
                }
            }

            if !found_name {
                log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
                success = false;
            }
        }
    } else {
        success = false;
        name := normalize_enum_value(value);
        for info_enum.names {
            if name == it {
                int_value = info_enum.values[it_index];
                success = true;
                break;
            }
        }

        if !success {
            log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
        }
    }

    if success {
        if int_info.signed {
            valid, low, high := Reflection.range_check_and_store(int_value, int_info, slot);
            if !valid {
                log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
                return remainder, false;
            }
        } else {
            valid, low, high := Reflection.range_check_and_store(cast(u64) int_value, int_info, slot);
            if !valid {
                log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
                return remainder, false;
            }
        }
    }

    return remainder, success;
}

parse_array :: (str: string, slot: *u8, info: *Type_Info_Array, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
	element_size: int;
	if slot {
		element_size = info.element_type.runtime_size;
		assert(element_size != -1, "Unknown element size");
	}

	assert(str[0] == #char "[", "Invalid object start %", str);
	remainder := advance(str);
	remainder = trim_left(remainder, WHITESPACE_CHARS);
	if remainder[0] == #char "]" {
		remainder = advance(remainder);
		// @Robustness: Do we need to zero out the array?
		return remainder, true;
	}


	if slot {
		array: Resizable_Array;

        initializer: (*void) #no_context;
        if info.element_type.type == .STRUCT {
            struct_info := cast(*Type_Info_Struct) info.element_type;
            initializer = struct_info.initializer;
        }

		while true {
			maybe_grow(*array, element_size);
            element_data := array.data + array.count * element_size;
			if initializer {
				initializer(element_data);
			} else {
				memset(element_data, 0, element_size);
			}

			success: bool;
			remainder, success = parse_value(remainder, element_data, info.element_type, ignore_unknown, "", rename=rename);
			if !success	return remainder, false;

			array.count += 1;

			remainder = trim_left(remainder, WHITESPACE_CHARS);
			if remainder[0] != #char ","	break;
			remainder = advance(remainder);
			remainder = trim_left(remainder, WHITESPACE_CHARS);
		}

		if info.array_type == .VIEW {
			view := (cast(*Array_View_64) slot);
			view.count = array.count;
			view.data = array.data;
		} else if info.array_count == -1 {
            // Resizable array
			(.*)(cast(*Resizable_Array) slot) = array;
		} else {
            // Fixed-size array
            if (info.array_count != array.count) {
                log_error("Expected array of size %, but found array of size %\n", info.array_count, array.count);
                return remainder, false;
            }

            memcpy(slot, array.data, array.count * element_size);
        }
	} else {
		while true {
			success: bool;
			remainder, success = parse_value(remainder, null, null, ignore_unknown, "", rename=rename);
			if !success	return remainder, false;

			remainder = trim_left(remainder, WHITESPACE_CHARS);
			if remainder[0] != #char ","	break;
			remainder = advance(remainder);
			remainder = trim_left(remainder, WHITESPACE_CHARS);
		}
	}

	if remainder[0] != #char "]"	return remainder, false;
	remainder = advance(remainder);
	return remainder, true;
}

Member_Offset :: struct {
    member: *Type_Info_Struct_Member;
    offset_in_bytes: s64;
}

// This procedure is somewhat copied from Basic.get_field.
fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Struct, rename: Rename_Proc, base_offset := 0) {
    for * member: info.members {
        offset := base_offset + member.offset_in_bytes;
        name := rename(member);
        assert(!table_find_pointer(table, name), "Redeclaration of member \"%\": % vs. %", name, (.*)member, (.*)table_find_pointer(table, name));
        table_set(table, name, .{member, offset});
        if (member.flags & .USING) && (member.type.type == .STRUCT) {
            fill_member_table(table, cast(*Type_Info_Struct)member.type, rename, offset);
        }
    }
}


parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
	assert(str[0] == #char "{", "Invalid object start %", str);
	remainder := advance(str);
	remainder = trim_left(remainder, WHITESPACE_CHARS);
	if remainder[0] == #char "}" {
		remainder = advance(remainder);
		return remainder, true;
	}

    // @Speed: Building this table every time is pretty silly.
    // We should probably either not build it at all or cache it somewhere.
	member_table: Table(string, Member_Offset);
	init(*member_table);
	defer deinit(*member_table);

	if info fill_member_table(*member_table, info, rename);

	while true {
		if remainder[0] != #char "\""	return remainder, false;

		key: string;
		success: bool;
		key, remainder, success = parse_string(remainder);
		if !success		return remainder, false;
		defer free(key);

		member_found, member_offset := table_find(*member_table, key);

		member_slot: *u8;
		member_info: *Type_Info;
		if member_found {
			member_slot = slot + member_offset.offset_in_bytes;
			member_info = member_offset.member.type;
		} else if !ignore_unknown {
			log_error("Missing member % in %", key, (.*)info);
			return remainder, false;
		}

		remainder = trim_left(remainder, WHITESPACE_CHARS);
		if remainder[0] != #char ":"	return remainder, false;
		remainder = advance(remainder);
		remainder = trim_left(remainder, WHITESPACE_CHARS);
		remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename);
		if !success		return remainder, false;

		remainder = trim_left(remainder, WHITESPACE_CHARS);
		if remainder[0] != #char ","	break;
		remainder = advance(remainder);
		remainder = trim_left(remainder, WHITESPACE_CHARS);
	}

	if remainder[0] != #char "}"	return remainder, false;
	remainder = advance(remainder);
	return remainder, true;
}

parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string) -> bool, remainder: string {
    if info.signed {
        success, remainder := parse_and_write_integer(info, pointer, string_value, signed = true);
        return success, remainder;
    } else {
        success, remainder := parse_and_write_integer(info, pointer, string_value, signed = false);
        return success, remainder;
    }
}

parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string, $signed: bool) -> bool, remainder: string {
    #if signed {
        Int_Type :: s64;
    } else {
        Int_Type :: u64;
    }

    int_value, int_success, remainder := string_to_int(string_value, T = Int_Type);
    if !int_success {
        #if signed {
            log_error("Could not parse \"%\" as an integer.", string_value);
        } else {
            log_error("Could not parse \"%\" as an unsigned integer.", string_value);
        }
        return false, remainder;
    }

    valid, low, high := Reflection.range_check_and_store(int_value, info, pointer);

    if !valid {
        log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
        return false, remainder;
    }

    return true, remainder;
}

Reflection :: #import "Reflection";

