/*
    Borealis, a Nintendo Switch UI Library
    Copyright (C) 2019-2020  natinusala
    Copyright (C) 2019  p-sam

    This program 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 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include <math.h>

#include <borealis/application.hpp>
#include <borealis/button.hpp>
#include <borealis/i18n.hpp>

using namespace brls::i18n::literals;

namespace brls
{

// TODO: Disabled bordered button
// TODO: Regular disabled button

Button::Button(ButtonStyle style)
    : style(style)
{
    this->registerAction("brls/hints/ok"_i18n, Key::A, [this] { return this->onClick(); });
}

LabelStyle Button::getLabelStyle()
{
    switch (this->style)
    {
        case ButtonStyle::BORDERLESS:
            return LabelStyle::BUTTON_BORDERLESS;
        case ButtonStyle::DIALOG:
            return LabelStyle::BUTTON_DIALOG;
        case ButtonStyle::CRASH:
            return LabelStyle::CRASH;
        case ButtonStyle::BORDERED:
            return LabelStyle::BUTTON_BORDERED;
        case ButtonStyle::REGULAR:
            return LabelStyle::BUTTON_REGULAR;
        case ButtonStyle::PRIMARY:
        default:
            if (this->state == ButtonState::DISABLED)
                return LabelStyle::BUTTON_PRIMARY_DISABLED;
            else
                return LabelStyle::BUTTON_PRIMARY;
    }
}

Button::~Button()
{
    if (this->label != nullptr)
        delete this->label;
    if (this->image != nullptr)
        delete this->image;
}

void Button::layout(NVGcontext* vg, Style* style, FontStash* stash)
{
    unsigned imageWidth  = this->label ? this->getHeight() : this->getWidth();
    unsigned imageHeight = this->getHeight();

    if (!this->image)
        imageWidth = 0;

    if (this->label != nullptr)
    {
        this->label->setWidth(this->getWidth() - imageWidth);
        this->label->invalidate(true);
        this->label->setBoundaries(
            this->x + imageWidth,
            this->y + this->getHeight() / 2 - this->label->getHeight() / 2,
            this->label->getWidth(),
            this->label->getHeight());
        this->label->invalidate();
    }
    if (this->image != nullptr)
    {
        this->image->setHeight(imageHeight);
        this->image->setWidth(imageWidth);
        this->image->invalidate(true);
        this->image->setBoundaries(
            this->x,
            this->y + this->getHeight() / 2 - this->image->getHeight() / 2,
            this->image->getWidth(),
            this->image->getHeight());
    }
}

Button* Button::setLabel(std::string label)
{
    if (this->label != nullptr)
        delete this->label;

    this->label = new Label(this->getLabelStyle(), label, true);

    this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
    this->label->setParent(this);

    return this;
}

Button* Button::setImage(std::string path)
{
    this->image = new Image(path);
    this->image->setParent(this);
    return this;
}

Button* Button::setImage(unsigned char* buffer, size_t bufferSize)
{
    this->image = new Image(buffer, bufferSize);
    this->image->setParent(this);
    return this;
}

void Button::setState(ButtonState state)
{
    this->state = state;
    if (this->label != nullptr)
        this->label->setStyle(this->getLabelStyle());
}

ButtonState Button::getState()
{
    return this->state;
}

void Button::getHighlightInsets(unsigned* top, unsigned* right, unsigned* bottom, unsigned* left)
{
    if (this->style == ButtonStyle::DIALOG)
    {
        View::getHighlightInsets(top, right, bottom, left);
        *right -= 1;
        return;
    }

    Style* style = Application::getStyle();
    *top         = style->Button.highlightInset;
    *right       = style->Button.highlightInset;
    *bottom      = style->Button.highlightInset;
    *left        = style->Button.highlightInset;
}

void Button::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, Style* style, FrameContext* ctx)
{
    float cornerRadius = this->cornerRadiusOverride ? this->cornerRadiusOverride : (float)style->Button.cornerRadius;

    // Background
    switch (this->style)
    {
        case ButtonStyle::PRIMARY:
        {
            nvgFillColor(vg, a(this->state == ButtonState::DISABLED ? ctx->theme->buttonPrimaryDisabledBackgroundColor : ctx->theme->buttonPrimaryEnabledBackgroundColor));
            nvgBeginPath(vg);
            nvgRoundedRect(vg, x, y, width, height, cornerRadius);
            nvgFill(vg);
            break;
        }
        case ButtonStyle::REGULAR:
        {
            nvgFillColor(vg, a(ctx->theme->buttonRegularBackgroundColor));
            nvgBeginPath(vg);
            nvgRoundedRect(vg, x, y, width, height, cornerRadius);
            nvgFill(vg);

            nvgStrokeColor(vg, a(ctx->theme->buttonRegularBorderColor));
            nvgStrokeWidth(vg, style->Button.regularBorderThickness);
            nvgBeginPath(vg);
            nvgRoundedRect(vg, x, y, width, height, cornerRadius);
            nvgStroke(vg);
            break;
        }
        case ButtonStyle::BORDERED:
        {
            nvgStrokeColor(vg, a(ctx->theme->buttonBorderedBorderColor));
            nvgStrokeWidth(vg, style->Button.borderedBorderThickness);
            nvgBeginPath(vg);
            nvgRoundedRect(vg, x, y, width, height, cornerRadius);
            nvgStroke(vg);
            break;
        }
        default:
            break;
    }

    // Shadow
    if (this->state == ButtonState::ENABLED && (this->style == ButtonStyle::PRIMARY || this->style == ButtonStyle::REGULAR))
    {
        float shadowWidth   = style->Button.shadowWidth;
        float shadowFeather = style->Button.shadowFeather;
        float shadowOpacity = style->Button.shadowOpacity;
        float shadowOffset  = style->Button.shadowOffset;

        NVGpaint shadowPaint = nvgBoxGradient(vg,
            x, y + shadowWidth,
            width, height,
            cornerRadius * 2, shadowFeather,
            RGBA(0, 0, 0, shadowOpacity * alpha), transparent);

        nvgBeginPath(vg);
        nvgRect(vg, x - shadowOffset, y - shadowOffset,
            width + shadowOffset * 2, height + shadowOffset * 3);
        nvgRoundedRect(vg, x, y, width, height, cornerRadius);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, shadowPaint);
        nvgFill(vg);
    }

    // Label
    if (this->label != nullptr)
        this->label->frame(ctx);

    if (this->image != nullptr)
        this->image->frame(ctx);
}

bool Button::onClick()
{
    if (this->state == ButtonState::DISABLED)
        return false;

    return this->clickEvent.fire(this);
}

GenericEvent* Button::getClickEvent()
{
    return &this->clickEvent;
}

void Button::setCornerRadius(float cornerRadius)
{
    this->cornerRadiusOverride = cornerRadius;

    if (this->image != nullptr)
        this->image->setCornerRadius(cornerRadius);
}
} // namespace brls
