// SPDX-License-Identifier: MIT
//
// Copyright (c) 2008-2022 Antonio Niño Díaz
//
// This file is part of Nitro Engine

#include "NEMain.h"

/// @file NEGUI.c

extern NE_Input ne_input;

static NE_GUIObj **NE_guipointers;
static int NE_GUI_OBJECTS;
static bool ne_gui_system_inited = false;

typedef struct {
    int x1, y1, x2, y2;
    int event; // 0 = nothing, 1 = just pressed, 2 = held, 3 = just released
    NE_Material *tex_1, *tex_2;
    // Colors when button isn't pressed / is pressed. The default is white
    u32 color1, color2;
    u32 alpha1, alpha2;
} ne_button_t;

typedef struct {
    int x1, y1, x2, y2;
    int event;
    bool checked;
    NE_Material *tex_1, *tex_2;
    u32 color1, color2;
    u32 alpha1, alpha2;
} ne_checkbox_t;

typedef struct {
    int x1, y1, x2, y2;
    int event;
    bool checked;
    int group;
    NE_Material *tex_1, *tex_2;
    u32 color1, color2;
    u32 alpha1, alpha2;
} ne_radiobutton_t;

typedef struct {
    int x1, y1, x2, y2;
    int event_minus, event_plus, event_bar;
    int value;
    int range, desp; //range = max - min; value + desp = real value
    bool isvertical;
    NE_Material *texbtn, *texbar, *texlong;
    int totalsize, barsize;
    int coord; // helper to avoid some frequent operations
    // 1,2 used for buttons, barcolor used for background of slidebar
    u32 color1, color2, barcolor;
    u32 alpha1, alpha2, baralpha;
} ne_slidebar_t;

// Internal use
static void NE_ResetRadioButtonGroup(int group)
{
    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] == NULL)
            continue;

        if (NE_guipointers[i]->type != NE_RadioButton)
            continue;

        ne_radiobutton_t *rabtn = (void *)NE_guipointers[i]->pointer;

        if (rabtn->group == group)
            rabtn->checked = false;
    }
}

static void NE_GUIUpdateButton(NE_GUIObj *obj)
{
    ne_button_t *button = (void *)obj;

    if (button->x1 < ne_input.touch.px && button->x2 > ne_input.touch.px
     && button->y1 < ne_input.touch.py && button->y2 > ne_input.touch.py)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            button->event = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (button->event == 1 || button->event == 2))
        {
            button->event = 2;
        }
        else if (ne_input.kup & KEY_TOUCH && button->event == 2)
        {
            button->event = 4;
        }
        else
        {
            button->event = 0;
        }
    }
    else
    {
        button->event = 0;
    }
}

static void NE_GUIUpdateCheckBox(NE_GUIObj *obj)
{
    ne_checkbox_t *chbox = (void *)obj;

    if (chbox->x1 < ne_input.touch.px && chbox->x2 > ne_input.touch.px
     && chbox->y1 < ne_input.touch.py && chbox->y2 > ne_input.touch.py)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            chbox->event = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (chbox->event == 1 || chbox->event == 2))
        {
            chbox->event = 2;
        }
        else if (ne_input.kup & KEY_TOUCH && chbox->event == 2)
        {
            chbox->event = 4;
            chbox->checked = !chbox->checked;
        }
        else
        {
            chbox->event = 0;
        }
    }
    else
    {
        chbox->event = 0;
    }
}

static void NE_GUIUpdateRadioButton(NE_GUIObj *obj)
{
    ne_radiobutton_t *rabtn = (void *)obj;

    if (rabtn->x1 < ne_input.touch.px && rabtn->x2 > ne_input.touch.px
     && rabtn->y1 < ne_input.touch.py && rabtn->y2 > ne_input.touch.py)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            rabtn->event = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (rabtn->event == 1 || rabtn->event == 2))
        {
            rabtn->event = 2;
        }
        else if (ne_input.kup & KEY_TOUCH && rabtn->event == 2)
        {
            rabtn->event = 4;
            NE_ResetRadioButtonGroup(rabtn->group);
            rabtn->checked = true;
        }
        else
        {
            rabtn->event = 0;
        }
    }
    else
    {
        rabtn->event = 0;
    }
}

