module AhornPersonalAdditives

using ..Ahorn, Maple, Gtk, Gtk.ShortNames, Ahorn.Form

const entityTriggerUnion = Union{Maple.Entity, Maple.Trigger}

lastPresetWindow = nothing
lastPresetWindowDestroyed = false

function getPresets()
    presets = get(Ahorn.persistence, "lilybeevee_additives_presets", Dict{String, Any}())
    Ahorn.persistence["lilybeevee_additives_presets"] = presets

    return presets
end

function fillDefaultOptions(options::Dict{String, Any})
    options["name"] = get(options, "name", "")
    options["keepSize"] = get(options, "keepSize", false)
end

function createPreset(target::entityTriggerUnion, options::Dict{String, Any}=Dict{String, Any}())
    horizontal, vertical = Ahorn.canResizeWrapper(target)
    placementType = horizontal || vertical ? "rectangle" : "point"

    ignored = ["x", "y"]

    x, y = target.data["x"], target.data["y"]

    savedData = Dict{String, Any}()
    for (k, v) in target.data
        if !(k in ignored)
            if k == "nodes"
                nodes = Array{Integer, 1}[]
                for node in v
                    push!(nodes, Integer[node[1] - x, node[2] - y])
                end
                savedData["__nodes"] = nodes
            else
                savedData[k] = v
            end
        end
    end

    fillDefaultOptions(options)

    if isempty(options["name"])
        options["name"] = "$(target.name) (Preset)"
    end

    return Dict{String, Any}(
        "type" => isa(target, Maple.Entity) ? "entity" : "trigger",
        "name" => target.name,
        "placement" => placementType,
        "data" => savedData,
        "options" => options
    )
end

function createPlacement(preset::Dict{String, Any})
    constructor = get(preset, "type", "entity") == "entity" ? Maple.Entity : Maple.Trigger

    placementName = get(preset, "name", "")
    placementType = get(preset, "placement", "point")
    placementData = deepcopy(get(preset, "data", Dict{String, Any}()))

    options = get(preset, "options", Dict{String, Any}())

    if !get(options, "keepSize", false)
        delete!(placementData, "width")
        delete!(placementData, "height")
    else
        placementType = "point"
    end

    return Ahorn.EntityPlacement(
        (x::Number, y::Number) -> constructor(placementName, x=x, y=y),
        placementType,
        placementData,
        function(entity::entityTriggerUnion)
            nodes = get(entity.data, "__nodes", Array{Integer, 1}[])
            if length(nodes) > 0
                x, y = entity.data["x"], entity.data["y"]
                entity.data["nodes"] = Tuple{Integer, Integer}[]
                for node in nodes
                    push!(entity.data["nodes"], (node[1] + x, node[2] + y))
                end
            end
            delete!(entity.data, "__nodes")
        end
    )
end


function createPresetFormWindow(title::String, sections::Union{Array{Form.Section, 1}, Form.Section}; columns::Integer=4, separateGroups::Bool=true,
    gridIfSingleSection::Bool=true, buttonText::String="Add", callback::Function=(data) -> println(data), editProperties::Function=() -> println("clicked"), 
    removeCallback::Union{Function, Nothing}=nothing, parent::Gtk.GtkWindow=Ahorn.window, icon::Pixbuf=Ahorn.windowIcon, canResize::Bool=false)

    sections = isa(sections, Form.Section) ? Form.Section[sections] : sections
    propertiesButton = Form.Button("Edit Properties")
    finishButton = Form.Button(buttonText)
    removeButton = Form.Button("Remove")

    content = Form.generateSectionsNotebook(sections, columns=columns, separateGroups=separateGroups, gridIfSingleSection=gridIfSingleSection)

    window = Form.Window(title, -1, -1, canResize, icon=icon) |> (Form.Frame() |> (box = Form.Box(:v)))
    
    push!(box, content)
    push!(box, finishButton)
    if removeCallback != nothing
        push!(box, removeButton)
    end
    push!(box, propertiesButton)

    @Ahorn.guarded signal_connect(propertiesButton, "clicked") do args...
        editProperties()
    end

    if removeCallback != nothing
        @Ahorn.guarded signal_connect(removeButton, "clicked") do args...
            remove = Ahorn.topMostAskDialog("Really remove this preset?", lastPresetWindow)

            if remove
                removeCallback()
            end
        end
    end

    @Ahorn.guarded signal_connect(finishButton, "clicked") do args...
        data, incorrectOptions = Form.getSectionsData(sections)

        if data !== nothing
            callback(data)

        else
            Ahorn.topMostInfoDialog(Form.getIncorrectOptionsMessage(incorrectOptions), window)
        end
    end

    return window
end

function spawnPresetWindow(title::String, options::Array{Form.Option, 1}, callback::Function, editProperties::Function, removeCallback::Union{Function, Nothing}, lockedPositions::Array{String, 1}=String[], buttonText::String="Add")
    #keepPreviousPosition = get(Ahorn.config, "properties_keep_previous_position", true)
    keepPreviousPosition = true

    winX, winY = 0, 0
    winScreen = nothing

    section = Form.Section("properties", options, fieldOrder=lockedPositions)
    presetWindow = createPresetFormWindow(title, section, buttonText=buttonText, callback=callback, editProperties=editProperties, removeCallback=removeCallback)
    @Ahorn.guarded signal_connect(presetWindow, :destroy) do widget
        global lastPresetWindowDestroyed = true
    end

    visible(presetWindow, false)

    if keepPreviousPosition && isa(lastPresetWindow, Gtk.GtkWindowLeaf) && !lastPresetWindowDestroyed
        winX, winY = GAccessor.position(lastPresetWindow)
        winScreen = GAccessor.screen(lastPresetWindow)

        GAccessor.position(presetWindow, winX, winY)
        GAccessor.screen(presetWindow, winScreen)
    end

    GAccessor.transient_for(presetWindow, Ahorn.window)

    showall(presetWindow)
    visible(presetWindow, true)

    if isa(lastPresetWindow, Gtk.GtkWindowLeaf)
        Gtk.destroy(lastPresetWindow)
    end

    global lastPresetWindow = presetWindow
    global lastPresetWindowDestroyed = false
