const std = @import("std");
const zm = @import("zmath");
const bufzilla = @import("bufzilla");

const entity = @import("entity.zig");

pub const SERVER_PORT: u16 = 1337;

pub const Message = union(enum) {
    spawn_entity: SpawnEntity,
    // later: despawn_entity, update_entity, snapshot, etc.
};

pub const SpawnEntity = union(enum) {
    elf_v1: Elf_v1,
    monster_v1: Monster_v1,
    projectile_v1: Projectile_v1,
};

pub fn makeSpawnMessage(comptime T: type, e: T) Message {
    if (T == entity.Elf) {
        return .{ .spawn_entity = .{ .elf_v1 = Elf_v1.init(e) } };
    } else if (T == entity.Monster) {
        return .{ .spawn_entity = .{ .monster_v1 = Monster_v1.init(e) } };
    } else if (T == entity.Projectile) {
        return .{ .spawn_entity = .{ .projectile_v1 = Projectile_v1.init(e) } };
    }
}

// pub fn write_message(writer: *std.Io.Writer, msg_type: MessageType, payload: []const u8) !void {
//     try writer.writeByte(@intFromEnum(msg_type));
//     try writer.writeInt(u32, @intCast(payload.len), .little);
//     try writer.writeAll(payload);
// }

// pub fn read_message(reader: *std.Io.Reader, allocator: std.mem.Allocator) !struct {
//     msg_type: MessageType,
//     payload: []u8,
// } {
//     const msg_type = try reader.readByte();
//     const size = try reader.readInt(u32, .little);

//     const payload = try allocator.alloc(u8, size);
//     errdefer allocator.free(payload);

//     try reader.readNoEof(payload);

//     return .{
//         .msg_type = @enumFromInt(msg_type),
//         .payload = payload,
//     };
// }

pub const Elf_v1 = struct {
    id: entity.id = entity.INVALID_ID,
    pos: zm.Vec,
    hp: i32,

    pub fn init(elf: entity.Elf) Elf_v1 {
        return .{
            .id = elf.id,
            .pos = elf.pos,
            .hp = elf.hp,
        };
    }

    pub fn decode(r: *bufzilla.Reader) !Elf_v1 {
        _ = r;
        return .{
            // .id = try r.readInt(u64, .little),
            // .pos = try readVec4(r),
            // .vel = try readVec4(r),
        };
    }
};

pub const Monster_v1 = struct {
    id: entity.id = entity.INVALID_ID,
    pos: zm.Vec,
    hp: i32,

    pub fn init(monster: entity.Monster) Monster_v1 {
        return .{
            .id = monster.id,
            .pos = monster.pos,
            .hp = monster.hp,
        };
    }

    // pub fn encode(self: Monster_v1, w: *std.Io.Writer) !void {
    //     try w.writeInt(u64, self.id, .little);
    //     try writeVec4(w, self.pos);
    //     try writeVec4(w, self.vel);
    // }

    pub fn decode(r: *std.Io.Reader) !Monster_v1 {
        return .{
            .id = try r.readInt(u64, .little),
            .pos = try readVec4(r),
            .vel = try readVec4(r),
        };
    }
};

pub const Projectile_v1 = struct {
    id: entity.id = entity.INVALID_ID,
    pos: zm.Vec,
    vel: zm.Vec,

    pub fn init(projectile: entity.Projectile) Projectile_v1 {
        return .{
            .id = projectile.id,
            .pos = projectile.pos,
            .vel = projectile.vel,
        };
    }

    // pub fn encode(self: Projectile_v1, w: *std.Io.Writer) !void {
    //     try w.writeInt(u64, self.id, .little);
    //     try writeVec4(w, self.pos);
    //     try writeVec4(w, self.vel);
    // }

    pub fn decode(r: *std.Io.Reader) !Projectile_v1 {
        return .{
            .id = try r.readInt(u64, .little),
            .pos = try readVec4(r),
            .vel = try readVec4(r),
        };
    }
};

fn writeVec4(w: *std.Io.Writer, v: zm.Vec) !void {
    const a = zm.vecToArr4(v);
    inline for (a) |f| {
        try w.writeFloat(f32, f, .little);
    }
}

fn readVec4(r: *std.Io.Reader) !zm.Vec4 {
    var a: [4]f32 = undefined;
    inline for (&a) |*f| {
        f.* = try r.readFloat(f32, .little);
    }
    return zm.loadArr4(a);
}
