/**
 * @file GuiElements.cpp
 * @author Petr Egert (egertpet@fel.cvut.cz)
 * @brief Contains implementation of all GUI element classes
 */

#include "GuiElements.h"

#include <sstream>
#include <iomanip>

using namespace std;
using namespace RD;

////////////////
// GuiElement
////////////////

/**
 * @brief Releases the current element
 *
 * By default releases all children elements
 */
void GuiElement::Release()
{
    ReleaseChildren();
}

/**
 * @brief Updates the current element
 *
 * By default updates all children
 */
void GuiElement::Update()
{
    UpdateChildren();
}

/**
 * @brief Renders the current element
 *
 * By default renders all children elements
 */
void GuiElement::Render()
{
    RenderChildren();
}

/**
 * @brief Recursively releases all children elements
 */
void GuiElement::ReleaseChildren()
{
    for(ChildrenContainer::iterator iter = children.begin(); iter != children.end(); ++iter)
    {
        (*iter)->Release();
        delete *iter;
    }
    children.clear();
}

/**
 * @brief Recursively updates all children elements
 */
void GuiElement::UpdateChildren()
{
    for(ChildrenContainer::iterator iter = children.begin(); iter != children.end(); ++iter)
    {
        (*iter)->Update();
    }
}

/**
 * @brief Recursively renders all children elements
 */
void GuiElement::RenderChildren()
{
    for(ChildrenContainer::iterator iter = children.begin(); iter != children.end(); ++iter)
    {
        (*iter)->Render();
    }
}

/**
 * @brief Adds a new child element to this element
 * @param element Element to be added as child
 */
void GuiElement::AddChild(GuiElement *element)
{
    if(element)
    {
        element->parent = this;
        element->gui = gui;
        children.push_back(element);
    }
}

/**
 * @brief Sets the element position
 *
 * Transform matrix is automaticaly updated
 * @param pos New position
 */
void GuiElement::SetPos(const vec2 &pos)
{
    this->pos = pos;
    UpdateMatrix();
}

/**
 * @brief Sets the element size
 *
 * Transform matrix is automaticaly updated
 * @param size New size
 */
void GuiElement::SetSize(const vec2 &size)
{
    this->size = size;
    UpdateMatrix();
}

/**
 * @brief Sets the element rotation angle (in radians)
 *
 * Transform matrix is automaticaly updated
 * @param rot New rotation angle (in radians)
 */
void GuiElement::SetRot(float rot)
{
    this->rot = rot;
    UpdateMatrix();
}

/**
 * @brief Sets the element rotation center
 *
 * Transform matrix is automaticaly updated
 * @param rotCenter New rotation center
 */
void GuiElement::SetRotCenter(const vec2 &rotCenter)
{
    this->rotCenter = rotCenter;
    UpdateMatrix();
}

/**
 * @brief Sets the GUI this element belongs to
 *
 * This function does NOT register the element as a child of the GUI, in only provides information to the element
 * Transform matrix is automaticaly updated
 * @param gui GUI to register this element to
 */
void GuiElement::SetGui(Gui *gui)
{
    this->gui = gui;
    if(gui) UpdateMatrix();
}

/**
 * @brief Sets a custom transformation matrix for the element
 *
 * hasCustomTransform is set to true and default transform computation is disabled
 * @param transform New transformation matrix
 */
void GuiElement::SetTransform(const mat44 &transform)
{
    hasCustomTransform = true;
    this->transform = transform;
    UpdateMatrix();
}

/**
 * @brief Updates the elements tranformation matrix for the current position, rotation (with offset) and scale
 * @param recursive If true, matrices are updated recursively for the element and all it's children. Default true
 */
void GuiElement::UpdateMatrix(bool recursive)
{
    //(gui->height - y) - height
    if(!hasCustomTransform)
    {
        if(parent)
        {
            transform = parent->transform * (mat44::Translation(pos.x, pos.y, 0.0) * mat44::Scaling(size.x, size.y, 1.0) * mat44::RotationZOffset(rot, rotCenter));
        }
        else
        {
            transform = mat44::Translation(pos.x, pos.y, 0.0) * mat44::Scaling(size.x, size.y, 1.0) * mat44::RotationZOffset(rot, rotCenter);
        }
    }

    if(recursive)
    {
        for(ChildrenContainer::iterator iter = children.begin(); iter != children.end(); ++iter)
        {
            (*iter)->UpdateMatrix();
        }
    }
}

