local battleMenu = require("scripts/battleMenu")
local textFiles = require("scripts/textFiles")

local battleMessages = require("scripts/battleMessages")
local battleGeneral = require("scripts/battleGeneral")
local battleCamera = require("scripts/battleCamera")
local battlePlayer = require("scripts/battlePlayer")
local battleStart = require("scripts/battleStart")

local onlinePlay = require("scripts/onlinePlay")
local onlinePlayPlayers = require("scripts/onlinePlay_players")

local textplus = require("textplus")
local easing = require("ext/easing")

local levelSelect
local maskCollection

local playerSelect = {}


local cameraCentreMoveTimer = 0
local cameraMoveStartX


-- Menus
do
    playerSelect.optionData = {
        localMultiplayer = {playerCount = 2,layerName = "2 players",warpIndices = {1,2}},
        onlineMultiplayer = {playerCount = 1,layerName = "online",warpIndices = {3}},
        recon = {playerCount = 1,layerName = "recon",warpIndices = {4}},
    }

    playerSelect.optionNames = {
        "localMultiplayer",
        "onlineMultiplayer",
        "recon",
    }

    -- Initial 2/3/4 player menu
    playerSelect.initialMenu = battleMenu.createMenu{
        format = {elementGapY = 12,offsetX = 800,cameraOffsetY = 96},
        optionFormat = {
            boxMarginY = 16,
            getGraphicsPosFunc = function(option)
                return option.x + option.y/option.totalHeight*96,option.y
            end,
            useLargestWidth = true,
        },
        cantGoBack = true,
    }
    playerSelect.initialMenu.openFunc = function(menu)
        local text = textFiles.menu.players

        menu:addOption{text = text.localMultiplayer.option,descriptionText = text.localMultiplayer.description,data = {optionName = "localMultiplayer"},runFunction = function(option)
            battleGeneral.gameData.playerCount = 2
            battleMenu.closeAll()

            SFX.play(14)
        end}

        if onlinePlay.socketLoaded then
            menu:addOption{text = text.onlineMultiplayer.option,descriptionText = text.onlineMultiplayer.description,data = {optionName = "onlineMultiplayer"},openMenu = playerSelect.onlineModeMenu}
        else
            menu:addOption{text = text.onlineMultiplayer.option,descriptionText = text.onlineMultiplayer.description,data = {optionName = "onlineMultiplayer"},openMenu = playerSelect.onlineHelpMenu}
        end

        menu:addOption{text = text.recon.option,descriptionText = text.recon.description,data = {optionName = "recon"},runFunction = function(option)
            battleGeneral.gameData.playerCount = 1
            battleMenu.closeAll()

            SFX.play(14)
        end}
    end


    -- Online options
    playerSelect.onlineModeMenu = battleMenu.createMenu{
        format = {offsetX = 800,cameraOffsetY = 96},
        optionFormat = {
            getGraphicsPosFunc = function(option)
                return option.x - option.y/option.totalHeight*32,option.y
            end,
            useLargestWidth = true,
        },
    }
    playerSelect.onlineModeMenu.openFunc = function(menu)
        local text = textFiles.menu.players.online

        menu:addOption{text = text.hostGame,openMenu = playerSelect.joinMenu,openMenuArgs = {join = false}}
        menu:addOption{text = text.joinHost,openMenu = playerSelect.joinMenu,openMenuArgs = {join = true}}
        menu:addOption{text = text.help.option,openMenu = playerSelect.onlineHelpMenu}
    end

    -- Host/join host menu
    local function isValidIPAddress(text)
        local numbers = {text:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
    
        for _,value in ipairs(numbers) do
            value = tonumber(value)
    
            if value == nil or value < 0 or value > 255 then
                return false
            end
        end
    
        return (#numbers > 0)
    end

    local function canTypeIPAddress(text)
        return text:match("^([%d.]*)$")
    end

    local usernameSelection = battleMenu.createSelection("onlineUsername",battleGeneral.saveData, battleMenu.SELECTION_TEXT)
    local colorSelection = battleMenu.createSelection("onlineColor",battleGeneral.saveData, battleMenu.SELECTION_NUMBERS,1,#battleGeneral.playerColors)
    local ipSelection = battleMenu.createSelection("onlineIP",battleGeneral.gameData, battleMenu.SELECTION_TEXT,"",canTypeIPAddress,isValidIPAddress)
    local showAddress = battleMenu.createSelection("showAddress",battleGeneral.gameData, battleMenu.SELECTION_CHECKBOX,false)

    local function generateColorNames()
        local tbl = {}

        for i = 1,#battleGeneral.playerColors do
            tbl[i] = "<image \"resources/menu/colors/".. i.. ".png\">"
        end

        return tbl
    end
    
    playerSelect.joinMenu = battleMenu.createMenu{
        format = {offsetX = 800,cameraOffsetY = 96,elementGapY = 16},
        optionFormat = {
            textScale = 2,
            useLargestWidth = true,
            boxMarginX = 24,
            boxMarginY = 16,
        },
    }
    playerSelect.joinMenu.openFunc = function(menu)
        local text = textFiles.menu.players.online

        menu:addOption{text = text.username,selectionPlaceholder = text.usernamePlaceholder,selection = usernameSelection,format = {selectionMaxWidth = battleGeneral.usernameMaxWidth}}
        menu:addOption{text = text.color,selection = colorSelection,selectionNames = generateColorNames(),format = {dontTintSelection = true}}

        if menu.args.join then
            menu:addOption{text = text.ip,selectionPlaceholder = text.ipPlaceholder,selection = ipSelection,format = {selectionMaxWidth = 240},selectionIsHiddenFunction = function(option)
                return not showAddress:get()
            end}
            menu:addOption{text = text.showAddress,selection = showAddress}

            menu:addOption{text = text.joinOption,format = {useLargestWidth = false},runFunction = function(option)
                local ip = ipSelection:get()
                local name = usernameSelection:get()
                local colorIdx = colorSelection:get()

                local err

                if ip == "" or not isValidIPAddress(ip) then
                    err = textFiles.onlineErrors.invalidIP
                elseif name:find("^%s+$") then
                    err = textFiles.onlineErrors.invalidName
                else
                    SFX.play(14)
                    
                    err = onlinePlay.joinHost(ip,name,colorIdx)
                end

                if err == nil then
                    battleMenu.closeAll()
                    return
                end

                playerSelect.errorMenu:open({errorMessage = err},1,player,false)
                SFX.play(battleMenu.optionFormatDefaults.unselectableSound)
            end}
        else
            menu:addOption{text = text.hostOption,format = {useLargestWidth = false},runFunction = function(option)
                local colorIdx = colorSelection:get()
                local name = usernameSelection:get()

                local err

                if name:find("^%s+$") then
                    err = textFiles.onlineErrors.invalidName
                else
                    err = onlinePlay.host(name,colorIdx)
                end

                if err == nil then
                    battleMenu.closeAll()
                    return
                end
                
                playerSelect.errorMenu:open({errorMessage = err},1,player,false)
                SFX.play(battleMenu.optionFormatDefaults.unselectableSound)
            end}
        end
    end


    -- Online error
    playerSelect.errorMenu = battleMenu.createMenu{
        format = {hasBox = true,offsetX = 800,cameraOffsetY = 96,elementGapY = 16},
        optionFormat = {hasBox = false,textScale = 2},
        textFormat = {hasBox = false,textScale = 2,textMaxWidth = 448},
    }
    playerSelect.errorMenu.openFunc = function(menu)
        local text = textFiles.menu.players.online
        
        menu:addText{text = menu.args.errorMessage}
        menu:addOption{text = text.errorOK,runFunction = function(option)
            menu:close()
        end}
    end


    -- Online help
    playerSelect.onlineHelpMenu = battleMenu.createMenu{
        format = {hasBox = true,offsetX = 800,cameraOffsetY = 96,elementGapY = 16},
        optionFormat = {hasBox = false,textScale = 2},
        textFormat = {hasBox = false,textScale = 2,textMaxWidth = 600},
    }
    playerSelect.onlineHelpMenu.openFunc = function(menu)
        local text = textFiles.menu.players.online.help

        if not text.main:find("mrdoublea232") then
            return
        end
        
        menu:addText{text = text.main}
        menu:addOption{text = text.back,runFunction = function(option)
            menu:close()
        end}
    end



    -- Disconnect
    playerSelect.disconnectMenu = battleMenu.createMenu{
        format = {hasBackground = true,hasBox = true,offsetY = 600,elementGapY = 16},
        optionFormat = {hasBox = false,textScale = 2},
        textFormat = {hasBox = false,textScale = 2,textMaxWidth = 448}
    }
    playerSelect.disconnectMenu.openFunc = function(menu)
        local text = textFiles.menu.players.online.disconnect
        
        if onlinePlay.currentMode == onlinePlay.MODE_HOST then
            menu:addText{text = text.noteHost}
        else
            menu:addText{text = text.noteClient}
        end
        
        menu:addOption{text = text.reject,runFunction = function(option)
            menu:close()
        end}
        menu:addOption{text = text.accept,runFunction = function(option)
            onlinePlay.disconnect()
            battleMenu.closeAll()

            playerSelect.initialMenu:open({},1,player,false)
        end}
    end
end


-- Title menu
do
    playerSelect.titleMenu = battleMenu.createMenu{
        format = {cameraOffsetY = 96},
        cantGoBack = true,
    }
    
    playerSelect.titleMenu.openFunc = function(menu)
        local font = textplus.loadFont("resources/font/outlinedFont.ini")

        menu.data.logoImage = Graphics.loadImageResolved("resources/logo.png")

        menu.data.startLayout = textplus.layout(textFiles.titleScreen.start,nil,{
            font = font,xscale = 2,yscale = 2,
            posFilter = function(x,y, fmt,glyph, width,height)
                local hopTimer = math.max(0,math.min(1,((lunatime.tick() - 64 - (x - menu.data.startTextX)*0.35) % 256)/16))
                local hopOffset = math.abs(math.sin(hopTimer*math.pi))*4

                return x,y - hopOffset
            end,
        })

        menu.data.versionLayout = textplus.layout(battleGeneral.versionNames[battleGeneral.gameVersion],nil,{
            font = font,xscale = 2,yscale = 2,
        })
        menu.data.versionTextX = 8
        menu.data.versionTextY = camera.height - menu.data.versionLayout.height - 4

        menu.data.nameLayout = textplus.layout("MrDoubleA",nil,{
            font = font,xscale = 2,yscale = 2,
        })
        menu.data.nameTextX = camera.width - menu.data.nameLayout.width - 8
        menu.data.nameTextY = camera.height - menu.data.versionLayout.height - 4

        if onlinePlay.socketLoaded then
            menu.data.onlineLayout = textplus.layout(textFiles.titleScreen.onlineEnabled,nil,{
                font = font,xscale = 2,yscale = 2,
            })
            menu.data.onlineTextX = menu.data.versionTextX
            menu.data.onlineTextY = menu.data.versionTextY

            menu.data.versionTextY = menu.data.versionTextY - menu.data.onlineLayout.height - 2
        end
    end

    function playerSelect.drawTitle()
        local menu = playerSelect.titleMenu

        if not menu.isActive then
            return
        end

        local x,y,scale,rotation = menu:transformPoint(0,0)

        local textColor = Color(menu.color.r*menu.color.a,menu.color.g*menu.color.a,menu.color.b*menu.color.a,menu.color.a)

        Graphics.drawBox{
            texture = menu.data.logoImage,priority = menu.format.priority,centred = true,
            width = menu.data.logoImage.width*scale,
            height = menu.data.logoImage.height*scale,
            x = x,y = y - 24,rotation = rotation,
            color = menu.color,
        }

        menu.data.startTextX = x - menu.data.startLayout.width*0.5
        textplus.render{
            layout = menu.data.startLayout,priority = menu.format.priority,color = textColor,
            x = menu.data.startTextX,
            y = y - menu.data.startLayout.height*0.5 + 128,
        }

        textplus.render{
            layout = menu.data.versionLayout,priority = menu.format.priority,color = textColor,
            x = x + menu.format.cameraOffsetX - camera.width*0.5 + menu.data.versionTextX,
            y = y + menu.format.cameraOffsetY - camera.height*0.5 + menu.data.versionTextY,
        }

        textplus.render{
            layout = menu.data.nameLayout,priority = menu.format.priority,color = textColor,
            x = x + menu.format.cameraOffsetX - camera.width*0.5 + menu.data.nameTextX,
            y = y + menu.format.cameraOffsetY - camera.height*0.5 + menu.data.nameTextY,
        }

        if menu.data.onlineLayout ~= nil then
            textplus.render{
                layout = menu.data.onlineLayout,priority = menu.format.priority,color = textColor,
                x = x + menu.format.cameraOffsetX - camera.width*0.5 + menu.data.onlineTextX,
                y = y + menu.format.cameraOffsetY - camera.height*0.5 + menu.data.onlineTextY,
            }
        end
    end
end


local function getPlayerPipeRefusal(warp)
    if warp.exitSection == maskCollection.section and levelSelect.state ~= levelSelect.STATE.INACTIVE then
        return textFiles.battleMessages.startRefusal.maskCollectionEnter
    end

    if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
        if warp.entranceSection ~= 0 or warp.exitSection ~= 0 then
            return nil
        end

        -- Only one player
        if onlinePlay.getUserCount() <= 1 then
            return textFiles.battleMessages.startRefusal.onePlayer
        end

        -- Someone is playing mask collection
        if maskCollection.anyoneIsInSection() then
            return textFiles.battleMessages.startRefusal.maskCollection
        end

        -- Empty team
        local teamHasPlayers = {false,false}

        for _,user in ipairs(onlinePlay.getUsers()) do
            local teamIdx = battlePlayer.getTeam(user.playerIdx)

            if teamIdx > 0 then
                teamHasPlayers[teamIdx] = true
            else
                teamHasPlayers[1] = true
                teamHasPlayers[2] = true
            end
        end

        if not teamHasPlayers[1] or not teamHasPlayers[2] then
            return textFiles.battleMessages.startRefusal.emptyTeam
        end
    end

    return nil
end

local function getInitialPlayerOption()
    if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
        return "onlineMultiplayer"
    elseif battleGeneral.gameData.playerCount > 1 then
        return "localMultiplayer"
    else
        return "recon"
    end
end

local function getCurrentPlayerOption()
    if playerSelect.initialMenu.isOpen then
        return playerSelect.initialMenu.options[playerSelect.initialMenu.optionIdx].data.optionName
    else
        return getInitialPlayerOption()
    end
end

local function getPlayerOptionData()
    local optionName = getCurrentPlayerOption()

    return playerSelect.optionData[optionName]
end


local playerPipeDelay = {}


function playerSelect.triggerPipeDelay(p,delay)
    playerPipeDelay[p.idx] = delay
end


local function movePlayerToPipe(p)
    local optionData = getPlayerOptionData()

    local warpIndex = optionData.warpIndices[p.idx] or optionData.warpIndices[1]
    local warp = Warp(warpIndex - 1)

    local b = p.sectionObj.boundary

    p.forcedState = FORCEDSTATE_PIPE
    p.forcedTimer = 102
    p:mem(0x15E,FIELD_WORD,warpIndex)

    p.x = warp.exitX + (warp.exitWidth - p.width)*0.5
    p.y = warp.exitY + warp.exitHeight + 8
    p.section = warp.exitSection

    p.frame = 15

    if (p.x + p.width*0.5) > (b.right + b.left)*0.5 then
        p.direction = DIR_LEFT
    else
        p.direction = DIR_RIGHT
    end
end

local function resetPlayersToPipes()
    local optionData = getPlayerOptionData()
    local pipeDelay = 0

    for _,p in ipairs(Player.get()) do
        local data = battlePlayer.getPlayerData(p)

        if onlinePlay.currentMode == onlinePlay.MODE_OFFLINE then
            battlePlayer.setPlayerIsActive(p,p.idx <= optionData.playerCount)
        end

        if data.isActive then
            movePlayerToPipe(p)

            -- Delay
            if #optionData.warpIndices == 1 then
                playerSelect.triggerPipeDelay(p,pipeDelay)
                pipeDelay = pipeDelay + 50
            end
        else
            p.forcedState = FORCEDSTATE_SWALLOWED
            p.forcedTimer = p.idx
            p:mem(0xBA,FIELD_WORD,p.idx)
        end
    end
end

local function handlePlayerPipeDelay(p)
    if playerPipeDelay[p.idx] == nil then
        return
    end

    if p.forcedState ~= FORCEDSTATE_PIPE then
        playerPipeDelay[p.idx] = nil
        return
    end

    playerPipeDelay[p.idx] = math.max(0,playerPipeDelay[p.idx] - 1)

    if playerPipeDelay[p.idx] > 0 then
        p.forcedTimer = 102
    else
        playerPipeDelay[p.idx] = nil
    end
end


local shownLayer

local function resetPipes(layerName,noSmoke)
    local layerObj = Layer.get(layerName)

    if shownLayer ~= nil then
        shownLayer:hide(noSmoke)
    end

    shownLayer = layerObj
    layerObj:show(noSmoke)

    resetPlayersToPipes()
end


function playerSelect.onCameraUpdate()
    if not battleGeneral.isInHub then
        return
    end

    if playerSelect.initialMenu.isOpen or playerSelect.titleMenu.isOpen then
        cameraMoveStartX = cameraMoveStartX or (camera.x + camera.width*0.5)
        cameraCentreMoveTimer = math.min(1,cameraCentreMoveTimer + 1/24)
    else
        cameraCentreMoveTimer = 0
        cameraMoveStartX = nil
    end

    if cameraCentreMoveTimer > 0 then
        local bounds = Section(battleCamera.getCameraSection(1)).origBoundary
        local targetX = (bounds.left + bounds.right)*0.5

        camera.x = math.floor(easing.inOutQuad(cameraCentreMoveTimer,cameraMoveStartX,targetX - cameraMoveStartX,1) - camera.width*0.5 + 0.5)
    end
end


local oldOptionName


function playerSelect.onStart()
    if not battleGeneral.isInHub then
        return
    end

    local optionData = getPlayerOptionData()

    resetPipes(optionData.layerName,true)

    if not battleGeneral.gameData.hasSeenTitle then
        playerSelect.titleMenu:open({},1,player,false)
        battleGeneral.gameData.hasSeenTitle = true
        battleGeneral.gameData.hasSelectedPlayerMode = true
    elseif not battleGeneral.gameData.hasSelectedPlayerMode then
        playerSelect.initialMenu:open({},1,player,false)
        battleGeneral.gameData.hasSelectedPlayerMode = true
    end
end


local invalidSound = Misc.resolveSoundFile("resources/menu/unselectable")
local pipeEnterCooldown = 0


function playerSelect.onTick()
    if not battleGeneral.isInHub then
        return
    end

    if playerSelect.initialMenu.isOpen or playerSelect.titleMenu.isOpen or battleStart.state ~= battleStart.STATE.INACTIVE then
        resetPlayersToPipes()
    else
        for _,p in ipairs(Player.get()) do
            handlePlayerPipeDelay(p)

            if p.forcedState == FORCEDSTATE_PIPE and (p.forcedTimer == 0 or (p.forcedTimer >= 100 and p.forcedTimer <= 101)) then
                local warp = Warp(p:mem(0x15E,FIELD_WORD) - 1)
                local refusalMessage = getPlayerPipeRefusal(warp)

                if refusalMessage ~= nil then
                    battleMessages.spawnStatusMessage(refusalMessage,Color.lightred,160)
                    SFX.play(invalidSound)

                    p.forcedTimer = 2
                end
            end
        end
    end
    
    if playerSelect.initialMenu.isOpen then
        local newOptionName = getCurrentPlayerOption()
        local optionData = playerSelect.optionData[newOptionName]

        if newOptionName ~= oldOptionName then
            resetPipes(optionData.layerName,false)
            oldOptionName = newOptionName

            battlePlayer.loadCharacters()
            battleCamera.initCameras()
        end
    else
        oldOptionName = nil
    end

    if battleMenu.currentMenu == playerSelect.titleMenu then
        for _,key in ipairs{"jump","run","altJump","altRun","dropItem","pause"} do
            if player.rawKeys[key] == KEYS_PRESSED then
                playerSelect.initialMenu:open({},1)
                SFX.play(14)
                break
            end
        end
    end


    pipeEnterCooldown = math.max(0,pipeEnterCooldown - 1)
end

function playerSelect.onDraw()
    playerSelect.drawTitle()

    --Text.print(getPlayerPipeRefusal(Warp(0)),32,32)
end


local function getProperPlayerCount()
    if onlinePlay.currentMode ~= onlinePlay.MODE_OFFLINE then
        return battleGeneral.maxOnlinePlayers
    end

    if playerSelect.onlineModeMenu.isOpen then
        -- Player count needs to be the full 10 before you even join - otherwise, things can go wrong when you join
        return battleGeneral.maxOnlinePlayers
    end

    return battleGeneral.maxLocalPlayers
end

function playerSelect.onDrawEnd()
    local properPlayerCount = getProperPlayerCount()

    if Player.count() ~= properPlayerCount then
        local startIndex = Player.count() + 1

        battlePlayer.setPlayerCount(properPlayerCount)

        for playerIndex = startIndex,properPlayerCount do
            battlePlayer.setPlayerIsActive(Player(playerIndex),false)
        end

        --Misc.dialog(properPlayerCount)
    end
end


function playerSelect.onMouseButtonEvent(button,state,mouseX,mouseY)
    if battleMenu.currentMenu == playerSelect.titleMenu and button == 0 and state == 0 then
        playerSelect.initialMenu:open({},1)
        SFX.play(14)
    end
end


function playerSelect.onWarpEnter(eventObj,warp,p)
    local refusalMessage = getPlayerPipeRefusal(warp)

    if refusalMessage ~= nil then
        if (warp.entranceDirection ~= 3 or p.keys.down == KEYS_PRESSED) and pipeEnterCooldown == 0 and onlinePlayPlayers.ownsPlayer(p) then
            battleMessages.spawnStatusMessage(refusalMessage,Color.lightred,160)
            SFX.play(invalidSound)

            pipeEnterCooldown = 64
        end

        eventObj.cancelled = true
    end
end


function onlinePlay.onConnect(playerIdx)
    for _,user in ipairs(onlinePlay.getUsers()) do
        local p = Player(user.playerIdx)

        if p.idx == playerIdx then
            movePlayerToPipe(p)
        end

        battlePlayer.setPlayerIsActive(p,true)
    end
end


function playerSelect.onInitAPI()
    registerEvent(playerSelect,"onCameraUpdate")

    registerEvent(playerSelect,"onStart")
    registerEvent(playerSelect,"onTick")
    registerEvent(playerSelect,"onDraw")
    registerEvent(playerSelect,"onDrawEnd")

    registerEvent(playerSelect,"onMouseButtonEvent")

    registerEvent(playerSelect,"onWarpEnter")


    levelSelect = require("levelSelect")
    maskCollection = require("maskCollection/scripts/maskCollection")
end


return playerSelect