package flag

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

Flag_Error :: enum {
	None,
	No_Base_Struct,
	Arg_Error,
	Arg_Unsupported_Field_Type,
	Arg_Not_Defined,
	Arg_Non_Optional,
	Value_Parse_Error,
	Tag_Error,
}

Flag :: struct {
	optional: bool,
	type:     ^runtime.Type_Info,
	data:     rawptr,
	tag_ptr:  rawptr,
	parsed:   bool,
}

Flag_Context :: struct {
	seen_flags: map[string]Flag,
}

parse_args :: proc(ctx: ^Flag_Context, args: []string) -> Flag_Error {

	using runtime;

	args := args;

	for {
		if len(args) == 0 {
			return .None;
		}

		arg := args[0];

		if len(arg) < 2 || arg[0] != '-' {
			return .Arg_Error;
		}

		minus_count := 1;

		if arg[1] == '-' {
			minus_count += 1;

			if len(arg) == 2 {
				return .Arg_Error;
			}
		}

		name := arg[minus_count:];

		if len(name) == 0 {
			return .Arg_Error;
		}

		args = args[1:];

		assign_index := strings.index(name, "=");

		value := "";

		if assign_index > 0 {
			value = name[assign_index + 1:];
			name = name[0:assign_index];
		}

		flag := &ctx.seen_flags[name];

		if flag == nil {
			return .Arg_Not_Defined;
		}

		if reflect.is_boolean(flag.type) {
			tmp: b64 = true;
			mem.copy(flag.data, &tmp, flag.type.size);
			flag.parsed = true;
			continue;
		} else if value == "" { // must be in the next argument
			if len(args) == 0 {
				return .Arg_Error;
			}

			value = args[0];
			args = args[1:];
		}

		#partial switch _ in flag.type.variant {
		case Type_Info_Integer:
			if v, ok := strconv.parse_int(value); ok {
				mem.copy(flag.data, &v, flag.type.size);
			} else {
				return .Value_Parse_Error;
			}
		case Type_Info_String:
			raw_string := cast(^mem.Raw_String)flag.data;
			raw_string.data = strings.ptr_from_string(value);
			raw_string.len = len(value);
		case Type_Info_Float:
			switch flag.type.size {
			case 32:
				if v, ok := strconv.parse_f32(value); ok {
					mem.copy(flag.data, &v, flag.type.size);
				} else {
					return .Value_Parse_Error;
				}
			case 64:
				if v, ok := strconv.parse_f64(value); ok {
					mem.copy(flag.data, &v, flag.type.size);
				} else {
					return .Value_Parse_Error;
				}
			}
		}

		flag.parsed = true;
	}

	return .None;
}

reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error {
	using runtime;

	if !reflect.is_struct(type_info_of(v.id)) {
		return .No_Base_Struct;
	}

	names := reflect.struct_field_names(v.id);
	types := reflect.struct_field_types(v.id);
	offsets := reflect.struct_field_offsets(v.id);
	tags := reflect.struct_field_tags(v.id);

	for name, i in names {
		flag: Flag;

		type := types[i];

		if named_type, ok := type.variant.(Type_Info_Named); ok {
			if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && union_type.maybe && len(union_type.variants) == 1 {
				flag.optional = true;
				flag.tag_ptr = rawptr(uintptr(union_type.tag_offset) + uintptr(v.data) + uintptr(offsets[i]));
				type = union_type.variants[0];
			} else {
				return .Arg_Unsupported_Field_Type;
			}
		}

		#partial switch _ in type.variant {
		case Type_Info_Integer, Type_Info_String, Type_Info_Boolean, Type_Info_Float:
			flag.type = type;
			flag.data = rawptr(uintptr(v.data) + uintptr(offsets[i]));
		case:
			return .Arg_Unsupported_Field_Type;
		}

		flag_name: string;

		if value, ok := reflect.struct_tag_lookup(tags[i], "flag"); ok {
			flag_name = cast(string)value;
		} else {
			return .Tag_Error;
		}

		ctx.seen_flags[flag_name] = flag;
	}

	return .None;
}

parse :: proc(v: any, args: []string) -> Flag_Error {

	if v == nil {
		return .None;
	}

	ctx: Flag_Context;

	if res := reflect_args_structure(&ctx, v); res != .None {
		return res;
	}

	if res := parse_args(&ctx, args); res != .None {
		return res;
	}

	//validate that the required flags were actually set
	for k, v in ctx.seen_flags {
		if v.optional && v.parsed {
			tag_value: i32 = 1;
			mem.copy(v.tag_ptr, &tag_value, 4); //4 constant is probably not portable, but it works for me currently
		} else if !v.parsed && !v.optional {
			return .Arg_Non_Optional;
		}
	}

	return .None;
}

usage :: proc(v: any) -> string {
	return "failed";
}