static void NE_GUIUpdateSlideBar(NE_GUIObj *obj)
{
    ne_slidebar_t *sldbar = (void *)obj;

    // Simplify code...
    int x1 = sldbar->x1, x2 = sldbar->x2;
    int y1 = sldbar->y1, y2 = sldbar->y2;
    int px = ne_input.touch.px, py = ne_input.touch.py;
    bool vertical = sldbar->isvertical;
    int coord = sldbar->coord, barsize = sldbar->barsize;
    int tmp1, tmp2; // auxiliary coordinates
    if (sldbar->isvertical)
    {
        tmp1 = y1 + (x2 - x1);
        tmp2 = y2 - (x2 - x1);
    }
    else
    {
        tmp1 = x1 + (y2 - y1);
        tmp2 = x2 - (y2 - y1);
    }

    // Plus button
    // -----------

    bool pluspressed;

    if (vertical)
        pluspressed = x1 < px && x2 > px && tmp2 < py && y2 > py;
    else
        pluspressed = tmp2 < px && x2 > px && y1 < py && y2 > py;

    if (pluspressed)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            sldbar->event_plus = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (sldbar->event_plus == 1 || sldbar->event_plus == 2))
        {
            sldbar->event_plus = 2;
            sldbar->value++;
        }
        else if (ne_input.kup & KEY_TOUCH && sldbar->event_plus == 2)
        {
            sldbar->event_plus = 4;
        }
        else
        {
            sldbar->event_plus = 0;
        }
    }
    else
    {
        sldbar->event_plus = 0;
    }

    // Minus button
    // ------------

    bool minuspressed;

    if (vertical)
        minuspressed = x1 < px && x2 > px && y1 < py && tmp1 > py;
    else
        minuspressed = x1 < px && tmp1 > px && y1 < py && y2 > py;

    if (minuspressed)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            sldbar->event_minus = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (sldbar->event_minus == 1 || sldbar->event_minus == 2))
        {
            sldbar->event_minus = 2;
            sldbar->value--;
        }
        else if ((ne_input.kup & KEY_TOUCH) && (sldbar->event_minus == 2))
        {
            sldbar->event_minus = 4;
        }
        else
        {
            sldbar->event_minus = 0;
        }
    }
    else
    {
        sldbar->event_minus = 0;
    }

    // Bar button
    // ----------

    if (sldbar->event_bar == 2)
    {
        int tmp = ((vertical) ? py : px) - tmp1 - (barsize >> 1);
        tmp *= sldbar->range;
        tmp = divf32(tmp << 12, (tmp2 - tmp1 - barsize) << 12) >> 12;
        sldbar->value = tmp;
    }

    sldbar->value = (sldbar->value > sldbar->range) ? sldbar->range : sldbar->value;
    sldbar->value = (sldbar->value < 0) ? 0 : sldbar->value;

    sldbar->coord = (sldbar->totalsize - barsize) * sldbar->value;
    sldbar->coord = divf32(sldbar->coord << 12, sldbar->range << 12) >> 12;
    sldbar->coord += (vertical) ? y1 + (x2 - x1) : x1 + (y2 - y1);
    coord = sldbar->coord;

    bool barpressed;

    if (vertical)
        barpressed = x1 < px && x2 > px && coord < py && (coord + barsize) > py;
    else
        barpressed = y1 < py && y2 > py && coord < px && (coord + barsize) > px;

    if (barpressed)
    {
        if (ne_input.kdown & KEY_TOUCH)
        {
            sldbar->event_bar = 1;
        }
        else if ((ne_input.kheld & KEY_TOUCH)
              && (sldbar->event_bar == 1 || sldbar->event_bar == 2))
        {
            sldbar->event_bar = 2;
        }
        else if (ne_input.kup & KEY_TOUCH && sldbar->event_bar == 2)
        {
            sldbar->event_bar = 4;
        }
        else
        {
            sldbar->event_bar = 0;
        }
    }
    else
    {
        sldbar->event_bar = 0;
    }
}

