local game = {}
local REQ_PATH = "games.my-body-is-a-garden."
local IMG_PATH = "games/my-body-is-a-garden/imgs/"
local SFX_PATH = "games/my-body-is-a-garden/sfx/"

local _loadImage = loadImage
local function loadImage(file)
    return _loadImage(IMG_PATH .. file)
end

local function loadImages(prefix, from, to)
    if not to then
        to = from
        from = 1
    end
    local images = {}
    for i = from, to do
        table.insert(images, loadImage(("%s-%04d.png"):format(prefix, i)))
    end
    return images
end

local function edible(typ)
    return typ == "worm" or typ == "shroom" or typ == "ant"
end

function game:setup()
    textSize(14)
    size(512, 480)
    noTint()
    self.gameState = "playing"
    self.wormCount = 0
    self.mushroomCount = 0
    self.antCount = 0
    self.wormRatio = 0
    self.mushroomRatio = 0
    self.antRatio = 0
    self.sidebarFrame = 0
    self.sidebarTimer = 0
    self.playerState = "normal"
    self.playerX = 1
    self.playerY = 3
    self.playerFrame = 0
    self.playerTimer = 0
    self.playerArousal = 0
    self.playerMoveTimer = 0
    self.playerMoving = false
    self.playerStates = { "normal", "pokey", "horny", "close", "cum"}
    self.sidebarAnimations = 
        { normal = "playerNormal"
        , pokey = "playerPokey"
        , horny = "playerHorny"
        , close = "playerClose"
        , cum = "playerCum"
        }
    self.sidebarStyncilAnimations = 
        { normal = "playerNormalStyncil"
        , pokey = "playerPokeyStyncil"
        , horny = "playerHornyStyncil"
        , close = "playerCloseStyncil"
        , cum = "playerCloseStyncil"
        }
    self.borderFrame = 0
    self.borderTimer = 0
    self.sprites = 
        { playerNormal = { loadImage("player-0001.png"), loadImage("player-0002.png") }
        , playerPokey = { loadImage("player-0003.png"), loadImage("player-0004.png") }
        , playerHorny = { loadImage("player-0005.png"), loadImage("player-0006.png") }
        , playerClose = { loadImage("player-0007.png"), loadImage("player-0008.png") }
        , playerCum = { loadImage("player-0009.png"), loadImage("player-0010.png") }
        , playerNormalStyncil = { loadImage("player-styncil-0001.png"), loadImage("player-styncil-0002.png") }
        , playerPokeyStyncil = { loadImage("player-styncil-0003.png"), loadImage("player-styncil-0004.png") }
        , playerHornyStyncil = { loadImage("player-styncil-0005.png"), loadImage("player-styncil-0006.png") }
        , playerCloseStyncil = { loadImage("player-styncil-0007.png"), loadImage("player-styncil-0008.png") }
        , playerCumStyncil = { loadImage("player-styncil-0009.png"), loadImage("player-styncil-0010.png") }
        , player = { loadImage("sprite-you-0001.png"), loadImage("sprite-you-0002.png") }
        , worm = { loadImage("sprite-worm-0001.png"), loadImage("sprite-worm-0002.png") }
        , shroom = { loadImage("sprite-shroom-0001.png"), loadImage("sprite-shroom-0002.png") }
        , grassA = { loadImage("sprite-grass-1-0001.png"), loadImage("sprite-grass-1-0002.png") }
        , grassB = { loadImage("sprite-grass-2-0001.png"), loadImage("sprite-grass-2-0002.png") }
        , ant = { loadImage("sprite-ant-0001.png"), loadImage("sprite-ant-0002.png") }
        , border = { loadImage("border-0001.png"), loadImage("border-0002.png") }
        , cumshot = loadImages("cum-shot", 11)
        , cumStainOne = { loadImage("screencum-0001.png"), loadImage("screencum-0002.png") }
        , cumStainTwo = { loadImage("screencum-0003.png"), loadImage("screencum-0004.png") }
        , cumStainThree = { loadImage("screencum-0005.png"), loadImage("screencum-0006.png") }
        , cumStainFour = { loadImage("screencum-0007.png"), loadImage("screencum-0008.png") }
        , cumStainFive = { loadImage("screencum-0009.png"), loadImage("screencum-0010.png") }
        , cumStainSix = { loadImage("screencum-0011.png"), loadImage("screencum-0012.png") }
        , transformAnt = loadImages("endings-ant", 5)
        , transformShroom = loadImages("endings-shroom", 5)
        , transformWorm = loadImages("endings-worm", 5)
        , transformAntShroom = loadImages("endings-antshroom", 8)
        , transformAntWorm = loadImages("endings-antworm", 8)
        , transformShroomWorm = loadImages("endings-shroomworm", 8)
        , transformAll = loadImages("endings-antshroomworm", 5)
        , endingAnt = loadImages("endings-Aend", 2) 
        , endingWorm = loadImages("endings-Wend", 2)
        , endingShroom = loadImages("endings-Send", 2)
        , endingAntShroom = loadImages("endings-ASend", 2)
        , endingAntWorm = loadImages("endings-AWend", 2)
        , endingShroomWorm = loadImages("endings-WSend", 2)
        , endingAll = loadImages("endings-ALLend", 2)
        }
    self.tfs = 
        { a = "transformAnt"
        , s = "transformShroom"
        , w =  "transformWorm"
        , as = "transformAntShroom"
        , aw = "transformAntWorm"
        , sw = "transformShroomWorm"
        , asw = "transformAll"
        }
    self.ending = nil
    self.tfTimer = 0
    self.tfFrame = 0
    self.endingTimer = 0
    self.endingFrame = 0
    self.endings = 
        { a = "endingAnt"
        , s = "endingShroom"
        , w =  "endingWorm"
        , as = "endingAntShroom"
        , aw = "endingAntWorm"
        , sw = "endingShroomWorm"
        , asw = "endingAll"
        }
    self.sfx = 
        { step = love.audio.newSource("sfx/grassy-footstep.wav", "static")
        }
    self.cumTimer = 0
    self.cumFrame = 0 
    self.cumStains = {}
    self.cumStainTypes = 
        { "cumStainOne"
        , "cumStainTwo"
        , "cumStainThree"
        , "cumStainFour"
        , "cumStainFive"
        , "cumStainSix" 
        }
    self.canSpawnCum = false
    self.plantTypes = { "worm", "shroom", "ant" }
    self.tiles = { "worm", "shroom", "ant", "grassA", "grassB" }
    self.plants = {}
    self.spawnPoints = require (REQ_PATH .. "spawnPoints")
    self.dialogue = require (REQ_PATH .. "dialogue")
    self:newGrid()
