#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "platform.h"
#include "render/render.h"
#include "enginestate.h"
#include "lib/types.h"
#include "lib/math.h"
#include "gui/gui.h"
#include "debug/logger.h"
#include "debug/log_viewer.h"
#include "gui/layout.h"
#include "stb_image.h"

#include <time.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <NetworkManager.h>
#include <libvirt/libvirt.h>
#include <mntent.h>
#include <sys/statvfs.h>
#include <sys/stat.h>


bool process_input(); // Returns true when the program needs to exit
void process_gui();
void app_init();
void app_deinit();

enum Section_Status
{
	SECTION_STATUS_NONE,
	SECTION_STATUS_OK,
	SECTION_STATUS_WARNING,
	SECTION_STATUS_ERROR
};

struct App_Data
{
	f64 glib_iteration_t = -1;
	f64  system_sample_t = -1;
	f64 network_sample_t = -1;
	f64    virt_sample_t = -1;
	f64      fs_sample_t = -1;

	f64 glib_iteration_delta = 0.1;
	f64  system_sample_delta = 0.2;
	f64 network_sample_delta = 0.2;
	f64    virt_sample_delta = 0.5;
	f64      fs_sample_delta = 1.0;

	bool show_grid = false;
};

struct System_Info
{
	char hostname[128];
	char kernel[128];
	time_t time;
	char cached_date_string[128];
	char cached_time_string[128];

	s32 cpus_total;
	s32 cpus_active;

	f32 load[3];
	s64 uptime;

	u64 ram_total;
	u64 ram_used;
	u64 ram_available;

	Section_Status status;
};

struct Network_Device
{
	char name[128];
	NMDeviceType type;
	NMDeviceState state;
};

struct Network_Info
{
	s32 device_count;
	Network_Device devices[32];

	Section_Status status;
};

struct Virt_Domain
{
	char name[128];
	s32 state;

	u32 vcpus;
	f32 cpu_usage;
	u64 prev_cpuTime;

	u64 ram_total;
	u64 ram_used;
	u64 ram_available;
};

struct Virt_Info
{
	s32 domain_count;
	Virt_Domain domains[32];

	Section_Status status;
};

struct FS_Entry
{
	char device[256];
	char directory[256];
	char type[128];
	u64 bytes_total;
	u64 bytes_available;
};

struct FS_Info
{
	s32 fs_count;
	FS_Entry fs[32];

	Section_Status status;
};

int fs_cmp(const void* a, const void *b)
{
	FS_Entry *fs_a = (FS_Entry*)a;
	FS_Entry *fs_b = (FS_Entry*)b;
	return strcmp(fs_a->directory, fs_b->directory);
}

System_Info system_info;
Network_Info network_info;
Virt_Info virt_info;
FS_Info fs_info;
App_Data app_data;

Gui_Style style_default;
void style_select(Gui_Context *ctx, Section_Status status)
{

	switch(status)
	{
		case SECTION_STATUS_NONE: {
			ctx->style.button_color = style_default.button_color;
			ctx->style.button_color_hovered = style_default.button_color_hovered;
			ctx->style.window_background_color = style_default.window_background_color;
			ctx->style.window_background_color_inactive = style_default.window_background_color_inactive;
			ctx->style.window_border_color = style_default.window_border_color;
			ctx->style.window_border_color_inactive = style_default.window_border_color_inactive;
		} break;
		case SECTION_STATUS_OK: {
			ctx->style.button_color_hovered = ctx->style.button_color = v4{0,1,0,1};
			ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{0,.6,0,1};
			ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{0,1,0,1};
		} break;
		case SECTION_STATUS_WARNING: {
			ctx->style.button_color_hovered = ctx->style.button_color = v4{1,.8,0,1};
			ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{.6,.5,0,1};
			ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{1,.8,0,1};
		} break;
		case SECTION_STATUS_ERROR: {
			ctx->style.button_color_hovered = ctx->style.button_color = v4{1,0,0,1};
			ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{0.6,0,0,1};
			ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{1,0,0,1};
		} break;
	}
}

