#include "debug.lua"
#include "registry.lua"
#include "umf.lua"
#include "ui.lua"
#include "utility.lua"
#include "tempest.lua"

------------------------------------------------
--- Infinite World Generator
--- By: Geneosis
------------------------------------------------

WorldGen = {}
WorldGen.directions = {"north", "east", "south", "west"}

function randVec(length)
	local v = VecNormalize(Vec(math.random(-100,100), math.random(-100,100), math.random(-100,100)))
	return VecScale(v, length)	
end

function init()

    first = true
    checkRegInitialized()
    SetEnvironmentProperty("skybox", "cloudy.dds")
    SetEnvironmentProperty("night", 0)
    SetEnvironmentProperty("fogcolor", 0.22, 0.17, 0.26)

    initTempest()
    SetBool("level.medieval_fight.survival", true)
    --initSpawning()

    initDebug()
    -- Speed of chunks generation
    WorldGen.bufferSpeed = 0
    WorldGen.maxBufferSpeed = 0.02
    -- Chunks are squares of the given chunkSize with unlimited vertical size
    WorldGen.chunkSizeVoxels = regGetInt('world.chunkSize')
    -- Registry value is in number of voxels, convert it to world length unit
    WorldGen.chunkSize = WorldGen.chunkSizeVoxels * 0.1
    -- Number of cnunks to render around the player
    WorldGen.renderDistance = regGetInt('world.renderDistance')
    -- Current chunk containing the player
    WorldGen.chunkPos = Vec()
    -- List of chunks discovered
    WorldGen.chunkIndexes = {}
    for i=-10, 10 do
        WorldGen.chunkIndexes[i] = {}
        for j=-10, 10 do
            WorldGen.chunkIndexes[i][j] = false
        end
    end
    -- Reverse mapping for texture indexes (index starts at 0)
    WorldGen.texturesIndex = {}
    for k,v in pairs(textures) do
        WorldGen.texturesIndex[v]=k-1
    end
    startTime = 0
end

function tick(dt)

    startTime = startTime + dt

    -- Display debug infos
    debugMod()
    -- Render chunks inside render distance
    RenderWorld()
    -- Stop rendering chunck outside render distance
    CleanupWorld()
    if startTime > 0.3 and startTime < 0.4 then
        first = false
        MakeHole(GetPlayerCameraTransform().pos, 10, 10, 10, true)
    end
    updateTempest(dt)
    updateSpawning(dt)

end

function RenderWorld()

    local dt = GetTimeStep()

    if WorldGen.bufferSpeed > 0 then
        WorldGen.bufferSpeed = WorldGen.bufferSpeed - 1 * dt
    else
        WorldGen.bufferSpeed = WorldGen.maxBufferSpeed
    end

    local pos = GetPlayerPos()
    WorldGen.chunkPos = GetChunckPos(pos)
    dbw("Player Pos", pos)
    dbw("Chunk Pos", WorldGen.chunkPos)

    local spiralLimit = WorldGen.renderDistance * 4
    local dir = 1

    local currX = 0
    local currY = 0


    --A very complicated way of making a spiral
    for i=1, spiralLimit + 1 do
        local jumpAmount = floatToInt(i / 2)

        if dir < 4 then
            dir = dir + 1
        else
            dir = 1
        end

        if dir == 1 then
            --Up
            for j=1, jumpAmount do
                RenderChunk(currX + WorldGen.chunkPos[1], currY + WorldGen.chunkPos[3])
                currY = currY + 1
            end
        elseif dir == 2 then
            --Right
            for j=1, jumpAmount do
                RenderChunk(currX + WorldGen.chunkPos[1], currY + WorldGen.chunkPos[3])
                currX = currX + 1
            end
        elseif dir == 3 then
            --Down
            for j=1, jumpAmount do
                RenderChunk(currX + WorldGen.chunkPos[1], currY + WorldGen.chunkPos[3])
                currY = currY - 1
            end
        elseif dir == 4 then
            --Left
            for j=1, jumpAmount do
                RenderChunk(currX + WorldGen.chunkPos[1], currY + WorldGen.chunkPos[3])
                currX = currX - 1
            end
        else
            DebugPrint("ERROR")
        end
    end
