local MAP_SCAN_DIR = 'maps_local/'

--[[

    love-td Map Editor
    main.lua
        
    * DEVELOPERS:   Joachim Andersen

--]]

local PROJECT_FILES = 
{
'gui',
'utility/utility',
'utility/utility_string'
}

local WIN_H, WIN_W = 600, 800

--[[

    Game callback handlers
    
--]]

local t_Callbacks = 
{
    ['focus'] =         {},
    ['keypressed'] =    {},
    ['keyreleased'] =   {},
    ['mousefocus'] =    {},
    ['mousepressed'] =  {},
    ['mousereleased'] = {},
    ['textinput'] =     {},
    ['threaderror'] =   {},
    ['visible'] =       {},
    ['update'] =        {},
    ['draw'] =          {}
}

for strCallback in pairs (t_Callbacks) do
    love[strCallback] = function (...)
        for _,strCallbackFunc in ipairs (t_Callbacks[strCallback]) do
            _G[strCallbackFunc](...)
        end
    end
end

function registerGameCallbackFunc (strCallback, strFunc)
    return table.insert (t_Callbacks[strCallback], strFunc)
end

function deregisterGameCallbackFunc (strCallback, strFunc)
    for k,strCallbackFunc in ipairs (t_Callbacks[strCallback]) do
        if (strCallbackFunc == strFunc) then
            return table.remove (t_Callbacks[strCallback], k)
        end
    end
end

--[[

    Map editor
    
--]]

local t_EditorData = {areaGroupCount = 0, nodes = {path = {}, area = {}}, showAreaNodes = true, showPathNodes = true}
local t_Maps = {}

local THUMBNAIL_SCALE = 0.15
local THUMBNAIL_SIZE = THUMBNAIL_SCALE * 800

local NODE_FILES = {start = love.graphics.newImage('node-start.png'), intermediate = love.graphics.newImage('node-intermediate.png'), stop = love.graphics.newImage('node-stop.png'), area = love.graphics.newImage('node-area.png')}

local function incrementAreaGroupCount ()
    t_EditorData.areaGroupCount = t_EditorData.areaGroupCount + 1
    return t_EditorData.areaGroupCount
end 

