local ffi = require("ffi")
local C = ffi.C

local net = {}

local Net = {}
Net.__index = Net

net.MODE_LOCAL = C.PXL8_NET_LOCAL
net.MODE_REMOTE = C.PXL8_NET_REMOTE

function Net.new(config)
    config = config or {}
    local cfg = ffi.new("pxl8_net_config")
    cfg.address = config.address or "127.0.0.1"
    cfg.mode = config.mode or C.PXL8_NET_REMOTE
    cfg.port = config.port or 7777

    local n = C.pxl8_net_create(cfg)
    if n == nil then
        return nil
    end
    return setmetatable({ _ptr = n }, Net)
end

function Net:connect()
    return C.pxl8_net_connect(self._ptr) == 0
end

function Net:connected()
    return C.pxl8_net_connected(self._ptr)
end

function Net:destroy()
    if self._ptr then
        C.pxl8_net_destroy(self._ptr)
        self._ptr = nil
    end
end

function Net:disconnect()
    C.pxl8_net_disconnect(self._ptr)
end

function Net:entities()
    local snap = C.pxl8_net_snapshot(self._ptr)
    if snap == nil then
        return {}
    end
    local ents = C.pxl8_net_entities(self._ptr)
    if ents == nil then
        return {}
    end
    local result = {}
    for i = 0, snap.entity_count - 1 do
        result[i + 1] = {
            entity_id = tonumber(ents[i].entity_id),
            userdata = ents[i].userdata
        }
    end
    return result
end

function Net:entity_prev_userdata(entity_id)
    return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id)
end

function Net:entity_userdata(entity_id)
    return C.pxl8_net_entity_userdata(self._ptr, entity_id)
end

function Net:input_at(tick)
    local input = C.pxl8_net_input_at(self._ptr, tick)
    if input == nil then return nil end
    return {
        buttons = input.buttons,
        look_dx = input.look_dx,
        look_dy = input.look_dy,
        move_x = input.move_x,
        move_y = input.move_y,
        yaw = input.yaw,
        tick = tonumber(input.tick),
        timestamp = tonumber(input.timestamp)
    }
end

function Net:input_oldest_tick()
    return tonumber(C.pxl8_net_input_oldest_tick(self._ptr))
end

function Net:input_push(input)
    local msg = ffi.new("pxl8_input_msg")
    msg.buttons = input.buttons or 0
    msg.look_dx = input.look_dx or 0
    msg.look_dy = input.look_dy or 0
    msg.move_x = input.move_x or 0
    msg.move_y = input.move_y or 0
    msg.yaw = input.yaw or 0
    msg.tick = input.tick or 0
    msg.timestamp = input.timestamp or 0
    C.pxl8_net_input_push(self._ptr, msg)
end

function Net:lerp_alpha()
    return C.pxl8_net_lerp_alpha(self._ptr)
end

function Net:needs_correction()
    return C.pxl8_net_needs_correction(self._ptr)
end

function Net:player_id()
    return tonumber(C.pxl8_net_player_id(self._ptr))
end

function Net:poll()
    return C.pxl8_net_poll(self._ptr)
end

function Net:predicted_state()
    return C.pxl8_net_predicted_state(self._ptr)
end

function Net:predicted_tick_set(tick)
    C.pxl8_net_predicted_tick_set(self._ptr, tick)
end

function Net:send_command(cmd)
    return C.pxl8_net_send_command(self._ptr, cmd) == 0
end

function Net:send_input(input)
    local msg = ffi.new("pxl8_input_msg")
    msg.buttons = input.buttons or 0
    msg.look_dx = input.look_dx or 0
    msg.look_dy = input.look_dy or 0
    msg.move_x = input.move_x or 0
    msg.move_y = input.move_y or 0
    msg.yaw = input.yaw or 0
    msg.tick = input.tick or 0
    msg.timestamp = input.timestamp or 0
    return C.pxl8_net_send_input(self._ptr, msg) == 0
end

function Net:snapshot()
    local snap = C.pxl8_net_snapshot(self._ptr)
    if snap == nil then
        return nil
    end
    return {
        entity_count = snap.entity_count,
        event_count = snap.event_count,
        player_id = tonumber(snap.player_id),
        tick = tonumber(snap.tick),
        time = snap.time
    }
end

function Net:spawn(x, y, z, yaw, pitch)
    local cmd = ffi.new("pxl8_command_msg")
    cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY
    C.pxl8_pack_f32_be(cmd.payload, 0, x or 0)
    C.pxl8_pack_f32_be(cmd.payload, 4, y or 0)
    C.pxl8_pack_f32_be(cmd.payload, 8, z or 0)
    C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0)
    C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0)
    cmd.payload_size = 20
    cmd.tick = 0
    return self:send_command(cmd)
end

function Net:tick()
    return tonumber(C.pxl8_net_tick(self._ptr))
end

function Net:update(dt)
    C.pxl8_net_update(self._ptr, dt)
end

net.Net = Net

return net