end

function displayPresetEditor(basePreset::Dict{String, Any}, toolsLayer::Ahorn.Layer, editing::Bool=false)
    preset = deepcopy(basePreset)
    options = preset["options"]
    fillDefaultOptions(options)

    options["name"] = replace(options["name"], "\n" => "\\n")

    editProperties = function()
        fakeTarget = get(preset, "type", "entity") == "entity" ? Maple.Entity(preset["name"]) : Maple.Trigger(preset["name"])
        fakeTarget.data = deepcopy(preset["data"])

        relativeNodes = get(fakeTarget.data, "__nodes", nothing)
        if relativeNodes != nothing
            fakeTarget.data["nodes"] = [(p[1], p[2]) for p in relativeNodes]
            delete!(fakeTarget.data, "__nodes")
        end

        pcallback = function(data::Dict{String, Any})
            updateTarget = true

            minWidth, minHeight = Ahorn.minimumSize(fakeTarget)
            hasWidth, hasHeight = haskey(fakeTarget.data, "width"), haskey(fakeTarget.data, "height")
            width, height = Int(get(data, "width", minWidth)), Int(get(data, "height", minHeight))

            if hasWidth && width < minWidth || hasHeight && height < minHeight
                updateTarget = Ahorn.topMostAskDialog("The size specified is smaller than the recommended minimum size ($minWidth, $minHeight)\nDo you want to keep this size regardless?", Ahorn.lastPropertyWindow)
            end

            if updateTarget
                newData = deepcopy(data)

                nodes = get(newData, "nodes", nothing)
                if nodes != nothing
                    newData["__nodes"] = [[first(p), last(p)] for p in nodes]
                    delete!(newData, "nodes")
                end

                merge!(preset["data"], newData)
                Ahorn.redrawLayer!(toolsLayer)
            end

            Gtk.destroy(Ahorn.lastPropertyWindow)
        end

        propertyIgnores = Ahorn.editingIgnored(fakeTarget, false)
        propertyOptions = Ahorn.propertyOptions(fakeTarget, propertyIgnores)
        propertyOrder = Ahorn.editingOrder(fakeTarget)

        if !isempty(propertyOptions)
            Ahorn.spawnPropertyWindow("$(Ahorn.baseTitle) - Editing preset '$(preset["options"]["name"])' - Properties", propertyOptions, pcallback, propertyOrder)
        end
    end

    callback = function(data::Dict{String, Any})
        update = true
        newData = deepcopy(data)

        if isempty(newData["name"])
            newData["name"] = options["name"]
        end

        oldName = options["name"]

        if newData["name"] != oldName && get(getPresets(), newData["name"], nothing) != nothing
            update = Ahorn.topMostAskDialog("There is already a preset with this name\nDo you want to overwrite it?", lastPresetWindow)
        end

        if update
            merge!(options, newData)

            options["name"] = replace(options["name"], "\\n" => "\n")
            oldName = replace(oldName, "\\n" => "\n")

            presets = getPresets()
            placements = preset["type"] == "entity" ? Ahorn.entityPlacements : Ahorn.triggerPlacements

            if options["name"] != oldName
                delete!(presets, oldName)
                delete!(placements, oldName)
            end
            presets[options["name"]] = preset
            placements[options["name"]] = createPlacement(preset)

            Ahorn.redrawLayer!(toolsLayer)
        end

        Gtk.destroy(lastPresetWindow)
        Ahorn.eventToModule(Ahorn.currentTool, "updateMaterials!")
    end

    removeCallback = nothing
    if editing
        removeCallback = function()
            presets = getPresets()
            placements = preset["type"] == "entity" ? Ahorn.entityPlacements : Ahorn.triggerPlacements

            coolerName = replace(options["name"], "\\n" => "\n")

            delete!(presets, coolerName)
            delete!(placements, coolerName)

            Ahorn.redrawLayer!(toolsLayer)

            Gtk.destroy(lastPresetWindow)
            Ahorn.eventToModule(Ahorn.currentTool, "updateMaterials!")
        end
    end

    buttonText = editing ? "Update" : "Add"

    spawnPresetWindow("$(Ahorn.baseTitle) - Editing preset '$(preset["options"]["name"])'", presetFormOptions(preset), callback, editProperties, removeCallback, presetOptionsOrder(), buttonText)
end

function presetFormOptions(preset::Dict{String, Any})
    res = Form.Option[]

    for (option, value) in preset["options"]
       push!(res, Form.suggestOption(Ahorn.humanizeVariableName(option), value, dataName=option, editable=true)) 
    end

    return res
end

function presetOptionsOrder()
    return String["name"]
end

# Load saved presets

function initPresets()
    savedPresets = getPresets()
    for (name, preset) in savedPresets
        placements = get(preset, "type", "entity") == "entity" ? Ahorn.entityPlacements : Ahorn.triggerPlacements
        placements[preset["options"]["name"]] = createPlacement(preset)
    end
end

end