/**
 * @brief Creates a deep copy of the element
 * @return Newly created element or NULL if an error occurs
 */
GuiElement *GuiElement::Clone()
{
    GuiElement *ret = CloneSelf();
    if(!ret) return NULL;

    ret->parent = NULL;

    if(!CloneChildren(ret))
    {
        ret->Release();
        delete ret;
        return NULL;
    }

    return ret;
}

/**
 * @brief This function is used by Clone() to create a shallow copy of the element
 *
 * Should be overriden for every class inheriting from the basic GuiElement
 * @return Pointer to a shallow copy of the element
 */
GuiElement *GuiElement::CloneSelf()
{
    GuiElement *ret = new GuiElement();
    *ret = *this;
    return ret;
}

/**
 * @brief This function is used by Clone() to recursively clone all the element's children
 *
 * OnAfterChildClone() is called after each successful chilren clone
 * @param target Target GUI element to which the children from the current element should be cloned
 * @return true if all the children can be succssfuly cloned, false otherwise
 */
bool GuiElement::CloneChildren(GuiElement *target)
{
    if(!target) return false;
    target->children.clear();

    for(ChildrenContainer::iterator iter = children.begin(); iter != children.end(); ++iter)
    {
        GuiElement *elem = (*iter)->Clone();
        if(!elem)
        {
            return false;
        }
        target->AddChild(elem);
        OnAfterChildClone(target, *iter, elem);
    }
    return true;
}

/**
 * @brief This function is called after each successful Clone() of the elements children in CloneChildren()
 *
 * The main purpouse of this function is to be able to properly assign the newly created children to class specific parameters in compound elements
 *
 * Example:
 * if(myChild == this->myTextNode) target->myTextNode = targetChild;
 *
 * Should be overloaded in all compound elements
 * @param target The element whose children are being created
 * @param myChild Child element of the original node
 * @param targetChild The newly created element in the target node
 */
void GuiElement::OnAfterChildClone(GuiElement *target, GuiElement *myChild, GuiElement *targetChild)
{

}

////////////////////
//GuiElementImage
////////////////////

/**
 * @brief Renders the image element
 */
void GuiElementImage::Render()
{
    if(!texture) return;
    Renderer::instance->SetTexture(texture);
    Renderer::instance->SetTransform(transform);
    Renderer::instance->DrawQuad();
    RenderChildren();
}

/**
 * @brief Sets a texture for the element
 */
void GuiElementImage::SetTexture(Texture *texture)
{
    this->texture = texture;
}

GuiElement *GuiElementImage::CloneSelf()
{
    GuiElementImage *ret = new GuiElementImage();
    *ret = *this;
    return ret;
}

////////////////////
// GuiElementText
////////////////////

/**
 * @brief Renders the text using the currently set font
 */
void GuiElementText::Render()
{
    Renderer::instance->SetTransform(transform);
    if(font) font->DrawString(text);
    RenderChildren();
}

/**
 * @brief Sets the font used to draw the text
 * @param font Font to be used
 */
void GuiElementText::SetFont(Font *font)
{
    this->font = font;
}

/**
 * @brief Sets the text to be drawn
 * @param text Text to be drawn
 */
void GuiElementText::SetText(const std::string &text)
{
    this->text = text;
}

GuiElement *GuiElementText::CloneSelf()
{
    GuiElementText *ret = new GuiElementText();
    *ret = *this;
    return ret;
}

//////////////////////////
// GuiElementTextNumber
//////////////////////////
/**
 * @brief Sets the value to be shown
 * @param value Value to be shown
 */
void GuiElementTextNumber::SetValue(float value)
{
    this->value = value;
    UpdateValue();
}