void NE_GUIUpdate(void)
{
    if (!ne_gui_system_inited)
        return;

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] == NULL)
            continue;

        NE_GUITypes type = NE_guipointers[i]->type;

        if (type == NE_Button)
            NE_GUIUpdateButton(NE_guipointers[i]->pointer);
        else if (type == NE_CheckBox)
            NE_GUIUpdateCheckBox(NE_guipointers[i]->pointer);
        else if (type == NE_RadioButton)
            NE_GUIUpdateRadioButton(NE_guipointers[i]->pointer);
        else if (type == NE_SlideBar)
            NE_GUIUpdateSlideBar(NE_guipointers[i]->pointer);
        else
            NE_DebugPrint("Unknown GUI object type: %d", type);
    }
}

static void NE_GUIDrawButton(NE_GUIObj *obj, int priority)
{
    ne_button_t *button = (void *)obj;
    NE_Material *tex;
    u32 color;

    if (button->event > 0)
    {
        // Pressed
        GFX_POLY_FORMAT = POLY_ALPHA(button->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        tex = button->tex_2;
        color = button->color2;
    }
    else
    {
        // Not-pressed
        GFX_POLY_FORMAT = POLY_ALPHA(button->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        tex = button->tex_1;
        color = button->color1;
    }

    if (tex == NULL)
    {
        NE_2DDrawQuad(button->x1, button->y1, button->x2, button->y2,
                      priority, color);
    }
    else
    {
        NE_2DDrawTexturedQuadColor(button->x1, button->y1, button->x2,
                                   button->y2, priority, tex, color);
    }
}

static void NE_GUIDrawCheckBox(NE_GUIObj *obj, int priority)
{
    ne_checkbox_t *chbox = (void *)obj;
    u32 color;

    if (chbox->event > 0)
    {
        GFX_POLY_FORMAT = POLY_ALPHA(chbox->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = chbox->color2;
    }
    else
    {
        GFX_POLY_FORMAT = POLY_ALPHA(chbox->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = chbox->color1;
    }

    NE_Material *tex = chbox->checked ? chbox->tex_2 : chbox->tex_1;

    if (tex == NULL)
    {
        NE_2DDrawQuad(chbox->x1, chbox->y1, chbox->x2, chbox->y2,
                      priority, color);
    }
    else
    {
        NE_2DDrawTexturedQuadColor(chbox->x1, chbox->y1, chbox->x2,
                                   chbox->y2, priority, tex, color);
    }
}

static void NE_GUIDrawRadioButton(NE_GUIObj *obj, int priority)
{
    ne_radiobutton_t *rabtn = (void *)obj;
    u32 color;

    if (rabtn->event > 0)
    {
        GFX_POLY_FORMAT = POLY_ALPHA(rabtn->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = rabtn->color2;
    }
    else
    {
        GFX_POLY_FORMAT = POLY_ALPHA(rabtn->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = rabtn->color1;
    }

    NE_Material *tex = rabtn->checked ?  rabtn->tex_2 : rabtn->tex_1;

    if (tex == NULL)
    {
        NE_2DDrawQuad(rabtn->x1, rabtn->y1, rabtn->x2, rabtn->y2,
                  priority, color);
    }
    else
    {
        NE_2DDrawTexturedQuadColor(rabtn->x1, rabtn->y1, rabtn->x2,
                       rabtn->y2, priority, tex, color);
    }
}

static void NE_GUIDrawSlideBar(NE_GUIObj *obj, int priority)
{
    ne_slidebar_t *sldbar = (void *)obj;
    u32 color;

    // Helper variables

    int x1 = sldbar->x1, x2 = sldbar->x2;
    int y1 = sldbar->y1, y2 = sldbar->y2;
    int tmp1, tmp2; // Auxiliary coordinates
    if (sldbar->isvertical)
    {
        tmp1 = y1 + (x2 - x1);
        tmp2 = y2 - (x2 - x1);
    }
    else
    {
        tmp1 = x1 + (y2 - y1);
        tmp2 = x2 - (y2 - y1);
    }

    // Load texture of the two buttons
    NE_Material *tex = sldbar->texbtn;

    // Plus button
    // -----------

    if (sldbar->event_plus > 0)
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color2;
    }
    else
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color1;
    }

    if (sldbar->isvertical)
    {
        if (tex == NULL)
            NE_2DDrawQuad(x1, tmp2, x2, y2, priority, color);
        else
            NE_2DDrawTexturedQuadColor(x1, tmp2, x2, y2, priority, tex, color);
    }
    else
    {
        if (tex == NULL)
            NE_2DDrawQuad(tmp2, y1, x2, y2, priority, color);
        else
            NE_2DDrawTexturedQuadColor(tmp2, y1, x2, y2, priority, tex, color);
    }

    // Minus button
    // ------------

    if (sldbar->event_minus > 0)
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color2;
    }
    else
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color1;
    }

    if (sldbar->isvertical)
    {
        if (tex == NULL)
            NE_2DDrawQuad(x1, y1, x2, tmp1, priority, color);
        else
            NE_2DDrawTexturedQuadColor(x1, y1, x2, tmp1, priority, tex, color);
    }
    else
    {
        if (tex == NULL)
            NE_2DDrawQuad(x1, y1, tmp1, y2, priority, color);
        else
            NE_2DDrawTexturedQuadColor(x1, y1, tmp1, y2, priority, tex, color);
    }

    // Bar button
    // ----------

    // Load texture of the bar button
    tex = sldbar->texbar;

    if (sldbar->event_bar > 0)
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha2)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color2;
    }
    else
    {
        GFX_POLY_FORMAT = POLY_ALPHA(sldbar->alpha1)
                        | POLY_ID(NE_GUI_POLY_ID) | NE_CULL_NONE;
        color = sldbar->color1;
    }

    if (sldbar->isvertical)
    {
        if (tex == NULL)
        {
            NE_2DDrawQuad(x1, sldbar->coord,
                          x2, sldbar->coord + sldbar->barsize,
                          priority, color);
        }
        else
        {
            NE_2DDrawTexturedQuadColor(x1, sldbar->coord,
                                       x2, sldbar->coord + sldbar->barsize,
                                       priority, tex, color);
        }
    }
    else
    {
        if (tex == NULL)
        {
            NE_2DDrawQuad(sldbar->coord, y1,
                          sldbar->coord + sldbar->barsize, y2,
                          priority, color);
        }
        else
        {
            NE_2DDrawTexturedQuadColor(sldbar->coord, y1,
                                       sldbar->coord + sldbar->barsize, y2,
                                       priority, tex, color);
        }
    }

    // Slide bar
    // ---------

    // Load texture and color of the slide bar background
    tex = sldbar->texlong;
    color = sldbar->barcolor;
    GFX_POLY_FORMAT = POLY_ALPHA(sldbar->baralpha)
                    | POLY_ID(NE_GUI_POLY_ID_ALT) | NE_CULL_NONE;

    // Now we need to use `priority + 1` as priority. The bar button must
    // be in front of bar. `priority + 1` is less priority than `priority`.

    if (sldbar->isvertical)
    {
        if (tex == NULL)
        {
            NE_2DDrawQuad(x1, tmp1, x2, tmp2, priority + 1, color);
        }
        else
        {
            NE_2DDrawTexturedQuadColor(x1, tmp1, x2, tmp2,
                                       priority + 1, tex, color);
        }
    }
    else
    {
        if (tex == NULL)
        {
            NE_2DDrawQuad(tmp1, y1, tmp2, y2, priority + 1, color);
        }
        else
        {
            NE_2DDrawTexturedQuadColor(tmp1, y1, tmp2, y2,
                                       priority + 1, tex, color);
        }
    }
}