// Icons
r_texture icon_disk;
r_texture icon_lan;
r_texture icon_ram;
r_texture icon_vpn;
r_texture icon_wifi;
r_texture icon_cpu;


u32 seconds_to_duration_text(char *text, f64 seconds, bool show_millis = false)
{
	u32 written = 0;
	u32 days = seconds / (24 * 3600);
	seconds -= days * (24 * 3600);
	u32 hours = seconds / 3600;
	seconds -= hours * 3600;
	u32 minutes = seconds / 60;
	seconds -= minutes * 60;

	if(days)
		written += sprintf(text + written, "%s%dd", (written ? " " : ""), days);
	if(days || hours)
		written += sprintf(text + written, "%s%dh", (written ? " " : ""), hours);
	if(days || hours || minutes)
		written += sprintf(text + written, "%s%dm", (written ? " " : ""), minutes);
	if(days || hours || minutes || seconds)
	{
		if(show_millis)
			written += sprintf(text + written, "%s%.3lfs", (written ? " " : ""), seconds);
		else
			written += sprintf(text + written, "%s%.0lfs", (written ? " " : ""), seconds);
	}

	return written;
}


r_texture load_image_monochrome(const char *filename)
{
	s32 width, height, channels;
	stbi_set_flip_vertically_on_load(false);
	u8 *data = stbi_load(filename, &width, &height, &channels, 1);

	for(u64 i = 0; i < (width*height); i++) // Flipped colors
		data[i] = 0xFF - data[i];

	return r_texture_create(data, {width, height}, R_TEXTURE_ALPHA);
}


int main(int argc, char *argv[])
{
	bool status;

	LOG_INIT();
	p_init(true);

	LogViewer log_viewer = LogViewer::Init(&global_logger);

	p_window_open();
	p_window_name((char*)"Server Monitor");

	r_init();
	gui_init();

	log_viewer.Print_New_Messages_On_Console();


	app_init();


	f64 start = p_time();
	f64 prev_t = 0;
	f32 max_render_time = 1.0 / 30.0;
	f64 processing_start = 0;
	while(1)
	{
		// Engine
		engine.time = p_time() - start;
		engine.delta_t = engine.time - prev_t;
		//LOG(LOG_INFO, "Frame time: %.3lf ms    FPS: %.2lf", 1000*delta_t, 1/delta_t);
		r_time_update(engine.time);

		// @Hack: @Performance: Reduced framerate to use less resources. Text rendering is highly inefficient right now. Maybe it will improve later and this will not be necessary anymore.
		f64 processing_time = engine.time - processing_start;
		if(processing_time < max_render_time)
			p_wait(maximum(0, (max_render_time - 1.02*processing_time) * 1000));
		processing_start = p_time() - start;


		// Input
		bool exit = process_input();
		if(exit)
			break;

		// GUI
		r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
		r_clear({0,0,0,0});

		process_gui();

		log_viewer.Print_New_Messages_On_Console();
		r_swap();
		prev_t = engine.time;
	}

	app_deinit();
	gui_deinit();
	p_window_close();

	LOG_DEINIT();
	p_deinit();
	return 0;
}



bool process_input()
{
	// Events
	Event event;
	while(p_next_event(&event))
	{
		gui_handle_event(&event);

		switch(event.type)
		{
			case EVENT_QUIT: {
				return true;
			};
			case EVENT_MOUSE_MOVE:
			{

			} break;
			case EVENT_KEY:
			{
				switch(event.key.key_code)
				{
					case KEY_G:
					case KEY_F2: {
						if(event.key.pressed)
							app_data.show_grid = !app_data.show_grid;
					} break;
					default: {
						// Other keys. Put default to suppress warnings
					} break;
				}
			} break;
			case EVENT_RESIZE:
			{
				//LOG(LOG_DEBUG, "New size: %u %u", signal.resize.width, signal.resize.height);
				s32 width  = event.resize.width;
				s32 height = event.resize.height;
				r_size_update(width, height);

				global_gui_state.default_context.width  = width;
				global_gui_state.default_context.height = height;

				engine.gui_scaling = minimum(width, height) * 0.03;
				global_gui_state.default_context.style.font_size = engine.gui_scaling;
				global_gui_state.default_context.style.button_radius = engine.gui_scaling / 10;

			} break;
			case EVENT_FOCUS:
			{
			} break;
			case EVENT_UNFOCUS:
			{
			} break;
		}
	}

	return false;
}




