--[[-- Core Module - Gui
    @module Gui
    @alias Prototype
]]

--- Checkboxs.
-- Gui class define for checkbox and radiobuttons
-- @section checkboxs

--[[
>>>> Using an option set
    An option set is a set of radio buttons where only one of them can be active at a time, this means that when one
    is clicked all the other ones are set to false, an option set must be defined before hand and will always store
    its state but is not limited by how it can categorize the store.

    First you must register the store with a name and a update callback, and an optional function for categorize:

    local example_option_set =
    Gui.new_option_set('example-option-set',function(value,category)
        game.print('Example options set '..category..' is now: '..tostring(value))
    end,Gui.categorize_by_player)

    Then you must register some radiobutton defines and include them in the option set:

    local example_option_one =
    Gui.new_radiobutton()
    :set_caption('Option One')
    :add_as_option(example_option_set,'One')

    local example_option_two =
    Gui.new_radiobutton()
    :set_caption('Option Two')
    :add_as_option(example_option_set,'Two')

    Note that these radiobuttons can still have on_element_update events but this may result in a double trigger of events as
    the option set update is always triggered; also add_store cant be used as the option set acts as the store however get
    and set store will still work but will effect the option set rather than the individual radiobuttons.

>>>> Functions
    Checkbox.new_checkbox(name) --- Creates a new checkbox element define
    Checkbox._prototype_checkbox:on_element_update(callback) --- Registers a handler for when an element instance updates
    Checkbox._prototype_checkbox:on_store_update(callback) --- Registers a handler for when the stored value updates

    Checkbox.new_radiobutton(name) --- Creates a new radiobutton element define
    Checkbox._prototype_radiobutton:on_element_update(callback) --- Registers a handler for when an element instance updates
    Checkbox._prototype_radiobutton:on_store_update(callback) --- Registers a handler for when the stored value updates
    Checkbox._prototype_radiobutton:add_as_option(option_set,option_name) --- Adds this radiobutton to be an option in the given option set (only one can be true at a time)

    Checkbox.new_option_set(name,callback,categorize) --- Registers a new option set that can be linked to radiobutton (only one can be true at a time)
    Checkbox.draw_option_set(name,element) --- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)

    Checkbox.reset_radiobutton(element,exclude,recursive) --- Sets all radiobutton in a element to false (unless excluded) and can act recursively

    Other functions present from expcore.gui.core
]]
local Gui = require 'expcore.gui.core' --- @dep expcore.gui.core
local Prototype = require 'expcore.gui.prototype' --- @dep expcore.gui.prototype
local Store = require 'expcore.store' --- @dep expcore.store
local Game = require 'utils.game' --- @dep utils.game

--- Store call for store update
-- @tparam table define the define that this is acting on
-- @tparam LuaGuiElement element the element that triggered the event
-- @tparam boolean value the new state of the checkbox
local function store_update(define,element,value)
    element.state = value
    local player = Game.get_player_by_index(element.player_index)
    define:raise_event('on_element_update',player,element,value)
end

local Checkbox = {
    option_sets={},
    option_categorize={},
    _prototype_checkbox=Prototype.extend{
        on_element_update = Prototype.event,
        on_store_update = Prototype.event,
        add_store = Prototype.store(false,store_update),
        add_sync_store = Prototype.store(true,store_update)
    },
    _prototype_radiobutton=Prototype.extend{
        on_element_update = Prototype.event,
        on_store_update = Prototype.event,
        add_store = Prototype.store(false,store_update),
        add_sync_store = Prototype.store(true,store_update)
    }
}

--- Creates a new checkbox element define
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new checkbox element define
function Checkbox.new_checkbox(name)

    local self = Gui.new_define(Checkbox._prototype_checkbox,name)
    self.draw_data.type = 'checkbox'
    self.draw_data.state = false

    self:on_draw(function(player,element)
        if self.store then
            local category = self.categorize and self.categorize(element) or nil
            local state = self:get_store(category,true)
            if state then element.state = true end
        end
    end)

    Gui.on_checked_state_changed(self.name,function(event)
        local element = event.element

        if self.option_set then
            local value = Checkbox.option_sets[self.option_set][element.name]
            local category = self.categorize and self.categorize(element)
            self:set_store(category,value)

        elseif self.store then
            local value = element.state
            local category = self.categorize and self.categorize(element)
            self:set_store(category,value)

        else
            self:raise_event('on_element_update',event.player,element,element.state)

        end
    end)

    return self
end