void NE_GUIDraw(void)
{
    if (!ne_gui_system_inited)
        return;

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] == NULL)
            continue;

        NE_GUIObj *obj = NE_guipointers[i]->pointer;
        NE_GUITypes type = NE_guipointers[i]->type;
        int priority = i + NE_GUI_MIN_PRIORITY;

        if (type == NE_Button)
            NE_GUIDrawButton(obj, priority);
        else if (type == NE_CheckBox)
            NE_GUIDrawCheckBox(obj, priority);
        else if (type == NE_RadioButton)
            NE_GUIDrawRadioButton(obj, priority);
        else if (type == NE_SlideBar)
            NE_GUIDrawSlideBar(obj, priority);
        else
            NE_DebugPrint("Unknown GUI object type: %d", type);
    }
}

NE_GUIObj *NE_GUIButtonCreate(s16 x1, s16 y1, s16 x2, s16 y2)
{
    if (!ne_gui_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] != NULL)
            continue;

        ne_button_t *ptr = malloc(sizeof(ne_button_t));
        if (ptr == NULL)
        {
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i] = malloc(sizeof(NE_GUIObj));
        if (NE_guipointers[i] == NULL)
        {
            free(ptr);
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i]->pointer = (void *)ptr;
        NE_guipointers[i]->type = NE_Button;

        ptr->x1 = x1;
        ptr->y1 = y1;
        ptr->x2 = x2;
        ptr->y2 = y2;
        ptr->event = -1;
        ptr->tex_1 = ptr->tex_2 = NULL;
        ptr->color1 = ptr->color2 = NE_White;
        ptr->alpha1 = ptr->alpha2 = 31;

        return NE_guipointers[i];
    }

    NE_DebugPrint("No free slots");

    return NULL;
}