/**
 * @brief Sets the decimal precision with which the value is shown
 * @param precision Precision (number of decimal points)
 * @see std::setprecision()
 */
void GuiElementTextNumber::SetPrecision(int precision)
{
    this->precision = precision;
    UpdateValue();
}

GuiElement *GuiElementTextNumber::CloneSelf()
{
    GuiElementText *ret = new GuiElementTextNumber();
    *ret = *this;
    return ret;
}

/**
 * @brief Converts the currently set value to the string that is drawn
 */
void GuiElementTextNumber::UpdateValue()
{
    ostringstream str;
    str << fixed << setprecision(precision) << value;

    SetText(str.str());
}

////////////////////////
// GuiElementTextVec3
////////////////////////
/**
 * @brief Sets the value to be shown
 * @param value Value to be shown
 */
void GuiElementTextVec3::SetValue(const vec3 &value)
{
    this->value = value;
    UpdateValue();
}

/**
 * @brief Sets the decimal precision with which the value is shown
 * @param precision Precision (number of decimal points)
 * @see std::setprecision()
 */
void GuiElementTextVec3::SetPrecision(int precision)
{
    this->precision = precision;
    UpdateValue();
}

GuiElement *GuiElementTextVec3::CloneSelf()
{
    GuiElementText *ret = new GuiElementTextVec3();
    *ret = *this;
    return ret;
}

/**
 * @brief Converts the currently set value to the string that is drawn
 *
 * Space is used as separator between the individual components
 */
void GuiElementTextVec3::UpdateValue()
{
    ostringstream str;
    str << fixed << setprecision(precision) << value.x << " " << value.y << " " << value.z;

    SetText(str.str());
}

////////////////////
// GuiElementDial
////////////////////

/**
 * @brief Class initialization function. Creates baseElem and needleElem
 */
void GuiElementDial::Init()
{
    baseElem = new GuiElementImage();
    baseElem->Init();
    AddChild(baseElem);

    needleElem = new GuiElementImage();
    needleElem->Init();
    AddChild(needleElem);
}

/**
 * @brief Renders the whole dial
 *
 * baseElem and needleElem get rendered automatically, so it's NOT necessary to call Render() on them manually
 */
void GuiElementDial::Render()
{
    RenderChildren();
}

/**
 * @brief Sets the range of values
 *
 * Used for computation of the dial rotation. The value is NOT automatically clamped to the range
 * @param range Range of the values
 */
void GuiElementDial::SetRange(const vec2 &range)
{
    this->range = range;
    UpdateValue();
}

/**
 * @brief Sets the range of rotation of the needle for minimal/maximal value
 *
 * Used for computation of the dial rotation
 * The rotation is NOT automatically clamped to the range
 * Example:
 * Range is set to -1.0, 1.0
 * RotRange is set to -PI, 1.234
 * For value = -1.0, the needle rotation is -PI
 * For value = 1.0, the needle rotation is 1.234
 * Values between are linear interpolated
 *
 * @param rotRange Range of rotation (in radians)
 */
void GuiElementDial::SetRotRange(const vec2 &rotRange)
{
    this->rotRange = rotRange;
    UpdateValue();
}

/**
 * @brief Sets the value of the dial
 * @param value Value to be set
 */
void GuiElementDial::SetValue(float value)
{
    this->value = value;
    UpdateValue();
}


GuiElement *GuiElementDial::CloneSelf()
{
    GuiElementDial *ret = new GuiElementDial();
    *ret = *this;
    return ret;
}

void GuiElementDial::OnAfterChildClone(GuiElement *target, GuiElement *myChild, GuiElement *targetChild)
{
    if(myChild == baseElem) ((GuiElementDial*)target)->baseElem = (GuiElementImage*) targetChild;
    else if(myChild == needleElem) ((GuiElementDial*)target)->needleElem = (GuiElementImage*) targetChild;
}

/**
 * @brief Calculates the needle rotation based on current value, range and rotRange
 */
void GuiElementDial::UpdateValue()
{
    float valNormalized = (value - range.x) / (range.y - range.x);
    float valRot = rotRange.x + valNormalized * (rotRange.y - rotRange.x);
    needleElem->SetRot(valRot);
}