void system_info_gui(Gui_Layout_Grid *grid)
{
	Gui_Context *ctx = &global_gui_state.default_context;

	// No window info
	// Clock and date
	system_info.time = time(NULL);
	struct tm *time_info = localtime(&system_info.time);
	char clock[64], timezone[64];
	strftime(clock, 128, "%H:%M:%S", time_info);
	strftime(timezone, 128, "%Z", time_info);

	f32 old_font_size = ctx->style.font_size;
	ctx->style.font_size *= 3;
	gui_text_aligned(grid->rect_at({2,1}, {2,2}),    clock, GUI_ALIGN_CENTER);
	ctx->style.font_size = old_font_size;
	gui_text_aligned(grid->rect_at({2,3}, {2,1}), timezone, GUI_ALIGN_CENTER);
	gui_text_aligned(grid->rect_at({2,0}, {2,1}), system_info.cached_date_string, GUI_ALIGN_CENTER);

	// Host
	gui_text_aligned(grid->rect_at({0,0}, {2,1}), system_info.hostname, GUI_ALIGN_LEFT);
	// Uptime
	char uptime[128] = "Uptime: "; seconds_to_duration_text(uptime + strlen("Uptime: "), system_info.uptime);
	gui_text_aligned(grid->rect_at({4,0}, {2,1}), uptime, GUI_ALIGN_RIGHT);


	Rect r;
	Gui_Layout_Grid layout;


	// Window reserved for future system info
	r = grid->rect_at({0,1}, {2,3});
	layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, 3, 0.2*engine.gui_scaling);

	style_select(ctx, system_info.status);
	gui_window_start(r, 0xabc23401);
	style_select(ctx, SECTION_STATUS_NONE);

	gui_window_end();


	// Window with cpu n., load, ram
	r = grid->rect_at({4,1}, {2,3});
	layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 4, 3, 0.2*engine.gui_scaling);

	style_select(ctx, system_info.status);
	gui_window_start(r, 0xabcdef01);
	style_select(ctx, SECTION_STATUS_NONE);


	char load[128]; snprintf(load, 128, "Load: %.2f %.2f %.2f", system_info.load[0], system_info.load[1], system_info.load[2]);

	char processors[128]; snprintf(processors, 128, "CPUs: %d/%d", system_info.cpus_active, system_info.cpus_total);

	// slider for ram?
	char ram[128]; snprintf(ram, 128, "%.2f/%.2f GiB", system_info.ram_used / (1024.0*1024.0*1024.0), system_info.ram_total / (1024.0*1024.0*1024.0));
	f32 ram_ratio = (f32)system_info.ram_used / system_info.ram_total;

	gui_text_aligned(layout.cell(layout.max_cells_count.x), processors, GUI_ALIGN_LEFT);

	v2 ram_text_size = gui_text_compute_size("RAM: ");
	Rect ram_rect = layout.cell(layout.max_cells_count.x);
	ram_rect.position.x += ram_text_size.x;
	ram_rect.size.x -= ram_text_size.x;
	gui_text_aligned(layout.rect(), "RAM: ", GUI_ALIGN_LEFT);
	gui_slider_text(ram_rect, &ram_ratio, ram);

	gui_text_aligned(layout.cell(layout.max_cells_count.x), load, GUI_ALIGN_LEFT);

	gui_window_end();
}