NE_GUIObj *NE_GUICheckBoxCreate(s16 x1, s16 y1, s16 x2, s16 y2, bool initialvalue)
{
    if (!ne_gui_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] != NULL)
            continue;

        ne_checkbox_t *ptr = malloc(sizeof(ne_checkbox_t));
        if (ptr == NULL)
        {
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i] = malloc(sizeof(NE_GUIObj));
        if (NE_guipointers[i] == NULL)
        {
            free(ptr);
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i]->pointer = (void *)ptr;
        NE_guipointers[i]->type = NE_CheckBox;

        ptr->x1 = x1;
        ptr->y1 = y1;
        ptr->x2 = x2;
        ptr->y2 = y2;
        ptr->event = -1;
        ptr->tex_1 = ptr->tex_2 = NULL;
        ptr->color1 = ptr->color2 = NE_White;
        ptr->alpha1 = ptr->alpha2 = 31;
        ptr->checked = initialvalue;

        return NE_guipointers[i];
    }

    NE_DebugPrint("No free slots");

    return NULL;
}

NE_GUIObj *NE_GUIRadioButtonCreate(s16 x1, s16 y1, s16 x2, s16 y2, int group,
                                   bool initialvalue)
{
    if (!ne_gui_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] != NULL)
            continue;

        ne_radiobutton_t *ptr = malloc(sizeof(ne_radiobutton_t));
        if (ptr == NULL)
        {
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i] = malloc(sizeof(NE_GUIObj));
        if (NE_guipointers[i] == NULL)
        {
            free(ptr);
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i]->pointer = (void *)ptr;
        NE_guipointers[i]->type = NE_RadioButton;

        ptr->x1 = x1;
        ptr->y1 = y1;
        ptr->x2 = x2;
        ptr->y2 = y2;
        ptr->event = -1;
        ptr->tex_1 = ptr->tex_2 = NULL;
        ptr->color1 = ptr->color2 = NE_White;
        ptr->alpha1 = ptr->alpha2 = 31;
        ptr->group = group;

        if (initialvalue)
            NE_ResetRadioButtonGroup(group);

        ptr->checked = initialvalue;
        return NE_guipointers[i];
    }

    NE_DebugPrint("No free slots");

    return NULL;
}

NE_GUIObj *NE_GUISlideBarCreate(s16 x1, s16 y1, s16 x2, s16 y2, int min,
                                int max, int initialvalue)
{
    if (!ne_gui_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] != NULL)
            continue;

        ne_slidebar_t *ptr = malloc(sizeof(ne_slidebar_t));
        if (ptr == NULL)
        {
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i] = malloc(sizeof(NE_GUIObj));
        if (NE_guipointers[i] == NULL)
        {
            free(ptr);
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_guipointers[i]->pointer = (void *)ptr;
        NE_guipointers[i]->type = NE_SlideBar;

        ptr->x1 = x1;
        ptr->y1 = y1;
        ptr->x2 = x2;
        ptr->y2 = y2;
        ptr->event_minus = ptr->event_plus = ptr->event_bar = -1;
        ptr->texbtn = ptr->texbar = ptr->texlong = NULL;
        ptr->color1 = ptr->color2 = ptr->barcolor = NE_White;
        ptr->alpha1 = ptr->alpha2 = ptr->baralpha = 31;
        ptr->value = initialvalue - min;
        ptr->desp = min;
        ptr->range = max - min;

        ptr->isvertical = (x2 - x1 > y2 - y1) ? false : true;

        if (ptr->isvertical)
            ptr->totalsize = y2 - y1 - ((x2 - x1) << 1);
        else
            ptr->totalsize = x2 - x1 - ((y2 - y1) << 1);

        ptr->barsize = 100 - ptr->range;
        ptr->barsize = (ptr->barsize < 20) ?  (20 << 12) : (ptr->barsize << 12);
        ptr->barsize = (divf32(ptr->barsize, 100 << 12) * ptr->totalsize) >> 12;

        ptr->coord = (ptr->totalsize - ptr->barsize) * ptr->value;
        ptr->coord = divf32(ptr->coord << 12, ptr->range << 12) >> 12;
        ptr->coord += (ptr->isvertical) ?
                            ptr->y1 + (ptr->x2 - ptr->x1) :
                            ptr->x1 + (ptr->y2 - ptr->y1);

        return NE_guipointers[i];
    }

    NE_DebugPrint("No free slots");

    return NULL;
}