////////////////////////
// GuiElementDialText
////////////////////////
/**
 * @brief Class initialization function. Creates textElem
 */
void GuiElementDialText::Init()
{
    GuiElementDial::Init();

    textElem = new GuiElementTextNumber();
    textElem->Init();
    AddChild(textElem);
}

/**
 * @brief Sets the precision of the text element
 *
 * Convenience function
 * @param precision Precision
 * @see GuiElementTextNumber::SetPrecision()
 */
void GuiElementDialText::SetPrecision(int precision)
{
    textElem->SetPrecision(precision);
}

GuiElement *GuiElementDialText::CloneSelf()
{
    GuiElementDial *ret = new GuiElementDial();
    *ret = *this;
    return ret;
}

void GuiElementDialText::OnAfterChildClone(GuiElement *target, GuiElement *myChild, GuiElement *targetChild)
{
    GuiElementDial::OnAfterChildClone(target, myChild, targetChild);

    if(myChild == textElem) ((GuiElementDialText*)target)->textElem = (GuiElementTextNumber*) targetChild;
}

/**
 * @brief Calculates the needle rotation based on current value, range and rotRange
 */
void GuiElementDialText::UpdateValue()
{
    GuiElementDial::UpdateValue();
    textElem->SetValue(value);
}

/////////////////////
// GuiElementGauge
/////////////////////

/**
 * @brief Class initialization function. Creates baseElem, gaugeElem and coverElem
 */
void GuiElementGauge::Init()
{
    baseElem = new GuiElementImage();
    baseElem->Init();
    AddChild(baseElem);

    gaugeElem = new GuiElementImage();
    gaugeElem->Init();
    AddChild(gaugeElem);

    coverElem = new GuiElementImage();
    coverElem->Init();
    AddChild(coverElem);

}

/**
 * @brief Renders the whole gauge
 *
 * baseElem, gaugeElem and coverElem get rendered automatically, so it's NOT necessary to call Render() on them manually
 */
void GuiElementGauge::Render()
{
    RenderChildren();
}

/**
 * @brief Sets the range of values
 *
 * Used for computation of the gauge scale. The value is NOT automatically clamped to the range
 * @param range Range of the values
 */
void GuiElementGauge::SetRange(const vec2 &range)
{
    this->range = range;
    UpdateValue();
}

/**
 * @brief Sets the range of scale of the gauge for minimal/maximal value
 *
 * Used for computation of the gauge scale
 * The scale is NOT automatically clamped to the range
 * Example:
 * Range is set to -1.0, 1.0
 * ScaleRange is set to 0.5, 0.8
 * For value = -1.0, the gauge element has X scale 0.5
 * For value = 1.0, the gauge element has X scale 0.8
 * Values between are linear interpolated
 *
 * @param scaleRange Range of scaling
 */
void GuiElementGauge::SetScaleRange(const vec2 &scaleRange)
{
    this->scaleRange = scaleRange;
    UpdateValue();
}

/**
 * @brief Sets the value of the gauge
 * @param value Value to be set
 */
void GuiElementGauge::SetValue(float value)
{
    this->value = value;
    UpdateValue();
}


GuiElement *GuiElementGauge::CloneSelf()
{
    GuiElementGauge *ret = new GuiElementGauge();
    *ret = *this;
    return ret;
}

void GuiElementGauge::OnAfterChildClone(GuiElement *target, GuiElement *myChild, GuiElement *targetChild)
{
    if(myChild == baseElem) ((GuiElementGauge*)target)->baseElem = (GuiElementImage*) targetChild;
    else if(myChild == gaugeElem) ((GuiElementGauge*)target)->gaugeElem = (GuiElementImage*) targetChild;
    else if(myChild == coverElem) ((GuiElementGauge*)target)->coverElem = (GuiElementImage*) targetChild;
}

/**
 * @brief Calculates the gauge element X scale based on current value, range and scaleRange
 */
