---A cross-platform terminal ui library based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-2020, TBOOX Open Source Group.
--
-- @author      ruki
-- @file        choicebox.lua
--

-- load modules
local log       = require("ltui/base/log")
local view      = require("ltui/view")
local rect      = require("ltui/rect")
local panel     = require("ltui/panel")
local event     = require("ltui/event")
local action    = require("ltui/action")
local curses    = require("ltui/curses")
local button    = require("ltui/button")
local object    = require("ltui/object")

-- define module
local choicebox = choicebox or panel()

-- init choicebox
function choicebox:init(name, bounds)

    -- init panel
    panel.init(self, name, bounds)

    -- init items
    self._ITEMS = {}

    -- init start index
    self._STARTINDEX = 1
end

-- load values
function choicebox:load(values, selected)

    -- clear the views first
    self:clear()

    -- reset start index
    self._STARTINDEX = 1

    -- load items
    local items = {}
    for idx, value in ipairs(values) do
        table.insert(items, self:_load_item(value, idx, idx == selected))
    end
    self._ITEMS = items

    -- insert top-n items
    local startindex = self._STARTINDEX
    for idx = startindex, startindex + self:height() - 1 do
        local item = items[idx]
        if item then
            self:insert(item)
        else
            break
        end
    end

    -- select the first item
    self:select(self:first())

    -- on loaded
    self:action_on(action.ac_on_load)

    -- invalidate
    self:invalidate()
end

-- is scrollable?
function choicebox:scrollable()
    return #self:_items() > self:height()
end

-- scroll
function choicebox:scroll(count)
    if self:scrollable() then
        local items = self:_items()
        local totalcount = #items
        local startindex = self._STARTINDEX + count
        if startindex > totalcount then
            return
        elseif startindex < 1 then
            startindex = 1
        end
        self._STARTINDEX = startindex
        self:clear()
        for idx = startindex, startindex + self:height() - 1 do
            local item = items[idx]
            if item then
                item:bounds():move2(0, idx - startindex)
                self:insert(item)
            else
                break
            end
        end
        if count > 0 then
            self:select(self:first())
        else
            self:select(self:last())
        end
        self:invalidate()
    end
end

-- on resize
function choicebox:on_resize()
    local items = self:_items()
    local totalcount = #items
    local startindex = self._STARTINDEX
    for idx = 1, totalcount do
        local item = items[idx]
        if item then
            if idx >= startindex and idx < startindex + self:height() then
                if not self:view(item:name()) then
                    item:bounds():move2(0, idx - startindex)
                    self:insert(item)
                end
            else
                if self:view(item:name()) then
                    self:remove(item)
                end
            end
        end
    end
    panel.on_resize(self)
end

-- on event
function choicebox:on_event(e)
    if e.type == event.ev_keyboard then
        if e.key_name == "Down" then
            if self:current() == self:last() then
                self:scroll(self:height())
            else
                self:select_next()
            end
            self:_notify_scrolled()
            return true
        elseif e.key_name == "Up" then
            if self:current() == self:first() then
                self:scroll(-self:height())
            else
                self:select_prev()
            end
            self:_notify_scrolled()
            return true
        elseif e.key_name == "PageDown" or e.key_name == "PageUp" then
            local direction = e.key_name == "PageDown" and 1 or -1
            self:scroll(self:height() * direction)
            self:_notify_scrolled()
            return true
        elseif e.key_name == "Enter" or e.key_name == " " then
            self:_do_select()
            return true
        end
    elseif e.type == event.ev_command and e.command == "cm_enter" then
        self:_do_select()
        return true
    end
end

-- load a item with value
function choicebox:_load_item(value, index, selected)

    -- init text
    local text = (selected and "(X) " or "( ) ") .. tostring(value)

    -- init a value item view
    local item = button:new("choicebox.value." .. index,
                    rect:new(0, index - 1, self:width(), 1),
                    text,
                    function (v, e)
                        self:_do_select()
                    end)

    -- attach index and value
    item:extra_set("index", index)
    item:extra_set("value", value)
    return item
end

-- notify scrolled
function choicebox:_notify_scrolled()
    local totalcount = #self:_items()
    local startindex = self:current():extra("index")
    self:action_on(action.ac_on_scrolled, startindex / totalcount)
end

-- get all items
function choicebox:_items()
    return self._ITEMS
end

-- do select the current config
function choicebox:_do_select()

    -- clear selected text
    for v in self:views() do
        local text = v:text()
        if text and text:startswith("(X) ") then
            local t = v:extra("value")
            v:text_set("( ) " .. tostring(t))
        end
    end

    -- get the current item
    local item = self:current()

    -- do action: on selected
    local index = item:extra("index")
    local value = item:extra("value")
    self:action_on(action.ac_on_selected, index, value)

    -- update text
    item:text_set("(X) " .. tostring(value))
end

-- return module
return choicebox
