#include "vertical_menu.h"
#include "sprite_data.h"
#include "global_frame_controller.h"
#include "text_engine.h"
#include "translated_text.h"

#define TILE_HEIGHT 8
#define TILE_WIDTH 8

i_item_widget::~i_item_widget()
{
}

i_vertical_menu_state_changed_handler::~i_vertical_menu_state_changed_handler()
{
}

i_run_cycle_handler::~i_run_cycle_handler()
{
}

static unsigned get_num_visible_items(unsigned vertical_menu_height, unsigned margin_top, unsigned margin_bottom, unsigned item_height)
{
    return ((vertical_menu_height - margin_top - margin_bottom) / item_height);
}

static unsigned get_viewport_end_index(unsigned viewport_start_index, unsigned num_visible_items, unsigned total_items)
{
    unsigned end_index = viewport_start_index + num_visible_items;
    // bounds check to make sure we don't go past the total number of items
    end_index = std::min(end_index, total_items);

    return end_index;
}

vertical_menu::vertical_menu(const vertical_menu_settings &settings)
    : settings_(settings)
    , state_changed_handler_(nullptr)
    , run_cycle_handler_(nullptr)
    , focused_index_(settings.initial_focus_index)
    , viewport_start_index_(0)
    , items_()
    , is_focused_(true)
{
}

vertical_menu::~vertical_menu()
{
    clear_item_widgets();
}

void vertical_menu::show()
{
    add_menu_box(settings_.x / TILE_WIDTH, settings_.y / TILE_HEIGHT, settings_.width, settings_.height);

    update_viewport();

    if(state_changed_handler_)
    {
        state_changed_handler_->on_show();
    }

    // initial selection change notification
    const unsigned render_index = focused_index_ - viewport_start_index_;
    handle_selection_change(focused_index_, settings_.x, settings_.y + settings_.margin_top + (render_index * settings_.item_height));
}

i_item_widget* vertical_menu::get_item_widget_at(unsigned index) const
{
    if(index >= items_.size())
    {
        return nullptr;
    }
    return items_[index];
}

void vertical_menu::hide()
{
    if(state_changed_handler_)
    {
        state_changed_handler_->on_hide();
    }

    clear_viewport();

    reload_textbox_background();
}

const vertical_menu_settings& vertical_menu::get_settings() const
{
    return settings_;
}

void vertical_menu::set_settings(const vertical_menu_settings &settings)
{
    settings_ = settings;
}

void vertical_menu::add_item_widgets(i_item_widget **item_widgets, unsigned num_widgets)
{
    items_.reserve(items_.size() + num_widgets);
    for (unsigned i = 0; i < num_widgets; ++i)
    {
        add_item_widget(item_widgets[i]);
    }
}

void vertical_menu::add_item_widget(i_item_widget *item_widget)
{
    items_.push_back(item_widget);
}

void vertical_menu::clear_item_widgets()
{
    if (settings_.should_delete_item_widgets_on_destruct)
    {
        for (unsigned i = 0; i < items_.size(); ++i)
        {
            delete items_[i];
        }
    }
    items_.clear();
    focused_index_ = settings_.initial_focus_index;
    viewport_start_index_ = 0;
}

void vertical_menu::set_focused(bool is_focused)
{
    is_focused_ = is_focused;
    if(state_changed_handler_ && settings_.should_hide_state_changed_handler_on_not_focused)
    {
        if(is_focused_)
        {
            state_changed_handler_->on_show();
        }
        else
        {
            state_changed_handler_->on_hide();
        }
    }
}