void NE_GUIButtonConfig(NE_GUIObj *btn, NE_Material *material, u32 color,
                        u32 alpha, NE_Material *pressedmaterial,
                        u32 pressedcolor, u32 pressedalpha)
{
    NE_AssertPointer(btn, "NULL pointer");
    NE_Assert(btn->type == NE_Button, "Not a button");

    ne_button_t *button = btn->pointer;

    button->tex_1 = material;
    button->tex_2 = pressedmaterial;
    button->color1 = color;
    button->color2 = pressedcolor;
    button->alpha1 = alpha;
    button->alpha2 = pressedalpha;
}

void NE_GUICheckBoxConfig(NE_GUIObj *chbx, NE_Material *materialtrue,
                          NE_Material *materialfalse, u32 color, u32 alpha,
                          u32 pressedcolor, u32 pressedalpha)
{
    NE_AssertPointer(chbx, "NULL pointer");
    NE_Assert(chbx->type == NE_CheckBox, "Not a check box");

    ne_checkbox_t *checkbox = chbx->pointer;

    checkbox->tex_1 = materialfalse;
    checkbox->tex_2 = materialtrue;
    checkbox->color1 = color;
    checkbox->color2 = pressedcolor;
    checkbox->alpha1 = alpha;
    checkbox->alpha2 = pressedalpha;
}

void NE_GUIRadioButtonConfig(NE_GUIObj *rdbtn, NE_Material *materialtrue,
                             NE_Material *materialfalse, u32 color, u32 alpha,
                             u32 pressedcolor, u32 pressedalpha)
{
    NE_AssertPointer(rdbtn, "NULL pointer");
    NE_Assert(rdbtn->type == NE_RadioButton, "Not a radio button");

    ne_radiobutton_t *radiobutton = rdbtn->pointer;

    radiobutton->tex_1 = materialfalse;
    radiobutton->tex_2 = materialtrue;
    radiobutton->color1 = color;
    radiobutton->color2 = pressedcolor;
    radiobutton->alpha1 = alpha;
    radiobutton->alpha2 = pressedalpha;
}

void NE_GUISlideBarConfig(NE_GUIObj *sldbar, NE_Material *matbtn,
                          NE_Material *matbarbtn, NE_Material *matbar,
                          u32 normalcolor, u32 pressedcolor, u32 barcolor,
                          u32 alpha, u32 pressedalpha, u32 baralpha)
{
    NE_AssertPointer(sldbar, "NULL pointer");
    NE_Assert(sldbar->type == NE_SlideBar, "Not a slide bar");

    ne_slidebar_t *slidebar = sldbar->pointer;

    slidebar->texbtn = matbtn;
    slidebar->texbar = matbarbtn;
    slidebar->texlong = matbar;
    slidebar->color1 = normalcolor;
    slidebar->color2 = pressedcolor;
    slidebar->barcolor = barcolor;
    slidebar->alpha1 = alpha;
    slidebar->alpha2 = pressedalpha;
    slidebar->baralpha = baralpha;
}

