package odinfmt

import "core:os"
import "core:odin/tokenizer"
import "core:odin/format"
import "core:fmt"
import "core:strings"
import "core:path/filepath"
import "core:time"
import "core:mem"

import "flag"

Args :: struct {
	write: Maybe(bool) `flag:"w" usage:"write the new format to file"`,
}

print_help :: proc(args: []string) {
	if len(args) == 0 {
		fmt.eprint("odinfmt ");
	} else {
		fmt.eprintf("%s ", args[0]);
	}
	fmt.eprintln();
}

print_arg_error :: proc(args: []string, error: flag.Flag_Error) {
	switch error {
	case .None:
		print_help(args);
	case .No_Base_Struct:
		fmt.eprintln(args[0], "no base struct");
	case .Arg_Error:
		fmt.eprintln(args[0], "argument error");
	case .Arg_Unsupported_Field_Type:
		fmt.eprintln(args[0], "argument: unsupported field type");
	case .Arg_Not_Defined:
		fmt.eprintln(args[0], "argument: no defined");
	case .Arg_Non_Optional:
		fmt.eprintln(args[0], "argument: non optional");
	case .Value_Parse_Error:
		fmt.eprintln(args[0], "argument: value parse error");
	case .Tag_Error:
		fmt.eprintln(args[0], "argument: tag error");
	}
}

format_file :: proc(filepath: string) -> (string, bool) {
	if data, ok := os.read_entire_file(filepath); ok {
		return format.format(filepath, string(data), format.default_style);
	} else {
		return "", false;
	}
}

files: [dynamic]string;

walk_files :: proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool) {
	if info.is_dir {
		return 0, false;
	}

	if filepath.ext(info.name) != ".odin" {
		return 0, false;
	}

	append(&files, strings.clone(info.fullpath));

	return 0, false;
}

main :: proc() {
	init_global_temporary_allocator(mem.megabytes(100));

	args: Args;

	if len(os.args) < 2 {
		print_help(os.args);
		os.exit(1);
	}

	if res := flag.parse(args, os.args[1:len(os.args) - 1]); res != .None {
		print_arg_error(os.args, res);
		os.exit(1);
	}

	path := os.args[len(os.args) - 1];

	tick_time := time.tick_now();

	write_failure := false;

	if os.is_file(path) {
		if _, ok := args.write.(bool); ok {
			backup_path := strings.concatenate({path, "_bk"});
			defer delete(backup_path);

			if data, ok := format_file(path); ok {
				os.rename(path, backup_path);

				if os.write_entire_file(path, transmute([]byte)data) {
					os.remove(backup_path);
				}
			} else {
				fmt.eprintf("failed to write %v", path);
				write_failure = true;
			}
		} else {
			if data, ok := format_file(path); ok {
				fmt.println(data);
			}
		}
	} else if os.is_dir(path) {
		filepath.walk(path, walk_files);

		for file in files {
			fmt.println(file);

			backup_path := strings.concatenate({file, "_bk"});
			defer delete(backup_path);

			if data, ok := format_file(file); ok {

				if _, ok := args.write.(bool); ok {
					os.rename(file, backup_path);

					if os.write_entire_file(file, transmute([]byte)data) {
						os.remove(backup_path);
					}
				} else {
					fmt.println(data);
				}
			} else {
				fmt.eprintf("failed to format %v", file);
				write_failure = true;
			}
		}

		fmt.printf("formatted %v files in %vms", len(files), time.duration_milliseconds(time.tick_lap_time(&tick_time)));
	} else {
		fmt.eprintf("%v is neither a directory nor a file \n", path);
		os.exit(1);
	}

	os.exit(1 if write_failure else 0);
}