void network_gui(Gui_Layout_Grid *grid)
{
	Gui_Context *ctx = &global_gui_state.default_context;

	Rect r = grid->rect_at({0,5}, {2,grid->max_cells_count.y - 5});
	Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);

	style_select(ctx, network_info.status);
	gui_window_start(r, 0xabcdef02);
	style_select(ctx, SECTION_STATUS_NONE);

	gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Network", GUI_ALIGN_CENTER);
	layout.row();

	r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});
	layout = gui_layout_grid_create_by_divisions(r.position, r.size, layout.max_cells_count.x, layout.max_cells_count.y/3, 0.2*engine.gui_scaling);

	for(s32 i = 0; i < network_info.device_count; i++)
	{
		Network_Device *device = &network_info.devices[i];

		if(device->type != NM_DEVICE_TYPE_ETHERNET && device->type != NM_DEVICE_TYPE_WIFI && device->type != NM_DEVICE_TYPE_WIREGUARD)
			continue;

		{
			Section_Status status = SECTION_STATUS_OK;
			if(device->state == NM_DEVICE_STATE_PREPARE ||
				device->state == NM_DEVICE_STATE_CONFIG ||
				device->state == NM_DEVICE_STATE_IP_CONFIG ||
				device->state == NM_DEVICE_STATE_IP_CHECK)
			{
				// Bump status
				if(status < SECTION_STATUS_WARNING)
					status = SECTION_STATUS_WARNING;
			}
			else if(device->state != NM_DEVICE_STATE_ACTIVATED)
			{
				// Bump status
				if(status < SECTION_STATUS_ERROR)
					status = SECTION_STATUS_ERROR;
			}

			Rect r = layout.cell();
			style_select(ctx, status);
			gui_panel(r);
			style_select(ctx, SECTION_STATUS_NONE);

			Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 9, 4, 0.1*engine.gui_scaling);

			const char *type_text = "";
			r_texture *icon = &icon_lan;
			switch(device->type)
			{
				case NM_DEVICE_TYPE_ETHERNET: {
					icon = &icon_lan;
					type_text = "Ethernet";
				} break;
				case NM_DEVICE_TYPE_WIFI: {
					icon = &icon_wifi;
					type_text = "WiFi";
				} break;
				case NM_DEVICE_TYPE_TUN: {
					icon = &icon_vpn;
					type_text = "TAP/TUN";
				} break;
				case NM_DEVICE_TYPE_BRIDGE: {
					icon = &icon_lan;
					type_text = "Bridge";
				} break;
				case NM_DEVICE_TYPE_VLAN: {
					icon = &icon_lan;
					type_text = "vlan";
				} break;
				case NM_DEVICE_TYPE_WIREGUARD: {
					icon = &icon_vpn;
					type_text = "VPN";
				} break;
				default: {
					icon = &icon_lan;
					type_text = "";
				} break;
			}

			const char *state_text = "";
			switch(device->state)
			{
				case NM_DEVICE_STATE_UNKNOWN: state_text = "UNKNOWN"; break;
				case NM_DEVICE_STATE_UNMANAGED: state_text = "UNMANAGED"; break;
				case NM_DEVICE_STATE_UNAVAILABLE: state_text = "UNAVAILABLE"; break;
				case NM_DEVICE_STATE_DISCONNECTED: state_text = "DISCONNECTED"; break;
				case NM_DEVICE_STATE_PREPARE: state_text = "PREPARE"; break;
				case NM_DEVICE_STATE_CONFIG: state_text = "CONFIG"; break;
				case NM_DEVICE_STATE_NEED_AUTH: state_text = "NEED_AUTH"; break;
				case NM_DEVICE_STATE_IP_CONFIG: state_text = "IP_CONFIG"; break;
				case NM_DEVICE_STATE_IP_CHECK: state_text = "IP_CHECK"; break;
				case NM_DEVICE_STATE_SECONDARIES: state_text = "SECONDARIES"; break;
				case NM_DEVICE_STATE_ACTIVATED: state_text = "ACTIVATED"; break;
				case NM_DEVICE_STATE_DEACTIVATING: state_text = "DEACTIVATING"; break;
				case NM_DEVICE_STATE_FAILED: state_text = "FAILED"; break;
				default: gui_button(layout.cell(), ""); break;
			}

			Rect icon_r = layout.cell(1);
			icon_r.position += (icon_r.size - v2{1,1}*minimum(icon_r.size.x, icon_r.size.y)) * .5;
			icon_r.size = v2{1,1} * minimum(icon_r.size.x, icon_r.size.y);
			gui_image(icon_r, icon);
			gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), device->name);

			layout.cell(1);
			gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), type_text);

			layout.row(2);
			layout.cell(1);
			gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), state_text);
		}
	}
	gui_window_end();
}