MenuInputHandleState vertical_menu::handle_input()
{
    MenuInputHandleState result;
    bool did_navigate = false;

    // If no items or not focused, there's nothing to handle.
    if(items_.size() <= 1 || !is_focused_)
    {
        return MenuInputHandleState::NOT_HANDLED;
    }

    // the focused item widget gets the first chance to handle input, 
    // since it might have some special behavior for certain keys.
    result = items_[focused_index_]->handle_input();
    if(result == MenuInputHandleState::HANDLED_UPDATE_VIEWPORT)
    {
        // the child widget is requesting that we update the viewport.
        //  so we do that here and then degrade the result to HANDLED, 
        // since we've already done the viewport update that the child widget requested.
        update_viewport();
        result = MenuInputHandleState::HANDLED; // degrade to HANDLED after updating the viewport
    }
    if(result != MenuInputHandleState::NOT_HANDLED)
    {
        return result;
    }

    // if the user presses B, and the current settings allow it,
    // we will return the CANCELLED state, which the caller can use to know that they should exit the menu.
    if(settings_.allow_cancel && key_hit(KEY_B))
    {
        return MenuInputHandleState::CANCELLED;
    }

    if(key_hit(KEY_DOWN) && focused_index_ < (items_.size() - 1u))
    {
        const unsigned current_viewport_end_index = get_viewport_end_index(viewport_start_index_, get_num_visible_items(settings_.height, settings_.margin_top, settings_.margin_bottom, settings_.item_height), items_.size());
        ++focused_index_;
        if(focused_index_ >= current_viewport_end_index)
        {
            ++viewport_start_index_;
        }
        did_navigate = true;
    }
    else if(key_hit(KEY_UP) && focused_index_ > 0)
    {
        --focused_index_;
        if(focused_index_ < viewport_start_index_)
        {
            --viewport_start_index_;
        }
        did_navigate = true;
    }

    // if we did navigate, we need to update the viewport and state changed handler.
    if(did_navigate)
    {
        const unsigned render_index = focused_index_ - viewport_start_index_;
        update_viewport();
        handle_selection_change(focused_index_, settings_.x, settings_.y + settings_.margin_top + (render_index * settings_.item_height));
        return MenuInputHandleState::HANDLED;
    }

    return MenuInputHandleState::NOT_HANDLED;
}

void vertical_menu::update_viewport()
{
    uint8_t decompression_buffer[2048];
    text_data_table text_table(decompression_buffer);

    clear_viewport();

    if(settings_.text_table_index != INT32_MAX)
    {
        text_table.decompress(get_compressed_text_table(settings_.text_table_index));
    }

    const unsigned num_visible_items = get_num_visible_items(settings_.height, settings_.margin_top, settings_.margin_bottom, settings_.item_height);
    const unsigned viewport_end_index = get_viewport_end_index(viewport_start_index_, num_visible_items, items_.size());
    unsigned cur_y = settings_.y + settings_.margin_top;

    for (unsigned i = viewport_start_index_; i < viewport_end_index; ++i)
    {
        items_[i]->render_item(text_table, settings_.x, cur_y, (i == focused_index_));
        cur_y += settings_.item_height;
    }
}

void vertical_menu::clear_viewport()
{
    // tte_erase_rect expects (left, top, right, bottom), not (x, y, width, height)
    tte_erase_rect(settings_.x, settings_.y, settings_.x + settings_.width, settings_.y + settings_.height);
}

unsigned vertical_menu::run()
{
    MenuInputHandleState input_result;

    while(true)
    {
        key_poll(); // Reset the buttons

        input_result = handle_input();

        switch(input_result)
        {
            case MenuInputHandleState::CHOICE_MADE:
                return focused_index_;
            case MenuInputHandleState::CANCELLED:
                return UINT32_MAX;
            default:
                break;
        }

        // if any global elements need to be updated
        // on every cycle (animations, for example)
        // the run_cycle_handler can be used for that.
        if(run_cycle_handler_)
        {
            run_cycle_handler_->on_run_cycle();
        }

        global_next_frame();
    }
    // should never happen
    return UINT32_MAX;
}

void vertical_menu::set_state_changed_handler(i_vertical_menu_state_changed_handler *handler)
{
    state_changed_handler_ = handler;
}

void vertical_menu::set_run_cycle_handler(i_run_cycle_handler *handler)
{
    run_cycle_handler_ = handler;
}

void vertical_menu::handle_selection_change(unsigned new_index, unsigned x, unsigned y)
{
    if(state_changed_handler_)
    {
        state_changed_handler_->on_selection_changed(new_index, x, y);
    }
}

simple_item_renderer::simple_item_renderer(const simple_item_widget_data &data)
    : data_(data)
{
}

simple_item_renderer::~simple_item_renderer()
{
}

const simple_item_widget_data& simple_item_renderer::get_data() const
{
    return data_;
}

void simple_item_renderer::render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused)
{
    tte_set_pos(x + data_.text.margin_left, y + data_.text.margin_top);
    ptgb_write_simple(text_table.get_text_entry(data_.text.text_table_index), true);
}

MenuInputHandleState simple_item_renderer::handle_input()
{
    if(!key_hit(KEY_A))
    {
        return MenuInputHandleState::NOT_HANDLED;
    }

    if(data_.on_execute_callback)
    {
        data_.on_execute_callback(this);
        return MenuInputHandleState::HANDLED;
    }
    return MenuInputHandleState::CHOICE_MADE;
}