import std::io;
import std::io::file;
import std::collections::object;
import std::os::process;
import std::encoding::json;
import std::time;

import conf;
import ugui;
import ugui::sdl::ren;


const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv";
const char[*] FS_PATH = "resources/shaders/compiled/ugui.frag.spv";

const char[*] STYLESHEET_PATH = "resources/style.css";

const bool LIMIT_FPS = true;
const bool VSYNC = true;


fn int main(String[] args)
{
	String path = args[1];
	File file = file::open(path, "r")!!;

	// load configuration in memory as an object
	Object* conf = json::parse(mem, &file)!!;
	defer conf.free();

	// ---------------------------------------------- UI initialization ---------------------------------------------- //
	ArenaAllocator arena;
	char[] arena_mem = mem::new_array(char, 1024*1024);
	defer (void)mem::free(arena_mem);
	arena.init(arena_mem);

	ugui::Ctx ui;
	ui.init(&arena)!!;
	defer ui.free();

	ren::Renderer ren;
	ren.init("Qemu Manager", 800, 600, VSYNC);
	defer ren.free();
	ui.input_window_size(800, 600)!!;
	
	ui.load_font(&arena, "font1", "resources/hack-nerd.ttf", 16)!!;
	ren.font_atlas_id = ui.get_font_id("font1");
	Atlas* font_atlas = ui.get_font_atlas("font1")!!;
	ren.new_texture("font1", JUST_ALPHA, font_atlas.buffer, font_atlas.width, font_atlas.height);
	
	ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!;
	ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!;
	ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons");
	Atlas* sprite_atlas = &(ui.sprite_atlas.atlas);
	ren.new_texture("icons", FULL_COLOR, sprite_atlas.buffer, sprite_atlas.width, sprite_atlas.height);

	ren.load_spirv_shader_from_file("UGUI_PIPELINE", VS_PATH, FS_PATH, 2, 0);
	ren.create_pipeline("UGUI_PIPELINE", RECT);

	ui.import_style_from_file(STYLESHEET_PATH);
	ren::pre(ren.win);
	// -------------------------------------------- End UI initialization -------------------------------------------- //

	StrList cmd = conf::get_qemu_cmdline(mem, conf)!!;
	defer cmd.free();
	String vm_name = conf.get_string("name")!!;
	String vm_disk = conf.get_string("disk").to_expanded_path(tmem).str_view()!!;
	String vm_disk_size = bytes_to_human_readable(tmem, file::get_size(vm_disk))!!;

	bool vm_on = false;
	SubProcess vm_proc;
	defer (void)vm_proc.join();

	ConfFList conf_list = conf::get_config_list(tmem)!!;
	foreach (cf : conf_list) {
		io::printn(cf);
	}

	bool quit;
	Clock sleep_clock;
	while (!quit) {
		sleep_clock.mark();
		quit = ui.handle_events()!!;
		vm_on = vm_proc.is_running()!!;

		/* Start UI Handling */
		ui.frame_begin()!!;

		if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;

		ui.@div(ugui::@grow(), ugui::@grow(), COLUMN) {
			ui.@div(ugui::@grow(), ugui::@fit(20), ROW, LEFT) {
				ui.text("Machine:")!!;
				ui.separator(ugui::@grow(), ugui::@grow())!!;
				ui.text(vm_name)!!;

				static bool popup;
				static Point popup_pos;
				if (ui.button("v")!!.mouse_release) {
					popup = true;
					popup_pos = ui.input.mouse.pos;
				}
				ui.@popup(&popup, popup_pos, ugui::@fit(), ugui::@fit(), COLUMN) {
					foreach (idx, c : conf_list) {
						ui.text(c.name, idx)!!;
					}
				}!!;

			}!!;
			ui.hor_line()!!;
			ui.@div(ugui::@grow(), ugui::@grow(), COLUMN, scroll_y: true) {
				ui.text(string::tformat("disk: %s (%s)", vm_disk, vm_disk_size))!!;
				ui.text(string::tformat("runner: %s", conf.get_string("qemu")))!!;
				ui.text(string::tformat("memory: %s", conf.get_string("memory")))!!;
				ui.text(string::tformat("processors: %s", conf.get_string("processors")))!!;
			}!!;
			ui.hor_line()!!;
			ui.@div(ugui::@grow(), ugui::@fit(), ROW) {
				if (!vm_on) {
					if (ui.button("START")!!.mouse_release) {
						vm_proc = process::create(cmd.array_view(), {.inherit_stdio=true, .inherit_environment=true})!!;
						vm_on = true;
					}
				} else {
					if (ui.button("STOP")!!.mouse_release) {
						vm_proc.terminate()!!;
						vm_on = false;
					}
				}
			}!!;
		}!!;

		ui.frame_end()!!;
		/* End UI Handling */
		
		/* Start UI Drawing */
		ren.begin_render(true);
		ren.render_ugui(&ui.cmd_queue);
		ren.end_render();
		/* End Drawing */

		// wait for the next event, timeout after 100ms
		int timeout = LIMIT_FPS ? (int)(100.0-sleep_clock.mark().to_ms()-0.5) : 0;
		if (ui.skip_frame) timeout = 0;
		ren::wait_events(timeout);
	}

	return 0;
}


fn String bytes_to_human_readable(Allocator allocator, usz bytes, int dp = 1) {
	static usz thresh = 1024;
	static String[] units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"};

	double b = bytes;
	int u = 0;
	for (; b > thresh && u < units.len; u++) {
		b /= thresh;
	}

	return string::format(allocator, "%.*f %s", dp, b, units[u]);
}