--- Creates a new radiobutton element define, has all functions checkbox has
-- @tparam[opt] string name the optional debug name that can be added
-- @treturn table the new button element define
function Checkbox.new_radiobutton(name)
    local self = Checkbox.new_checkbox(name)
    self.draw_data.type = 'radiobutton'

    local mt = getmetatable(self)
    mt.__index = Checkbox._prototype_radiobutton

    return self
end

--- Adds this radiobutton to be an option in the given option set (only one can be true at a time)
-- @tparam string option_set the name of the option set to add this element to
-- @tparam string option_name the name of this option that will be used to identify it
-- @treturn self the define to allow chaining
function Checkbox._prototype_radiobutton:add_as_option(option_set,option_name)
    self.option_set = option_set
    self.option_name = option_name or self.name

    Checkbox.option_sets[option_set][self.option_name] = self.name
    Checkbox.option_sets[option_set][self.name] = self.option_name

    self:add_store(Checkbox.option_categorize[option_set])

    return self
end

--- Gets the stored value of the radiobutton or the option set if present
-- @tparam string category[opt] the category to get such as player name or force name
-- @tparam boolean internal used to prevent stackover flow
-- @treturn any the value that is stored for this define
function Checkbox._prototype_radiobutton:get_store(category,internal)
    if not self.store then return end
    local location = not internal and self.option_set or self.store
    return Store.get(location,category)
end

--- Sets the stored value of the radiobutton or the option set if present
-- @tparam string category[opt] the category to get such as player name or force name
-- @tparam boolean value the value to set for this define, must be valid for its type ie for checkbox etc
-- @tparam boolean internal used to prevent stackover flow
-- @treturn boolean true if the value was set
function Checkbox._prototype_radiobutton:set_store(category,value,internal)
    if not self.store then return end
    local location = not internal and self.option_set or self.store
    return Store.set(location,category,value)
end

--- Registers a new option set that can be linked to radiobuttons (only one can be true at a time)
-- @tparam string name the name of the option set, must be unique
-- @tparam function callback the update callback when the value of the option set changes
-- callback param - value string - the new selected option for this option set
-- callback param - category string - the category that updated if categorize was used
-- @tparam function categorize the function used to convert an element into a string
-- @treturn string the name of this option set to be passed to add_as_option
function Checkbox.new_option_set(name,callback,categorize)

    Store.register(name,function(value,category)
        local options = Checkbox.option_sets[name]
        for opt_name,define_name in pairs(options) do
            if Gui.defines[define_name] then
                local define = Gui.get_define(define_name)
                local state = opt_name == value
                define:set_store(category,state,true)
            end
        end
        callback(value,category)
    end)

    Checkbox.option_categorize[name] = categorize
    Checkbox.option_sets[name] = {}

    return name
end

--- Draws all radiobuttons that are part of an option set at once (Gui.draw will not work)
-- @tparam string name the name of the option set to draw the radiobuttons of
-- @tparam LuaGuiElement element the parent element that the radiobuttons will be drawn to
function Checkbox.draw_option_set(name,element)
    if not Checkbox.option_sets[name] then return end
    local options = Checkbox.option_sets[name]

    for _,option in pairs(options) do
        if Gui.defines[option] then
            Gui.defines[option]:draw_to(element)
        end
    end

end

--- Sets all radiobutton in a element to false (unless excluded) and can act recursively
-- @tparam LuaGuiElement element the root gui element to start setting radio buttons from
-- @tparam[opt] table exclude ?string|table the name of the radiobutton to exclude or a of radiobuttons where true will set the state true
-- @tparam[opt=false] ?number|boolean recursive if true will recur as much as possible, if a will recur that number of times
-- @treturn boolean true if successful
function Checkbox.reset_radiobuttons(element,exclude,recursive)
    if not element or not element.valid then return end
    exclude = type(exclude) == 'table' and exclude or exclude ~= nil and {[exclude]=true} or {}
    recursive = type(recursive) == 'number' and recursive-1 or recursive

    for _,child in pairs(element.children) do
        if child and child.valid and child.type == 'radiobutton' then
            local state = exclude[child.name] or false
            local define = Gui.defines[child.name]

            if define then
                local category = define.categorize and define.categorize(child) or state
                define:set_store(category,state)

            else
                child.state = state

            end

        elseif child.children and (type(recursive) == 'number' and recursive >= 0 or recursive == true) then
            Checkbox.reset_radiobutton(child,exclude,recursive)

        end
    end

    return true
end

return Checkbox