end

-- Return the chunk index coordinates of a given world coordinate
function GetChunckPos(worldPos)
    return Vec(floatToInt(worldPos[1] / WorldGen.chunkSize), 0, floatToInt(worldPos[3] / WorldGen.chunkSize))
end

function GetChunkTag(posX, posY)
    return "chunk_"..posX.."_"..posY
end

function IsGeneratedShape(shape)
    return HasTag(shape, "generatedFloor") or HasTag(shape, "generatedWall") or HasTag(shape, "generatedCeiling")
end

function RenderChunk(inputX, inputY)
    if WorldGen.chunkIndexes[inputX] == nil then
        WorldGen.chunkIndexes[inputX] = {}
        dbp("Expanding X to "..inputX)
    end

    if WorldGen.chunkIndexes[inputX][inputY] == nil then
        WorldGen.chunkIndexes[inputX][inputY] = false
        dbp("Expanding Y to "..inputY)
    end

    if (not WorldGen.chunkIndexes[inputX][inputY]) and (WorldGen.bufferSpeed <= 0) then
        WorldGen.bufferSpeed = WorldGen.maxBufferSpeed
        WorldGen.chunkIndexes[inputX][inputY] = true
        -- Here you can load your own .vox or .xml file containing one chunk
        -- In this case I will be generating the xml from code
        local generationType = regGetString('world.generate')
        RenderFloor(inputX, inputY)
        if (generationType == 'floor + walls') or (generationType == 'floor + walls + ceiling') then
            RenderWalls(inputX, inputY)
        end
        if generationType == 'floor + walls + ceiling' then
            RenderCeiling(inputX, inputY)
        end
    end
end

function RenderFloor(inputX, inputY)
    local material = regGetString('floor.material')
    regSetBool('floor.transparent', material == "glass")
    local r = regGetFloat('floor.color.r')/255
    local g = regGetFloat('floor.color.g')/255
    local b = regGetFloat('floor.color.b')/255
    local a = regGetBool('floor.transparent') and 0.5 or 1 -- Any value other than 1 is the same transparency (0 cause a bug)
    local textureIndex = WorldGen.texturesIndex[regGetString('floor.texture')]
    local textureOpacity = regGetInt('floor.textureOpacity')/100
    local metallic = regGetInt('floor.metallic')/100
    local roughness = regGetInt('floor.roughness')/100
    local emissive = regGetInt('floor.emissive')/10
    local thickness = regGetInt('floor.thickness')
    local tags = "generatedFloor "..GetChunkTag(inputX, inputY)
    if regGetBool('floor.unbreakable') then
        tags = tags.." unbreakable"
    end
    local xmlString = "<voxbox \
    size='"..WorldGen.chunkSizeVoxels.." "..thickness.." "..WorldGen.chunkSizeVoxels.."' \
    material='"..material.."' \
    color='"..r.." "..g.." "..b.." "..a.."' \
    pbr='"..metallic.." "..roughness.." "..(0).." "..emissive.."' \
    texture='"..textureIndex.." "..textureOpacity.."' \
    tags='"..tags.."' \
    />"
    --dbp("floor XML="..xmlString)
    Spawn(xmlString, Transform(Vec((inputX * WorldGen.chunkSize) - (WorldGen.chunkSize/2), -thickness*0.1, (inputY * WorldGen.chunkSize)  - (WorldGen.chunkSize/2))), true)
end

