local ecs = ...
local world = ecs.world
local w = world.w

local RENDER_LAYER <const>  = ecs.require "engine.render_layer".RENDER_LAYER
local DELTA_TIME <const>    = require"gameplay.interface.constant".DELTA_TIME

local mc        = import_package "ant.math".constant

local math3d    = require "math3d"

local iom       = ecs.require "ant.objcontroller|obj_motion"
local irl       = ecs.require "ant.render|render_layer.render_layer"
local iupdate   = ecs.require "update_system"
local imessage = ecs.require "message_sub"

local function _set_color(instant, color)
    imessage:pub("material", instant, "set_property", "u_emissive_factor", color)
    imessage:pub("material", instant, "set_property", "u_basecolor_factor", color)
end

local function create_object(prefab, srt)
    return world:create_instance {
        prefab = prefab,
        on_ready = function (self)
            local root <close> = world:entity(self.tag['*'][1])
            iom.set_srt(root, srt.s, srt.r, srt.t)

            for _, eid in ipairs(self.tag['*']) do
                local e <close> = world:entity(eid, "render_object?in")
                if e.render_object then
                    irl.set_layer(e, RENDER_LAYER.SELECTED_BOXES)
                end
            end
        end,
    }
end

local mt = {}
mt.__index = mt

local CORNER_DIRECTIONS <const> = {
    math3d.constant("v4", {-1, 0, 1}),  -- left top
    math3d.constant("v4", {-1, 0,-1}), -- left bottom
    math3d.constant("v4", { 1, 0, 1}),   -- right top
    math3d.constant("v4", { 1, 0,-1}),  -- right bottom
}

local CORNER_QUATERNIONS <const> = {
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(180)})), -- left top
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(90)}) ), -- left bottom
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(270)})), -- right top
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(0)})  ), -- right bottom
}
local LINE_DIRECTIONS <const> = {
    mc.ZAXIS,
    mc.NZAXIS,
    mc.NXAXIS,
    mc.XAXIS,
}

local LINE_QUATERNIONS <const> = {
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(0)}) ), -- top
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(0)}) ), -- bottom
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(90)})), -- left
    math3d.constant("quat", math3d.quaternion({axis=mc.YAXIS, r=math.rad(90)})), -- right
}

local LINE_SCALE <const> = {
    function(w, h) return math3d.vector((w-1)*2, 1, 1) end, -- top
    function(w, h) return math3d.vector((w-1)*2, 1, 1) end, -- bottom
    function(w, h) return math3d.vector((h-1)*2, 1, 1) end, -- left
    function(w, h) return math3d.vector((h-1)*2, 1, 1) end, -- right
}

local LINE_OFFSET <const> = {
    math3d.constant("v4", {0,  0, 3.8}), -- top
    math3d.constant("v4", {0,  0, 5}),   -- bottom
    math3d.constant("v4", {5,  0, 0}),   -- left
    math3d.constant("v4", {3.8,0, 0}),   -- right
}

local LINE_CHECK <const> = {
    function(w, h) return w - 1 > 0 end, -- top
    function(w, h) return w - 1 > 0 end, -- bottom
    function(w, h) return h - 1 > 0 end, -- left
    function(w, h) return h - 1 > 0 end, -- right
}

local function _remove(self)
    for _, v in pairs(self.corners) do
        imessage:pub("remove", v)
    end
    for _, v in pairs(self.lines) do
        imessage:pub("remove", v)
    end
end

function mt:remove()
    self.removed = true
    _remove(self)
end

function mt:set_position(center)
    assert(not self.removed)
    self.center.v = center
    for idx, o in pairs(self.corners) do
        local position = math3d.live(math3d.muladd(CORNER_DIRECTIONS[idx], self.corner_offset, self.center))
        imessage:pub("obj_motion", o, "set_position", position)
    end
    for idx, o in pairs(self.lines) do
        local position = math3d.live(math3d.add(math3d.muladd(LINE_DIRECTIONS[idx], self.line_offset, self.center), LINE_OFFSET[idx]))
        imessage:pub("obj_motion", o, "set_position", position)
    end