void GuiElementGauge::UpdateValue()
{
    float valNormalized = (value - range.x) / (range.y - range.x);
    valNormalized = (valNormalized > 1.0) ? 1.0 : ((valNormalized < 0.0) ? 0.0 : valNormalized);
    float valScale = valNormalized * (scaleRange.y - scaleRange.x);

    gaugeElem->SetPos(vec2(scaleRange.x, gaugeElem->pos.y));
    gaugeElem->SetSize(vec2(valScale, gaugeElem->size.y));
}

//////////////////////
// GuiElementSlider
//////////////////////

/**
 * @brief Class initialization function. Creates baseElem and thumbElem
 */
void GuiElementSlider::Init()
{
    baseElem = new GuiElementImage();
    baseElem->Init();
    AddChild(baseElem);

    thumbElem = new GuiElementImage();
    thumbElem->Init();
    AddChild(thumbElem);
}

/**
 * @brief Renders the whole slider
 *
 * baseElem and thumbElem get rendered automatically, so it's NOT necessary to call Render() on them manually
 */
void GuiElementSlider::Render()
{
    RenderChildren();
}

/**
 * @brief Sets the range of values
 *
 * Used for computation of the thumb position. The value is NOT automatically clamped to the range
 * @param range Range of the values
 */
void GuiElementSlider::SetRange(const vec2 &range)
{
    this->range = range;
    UpdateValue();
}


/**
 * @brief Sets the range of position of the thumb for minimal/maximal value
 *
 * Used for computation of the thumb position
 * The position is NOT automatically clamped to the range
 * Example:
 * Range is set to -1.0, 1.0
 * PosRange is set to 0.1, 0.9
 * For value = -1.0, the thumb element has X position 0.1
 * For value = 1.0, the thumb element has X position 0.9
 * Values between are linear interpolated
 *
 * @param posRange Range of position
 */
void GuiElementSlider::SetPosRange(const vec2 &posRange)
{
    this->posRange = posRange;
    UpdateValue();
}

/**
 * @brief Sets the value of the slider
 * @param value Value to be set
 */
void GuiElementSlider::SetValue(float value)
{
    this->value = value;
    UpdateValue();
}


GuiElement *GuiElementSlider::CloneSelf()
{
    GuiElementSlider *ret = new GuiElementSlider();
    *ret = *this;
    return ret;
}

void GuiElementSlider::OnAfterChildClone(GuiElement *target, GuiElement *myChild, GuiElement *targetChild)
{
    if(myChild == baseElem) ((GuiElementSlider*)target)->baseElem = (GuiElementImage*) targetChild;
    else if(myChild == thumbElem) ((GuiElementSlider*)target)->thumbElem = (GuiElementImage*) targetChild;
}

/**
 * @brief Calculates the thumb element X position based on current value, range and posRange
 */
void GuiElementSlider::UpdateValue()
{
    float valNormalized = (value - range.x) / (range.y - range.x);
    valNormalized = (valNormalized > 1.0) ? 1.0 : ((valNormalized < 0.0) ? 0.0 : valNormalized);
    float valPos = posRange.x + valNormalized * (posRange.y - posRange.x);

    thumbElem->SetPos(vec2(valPos, thumbElem->pos.y));
}

/////////////////////////
// GuiElementTextInput
/////////////////////////

GuiElement *GuiElementTextInput::CloneSelf()
{
    GuiElementTextInput *ret = new GuiElementTextInput();
    *ret = *this;
    return ret;
}

/**
 * @brief Processes a single key input and updates text accordingly
 *
 * Only basic printable chars (ASCII 32 to 126) and backspace (ASCII 8) are handled
 * @param key Ascii key code
 */
void GuiElementTextInput::ProcessKey(unsigned char key)
{
    if(key == 0x08) //backspace
    {
        if(text.size())
        {
            text.resize(text.size() - 1);
        }
    }
    else if ((key >= 0x20) && (key <= 0x7E))    //basic printable characters
    {
        text += key;
    }
}

/**
 * @brief Returns current text of the element
 * @return Current text
 */
std::string GuiElementTextInput::GetText()
{
    return text;
}