function RenderWalls(inputX, inputY)
    local material = regGetString('walls.material')
    regSetBool('walls.transparent', material == "glass")
    local r = regGetFloat('walls.color.r')/255
    local g = regGetFloat('walls.color.g')/255
    local b = regGetFloat('walls.color.b')/255
    local a = regGetBool('walls.transparent') and 0.5 or 1 -- Any value other than 1 is the same transparency (0 cause a bug)
    local textureIndex = WorldGen.texturesIndex[regGetString('walls.texture')]
    local textureOpacity = regGetInt('walls.textureOpacity')/100
    local metallic = regGetInt('walls.metallic')/100
    local roughness = regGetInt('walls.roughness')/100
    local emissive = regGetInt('walls.emissive')/10
    local thickness = regGetInt('walls.thickness')
    local height = regGetInt('walls.height')
    local tags = "generatedWall "..GetChunkTag(inputX, inputY)
    if regGetBool('walls.unbreakable') then
        tags = tags.." unbreakable"
    end

    local wallsToRender = GetWallsToRender(inputX, inputY)
    --local noWall = true
    for i,direction in pairs(wallsToRender) do
        return
    end
        local rot = QuatEuler(0, 180, 0)
        local xOffset = 0
        local yOffset = 0
        local randDir = math.random(1, 4)
        if randDir == 1 then
            --r, g, b = 1, 0, 0
            xOffset = -(thickness * 0.1) / 2
            yOffset = -(thickness * 0.1) / 2
        end
        if randDir == 2 then
            --r, g, b = 0, 1, 0
            rot = QuatEuler(0, -90, 0)
            xOffset = ((thickness * 0.1) / 2) + WorldGen.chunkSize
            yOffset = -(thickness * 0.1) / 2
        end
        if randDir == 3 then
            --r, g, b = 0, 0, 1
            xOffset = -(thickness * 0.1) / 2
            yOffset = -(thickness * 0.1) / 2 + WorldGen.chunkSize
        end
        if randDir == 4 then
            --r, g, b = 0, 1, 1
            rot = QuatEuler(0, 90, 0)
            xOffset = (thickness * 0.1) / 2
            yOffset = -(thickness * 0.1) / 2
        end

        local xmlString = "<voxbox \
        size='"..(WorldGen.chunkSizeVoxels+thickness).." "..height.." "..thickness.."' \
        material='"..material.."' \
        color='"..r.." "..g.." "..b.." "..a.."' \
        pbr='"..metallic.." "..roughness.." "..(0).." "..emissive.."' \
        texture='"..textureIndex.." "..textureOpacity.."' \
        tags='"..tags.."' \
        />"
        --dbp("wall XML="..xmlString)
        local randValue = math.random(1, 100)
        local fire = false
        if randValue < 26 then
            xmlString = "spawn/pine-small.xml"
        elseif randValue < 49 then
            xmlString = "spawn/pine-medium.xml"
        elseif randValue < 76 then
            xmlString = "spawn/pine-large.xml"
        elseif randValue < 100 then
            xmlString = "spawn/pine-huge.xml"
        elseif randValue < 101 then
            xmlString = "spawn/house.xml"
            fire = true or (math.random(1, 2) == 1)
        end
        local spawnPos = Vec((inputX * WorldGen.chunkSize) - (WorldGen.chunkSize/2) + xOffset, 0, (inputY * WorldGen.chunkSize) - (WorldGen.chunkSize/2) + yOffset)
        local offsetVec = randVec(3)
        offsetVec[2] = 0
        spawnPos = VecAdd(spawnPos, offsetVec)
        Spawn(xmlString, Transform(spawnPos, rot), true)
        if fire then
            SpawnFire(VecAdd(spawnPos, randVec(0.5)))
        end
        --break
    --end
    
end

