const std = @import("std");

const main = @import("main");
const ConnectionManager = main.network.ConnectionManager;
const settings = main.settings;
const Vec2f = main.vec.Vec2f;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const Texture = main.graphics.Texture;

const gui = @import("../gui.zig");
const GuiComponent = gui.GuiComponent;
const GuiWindow = gui.GuiWindow;
const Button = @import("../components/Button.zig");
const HorizontalList = @import("../components/HorizontalList.zig");
const Label = @import("../components/Label.zig");
const TextInput = @import("../components/TextInput.zig");
const VerticalList = @import("../components/VerticalList.zig");

pub var window = GuiWindow{
	.contentSize = Vec2f{128, 256},
};

const padding: f32 = 8;
const width: f32 = 128;
var buttonNameArena: main.heap.NeverFailingArenaAllocator = undefined;

pub var needsUpdate: bool = false;

var deleteIcon: Texture = undefined;
var fileExplorerIcon: Texture = undefined;

const WorldInfo = struct {
	lastUsedTime: i64,
	name: []const u8,
	fileName: []const u8,
};
var worldList: main.ListUnmanaged(WorldInfo) = .{};

pub fn init() void {
	deleteIcon = Texture.initFromFile("assets/cubyz/ui/delete_icon.png");
	fileExplorerIcon = Texture.initFromFile("assets/cubyz/ui/file_explorer_icon.png");
}

pub fn deinit() void {
	deleteIcon.deinit();
	fileExplorerIcon.deinit();
}

pub fn openWorld(name: []const u8) void {
	const clientConnection = ConnectionManager.init(0, false) catch |err| {
		std.log.err("Encountered error while opening connection: {s}", .{@errorName(err)});
		return;
	};

	std.log.info("Opening world {s}", .{name});
	main.server.thread = std.Thread.spawn(.{}, main.server.start, .{name, clientConnection.localPort}) catch |err| {
		std.log.err("Encountered error while starting server thread: {s}", .{@errorName(err)});
		return;
	};
	main.server.thread.?.setName("Server") catch |err| {
		std.log.err("Failed to rename Server thread: {s}", .{@errorName(err)});
	};

	while(!main.server.running.load(.acquire)) {
		std.Thread.sleep(1_000_000);
	}
	clientConnection.world = &main.game.testWorld;
	const ipPort = std.fmt.allocPrint(main.stackAllocator.allocator, "127.0.0.1:{}", .{main.server.connectionManager.localPort}) catch unreachable;
	defer main.stackAllocator.free(ipPort);
	main.game.world = &main.game.testWorld;
	main.game.testWorld.init(ipPort, clientConnection) catch |err| {
		std.log.err("Encountered error while opening world: {s}", .{@errorName(err)});
	};
	for(gui.openWindows.items) |openWindow| {
		gui.closeWindowFromRef(openWindow);
	}
	gui.openHud();
}

fn openWorldWrap(index: usize) void { // TODO: Improve this situation. Maybe it makes sense to always use 2 arguments in the Callback.
	openWorld(worldList.items[index].fileName);
}

fn deleteWorld(index: usize) void {
	main.gui.closeWindow("delete_world_confirmation");
	main.gui.windowlist.delete_world_confirmation.setDeleteWorldName(worldList.items[index].fileName);
	main.gui.openWindow("delete_world_confirmation");
}

fn openFolder(index: usize) void {
	const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}", .{worldList.items[index].fileName}) catch unreachable;
	defer main.stackAllocator.free(path);

	main.files.openDirInWindow(path);
}

pub fn update() void {
	if(needsUpdate) {
		needsUpdate = false;
		onClose();
		onOpen();
	}
}

pub fn onOpen() void {
	buttonNameArena = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator);
	const list = VerticalList.init(.{padding, 16 + padding}, 300, 8);
	list.add(Label.init(.{0, 0}, width, "**Select World**", .center));
	list.add(Button.initText(.{0, 0}, 128, "Create New World", gui.openWindowCallback("save_creation")));
	readingSaves: {
		var dir = std.fs.cwd().makeOpenPath("saves", .{.iterate = true}) catch |err| {
			list.add(Label.init(.{0, 0}, 128, "Encountered error while trying to open saves folder:", .center));
			list.add(Label.init(.{0, 0}, 128, @errorName(err), .center));
			break :readingSaves;
		};
		defer dir.close();

		var iterator = dir.iterate();
		while(iterator.next() catch |err| {
			list.add(Label.init(.{0, 0}, 128, "Encountered error while iterating over saves folder:", .center));
			list.add(Label.init(.{0, 0}, 128, @errorName(err), .center));
			break :readingSaves;
		}) |entry| {
			if(entry.kind == .directory) {
				const worldInfoPath = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/world.zig.zon", .{entry.name}) catch unreachable;
				defer main.stackAllocator.free(worldInfoPath);
				const worldInfo = main.files.readToZon(main.stackAllocator, worldInfoPath) catch |err| {
					std.log.err("Couldn't open save {s}: {s}", .{worldInfoPath, @errorName(err)});
					continue;
				};
				defer worldInfo.deinit(main.stackAllocator);

				worldList.append(main.globalAllocator, .{
					.fileName = main.globalAllocator.dupe(u8, entry.name),
					.lastUsedTime = worldInfo.get(i64, "lastUsedTime", 0),
					.name = main.globalAllocator.dupe(u8, worldInfo.get([]const u8, "name", entry.name)),
				});
			}
		}
	}

	std.sort.insertion(WorldInfo, worldList.items, {}, struct {
		fn lessThan(_: void, lhs: WorldInfo, rhs: WorldInfo) bool {
			return rhs.lastUsedTime -% lhs.lastUsedTime < 0;
		}
	}.lessThan);

	for(worldList.items, 0..) |worldInfo, i| {
		const row = HorizontalList.init();
		row.add(Button.initText(.{0, 0}, 128, worldInfo.name, .{.callback = &openWorldWrap, .arg = i}));
		row.add(Button.initIcon(.{8, 0}, .{16, 16}, fileExplorerIcon, false, .{.callback = &openFolder, .arg = i}));
		row.add(Button.initIcon(.{8, 0}, .{16, 16}, deleteIcon, false, .{.callback = &deleteWorld, .arg = i}));
		row.finish(.{0, 0}, .center);
		list.add(row);
	}

	list.finish(.center);
	window.rootComponent = list.toComponent();
	window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding));
	gui.updateWindowPositions();
}

pub fn onClose() void {
	for(worldList.items) |worldInfo| {
		main.globalAllocator.free(worldInfo.fileName);
		main.globalAllocator.free(worldInfo.name);
	}
	worldList.clearAndFree(main.globalAllocator);
	buttonNameArena.deinit();
	if(window.rootComponent) |*comp| {
		comp.deinit();
	}
}
