/*
===========================================================================
Copyright (C) 2015-2023 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "ui_local.h"
#include <string.h>

Event EV_AutoIncrement
(
    "autoinc",
    EV_DEFAULT,
    NULL,
    NULL,
    "Used to auto increment the slider"
);

Event EV_AutoDecrement
(
    "autodec",
    EV_DEFAULT,
    NULL,
    NULL,
    "Used to auto decrement the slider"
);

Event EV_Layout_Range
(
    "setrange",
    EV_DEFAULT,
    "ff",
    "min max",
    "Used to set the range of a slider"
);

Event EV_Layout_SliderType
(
    "slidertype",
    EV_DEFAULT,
    "s",
    "sliderType(float|integer)",
    "Set the slider type to be floating point or integral"
);

Event EV_Layout_SliderStep
(
    "stepsize",
    EV_DEFAULT,
    "f",
    "stepsize",
    "Set the step size of the slider"
);

Event EV_Layout_SliderThumbShader
(
    "thumbshader",
    EV_DEFAULT,
    "sS",
    "shader depressed_shader",
    "Set the shader of the slider's thumb"
);

Event EV_Layout_SliderLeftShader
(
    "leftshader",
    EV_DEFAULT,
    "sS",
    "shader depressed_shader",
    "Set the shader of the slider's left arrow"
);

Event EV_Layout_SliderRightShader
(
    "rightshader",
    EV_DEFAULT,
    "sS",
    "shader depressed_shader",
    "Set the shader of the slider's right arrow"
);

CLASS_DECLARATION(UIWidget, UISlider, NULL) {
    {&W_LeftMouseDown,             &UISlider::Pressed                },
    {&W_LeftMouseDragged,          &UISlider::MouseDragged           },
    {&W_LeftMouseUp,               &UISlider::Released               },
    {&W_MouseExited,               &UISlider::MouseExited            },
    {&W_MouseEntered,              &UISlider::MouseEntered           },
    {&EV_AutoIncrement,            &UISlider::AutoIncrement          },
    {&EV_AutoDecrement,            &UISlider::AutoDecrement          },
    {&EV_Layout_Range,             &UISlider::LayoutSetRange         },
    {&EV_Layout_SliderType,        &UISlider::LayoutSetType          },
    {&EV_Layout_SliderStep,        &UISlider::LayoutSetStep          },
    // possible FAKK2 remnants below, not used currently
    {&EV_Layout_SliderThumbShader, &UISlider::LayoutSliderThumbShader},
    {&EV_Layout_SliderLeftShader,  &UISlider::LayoutSliderLeftShader },
    {&EV_Layout_SliderRightShader, &UISlider::LayoutSliderRightShader},
    {NULL,                         NULL                              }
};

UISlider::UISlider()
{
    m_sliderregion         = {};
    m_prev_arrow_region    = {};
    m_next_arrow_region    = {};
    m_minvalue             = 0.0f;
    m_maxvalue             = 100.0f;
    m_value                = 0.0f;
    m_oldvalue             = 0.0f;
    m_sliderwidth          = 10.0f;
    m_stepsize             = 1.0f;
    m_slidertype           = SLIDER_INTEGER;
    m_arrow_width          = 15;
    m_prev_arrow_depressed = qfalse;
    m_next_arrow_depressed = qfalse;
    m_thumb_depressed      = qfalse;
    m_initialized          = qfalse;

    m_prev_arrow_material         = uWinMan.RegisterShader("textures/menu/arrow_left");
    m_prev_arrow_material_pressed = uWinMan.RegisterShader("textures/menu/arrow_left_sel");
    m_next_arrow_material         = uWinMan.RegisterShader("textures/menu/arrow_right");
    m_next_arrow_material_pressed = uWinMan.RegisterShader("textures/menu/arrow_right_sel");
    m_thumbmaterial               = uWinMan.RegisterShader("textures/menu/slider_thumb");
    m_thumbmaterial_pressed       = uWinMan.RegisterShader("textures/menu/slider_thumb_sel");

    // these are not initialized in the original game
    m_bar_material = NULL; // seems to be unused
    m_maxx         = 0;
    m_minx         = 0;
}

void UISlider::setThumb(void)
{
    // not present in disassembly
}

void UISlider::Pressed(Event *ev)
{
    float xpos = ev->GetFloat(1);
    float ypos = ev->GetFloat(2);

    if (m_prev_arrow_region.contains(xpos, ypos)) // click left arrow
    {
        Decrement();
        m_prev_arrow_depressed = qtrue;
        CancelEventsOfType(&EV_AutoDecrement);
        Event *newEv = new Event(EV_AutoDecrement);
        PostEvent(newEv, 0.2f);
        return;
    } else if (m_next_arrow_region.contains(xpos, ypos)) // click right arrow
    {
        Increment();
        m_next_arrow_depressed = qtrue;
        CancelEventsOfType(&EV_AutoIncrement);
        Event *newEv = new Event(EV_AutoIncrement);
        PostEvent(newEv, 0.2f);
        return;
    }

    // click thumb
    m_oldvalue        = m_value;
    m_thumb_depressed = qtrue;

    if (xpos <= m_minx) {
        m_value = m_minvalue;
    } else if (xpos >= m_maxx) {
        m_value = m_maxvalue;
    } else {
        // calculate value based on thumb position
        m_value = (xpos - m_minx) / (m_maxx - m_minx) * (m_maxvalue - m_minvalue) + m_minvalue;
    }

    UpdateData();
}

void UISlider::Released(Event *ev)
{
    m_prev_arrow_depressed = qfalse;
    m_next_arrow_depressed = qfalse;
    m_thumb_depressed      = qfalse;
}

void UISlider::Increment(void)
{
    m_value = Q_clamp_float(m_value + m_stepsize, m_minvalue, m_maxvalue);
    UpdateData();
}

void UISlider::Decrement(void)
{
    m_value = Q_clamp_float(m_value - m_stepsize, m_minvalue, m_maxvalue);
    UpdateData();
}

void UISlider::AutoIncrement(Event *ev)
{
    if (m_next_arrow_depressed) {
        if (m_next_arrow_region.contains(uid.mouseX, uid.mouseY)) {
            Increment();
            Event *event = new Event(EV_AutoIncrement);
            PostEvent(event, 0.1f);
        } else {
            Released(NULL);
        }
    }
}

void UISlider::AutoDecrement(Event *ev)
{
    if (m_prev_arrow_depressed) {
        if (m_prev_arrow_region.contains(uid.mouseX, uid.mouseY)) {
            Decrement();
            Event *event = new Event(EV_AutoDecrement);
            PostEvent(event, 0.1f);
        } else {
            Released(NULL);
        }
    }
}

void UISlider::MouseExited(Event *ev)
{
    if (m_thumb_depressed) {
        m_value = m_oldvalue;
        UpdateData();
    }

    m_thumb_depressed      = qfalse;
    m_prev_arrow_depressed = qfalse;
    m_next_arrow_depressed = qfalse;
}

void UISlider::MouseDragged(Event *ev)
{
    float xpos = ev->GetFloat(1);
    float ypos = ev->GetFloat(2);

    if (m_thumb_depressed) {
        if (xpos <= m_minx) {
            m_value = m_minvalue;
        } else if (xpos >= m_maxx) {
            m_value = m_maxvalue;
        } else {
            // calculate value based on position
            m_value = (xpos - m_minx) / (m_maxx - m_minx) * (m_maxvalue - m_minvalue) + m_minvalue;
        }

        UpdateData();
        return;
    }

    if ((!m_prev_arrow_depressed && m_prev_arrow_region.contains(xpos, ypos))
        || (!m_next_arrow_depressed && m_next_arrow_region.contains(xpos, ypos))) {
        Pressed(ev);
    }
}

void UISlider::MouseEntered(Event *ev)
{
    int buttons = ev->GetInteger(3);
    uWinMan.ActivateControl(this);
    if (buttons & 1) // left mouse button
    {
        Pressed(ev);
    }
}

void UISlider::UpdateData(void)
{
    if (m_cvarname.length()) {
        if (m_slidertype == SLIDER_INTEGER) {
            UI_SetCvarInt(m_cvarname, m_value);
        } else {
            UI_SetCvarFloat(m_cvarname, m_value);
        }
    }

    if (m_command.length()) {
        char text[32];
        if (m_slidertype == SLIDER_INTEGER) {
            Q_snprintf(text, 32, "%d", (int)m_value);
        } else {
            Q_snprintf(text, 32, "%.3f", m_value);
        }

        m_command.append(' ');
        m_command.append(text);
        m_command.append('\n');
        Cbuf_AddText(m_command);
    }
}

void UISlider::UpdateUIElement(void)
{
    m_value = UI_GetCvarFloat(m_cvarname, 0.0f);
}

void UISlider::LayoutSetRange(Event *ev)
{
    float min = ev->GetFloat(1);
    float max = ev->GetFloat(2);
    setRange(min, max);
}

void UISlider::LayoutSetType(Event *ev)
{
    str type = ev->GetString(1);
    if (!Q_stricmp(type, "float")) {
        setType(SLIDER_FLOAT);
    } else if (!Q_stricmp(type, "integer")) {
        setType(SLIDER_INTEGER);
    }
}

void UISlider::LayoutSetStep(Event *ev)
{
    m_stepsize = ev->GetFloat(1);
}

void UISlider::LayoutSliderBGShader(Event *ev)
{
    // not present in disassembly
}

void UISlider::LayoutSliderThumbShader(Event *ev)
{
    str shader      = ev->GetString(1);
    m_thumbmaterial = uWinMan.RegisterShader(shader);

    if (ev->NumArgs() <= 1) {
        m_thumbmaterial_pressed = m_thumbmaterial;
    } else {
        str pressedShader       = ev->GetString(2);
        m_thumbmaterial_pressed = uWinMan.RegisterShader(pressedShader);
    }

    m_sliderwidth = uii.Rend_GetShaderWidth(m_thumbmaterial->GetMaterial());
    m_initialized = qfalse;
}

void UISlider::LayoutSliderLeftShader(Event *ev)
{
    str shader            = ev->GetString(1);
    m_prev_arrow_material = uWinMan.RegisterShader(shader);

    if (ev->NumArgs() <= 1) {
        m_prev_arrow_material_pressed = m_prev_arrow_material;
    } else {
        str pressedShader             = ev->GetString(2);
        m_prev_arrow_material_pressed = uWinMan.RegisterShader(pressedShader);
    }

    m_arrow_width = uii.Rend_GetShaderWidth(m_prev_arrow_material->GetMaterial());
    m_initialized = qfalse;
}

void UISlider::LayoutSliderRightShader(Event *ev)
{
    str shader            = ev->GetString(1);
    m_next_arrow_material = uWinMan.RegisterShader(shader);

    if (ev->NumArgs() <= 1) {
        m_next_arrow_material_pressed = m_next_arrow_material;
    } else {
        str pressedShader             = ev->GetString(2);
        m_next_arrow_material_pressed = uWinMan.RegisterShader(pressedShader);
    }

    m_arrow_width = uii.Rend_GetShaderWidth(m_next_arrow_material->GetMaterial());
    m_initialized = qfalse;
}

void UISlider::FrameInitialized(void)
{
    float cframeX          = m_clippedframe.pos.x;
    float cframeY          = m_clippedframe.pos.y;
    float cframeWidth      = m_clippedframe.size.width;
    float frameHeight      = m_frame.size.height;
    float scaledArrowWidth = m_arrow_width * m_vVirtualScale[0];
    float scaledThumbWidth = m_sliderwidth * m_vVirtualScale[0];

    m_prev_arrow_region.pos.x       = cframeX;
    m_prev_arrow_region.pos.y       = cframeY;
    m_prev_arrow_region.size.height = frameHeight;
    m_prev_arrow_region.size.width  = scaledArrowWidth;

    m_next_arrow_region.pos.x       = cframeX + cframeWidth - scaledArrowWidth;
    m_next_arrow_region.pos.y       = cframeY;
    m_next_arrow_region.size.height = frameHeight;
    m_next_arrow_region.size.width  = scaledArrowWidth;

    m_sliderregion.pos.x       = cframeX + scaledArrowWidth;
    m_sliderregion.pos.y       = cframeY;
    m_sliderregion.size.height = frameHeight;
    // Bug in original game, width calculation was wrong:
    //m_sliderregion.size.width = scaledThumbWidth - scaledArrowWidth;
    m_sliderregion.size.width = cframeWidth - scaledArrowWidth * 2;

    // width of the "dead zone" of the slider where the thumb cannot reach
    float unreachableEdgeWidth = scaledArrowWidth + scaledThumbWidth * 0.5f;

    m_minx = cframeX + unreachableEdgeWidth;
    m_maxx = cframeX + m_frame.size.width - unreachableEdgeWidth;
}

void UISlider::Draw(void)
{
    if (!m_initialized) {
        m_initialized = qtrue;
        FrameInitialized();
    }

    UpdateUIElement();

    // current horizontal position of the thumb
    float sliderpos        = (m_value - m_minvalue) / (m_maxvalue - m_minvalue);
    sliderpos              = Q_clamp_float(sliderpos, 0.0f, 1.0f);
    float scaledArrowWidth = m_arrow_width * m_vVirtualScale[0];
    float scaledThumbWidth = m_sliderwidth * m_vVirtualScale[0];
    float x                = (m_maxx - m_minx) * sliderpos + scaledArrowWidth;

    // draw the thumb
    if (m_thumbmaterial) {
        UIReggedMaterial *material = m_thumb_depressed ? m_thumbmaterial_pressed : m_thumbmaterial;
        uii.Rend_DrawPicStretched(
            x, 0.0f, scaledThumbWidth, m_frame.size.height, 0.0f, 0.0f, 1.0f, 1.0f, material->GetMaterial()
        );
    } else {
        Draw3DBox(x, 0.0f, scaledThumbWidth, m_frame.size.height, qfalse, m_border_color, m_local_alpha);
    }

    if (m_prev_arrow_material == m_prev_arrow_material_pressed) {
        Draw3DBox(
            0.0f, 0.0f, scaledArrowWidth, m_frame.size.height, m_prev_arrow_depressed, m_border_color, m_local_alpha
        );

        uii.Rend_DrawPicStretched(
            2.0f,
            2.0f,
            scaledArrowWidth - 4.0f,
            m_frame.size.height - 4.0f,
            0.0f,
            0.0f,
            1.0f,
            1.0f,
            m_prev_arrow_material->GetMaterial()
        );
    } else {
        uihandle_t material;
        if (m_prev_arrow_depressed) {
            material = m_prev_arrow_material_pressed->GetMaterial();
        } else {
            material = m_prev_arrow_material->GetMaterial();
        }

        uii.Rend_DrawPicStretched(0.0f, 0.0f, scaledArrowWidth, m_frame.size.height, 0.0f, 0.0f, 1.0f, 1.0f, material);
    }

    if (m_next_arrow_material == m_next_arrow_material_pressed) {
        Draw3DBox(
            m_frame.size.width - scaledArrowWidth,
            0.0f,
            scaledArrowWidth,
            m_frame.size.height,
            m_next_arrow_depressed,
            m_border_color,
            m_local_alpha
        );

        uii.Rend_DrawPicStretched(
            m_frame.size.width - scaledArrowWidth + 2.0f,
            2.0f,
            scaledArrowWidth - 4.0f,
            m_frame.size.height - 4.0f,
            0.0f,
            0.0f,
            1.0f,
            1.0f,
            m_next_arrow_material->GetMaterial()
        );
    } else {
        uihandle_t material;
        if (m_next_arrow_depressed) {
            material = m_next_arrow_material_pressed->GetMaterial();
        } else {
            material = m_next_arrow_material->GetMaterial();
        }

        uii.Rend_DrawPicStretched(
            m_frame.size.width - scaledArrowWidth,
            0.0f,
            scaledArrowWidth,
            m_frame.size.height,
            0.0f,
            0.0f,
            1.0f,
            1.0f,
            material
        );
    }
}

void UISlider::setType(slidertype_t type)
{
    m_slidertype = type;
}

qboolean UISlider::KeyEvent(int key, unsigned int time)
{
    if (key == K_RIGHTARROW) {
        Increment();
    } else if (key == K_LEFTARROW) {
        Decrement();
    } else {
        return qfalse;
    }

    return qtrue;
}

void UISlider::setRange(float min, float max)
{
    m_minvalue = min;
    m_maxvalue = max;
}

void UISlider::setStep(float value)
{
    m_stepsize = value;
}
