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

local world = {}

world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN

local World = {}
World.__index = World

function World.new()
    local w = C.pxl8_world_create()
    if w == nil then
        return nil
    end
    return setmetatable({ _ptr = w }, World)
end

function World:apply_textures(texture_defs)
    local count = #texture_defs
    local textures = ffi.new("pxl8_world_texture[?]", count)

    for i, def in ipairs(texture_defs) do
        local idx = i - 1
        ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
        textures[idx].texture_id = def.texture_id or 0

        if def.rule then
            textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
                function(normal, face, bsp)
                    return def.rule(normal[0], face, bsp)
                end)
        else
            textures[idx].rule = nil
        end
    end

    return C.pxl8_world_apply_textures(self._ptr, textures, count)
end

function World:check_collision(x, y, z, radius)
    local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
    return C.pxl8_world_check_collision(self._ptr, pos, radius)
end

function World:destroy()
    if self._ptr then
        C.pxl8_world_destroy(self._ptr)
        self._ptr = nil
    end
end

function World:generate(params)
    local c_params = ffi.new("pxl8_procgen_params")
    c_params.type = params.type or C.PXL8_PROCGEN_ROOMS
    c_params.width = params.width or 32
    c_params.height = params.height or 32
    c_params.depth = params.depth or 0
    c_params.seed = params.seed or 0
    c_params.min_room_size = params.min_room_size or 5
    c_params.max_room_size = params.max_room_size or 10
    c_params.num_rooms = params.num_rooms or 8
    return C.pxl8_world_generate(self._ptr, core.gfx, c_params)
end

function World:is_loaded()
    return C.pxl8_world_is_loaded(self._ptr)
end

function World:load(filepath)
    return C.pxl8_world_load(self._ptr, filepath)
end

function World:render(camera_pos)
    local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
    C.pxl8_world_render(self._ptr, core.gfx, vec)
end

function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radius)
    local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
    local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
    local result = C.pxl8_world_resolve_collision(self._ptr, from, to, radius)
    return result.x, result.y, result.z
end

function World:set_wireframe(enabled, color)
    C.pxl8_world_set_wireframe(self._ptr, enabled, color or 15)
end

function World:unload()
    C.pxl8_world_unload(self._ptr)
end

world.World = World

return world