end

function mt:set_wh(w, h)
    assert(not self.removed)
    if self.w == w and self.h == h then
        return
    end

    self.w = w
    self.h = h

    _remove(self)

    local width = (w - 1) * 10
    local height = (h - 1) * 10
    self.corner_offset.v = math3d.vector(width / 2, 0, height / 2)
    self.line_offset.v = math3d.vector(w * 10 / 2, 0, h * 10 / 2)
    self.w = w
    self.h = h

    for idx = 1, #CORNER_DIRECTIONS do
        self.corners[idx] = create_object(self.prefabs[1], {
            s = mc.ONE,
            r = CORNER_QUATERNIONS[idx],
            t = math3d.live(math3d.muladd(CORNER_DIRECTIONS[idx], self.corner_offset, self.center)),
        })
        if mc.NULL ~= self.color then
            _set_color(self.corners[idx], self.color)
        end
    end

    if self.prefabs[2] then
        for idx, f in ipairs(LINE_CHECK) do
            if f(w, h) then
                self.lines[idx] = create_object(self.prefabs[2], {
                    s = math3d.live(LINE_SCALE[idx](w, h)),
                    r = LINE_QUATERNIONS[idx],
                    t = math3d.live(math3d.add(math3d.muladd(LINE_DIRECTIONS[idx], self.line_offset, self.center), LINE_OFFSET[idx])),
                })
                if mc.NULL ~= self.color then
                    _set_color(self.lines[idx], self.color)
                end
            end
        end
    end
end

function mt:set_color(color)
    assert(not self.removed)
    self.color.v = math3d.vector(color)
    for _, o in pairs(self.corners) do
        _set_color(o, math3d.live(color))
    end
    for _, o in pairs(self.lines) do
        _set_color(o, math3d.live(color))
    end
end

function mt:set_color_transition(color, duration)
    assert(not self.removed)
    if math3d.isequal(color, self.color) then
        return
    end

    local t = 0
    local from = self.color
    local to = color

    iupdate.add(function()
        if self.removed then
            return
        end
        t = t + DELTA_TIME
        self:set_color(math3d.lerp(from, to, t/duration))
        return t < duration
    end)
end

return function(prefabs, center, color, w, h)
    local width = (w - 1) * 10
    local height = (h - 1) * 10

    local M = {
        corners = {},
        corner_offset = math3d.ref(math3d.vector(width / 2, 0, height / 2)),
        line_offset = math3d.ref(math3d.vector(w * 10 / 2, 0, h * 10 / 2)),
        lines = {},
        center = math3d.ref(math3d.vector(center)),
        color = math3d.ref(color),
        prefabs = prefabs,
        w = w,
        h = h,
    }

    for idx = 1, #CORNER_DIRECTIONS do
        M.corners[idx] = create_object(prefabs[1], {
            s = mc.ONE,
            r = CORNER_QUATERNIONS[idx],
            t = math3d.live(math3d.muladd(CORNER_DIRECTIONS[idx], M.corner_offset, M.center)),
        })
        if mc.NULL ~= color then
            _set_color(M.corners[idx], color)
        end
    end

    if prefabs[2] then
        for idx, f in ipairs(LINE_CHECK) do
            if f(w, h) then
                M.lines[idx] = create_object(prefabs[2], {
                    s = math3d.live(LINE_SCALE[idx](w, h)),
                    r = LINE_QUATERNIONS[idx],
                    t = math3d.live(math3d.add(math3d.muladd(LINE_DIRECTIONS[idx], M.line_offset, M.center), LINE_OFFSET[idx])),
                })
                if mc.NULL ~= color then
                    _set_color(M.lines[idx], color)
                end
            end
        end
    end

    return setmetatable(M, mt)
end