function GetWallsToRender(inputX, inputY)

    local nbWalls = regGetInt('walls.count')
    local wallsPercent = regGetInt('walls.proba')/100
    local wallsPlaceable = {1, 2, 3, 4}
    local wallsToRender = {}
    -- Try to render as many walls as requested for the current chunk
    for i=1,nbWalls do
        local wallIndex = rdm(1, #wallsPlaceable)
        -- For each wall, only render it if the probability is reached
        if rdmf() <= wallsPercent then
            wallsToRender[#wallsToRender+1]=WorldGen.directions[wallsPlaceable[wallIndex]]
            table.remove(wallsPlaceable, wallIndex)
        end
    end
    return wallsToRender
    
end

function RenderCeiling(inputX, inputY)
    local material = regGetString('ceiling.material')
    regSetBool('ceiling.transparent', material == "glass")
    local r = regGetFloat('ceiling.color.r')/255
    local g = regGetFloat('ceiling.color.g')/255
    local b = regGetFloat('ceiling.color.b')/255
    local a = regGetBool('ceiling.transparent') and 0.5 or 1 -- Any value other than 1 is the same transparency (0 cause a bug)
    local textureIndex = WorldGen.texturesIndex[regGetString('ceiling.texture')]
    local textureOpacity = regGetInt('ceiling.textureOpacity')/100
    local metallic = regGetInt('ceiling.metallic')/100
    local roughness = regGetInt('ceiling.roughness')/100
    local emissive = regGetInt('ceiling.emissive')/10
    local thickness = regGetInt('ceiling.thickness')
    local height = regGetInt('walls.height')
    local tags = "generatedCeiling "..GetChunkTag(inputX, inputY)
    if regGetBool('ceiling.unbreakable') then
        tags = tags.." unbreakable"
    end
    local xmlString = "<voxbox \
    size='"..WorldGen.chunkSizeVoxels.." "..thickness.." "..WorldGen.chunkSizeVoxels.."' \
    material='"..material.."' \
    color='"..r.." "..g.." "..b.." "..a.."' \
    pbr='"..metallic.." "..roughness.." "..(0).." "..emissive.."' \
    texture='"..textureIndex.." "..textureOpacity.."' \
    tags='"..tags.."' \
    />"
    --dbp("ceiling XML="..xmlString)
    Spawn(xmlString, Transform(Vec((inputX * WorldGen.chunkSize) - (WorldGen.chunkSize/2), height * 0.1, (inputY * WorldGen.chunkSize) - (WorldGen.chunkSize/2))), true)
end

function CleanupWorld()

    if not regGetBool('world.despawnChunks') then
        return
    end

    -- Browse all chunks currently loaded
    for i,chunkLine in pairs(WorldGen.chunkIndexes) do
        for j,chunkLoaded in pairs(chunkLine) do
            if chunkLoaded then
                local chunkPos = Vec(i, 0, j)
                if db then
                    local min, max = GetChunkBounds(chunkPos)
                    AabbDraw(min, max, 0.5, 0.5, 0.5, 1)
                end
                -- If this chunk should not be rendreded any more
                if not IsChunkInRenderRange(chunkPos) then
                    -- Find all shapes in the chunk and delete them
                    local min, max = GetChunkBounds(chunkPos)
                    if db then
                        AabbDraw(min, max, 1, 0, 0, 1)
                    end
                    local chunkShapes = QueryAabbShapes(min, max)
                    for k=1, #chunkShapes do
                        local shape = chunkShapes[k]
                        -- For generated shapes, only delete those who belongs to the current chunk
                        if IsGeneratedShape(shape) then
                            if HasTag(shape, GetChunkTag(i, j)) then
                                SafelyDeleteShape(shape)
                            end
                        -- Delete other shapes if they are in contact with this chunk
                        else
                            SafelyDeleteShape(shape)
                        end
                    end
                    -- Mark chunk as not loaded
                    WorldGen.chunkIndexes[i][j] = false
                end
            end
        end
    end

end

function IsChunkInRenderRange(chunkPos)
    return (math.abs(WorldGen.chunkPos[1] - chunkPos[1]) <= WorldGen.renderDistance
        and math.abs(WorldGen.chunkPos[3] - chunkPos[3]) <= WorldGen.renderDistance)
end

function GetChunkBounds(chunkPos)
    -- Area containing containing a whole chunk (may contact with some walls/floors/ceilings from other chunks)
    local min = Vec((chunkPos[1] * WorldGen.chunkSize) - (WorldGen.chunkSize/2), -100, (chunkPos[3] * WorldGen.chunkSize) - (WorldGen.chunkSize/2))
    local max = Vec(min[1] + WorldGen.chunkSize, 100, min[3] + WorldGen.chunkSize)
    return min, max
end

function SafelyDeleteShape(shape)

    local shapeBody = GetShapeBody(shape)
    local vehicle = GetBodyVehicle(shapeBody)
    local vehicleMainBody = GetVehicleBody(vehicle)
    local foundMainBody = false
    -- For vehicles, delete the body instead to avoid hanging wheels
    if vehicle > 0 then
        if shapeBody == vehicleMainBody then
            Delete(shapeBody)
            Delete(shape)
        end
    -- Else simply delete the shape
    else
        Delete(shape)
    end

end