end

function game:plantItem(what)
    local index = love.math.random(#self.spawnPoints)
    local point = self.spawnPoints[index]
    self.spawnPoints[index] = self.spawnPoints[#self.spawnPoints]
    table.remove(self.spawnPoints)
    table.insert(self.plants, {
        type = what, x = point[1], y = point[2], frame = 0, timer = 0
    })
end

function game:newGrid()
    self.grid = {}
    for i = 1, 4 do 
        table.insert(self.grid, {})
    end
    for y = 0, 6 do
        for x = 0, 3 do
            if (x ~= self.playerX or y ~= self.playerY) and love.math.random() > 0.75 then
                self:spawnThing(self.tiles[love.math.random(1,3)], x, y)
            else
                self:spawnThing(self.tiles[love.math.random(4, 5)], x, y)
            end
        end
    end
end

function game:readGrid(x, y)
    return self.grid[1 + x][1 + y]
end

function game:spawnThing(what, x, y)
    self.grid[1 + x][1 + y] = 
        { x = x 
        , y = y 
        , type = what
        , frame = 0
        , timer = 0
        }
end

function game:animateGrid()
    for y = 1, 7 do
        for x = 1, 4 do
            local item = self.grid[x][y]
            if item then
                item.timer = item.timer + deltaTime / 1000
                if item.timer > 1 / 12 then
                    item.timer = 0
                    item.frame = (item.frame + 1) % 2
                end
            end
        end
    end
end

function game:gridDraw()
    self:animateGrid()
    for y = 1, 7 do
        for x = 1, 4 do
            local item = self.grid[x][y]
            if item then
                image(self.sprites[item.type][1 + item.frame], item.x * 64, item.y * 64 + 16)
            end
        end
    end 
end

function game:animatePlants()
    for _, plant in ipairs(self.plants) do
        plant.timer = plant.timer + deltaTime / 1000
        if plant.timer > 1 / 12 then
            plant.timer = 0
            plant.frame = (plant.frame + 1) % 2
        end 
    end
end

local mask_shader = love.graphics.newShader[[
   vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
      if (Texel(texture, texture_coords).rgb == vec3(0.0)) {
         // a discarded pixel wont be applied as the stencil.
         discard;
      }
      return vec4(1.0);
   }
]]

local function plantMask()
    love.graphics.setShader(mask_shader)
    image(game.sprites[game.sidebarStyncilAnimations[game.playerState]][1 + game.sidebarFrame])
    love.graphics.setShader()
end

function game:plantsDraw()
    love.graphics.stencil(plantMask, "replace", 1)
    love.graphics.setStencilTest("greater", 0)
    game:animatePlants()
    imageMode(CENTER)
    for _, plant in ipairs(self.plants) do
        image(self.sprites[plant.type][1 + plant.frame], plant.x, plant.y, 48, 48)
    end
    imageMode(CORNER)
    love.graphics.setStencilTest()
end

function game:sidebarAnimation()
    return self.sprites[self.sidebarAnimations[self.playerState]]
end

function game:sidebarCurrentFrame()
    return self:sidebarAnimation()[1 + self.sidebarFrame]
end

function game:sidebarFrameCount()
    return #self:sidebarAnimation()
end

function game:sidebarAnimate()
    self.sidebarTimer = self.sidebarTimer + deltaTime / 1000
    if self.sidebarTimer > 1 / 12 then
        self.sidebarTimer = 0
        self.sidebarFrame = self.sidebarFrame + 1
    end
    self.sidebarFrame = self.sidebarFrame % self:sidebarFrameCount()
end

function game:sidebarDraw()
    fill(48, 68, 70)
    rect(256, 0, 256, 480)
    game:sidebarAnimate()
    image(game:sidebarCurrentFrame())
end

function game:cumDraw()
    if self.playerState == "cum" then
        self.cumTimer = self.cumTimer + deltaTime / 1000
        if self.cumTimer > 1 / 12 then
            self.cumTimer = 0
            self.cumFrame = self.cumFrame + 1
            self.canSpawnCum = true 
        end
        self.cumFrame = self.cumFrame % 11
        if (self.cumFrame == 7 or self.cumFrame == 0) and self.canSpawnCum then
            self.canSpawnCum = false
            table.insert(self.cumStains, 
                { frame = 0
                , timer = 0
                , x = love.math.random(32, 224)
                , y = love.math.random(48, 432)
                , duration = 1
                , type = self.cumStainTypes[love.math.random(#self.cumStainTypes)]
                }
            )
        end
        image(self.sprites.cumshot[1 + self.cumFrame])
    end
end

function game:playerAnimation()
    return self.sprites.player
end

function game:playerCurrentFrame()
    return self:playerAnimation()[1 + self.playerFrame]
end

function game:playerFrameCount()
    return #self:playerAnimation()
end

function game:playerAnimate()
    self.playerTimer = self.playerTimer + deltaTime / 1000
    if self.playerTimer > 1 / 12 then
        self.playerTimer = 0
        self.playerFrame = self.playerFrame + 1
    end
    self.playerFrame = self.playerFrame % self:playerFrameCount()
end

function game:playerDraw()
    self:playerAnimate()
    image(self:playerCurrentFrame(), (self.playerX) * 64, (self.playerY) * 64 + 16)
end

function game:borderDraw()
    self.borderTimer = self.borderTimer + deltaTime / 1000
    if self.borderTimer > 1 / 12 then
        self.borderTimer = 0
        self.borderFrame = (self.borderFrame + 1) % 2
    end
    image(self.sprites.border[1 + self.borderFrame])
end

function game:cumStainsDraw()
    local stains = self.cumStains
    self.cumStains = {}
    imageMode(CENTER)
    for _, stain in ipairs(stains) do
        stain.duration = stain.duration - deltaTime / 1000
        if stain.duration > 0 then
            table.insert(self.cumStains, stain)
        end
        stain.timer = stain.timer + deltaTime / 1000
        if stain.timer > 1 / 12 then
            stain.frame = (stain.frame + 1) % 2
        end
        tint(255, 255, 255, 255 * stain.duration)
        image(self.sprites[stain.type][1 + stain.frame], stain.x, stain.y)
        noTint()
    end
    imageMode(CORNER)
end

function game:eat(tile)
    self.playerArousal = self.playerArousal + 1
    self:spawnThing(self.tiles[love.math.random(4,5)], self.playerX, self.playerY)
    self:plantItem(tile.type)
    self.playerState = self.playerStates[1 + math.min(math.floor(self.playerArousal / 13), 4)]
    if tile.type == "shroom" then
        self.mushroomCount = self.mushroomCount + 1
    elseif tile.type == "ant" then
        self.antCount = self.antCount + 1
    elseif tile.type == "worm" then
        self.wormCount = self.wormCount + 1
    end
    if self.mushroomCount == 0 and self.antCount == 0 and self.wormCount == 0 then
        return
    end
    self.mushroomRatio = self.mushroomCount / (self.mushroomCount + self.antCount + self.wormCount)
    self.antRatio = self.antCount / (self.mushroomCount + self.antCount + self.wormCount)
    self.wormRatio = self.wormCount / (self.mushroomCount + self.antCount + self.wormCount)
end 
function game:applyMoveDelay()
    if self.playerMoving and (keyIsDown "up" or keyIsDown "down" or keyIsDown "right" or keyIsDown "left") then 
        self.playerMoveTimer = self.playerMoveTimer + deltaTime / 1000
    else
        self.playerMoving = false
        self.playerMoveTimer = math.huge
    end
end
function game:canMove()
    return self.playerMoveTimer > 1/6 and (keyIsDown "up" or keyIsDown "down" or keyIsDown "right" or keyIsDown "left") and self.playerState ~= "cum"
end
function game:tryVertical()
    if keyIsDown "up" then
        self.playerY = (self.playerY + 6) % 7
        if self.playerY == 6 then
            self:newGrid()
        end
    elseif keyIsDown "down" then
        self.playerY = (self.playerY + 1) % 7
        if self.playerY == 0 then
            self:newGrid()
        end
    end 
end
function game:tryHorizontal()
    if keyIsDown "right" then
        self.playerX = (self.playerX + 1) % 4
        if self.playerX == 0 then
            self:newGrid()
        end
    elseif keyIsDown "left" then
        self.playerX = (self.playerX + 3) % 4
        if self.playerX == 3 then
            self:newGrid()
        end
    end
end
function game:move()
    self:applyMoveDelay()
    if self:canMove() then
        local sfx = self.sfx.step:clone()
        sfx:setPitch(random(0.9, 1.1))
        sfx:play()
        self.playerMoving = true
        self.playerMoveTimer = 0
        self:tryVertical()
        self:tryHorizontal()
        local tile = self:readGrid(self.playerX, self.playerY)
        if edible(tile.type) then
            self:eat(tile)
            if (self.wormCount + self.mushroomCount + self.antCount) % 4 == 0 then
                game:generateDialogue()
            end
        end
    end
end

local message = {}
local once = false 

function game:generateDialogue()
    message = {}
    local types = {}
    local most = math.max(self.wormCount, self.mushroomCount, self.antCount)
    if self.mushroomRatio > 0.28 then
        table.insert(types, "shroom")
    end

    if self.antRatio > 0.28 then
        table.insert(types, "ant")
    end

    if self.wormRatio > 0.28 then
        table.insert(types, "worm")
    end

    if most == self.wormCount and #types < 3 then
        table.insert(types, "worm")
    elseif most == self.mushroomCount and #types < 3 then
        table.insert(types, "shroom")
    elseif most == self.antCount and #types < 3 then
        table.insert(types, "ant")
    end

    if most == self.wormCount and #types < 3 then
        table.insert(types, "worm")
    elseif most == self.mushroomCount and #types < 3 then
        table.insert(types, "shroom")
    elseif most == self.antCount and #types < 3 then
        table.insert(types, "ant")
    end

    table.sort(types)
    local dialogue = {}
    for typ, lines in pairs(self.dialogue) do 
        dialogue[typ] = {}
        for _, line in ipairs(lines) do
            table.insert(dialogue[typ], line)
        end
    end

    for i, typ in ipairs(types) do
        local options = dialogue[typ]
        local pick = love.math.random(#options)
        local snippet = options[pick]
        options[pick] = options[#options]
        options[#options] = nil
        table.insert(message, snippet)
    end
end

function game:dialogueDraw()
    fill(24, 34, 28)
    rect(256, 320, 251, 148)
    fill(255)
    textAlign(LEFT, TOP)
    text(table.concat(message, " "), 266, 330, 231)
end

function game:draw()
    if self.gameState == "playing" then
        self:move()
        background(68, 65, 39)
        textAlign(CENTER, CENTER)
        self:gridDraw()
        self:playerDraw()
        self:borderDraw()
        self:sidebarDraw()
        self:plantsDraw()
        self:cumStainsDraw()
        self:cumDraw()
        self:dialogueDraw()
    elseif self.gameState == "transforming" then
        background(46, 49, 36)
        self.tfTimer = self.tfTimer + deltaTime / 1000
        if self.tfTimer > 1 / 6 and 1 + self.tfFrame < #self.sprites[self.tfs[self.ending]] then
            self.tfFrame = self.tfFrame + 1
            self.tfTimer = 0
        end
        image(self.sprites[self.tfs[self.ending]][1 + self.tfFrame])
    elseif self.gameState == "ending" then
        background(46, 49, 36)
        self.endingTimer = self.endingTimer + deltaTime / 1000
        if self.endingTimer > 1 / 6 then
            self.endingTimer = 0
            self.endingFrame = (self.endingFrame + 1) % 2
        end
        image(self.sprites[self.endings[self.ending]][1 + self.endingFrame])
    end
    if self.gameState ~= "playing" or self.playerState == "cum" then
        fill(24, 34, 28)
        rect(10, height - 44, textWidth("Space to Continue") + 20, 34)
        fill(255)
        textAlign(LEFT, TOP)
        text("Space to Continue", 20, height - 34)
    end
end

function game:keyPressed()
    if self.playerState == "cum" and self.gameState == "playing" and key == "space" then
        local anted, shroomed, wormed = self.antRatio > 0.28, self.mushroomRatio > 0.28, self.wormRatio > 0.28 
        if anted and shroomed and wormed then
            self.ending = "asw"
        elseif anted and shroomed then
            self.ending = "as"
        elseif anted and wormed then
            self.ending = "aw"
        elseif shroomed and wormed then
            self.ending = "sw"
        elseif anted then
            self.ending = "a"
        elseif shroomed then
            self.ending = "s" 
        elseif wormed then
            self.ending = "w"
        end
        self.gameState = "transforming"
    elseif self.gameState == "transforming" and 1 + self.tfFrame == #self.sprites[self.tfs[self.ending]] and key == "space" then
        self.gameState = "ending"
    elseif self.gameState == "ending" and key == "space" then
        self:setup()
    end
end

return {
      name = "My Body Is a Garden"
    , themes = {"Infestation", "Transformation", "Body Horror"}
    , about = "My body is a garden from which rot grows. March through the decay to find that which will make me whole. I do not crave beauty, I simply want change."
    , game = game
    , id = 2
}