function love.load ()
    
    love.filesystem.createDirectory (MAP_SCAN_DIR)
    
    registerGameCallbackFunc ('draw', 'renderMapNodes')
    
    for i,file in ipairs (PROJECT_FILES) do
        require (file)
    end
    
    love.graphics.setBackgroundColor (102, 102, 102)
    
    for _,strFile in ipairs (love.filesystem.getDirectoryItems(MAP_SCAN_DIR)) do
        if (strFile:sub(#strFile-3, #strFile) == '.png') then
            local image = love.graphics.newImage (MAP_SCAN_DIR .. strFile)
            if (image) then
                table.insert (t_Maps, {name = strFile, src = image})
            end
        end
    end
    
    local menu = {} -- Array containing GUI elements only
    for i,t_Map in ipairs (t_Maps) do
        local GUIImage = gui.createImage (0, 0, t_Map.src)
        GUIImage:setScale (THUMBNAIL_SCALE, THUMBNAIL_SCALE)
        
        local intY = 150
        if (i > 4) then
            intY = 280
            i = i - 4
        end
        
        GUIImage.fileName = t_Map.name
        GUIImage.oX, GUIImage.oY = THUMBNAIL_SIZE + THUMBNAIL_SIZE*(i-1) + 60*(i-1), intY
        GUIImage:center (GUIImage.oX, GUIImage.oY)
        GUIImage:setHoverHandler ('onThumbnailMouseEnter', 'onThumbnailMouseExit')
        GUIImage:setClickHandler ('onThumbnailClick')
        table.insert (menu, GUIImage)
    end
    t_EditorData.menu = menu
    
    return true
end

function onThumbnailMouseEnter (GUIImage)
    GUIImage:setScale (THUMBNAIL_SCALE + 0.02, THUMBNAIL_SCALE + 0.02)
    GUIImage:center (GUIImage.oX, GUIImage.oY)
end

function onThumbnailMouseExit (GUIImage)
    GUIImage:setScale (THUMBNAIL_SCALE, THUMBNAIL_SCALE)
    GUIImage:center (GUIImage.oX, GUIImage.oY)
end

function onThumbnailClick (GUIImage)
    local strMapFileName = GUIImage.fileName
    for _,GUIObj in ipairs (t_EditorData.menu) do GUIObj:destroy () end
    t_EditorData.menu = nil

    t_EditorData.currentMap = strMapFileName
    t_EditorData.currentMapData = love.graphics.newImage (MAP_SCAN_DIR .. strMapFileName)
    
    --[[
    
        Toolbox
    
    --]]
    
    local toolBox = {active = true, gui = {}}
    local intW, intH = WIN_W, 110
    local intX, intY = 0, WIN_H - intH
    
    local GUIRect = gui.createRectangle ('fill', intX, intY, intW, intH, {0, 0, 0, 150})
    GUIRect.oX, GUIRect.oY = intX, intY
    table.insert (toolBox.gui, GUIRect)
    
    local GUILabel = gui.createLabel ('Press left CTRL to toggle this toolbox\n\nDouble click on a node to create\nRight click on a node to remove', intX + 30, intY + 20, COLORS.white, 'VeraSans')
    GUILabel.oX, GUILabel.oY = GUILabel.x, GUILabel.y
    table.insert (toolBox.gui, GUILabel)
    
    local GUIButton = gui.createButton (intW - 200, GUILabel.oY, 180, 30, 'ADD AREA CIRCUIT', COLORS.markerblack, COLORS.white, 'OstrichSans')
    GUIButton.oX, GUIButton.oY = GUIButton.x, GUIButton.y
    table.insert (toolBox.gui, GUIButton)
    GUIButton:setClickHandler ('onAddCircuitClick')
    
    
    GUIButton = gui.createButton (intW - 200, GUILabel.oY + 35, 130, 30, 'SAVE MAP', COLORS.markerblack, COLORS.white, 'OstrichSans')
    GUIButton.oX, GUIButton.oY = GUIButton.x, GUIButton.y
    table.insert (toolBox.gui, GUIButton)
    GUIButton:setClickHandler ('onSaveMapClick')
    
    GUIButton = gui.createButton (intW - 200 - 180 - 10, GUILabel.oY + 35, 180, 30, 'HIDE AREA NODES', COLORS.markerblack, COLORS.white, 'OstrichSans')
    GUIButton.oX, GUIButton.oY = GUIButton.x, GUIButton.y
    table.insert (toolBox.gui, GUIButton)
    GUIButton:setClickHandler ('onHideAreaNodesClick')
    
    GUIButton = gui.createButton (intW - 200 - 180 - 10, GUILabel.oY, 180, 30, 'HIDE PATH NODES', COLORS.markerblack, COLORS.white, 'OstrichSans')
    GUIButton.oX, GUIButton.oY = GUIButton.x, GUIButton.y
    table.insert (toolBox.gui, GUIButton)
    GUIButton:setClickHandler ('onHidePathNodesClick')
    
    for _,GUIObj in ipairs (toolBox.gui) do
        GUIObj:setPostGui (true)
    end
    
    t_EditorData.toolBox = toolBox
    registerGameCallbackFunc ('keypressed', 'onKeyPressed')
    
    if (loadMap(strMapFileName)) then
        return true
    end
    
    local mapNode_start = createMapNode ('start', 1)
    mapNode_start.oX, mapNode_start.oY = 100, 50
    mapNode_start:center (mapNode_start.oX, mapNode_start.oY)
    
    local mapNode_stop = createMapNode ('stop', 2)
    mapNode_stop.oX, mapNode_stop.oY = 100, 75
    mapNode_stop:center (mapNode_stop.oX, mapNode_stop.oY)
end

function onHidePathNodesClick (GUIButton)
    for k,mapNode in ipairs (t_EditorData.nodes.path) do
        mapNode:setHidden (t_EditorData.showPathNodes)
    end
    t_EditorData.showPathNodes = not t_EditorData.showPathNodes
    
    if (t_EditorData.showPathNodes) then
        GUIButton:setText ('HIDE PATH NODES')
    else
        GUIButton:setText ('SHOW PATH NODES')
    end
end

function onHideAreaNodesClick (GUIButton)
    for k,t_Group in pairs (t_EditorData.nodes.area) do
        for i,mapNode in ipairs (t_Group) do
            mapNode:setHidden (t_EditorData.showAreaNodes)
        end
    end
    t_EditorData.showAreaNodes = not t_EditorData.showAreaNodes
    
    if (t_EditorData.showAreaNodes) then
        GUIButton:setText ('HIDE AREA NODES')
    else
        GUIButton:setText ('SHOW AREA NODES')
    end
end

function onSaveMapClick ()
    return saveMap (t_EditorData.currentMap)
end

function onAddCircuitClick ()
    t_EditorData.connectingNode = createMapNode ('area', 1, incrementAreaGroupCount())
end

function createMapNode (strType, intPos, intAreaGroup)
    local GUIImage = gui.createImage (0, 0, NODE_FILES[strType])
    GUIImage:setScale (0.10, 0.10)
    GUIImage.nodeType = strType
    
    GUIImage:setHoverHandler ('onMapNodeHoverEnter', 'onMapNodeHoverExit')
    GUIImage:setClickHandler ('onMapNodeClick')
    
    if (strType ~= 'area') then
        table.insert (t_EditorData.nodes.path, intPos, GUIImage)
        GUIImage.nodeSuperType = 'path'
    else
        if (not t_EditorData.nodes.area[intAreaGroup]) then
            t_EditorData.nodes.area[intAreaGroup] = {}
        end
        table.insert (t_EditorData.nodes.area[intAreaGroup], intPos, GUIImage)
        GUIImage.group = intAreaGroup
        GUIImage.nodeSuperType = 'area'
    end
    
    return GUIImage
end

function destroyMapNode (mapNode)
    if (mapNode.nodeSuperType == 'path') then
        table.removeArrayValue (t_EditorData.nodes.path, mapNode)
    else
        local t_Group = t_EditorData.nodes.area[mapNode.group]
        table.removeArrayValue (t_Group, mapNode)
        
        if (#t_Group == 0) then
            table.removeValue (t_EditorData.nodes.area, t_Group)
        end
    end
    
    mapNode:destroy ()
    return true
end

function onMapNodeHoverEnter (GUIImage)
    GUIImage:setScale (0.12, 0.12)
    GUIImage:center (GUIImage.oX, GUIImage.oY)
    t_EditorData.currentHover = GUIImage
end

function onMapNodeHoverExit (GUIImage)
    GUIImage:setScale (0.10, 0.10)
    GUIImage:center (GUIImage.oX, GUIImage.oY)
    if (t_EditorData.currentHover ~= GUIImage) then return true end
    
    t_EditorData.currentHover = nil
end

function onMapNodeClick (GUIImage, strButton, intX, intY)
    if (t_EditorData.currentHover) then
        GUIImage = t_EditorData.currentHover
    end

    if (t_EditorData.connectingNode) then
        local connectingNode = t_EditorData.connectingNode
        
        if (strButton == 'r') then
            t_EditorData.connectingNode = nil
            return destroyMapNode (connectingNode)
        end
        
        local function checkMapNodeCol (mapNode)
            if (mapNode ~= connectingNode) then
                if (isPointInsideBox(connectingNode.oX, connectingNode.oY, unpack(mapNode.bbox))) then
                    return true
                end
            end
            
            return false
        end
        
        for k,mapNode in ipairs (t_EditorData.nodes.path) do
            if (checkMapNodeCol(mapNode)) then return true end
        end
        
        for k,tGroup in ipairs (t_EditorData.nodes.area) do
            for i,mapNode in ipairs (tGroup) do
                if (checkMapNodeCol(mapNode)) then return true end
            end
        end
        
        t_EditorData.connectingNode = nil
    
        return true
    end

    if (strButton == 'r') then
        if (GUIImage.nodeSuperType == 'path') then
            if (GUIImage.nodeType == 'stop') or (GUIImage.nodeType == 'start') then
                return true
            end
            
            return destroyMapNode (GUIImage)
        else
            return destroyMapNode (GUIImage)
        end
        
    elseif (strButton ~= 'l') then return true end
        
    
    local intTick = getTickCount ()
    if (t_EditorData.clickNode == GUIImage) then
        -- Double click
        if ((intTick - t_EditorData.clickTick) < 400) then
            if (GUIImage.nodeSuperType == 'path') then
                if (GUIImage.nodeType ~= 'stop') then
                    for i,mapNode in ipairs (t_EditorData.nodes.path) do
                        if (mapNode == GUIImage) then
                            t_EditorData.connectingNode = createMapNode ('intermediate', i+1)
                        end
                    end
                    
                    return true
                end
            else
                for i,mapNode in ipairs (t_EditorData.nodes.area[GUIImage.group]) do
                    if (mapNode == GUIImage) then
                        t_EditorData.connectingNode = createMapNode ('area', i+1, GUIImage.group)
                    end
                end
            end
        end
    end
    
    t_EditorData.clickNode = GUIImage
    t_EditorData.clickTick = intTick
    t_EditorData.currentNode = GUIImage
    registerGameCallbackFunc ('update', 'handleMapNode')
end

function renderMapNodes ()
    if (t_EditorData.currentMapData) then
        love.graphics.draw (t_EditorData.currentMapData, 0, 0)
    end

    if t_EditorData.connectingNode then
        local int_mX, int_mY = love.mouse.getPosition ()
        t_EditorData.connectingNode:center (int_mX, int_mY)
        t_EditorData.connectingNode.oX, t_EditorData.connectingNode.oY = int_mX, int_mY
    end
    
    love.graphics.setColor (0, 0, 0, 255)
    
    if (t_EditorData.showPathNodes) then
        for i = 2, #t_EditorData.nodes.path do
            local mapNode_prev = t_EditorData.nodes.path[i-1]
            local mapNode_curr = t_EditorData.nodes.path[i]
            
            love.graphics.line (mapNode_prev.oX, mapNode_prev.oY, mapNode_curr.oX, mapNode_curr.oY)
        end
    end
    
    if (not t_EditorData.showAreaNodes) then return true end
    
    for k,t_Group in pairs (t_EditorData.nodes.area) do
        for i = 1, #t_Group do
            local mapNode_prev = t_Group[i-1] or t_Group[#t_Group]
            local mapNode_curr = t_Group[i]
            
            love.graphics.line (mapNode_prev.oX, mapNode_prev.oY, mapNode_curr.oX, mapNode_curr.oY)
        end
    end
end

function handleMapNode ()
    if (not love.mouse.isDown('l')) then
        deregisterGameCallbackFunc ('update', 'handleMapNode')
        t_EditorData.currentNode = nil
        return true
    end
    
    local mapNode = t_EditorData.currentNode
    local intX, intY = love.mouse.getPosition ()
    
    mapNode.oX = intX
    mapNode.oY = intY
    mapNode:center (intX, intY)
end

function onKeyPressed (strKey)
    -- Toggle toolbox
    if (strKey == 'lctrl') then
        if (not t_EditorData.toolBox.active) then
            for _,GUIObj in ipairs (t_EditorData.toolBox.gui) do
                GUIObj:setPosition (GUIObj.oX, GUIObj.oY)
            end
            t_EditorData.toolBox.active = true
        else
            for _,GUIObj in ipairs (t_EditorData.toolBox.gui) do
                GUIObj:setPosition (GUIObj.oX, WIN_H + 110)
            end
            t_EditorData.toolBox.active = false
        end
    end
end

--[[

    Map save/load
    
--]]

function loadMap (strFile)
    strFile = strFile:sub (1, #strFile-4) .. '.def'
    if (not love.filesystem.exists(strFile)) then
        return false
    end
    
    local file = love.filesystem.newFile (strFile)
    file:open ('r')
    
    local strData = file:read ()
    local t_Data = strData:split ('\n')
    
    t_Data[1] = t_Data[1]:split ('|')
    for k,v in ipairs (t_Data[1]) do
        local t_Pos = v:split (';')
        
        local mapNode
        if (k == 1) then
            mapNode = createMapNode ('start', 1)
        elseif (k == #t_Data[1]) then
            mapNode = createMapNode ('stop', k)
        else
            mapNode = createMapNode ('intermediate', k)
        end
        
        mapNode.oX, mapNode.oY = unpack (t_Pos)
        mapNode:center (mapNode.oX, mapNode.oY)
    end
    
    t_Data[2] = t_Data[2]:split ('F')
    for k,v in ipairs (t_Data[2]) do
        local t_Poly = v:split ('|')
        local intGroupID = incrementAreaGroupCount()
        for i,p in ipairs (t_Poly) do
            t_Pos = p:split (';')
            
            local mapNode = createMapNode('area', i, intGroupID)
            mapNode.oX, mapNode.oY = unpack (t_Pos)
            mapNode:center (mapNode.oX, mapNode.oY)
        end
    end
    
    return true
end

function saveMap (strFile)
    local strData = ''
    strFile = strFile:sub (1, #strFile-4)
    
    for i,mapNode in ipairs (t_EditorData.nodes.path) do
        strData = strData .. string.format('%d;%d', mapNode.oX, mapNode.oY)
        if (i < #t_EditorData.nodes.path) then
            strData = strData .. '|'
        end
    end
    
    strData = strData .. '\n'
    local intTablePos = 1
    
    for i,t_Group in pairs (t_EditorData.nodes.area) do
        for k,mapNode in ipairs (t_Group) do
            strData = strData .. string.format('%d;%d', mapNode.oX, mapNode.oY)
            if (k < #t_Group) then
                strData = strData .. '|'
            end
        end
        
        if not (intTablePos == table.getSize(t_EditorData.nodes.area)) then
            strData = strData..'F'
        end
        
        intTablePos = intTablePos + 1
    end
    
    local file = love.filesystem.newFile (strFile .. '.def')
    file:open ('w')
    
    file:write (strData, #strData)
    file:close ()
    
    return true
end