void vm_gui(Gui_Layout_Grid *grid)
{
	Gui_Context *ctx = &global_gui_state.default_context;

	Rect r = grid->rect_at({2,5}, {2,grid->max_cells_count.y - 5});
	Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);

	style_select(ctx, virt_info.status);
	gui_window_start(r, 0xabcdef03);
	style_select(ctx, SECTION_STATUS_NONE);

	gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Virtual machines", GUI_ALIGN_CENTER);
	layout.row();

	r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});

	static v2 area_size = r.size;
	static Rect inner_r = r;
	gui_scrollable_area_start(r, area_size, &inner_r);

	layout = gui_layout_grid_create_by_divisions(inner_r.position, inner_r.size, layout.max_cells_count.x, layout.max_cells_count.y/4, 0.2*engine.gui_scaling);

	for(s32 i = 0; i < virt_info.domain_count; i++)
	{
		Virt_Domain *domain = &virt_info.domains[i];

		// Status
		Section_Status status = SECTION_STATUS_NONE;
		switch(domain->state)
		{
			case VIR_DOMAIN_NOSTATE:
				status = SECTION_STATUS_NONE;
				break;
			case VIR_DOMAIN_RUNNING:
				status = SECTION_STATUS_OK;
				break;
			case VIR_DOMAIN_BLOCKED:
			case VIR_DOMAIN_SHUTOFF:
			case VIR_DOMAIN_CRASHED:
				status = SECTION_STATUS_ERROR;
				break;
			case VIR_DOMAIN_SHUTDOWN:
				status = SECTION_STATUS_ERROR;
				break;
			case VIR_DOMAIN_PAUSED:
			case VIR_DOMAIN_PMSUSPENDED:
				status = SECTION_STATUS_WARNING;
				break;
			default:
				status = SECTION_STATUS_NONE;
		}

		Rect r = layout.cell();
		area_size = r.position + r.size - inner_r.position;

		style_select(ctx, status);
		gui_panel(r);
		style_select(ctx, SECTION_STATUS_NONE);

		Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 2, 4, 0.1*engine.gui_scaling);

		gui_text(layout.cell(2), domain->name);


		char ram[128]; snprintf(ram, 128, "%.2f GB", domain->ram_total / (1024.0*1024.0*1024.0));
		gui_text(layout.cell(1), ram);

		char cpu_count[128]; snprintf(cpu_count, 128, "%hd vCPU", domain->vcpus);
		gui_text(layout.cell(1), cpu_count);

		layout.row(2);
		char cpu[128]; snprintf(cpu, 128, "CPU: %.2f%%", domain->cpu_usage * 100);
		gui_text(layout.cell(2), cpu);

		layout.row();
	}

	gui_scrollable_area_end();
	gui_window_end();
}


void fs_gui(Gui_Layout_Grid *grid)
{
	Gui_Context *ctx = &global_gui_state.default_context;

	Rect r = grid->rect_at({4,5}, {2,grid->max_cells_count.y - 5});
	Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);

	style_select(ctx, fs_info.status);
	gui_window_start(r, 0xabcdef04);
	style_select(ctx, SECTION_STATUS_NONE);

	gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Storage", GUI_ALIGN_CENTER);
	layout.row();

	r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});
	layout = gui_layout_grid_create_by_divisions(r.position, r.size, layout.max_cells_count.x, layout.max_cells_count.y/3, 0.2*engine.gui_scaling);



	for(s32 i = 0; i < fs_info.fs_count; i++)
	{
		s32 container_size = 2;
		if(strcmp(fs_info.fs[i].directory, "/boot") == 0 || strcmp(fs_info.fs[i].directory, "/boot/efi") == 0)
			container_size = 1;

		Rect r = layout.cell(container_size);
		gui_panel(r);

		Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 8, 4, 0.1*engine.gui_scaling);

		Rect icon_r = layout.cell(1);
		icon_r.position += (icon_r.size - v2{1,1}*minimum(icon_r.size.x, icon_r.size.y)) * .5;
		icon_r.size = v2{1,1} * minimum(icon_r.size.x, icon_r.size.y);

		gui_image(icon_r, &icon_disk);
		gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), fs_info.fs[i].directory);


		layout.row(2);
		char space[64]; snprintf(space, 64, "%.1f/%.1f GiB", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / (1024*1024*1024), (f32)fs_info.fs[i].bytes_total / (1024*1024*1024));
		char percentage[64]; snprintf(percentage, 64, "%.1f%%", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total * 100);
		f32 space_ratio = (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total;
		gui_slider_text(layout.cell(layout.max_cells_count.x), &space_ratio, space);
		gui_text_aligned(layout.cell(layout.max_cells_count.x), percentage, GUI_ALIGN_CENTER);
	}

	gui_window_end();
}