void NE_GUISlideBarSetMinMax(NE_GUIObj *sldbr, int min, int max)
{
    NE_AssertPointer(sldbr, "NULL pointer");
    NE_Assert(sldbr->type == NE_SlideBar, "Not a slide bar");

    ne_slidebar_t *slidebar = sldbr->pointer;

    slidebar->desp = min;
    slidebar->range = max - min;

    // Bar size
    slidebar->barsize = 100 - slidebar->range;
    slidebar->barsize = (slidebar->barsize < 20) ?
                        (20 << 12) : (slidebar->barsize << 12);
    slidebar->barsize =
        (divf32(slidebar->barsize, 100 << 12) * slidebar->totalsize) >> 12;

    // Current coordinate
    slidebar->coord =
        (slidebar->totalsize - slidebar->barsize) * slidebar->value;
    slidebar->coord =
         divf32(slidebar->coord << 12, slidebar->range << 12) >> 12;
    slidebar->coord += (slidebar->isvertical) ?
        slidebar->y1 + (slidebar->x2 - slidebar->x1) :
        slidebar->x1 + (slidebar->y2 - slidebar->y1);
}

NE_GUIState NE_GUIObjectGetEvent(const NE_GUIObj *obj)
{
    ne_slidebar_t *ptr;

    NE_AssertPointer(obj, "NULL pointer");

    switch (obj->type)
    {
        case NE_Button:
            return ((ne_button_t *) (obj->pointer))->event;
        case NE_CheckBox:
            return ((ne_checkbox_t *) (obj->pointer))->event;
        case NE_RadioButton:
            return ((ne_radiobutton_t *) (obj->pointer))->event;
        case NE_SlideBar:
            ptr = obj->pointer;
            return ptr->event_plus | ptr->event_minus | ptr->event_bar;
        default:
            NE_DebugPrint("Unknown GUI object type: %d", obj->type);
            return -1;
    }
}

bool NE_GUICheckBoxGetValue(const NE_GUIObj *chbx)
{
    NE_AssertPointer(chbx, "NULL pointer");
    NE_Assert(chbx->type == NE_CheckBox, "Not a check box");
    return ((ne_checkbox_t *)(chbx->pointer))->checked;
}

bool NE_GUIRadioButtonGetValue(const NE_GUIObj *rdbtn)
{
    NE_AssertPointer(rdbtn, "NULL pointer");
    NE_Assert(rdbtn->type == NE_RadioButton, "Not a radio button");
    return ((ne_radiobutton_t *)(rdbtn->pointer))->checked;
}

int NE_GUISlideBarGetValue(const NE_GUIObj *sldbr)
{
    NE_AssertPointer(sldbr, "NULL pointer");
    NE_Assert(sldbr->type == NE_SlideBar, "Not a slide bar");
    ne_slidebar_t *slidebar = sldbr->pointer;
    return slidebar->value + slidebar->desp;
}

void NE_GUIDeleteObject(NE_GUIObj *obj)
{
    NE_AssertPointer(obj, "NULL pointer");

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i] == obj)
        {
            free(obj->pointer);
            free(obj);
            NE_guipointers[i] = NULL;
            return;
        }
    }

    NE_DebugPrint("Object not found");
}

void NE_GUIDeleteAll(void)
{
    if (!ne_gui_system_inited)
        return;

    for (int i = 0; i < NE_GUI_OBJECTS; i++)
    {
        if (NE_guipointers[i])
        {
            free(NE_guipointers[i]->pointer);
            free(NE_guipointers[i]);
            NE_guipointers[i] = NULL;
        }
    }
}

int NE_GUISystemReset(int max_objects)
{
    if (ne_gui_system_inited)
        NE_GUISystemEnd();

    if (max_objects < 1)
        NE_GUI_OBJECTS = NE_GUI_DEFAULT_OBJECTS;
    else
        NE_GUI_OBJECTS = max_objects;

    NE_guipointers = calloc(NE_GUI_OBJECTS, sizeof(NE_guipointers));
    if (NE_guipointers == NULL)
    {
        NE_DebugPrint("Not enough memory");
        return -1;
    }

    ne_gui_system_inited = true;
    return 0;
}

void NE_GUISystemEnd(void)
{
    if (!ne_gui_system_inited)
        return;

    NE_GUIDeleteAll();

    free(NE_guipointers);

    ne_gui_system_inited = false;
}
