const std      = @import("std");
const zm       = @import("zmath");
const znet     = @import("znet");
const rl       = @import("raylib");
const bufzilla = @import("bufzilla");
const ft = @import("freetype");

const shared = @import("shared");

const client = @import("client.zig");
const entity = @import("entity.zig");
const misc   = @import("misc.zig");
const font   = @import("font.zig");

const dt          : f32 = 1.0 / 200.0;
var   t           : f32 = 0;
var   gt          : f32 = 0;
var   accumulator : f32 = 0;
var   k           : f32 = 1.0;
var   frame       : i32 = 0;

const screen_width = 640;
const screen_height = 480;

var running: bool = true;

var dbg_allocator: std.heap.DebugAllocator(.{}) = undefined;
const allocator = dbg_allocator.allocator();

var stdout: *std.io.Writer = undefined;

// var connection: ?std.net.Stream = undefined;

pub fn main() !void {
    std.log.info("Hello Client!", .{});

    try init();
    defer _ = dbg_allocator.deinit();
    try znet.init();
    defer znet.deinit();
    rl.initWindow(1280, 720, "zzz");
    defer rl.closeWindow();

    // kbts_shape_context *Context = kbts_CreateShapeContext(0, 0);

    font.ft_lib = try ft.Library.init();

    var f = try font.Font.init("assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf", 48, allocator);
    defer f.deinit(allocator);

    font.shader = try rl.loadShader(null, "assets/text.frag");
    const test_shader = try rl.loadShader(null, "assets/test.frag");

    // const img = rl.genImageColor(32, 32, .blank);
    // const tx = try rl.loadTextureFromImage(img);
    // rl.unloadImage(img);

    const host = try znet.Host.init(.{
        .addr = null,
        .peer_limit = 1,
        .channel_limit = .max,
        .incoming_bandwidth = .unlimited,
        .outgoing_bandwidth = .unlimited,
    });
    defer host.deinit();

    const peer = try host.connect(.{
        .addr = try .init(.{
            .ip = .{ .ipv4 = "127.0.0.1" },
            .port = .{ .uint = 5000 },
        }),
        .channel_limit = .max,
        .data = 0,
    });

    // connect() catch |err| switch (err) {
    //     error.ConnectionRefused => {
    //         std.log.err("server refused connection", .{});
    //     },
    //     else => {
    //         std.log.err("unexpected connect error: {}", .{err});
    //         return err;
    //     },
    // };

    // try stdout.flush();

    var the_chunk = try shared.chunk.initChunk(allocator);
    defer shared.chunk.deinitChunk(&the_chunk, allocator);

    shared.chunk.updateChunk(&the_chunk);

    //var elf = entity.Elf.init();

    // var send_buf: [1024]u8 = undefined;
    // var writer = if (connection) |*conn| conn.writer(&send_buf) else return;

    // try shared.protocol.sendHello(&writer.interface, .{ .msg = "Hello from client" });

    // var recv_buf: [1024]u8 = undefined;
    // var reader = if (connection) |*conn| conn.reader(&recv_buf) else return;
    // const line = try reader.interface().takeDelimiterExclusive('\n');
    // std.log.info("{s}", .{line});

    // const camera = rl.Camera{ .fovy = 45, .position = .{ .x = 0, .y = 0, .z = 10 }, .projection = .perspective, .target = .{ .x = 0, .y = 0, .z = 0 }, .up = .{ .x = 0, .y = 1, .z = 0 } };

    rl.initAudioDevice();

    if (rl.isAudioDeviceReady()) {
        std.log.info("audio device is ready!", .{});
    } else {
        std.log.info("audio device is NOT ready!", .{});
    }

    const music = try rl.loadMusicStream("assets/romantic-piano-431010.mp3");
    std.log.info("{}", .{rl.isMusicValid(music)});
    // rl.playMusicStream(music);
    std.log.info("is music playing? {}", .{rl.isMusicStreamPlaying(music)});

    var old_time : f32 = @floatCast(rl.getTime());

    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        const new_time : f32 = @floatCast(rl.getTime());
        var frame_time = new_time - old_time;
        old_time = new_time;

        if (frame_time > 0.25)
            frame_time = 0.25;

        t += frame_time;
        accumulator += frame_time * k;

        // std.log.info("t: {}", .{t});

        while (try host.service(0)) |event| switch (event) {
            .connect => |data| {
                _ = data;
                // std.log.info("{}", .{data.peer});
            },
            .disconnect => |data| {
                _ = data;
                // std.log.info("{}", .{data.peer});
            },
            .receive => |data| {
                defer data.packet.deinit();

                const slice = data.packet.dataSlice();

                var reader = bufzilla.Reader(.{}).init(slice);

                const id = try reader.readPath("id");

                std.log.info("{any}", .{id});
            },
        };

        rl.pollInputEvents();

        if (peer.state() == .connected) {
            const packet = try znet.Packet.init("Hello, Server!", 0, .reliable);
            try peer.send(packet);
        }
        // _ = peer;

        // Update
        //----------------------------------------------------------------------------------
        // TODO: Update your variables here
        //----------------------------------------------------------------------------------

        rl.updateMusicStream(music);

        while (accumulator > dt * k) {
            // update(dt * cast(float) k);
            accumulator -= dt * k;
            gt += dt * k;
        }

        // Draw
        //----------------------------------------------------------------------------------
        rl.clearBackground(.sky_blue);

        rl.beginDrawing();

        // f.render_text(
        //     "Hello,    Sa   ilor!",
        //     rl.Vector2.init(400, 400),
        //     true,
        //     rl.Color.white,
        //     rl.Color.blank,
        //     false,
        //     true
        // );

        rl.beginShaderMode(test_shader);

        rl.gl.rlBegin(rl.gl.rl_quads);

        {
            const topLeft     : rl.Vector2 = .{ .x = 0.0,                                .y = 0.0 };
            // const topRight    : rl.Vector2 = .{ .x = @floatFromInt(rl.getScreenWidth()), .y = 0.0 };
            // const bottomLeft  : rl.Vector2 = .{ .x = 0.0,                                .y = @floatFromInt(rl.getScreenHeight()) };
            const bottomRight : rl.Vector2 = .{ .x = @floatFromInt(rl.getScreenWidth()), .y = @floatFromInt(rl.getScreenHeight()) };

            rl.gl.rlVertex2f(topLeft.x, topLeft.y);
            rl.gl.rlVertex2f(topLeft.x, bottomRight.y);
            rl.gl.rlVertex2f(bottomRight.x, bottomRight.y);
            rl.gl.rlVertex2f(bottomRight.x, topLeft.y);

            //rl.gl.rlTexCoord2f(0.0, 0.0);
            // rl.gl.rlVertex2f(topLeft.x, topLeft.y);
            //rl.gl.rlTexCoord2f(0.0, 1.0);
            // rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y);
            //rl.gl.rlTexCoord2f(1.0, 0.0);
            // rl.gl.rlVertex2f(topRight.x, topRight.y);

            // rl.gl.rlTexCoord2f(1.0, 0.0);
            // rl.gl.rlVertex2f(topRight.x, topRight.y);
            // rl.gl.rlTexCoord2f(0.0, 1.0);
            // rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y);
            // rl.gl.rlTexCoord2f(1.0, 1.0);
            // rl.gl.rlVertex2f(bottomRight.x, bottomRight.y);
        }

        rl.gl.rlEnd();
        rl.endShaderMode();

        // f.texture.drawPro(
        //     .{.x = 0, .y = 0, .width = 4096, .height = 4096},
        //     .{.x = 0, .y = 0, .width = 512, .height = 512 },
        //     .zero(), 0, .white);

        // const connected_text = "Connected";
        //const not_connected_text = "Not Connected";

        // switch (peer.state()) {
        //     .connected => rl.drawText(connected_text,  @divFloor(rl.getScreenWidth(), 2) - @divFloor(rl.measureText(connected_text, f.baseSize), 2), 50, 20, .white),
        //     else       => rl.drawText(not_connected_text,  @divFloor(rl.getScreenWidth(), 2) - @divFloor(rl.measureText(not_connected_text, f.baseSize), 1), 50, 20, .white),
        // }
        //@divFloor(rl.getScreenWidth(), 2) - @divFloor(rl.measureText(connected_text, f.baseSize), 2), 50




        // // const font_size : i32 = 180;
        // // const text_size = rl.Vector2{.x = @floatFromInt(rl.measureText(connected_text, font_size)), .y = font_size};//rl.measureTextEx(try rl.getFontDefault(), connected_text, font_size, font_size / 10);
        // // const pos = rl.Vector2{.x = 0, .y = 0};
        // // rl.drawText(connected_text, pos.x, pos.y, font_size, .white);
        // // rl.drawRectangleLines(pos.x, pos.y, @intFromFloat(text_size.x), @intFromFloat(text_size.y), .red);
        // // rl.drawRectangle(pos.x, pos.y, rl.getScreenWidth(), rl.getScreenHeight(), .white);
        // rl.drawTexturePro(tx,
        //                   rl.Rectangle{.x = 0, .y = 0, .width = 32, .height = 32},
        //                   rl.Rectangle{.x = 100, .y = 100, .width = 500, .height = 500},
        //                   .{.x = 0, .y = 0},
        //                   0,
        //                   .white);

        //rl.drawLineV(.{.x = @floatFromInt(@divFloor(rl.getScreenWidth(), 2)), .y = 0}, .{.x = @floatFromInt(@divFloor(rl.getScreenWidth(), 2)), .y = @floatFromInt(rl.getScreenHeight())}, .red);

        //rl.drawText("Congrats! You created your first window!", rl.getMouseX(), rl.getMouseY(), 20, .white);
        //rl.drawRectangleLines(0, 0, 100, 100, .red);
        // misc.drawFPS(0, 0, frame_time, frame);

        //elf.draw();

        // rl.beginMode3D(camera);
        // rl.drawSphere(.{ .x = 0, .y = 0, .z = 0 }, 1, .red);
        // rl.endMode3D();

        rl.endDrawing();

        //----------------------------------------------------------------------------------

        rl.swapScreenBuffer();
    }
}

fn init() !void {
    dbg_allocator = std.heap.DebugAllocator(.{}).init;
    var stdout_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    stdout = &stdout_writer.interface;

    // font.shader = try rl.loadShader(null, "assets/test.frag");
}

fn connect() !void {
    // const address = try std.net.Address.parseIp4("127.0.0.1", shared.protocol.SERVER_PORT);

    // connection = try std.net.tcpConnectToAddress(address);

    // std.log.info("Connected to server", .{});
}

//fn handle_connection(connection: std.net.Server.Connection) !void {}

// test "simple test" {
//     const gpa = std.testing.allocator;
//     var list: std.ArrayList(i32) = .empty;
//     defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak!
//     try list.append(gpa, 42);
//     try std.testing.expectEqual(@as(i32, 42), list.pop());
// }

// test "fuzz example" {
//     const Context = struct {
//         fn testOne(context: @This(), input: []const u8) anyerror!void {
//             _ = context;
//             // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
//             try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
//         }
//     };
//     try std.testing.fuzz(Context{}, Context.testOne, .{});
// }