NMClient *nmclient;
virConnectPtr virt_connection;

void collect_static_data()
{
	// Hostname
	struct utsname host_info;
	uname(&host_info);

	system_info.hostname[0] = 0;
	if(host_info.nodename[0])
		strncpy(system_info.hostname, host_info.nodename, 128);
	snprintf(system_info.kernel, 128, "%s %s", host_info.sysname, host_info.release);
}

// key and value are views into buffer. Copy them if you need them for a longer lifetime
size_t meminfo_extract_next_entry(String *buffer, size_t start, String *key, String *value)
{
	size_t separator = 0;
	size_t end = 0;
	for(int i = start; i < buffer->size; i++)
	{
		if(buffer->text[i] == ':')
		{
			separator = i;
		}
		else if(buffer->text[i] == '\n')
		{
			end = i;
			break;
		}
	}

	if(end == 0) // no newline found
		end = buffer->size;

	if(key)
	{
		key->text = buffer->text + start;
		key->size = separator - start;
	}
	if(value)
	{
		// TODO: Check if seaparator is actually found. This will return the wrong data if separator is 0
		value->text = buffer->text + separator + 1;
		value->size = end - (separator + 1);
	}

	return end + 1 - start;
}

String String_trim(String s)
{
	String result;

	result.text = s.text;
	result.size = s.size;

	// Trim front
	while(result.size > 0 && result.text[0] == ' ')
	{
		result.text++;
		result.size--;
	}
	// Trim back
	while(result.size > 0 && result.text[result.size-1] == ' ')
	{
		result.size--;
	}

	return result;
}

