--# CubeLifeRenderer
-- Draws ConwaysCubeLifeModel onto a CubeMapSphere’s 6 images (DeepSeek branch).
-- Requires: each cell has .alive (0/1) and .age (0,1,2,...)

CubeLifeRenderer = class()

-- life: ConwaysCubeLifeModel
-- cms : CubeMapSphere (has cubeImages[1..6], textureSize, etc.)
function CubeLifeRenderer:init(life, cms)
    self.life   = life
    self.cms    = cms
    self.N      = life.N
    self.tex    = cms.textureSize
    self.cellPx = math.max(1, math.floor(self.tex / self.N + 0.5)) -- crisp pixels
    
    -- dead color is background; live colors come from age palette
    self.deadC  = color(0,0,0,255)
    
    -- one-time full clear
    self:fullRedraw()
    cms.needsTextureUpdate = true
end

-- ---- Age → color palette ----------------------------------------------------

function ageToColor(age)
    -- 0        : dead (renderer uses self.deadC)
    -- 1 (new)  : almost white with a dash of red
    -- 2        : yellow
    -- 3        : orange
    -- 4        : purple-ish
    -- 5+       : blue
    if age <= 0 then
        return color(0,0,0,255)
    elseif age == 1 then
        return color(232, 231, 217) 
    elseif age == 2 then
        return color(224, 197, 136) 
    elseif age == 3 then
        return color(224, 184, 136) 
    elseif age == 4 then
        return color(224, 168, 136) 
    else
        return color(231, 90, 96) 
    end
end

-- Optional: let callers swap the palette at runtime
function CubeLifeRenderer:setPalette(fn)
    self._palette = fn
end

function CubeLifeRenderer:_colorForCell(cell)
    if cell.alive ~= 1 then return self.deadC end
    local fn = self._palette or ageToColor
    return fn(cell.age or 0)
end

-- ---- Full redraw (init / clearAll) -----------------------------------------

function CubeLifeRenderer:fullRedraw()
    local N, s = self.N, self.cellPx
    for f=1,6 do
        local img = self.cms.cubeImages[f]
        setContext(img)
        pushStyle()
        noStroke()
        rectMode(CORNER)
        background(self.deadC)
        for y=1,N do
            for x=1,N do
                local cell = self.life.model.faces[f][y][x]
                if cell.alive == 1 then
                    fill(self:_colorForCell(cell))
                    local px = (x-1)*s
                    local py = (y-1)*s
                    rect(px, py, s, s)
                end
            end
        end
        popStyle()
        setContext()
    end
end

-- ---- Dirty updates only -----------------------------------------------------

function CubeLifeRenderer:applyDirty()
    local any = false
    local s = self.cellPx
    for f=1,6 do
        local dr = self.life:getDirtyRect(f)
        if dr then
            local img = self.cms.cubeImages[f]
            setContext(img)
            pushStyle()
            noStroke()
            rectMode(CORNER)
            for x,y in self.life:iterDirtyCells(f) do
                local cell = self.life.model.faces[f][y][x]
                local col  = self:_colorForCell(cell)
                fill(col)
                local px = (x-1)*s
                local py = (y-1)*s
                rect(px, py, s, s)
            end
            popStyle()
            setContext()
            any = true
        end
    end
    self.life:clearDirty()
    if any then self.cms.needsTextureUpdate = true end
end

-- ---- Input helper: world hit → toggle cell ---------------------------------

function CubeLifeRenderer:toggleAtWorldPoint(worldHit)
    local face, u, v = self.cms:sphereToCubemapFace(worldHit - self.cms.entity.position)
    if not face then return false end
    local x, y = self:_uvToCell(u, v)
    local cell = self.life.model.faces[face][y][x]
    self.life:setAlive(face, x, y, cell.alive == 0 and 1 or 0)
    self:applyDirty()
    return true
end

-- pixel space (0..tex) -> cell index 1..N (clamped)
function CubeLifeRenderer:_uvToCell(u, v)
    local s = self.cellPx
    local x = math.floor(u / s) + 1
    local y = math.floor(v / s) + 1
    if x < 1 then x = 1 elseif x > self.N then x = self.N end
    if y < 1 then y = 1 elseif y > self.N then y = self.N end
    return x, y
end

-- Smoothstep for a gentle ramp (0..1 -> 0..1)
function CubeLifeRenderer:_ease01(t)
    t = (t < 0) and 0 or ((t > 1) and 1 or t)
    return t * t * (3 - 2 * t)
end

-- Mix any color toward black; amt is 0..1 of *how much* to darken
function CubeLifeRenderer:_mixToBlack(c, amt)
    if amt <= 0 then return c end
    if amt > 1  then amt = 1 end
    -- keep alpha unchanged
    return color(c.r * (1 - amt), c.g * (1 - amt), c.b * (1 - amt), c.a)
end

-- Replace your _colorForCell with this version
function CubeLifeRenderer:_colorForCell(cell)
    if cell.alive ~= 1 then return self.deadC end
    local fn  = self._palette or ageToColor
    local col = fn(cell.age or 0)
    
    -- Darken gently as the static neighborhood ages toward decay
    local T = self.life and self.life.decayTurns or -1
    if T and T >= 0 then
        local ticks = cell.staticTicks or 0
        if ticks > 0 then
            -- fraction of the way to decay
            local frac = ticks / math.max(1, T)
            -- smooth it so it starts subtle and ramps late
            frac = self:_ease01(frac)
            -- cap the darkening so it never looks fully black while alive
            -- (e.g., at most 55% of the base color removed)
            local maxDarken = 0.55
            local amt = maxDarken * frac
            col = self:_mixToBlack(col, amt)
        end
    end
    
    return col
end