void collect_new_data_if_needed()
{
	if(engine.time - app_data.glib_iteration_t >= app_data.glib_iteration_delta)
	{
		g_main_context_iteration(NULL, false);
		app_data.glib_iteration_t = engine.time;
	}


	if(engine.time - app_data.system_sample_t >= app_data.system_sample_delta)
	{
		// Clock
		system_info.time = time(NULL);
		struct tm *time_info = localtime(&system_info.time);
		strftime(system_info.cached_date_string, 128, "%a %e %b %Y", time_info);
		strftime(system_info.cached_time_string, 128, "%H:%M:%S %Z", time_info);

		// CPU count
		system_info.cpus_total  = get_nprocs();
		system_info.cpus_active = get_nprocs_conf();

		// System info for later
		struct sysinfo sys_info;
		sysinfo(&sys_info);
		// Uptime
		system_info.uptime = sys_info.uptime;
		// Load
		system_info.load;
		for(int i = 0; i < 3; i++)
			system_info.load[i] = round((f32)sys_info.loads[i] / (1 << SI_LOAD_SHIFT) * 100) / 100;

		// Memory
		{
			p_file file;
			if(p_file_init(&file, "/proc/meminfo", P_FILE_READONLY))
			{
				u64 size = p_file_size(&file);
				if(size == 0)
					size = 1024;
				u8 backing_buff[size + 1];
				Buffer buffer = { .size = size + 1, .data = backing_buff };
				if(p_file_read(&file, &buffer, size + 1))
				{
					String s = { .size = buffer.size, .text = buffer.data };
					// Parse meminfo data
					size_t parsed = 0;
					while(1)
					{
						String key, value;
						size_t read = meminfo_extract_next_entry(&s, parsed, &key, &value);
						parsed += read;
						if(read == 0)
							break;

						key = String_trim(key);
						value = String_trim(value);

						if(strncmp((const char *)key.text, "MemTotal", key.size) == 0)
						{
							// LOG(LOG_INFO, "MemTotal: %.*s\n", value.size, value.text);
							u64 converted = 0;
							sscanf((const char *)value.text, "%lu", &converted);
							system_info.ram_total = converted * 1024;
						}
						else if(strncmp((const char *)key.text, "MemAvailable", key.size) == 0)
						{
							// LOG(LOG_INFO, "MemAvailable: %.*s\n", value.size, value.text);
							u64 converted = 0;
							sscanf((const char *)value.text, "%lu", &converted);
							system_info.ram_available = converted * 1024;
						}
					}
				}

				p_file_deinit(&file);
			}
		}

		// system_info.ram_total = sys_info.totalram * sys_info.mem_unit;
		// system_info.ram_available = (sys_info.freeram + sys_info.bufferram) * sys_info.mem_unit;
		// system_info.ram_used = sys_info.totalram * sys_info.mem_unit - system_info.ram_available;
		system_info.ram_used = system_info.ram_total - system_info.ram_available;


		// Status
		system_info.status = SECTION_STATUS_NONE;

		app_data.system_sample_t = engine.time;
	}


	if(engine.time - app_data.network_sample_t >= app_data.network_sample_delta)
	{
		const GPtrArray *devices = nm_client_get_devices(nmclient);
		network_info.device_count = devices->len;
		for(int i = 0; i < devices->len; i++)
		{
			NMDevice *device = (NMDevice*)devices->pdata[i];

			strncpy(network_info.devices[i].name, nm_device_get_iface(device), 128);
			network_info.devices[i].type  = nm_device_get_device_type(device);
			network_info.devices[i].state = nm_device_get_state(device);
		}

		// Update section status
		network_info.status = SECTION_STATUS_OK;
		for(int i = 0; i < network_info.device_count; i++)
		{
			Network_Device *device = &network_info.devices[i];
			if(device->type == NM_DEVICE_TYPE_ETHERNET || device->type == NM_DEVICE_TYPE_WIREGUARD)
			{
				if(device->state == NM_DEVICE_STATE_PREPARE ||
					device->state == NM_DEVICE_STATE_CONFIG ||
					device->state == NM_DEVICE_STATE_IP_CONFIG ||
					device->state == NM_DEVICE_STATE_IP_CHECK)
				{
					// Bump status
					if(network_info.status < SECTION_STATUS_WARNING)
						network_info.status = SECTION_STATUS_WARNING;
				}
				else if(device->state != NM_DEVICE_STATE_ACTIVATED)
				{
					// Bump status
					if(network_info.status < SECTION_STATUS_ERROR)
						network_info.status = SECTION_STATUS_ERROR;
				}
			}
		}

		app_data.network_sample_t = engine.time;
	}


	if(engine.time - app_data.virt_sample_t >= app_data.virt_sample_delta)
	{
		virDomainPtr *domains;
		virt_info.domain_count = virConnectListAllDomains(virt_connection, &domains, 0);

		for(int i = 0; i < virt_info.domain_count; i++)
		{
			Virt_Domain *dom = &virt_info.domains[i];
			// Name
			strncpy(dom->name, virDomainGetName(domains[i]), 128);

			// State
			virDomainInfo info;
			int res = virDomainGetInfo(domains[i], &info);
			dom->state = info.state;

			// CPU
			dom->vcpus = info.nrVirtCpu;

			dom->cpu_usage = (info.cpuTime - dom->prev_cpuTime) / ((engine.time - app_data.virt_sample_t) * 1e9 * dom->vcpus);
			dom->prev_cpuTime = info.cpuTime;

			dom->ram_total = info.memory * 1024; // mem * 1000 or mem * 1024 ??
			dom->ram_used = dom->ram_total;
			dom->ram_available = 0;
		}

		p_free(domains);

		// Update section status
		virt_info.status = SECTION_STATUS_OK;
		for(int i = 0; i < virt_info.domain_count; i++)
		{
			Virt_Domain *domain = &virt_info.domains[i];
			if(domain->state == VIR_DOMAIN_PAUSED ||
				domain->state == VIR_DOMAIN_PMSUSPENDED)
			{
				// Bump status
				if(virt_info.status < SECTION_STATUS_WARNING)
					virt_info.status = SECTION_STATUS_WARNING;
			}
			else if(domain->state != VIR_DOMAIN_RUNNING)
			{
				// Bump status
				if(virt_info.status < SECTION_STATUS_ERROR)
					virt_info.status = SECTION_STATUS_ERROR;
			}
		}

		app_data.virt_sample_t = engine.time;
	}


	if(engine.time - app_data.fs_sample_t >= app_data.fs_sample_delta)
	{
		FILE *file = setmntent("/proc/mounts", "r");

		fs_info.fs_count = 0;
		for(struct mntent *ent = getmntent(file); ent != NULL; ent = getmntent(file))
		{
			strncpy(fs_info.fs[fs_info.fs_count].device, ent->mnt_fsname, 256);
			strncpy(fs_info.fs[fs_info.fs_count].directory, ent->mnt_dir, 256);
			strncpy(fs_info.fs[fs_info.fs_count].type, ent->mnt_type, 128);
			fs_info.fs[fs_info.fs_count].bytes_total = 0;
			fs_info.fs[fs_info.fs_count].bytes_available = 0;

			struct stat s;
			if(stat(ent->mnt_fsname, &s) != 0 || !S_ISBLK(s.st_mode))
				continue;

			struct statvfs svfs;
			if(statvfs(ent->mnt_dir, &svfs) == 0)
			{
				fs_info.fs[fs_info.fs_count].bytes_total     = svfs.f_frsize * svfs.f_blocks;
				fs_info.fs[fs_info.fs_count].bytes_available = svfs.f_frsize * svfs.f_bavail;
			}
			fs_info.fs_count++;
		}

		endmntent(file);

		// Sort by mount directory
		qsort(fs_info.fs, fs_info.fs_count, sizeof(FS_Entry), fs_cmp);

		fs_info.status = SECTION_STATUS_NONE;
	}
}

void process_gui()
{
	collect_new_data_if_needed();

	gui_frame_begin(engine.time);

	Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{r_render_state.width,r_render_state.height} , 6, 22, 0.5*engine.gui_scaling);



	system_info_gui(&layout);
	network_gui(&layout);
	vm_gui(&layout);
	fs_gui(&layout);

	gui_frame_end();

	if(app_data.show_grid)
	{
		layout.cursor = {0,0};
		while(layout.cursor.y < layout.max_cells_count.y)
		{
			r_2d_immediate_rectangle_outline(layout.cell(), {0,1,0,1});
			char coords[32]; snprintf(coords, 32, "(%d, %d)", layout.cursor.x - 1, layout.cursor.y);
			gui_text_aligned(layout.rect(), coords, GUI_ALIGN_CENTER); // abuse: used after gui_frame_end
		}
	}
}

void app_init()
{
	app_data.system_sample_t  = 0;
	app_data.network_sample_t = 0;
	app_data.virt_sample_t    = 0;
	app_data.glib_iteration_t = 0;

	nmclient = nm_client_new(NULL, NULL);
	virt_connection = virConnectOpenReadOnly("qemu:///system");

	collect_static_data();

	style_default = global_gui_state.default_context.style;

	// Icons
	icon_disk = load_image_monochrome("assets/icons/harddisk-symbolic.png");
	icon_lan  = load_image_monochrome("assets/icons/lan-symbolic.png");
	icon_ram  = load_image_monochrome("assets/icons/memory-symbolic.png");
	icon_vpn  = load_image_monochrome("assets/icons/network-vpn-symbolic.png");
	icon_wifi = load_image_monochrome("assets/icons/network-wireless-symbolic.png");
	icon_cpu  = load_image_monochrome("assets/icons/processor-symbolic.png");
}

void app_deinit()
{
	virConnectClose(virt_connection);
}
