//  SPDX-License-Identifier: MIT
//
//  ES-DE Frontend
//  NinePatchComponent.cpp
//
//  Breaks up an image into 3x3 patches to accomodate resizing without distortions.
//

#include "components/NinePatchComponent.h"

#include "Log.h"
#include "ThemeData.h"
#include "resources/Font.h"
#include "resources/TextureResource.h"

NinePatchComponent::NinePatchComponent(const std::string& path)
    : mRenderer {Renderer::getInstance()}
    , mPath {path}
    , mCornerSize {16.0f, 16.0f}
    , mSharpCorners {false}
    , mFrameColor {mMenuColorFrame}
{
    if (!mPath.empty())
        buildVertices();
}

void NinePatchComponent::render(const glm::mat4& parentTrans)
{
    if (!isVisible())
        return;

    glm::mat4 trans {parentTrans * getTransform()};

    if (mTexture && mVertices != nullptr) {
        mRenderer->setMatrix(trans);
        (*mVertices)[0].opacity = mOpacity;
        (*mVertices)[0].shaderFlags = Renderer::ShaderFlags::PREMULTIPLIED;
        mTexture->bind(0);
        mRenderer->drawTriangleStrips(&mVertices->at(0), 6 * 9);
    }

    renderChildren(trans);
}

void NinePatchComponent::fitTo(glm::vec2 size, glm::vec3 position, glm::vec2 padding)
{
    size += padding;
    position[0] -= padding.x / 2.0f;
    position[1] -= padding.y / 2.0f;

    setSize(size + mCornerSize * 2.0f);
    setPosition(position.x + glm::mix(-mCornerSize.x, mCornerSize.x, mOrigin.x),
                position.y + glm::mix(-mCornerSize.y, mCornerSize.y, mOrigin.y));
}

void NinePatchComponent::setImagePath(const std::string& path)
{
    mPath = path;
    buildVertices();
}

void NinePatchComponent::setFrameColor(unsigned int frameColor)
{
    mFrameColor = frameColor;
    updateColors();
}

void NinePatchComponent::buildVertices()
{
    if (mSize.x == 0.0f || mSize.y == 0.0f)
        return;

    mVertices.reset();
    glm::vec2 relCornerSize {0.0f, 0.0f};

    // Don't scale the rasterized version of the frame as it would look bad.
    if (mPath.substr(mPath.size() - 4, std::string::npos) == ".png") {
        relCornerSize = mCornerSize;
    }
    else {
        // Scale the corner size relative to the screen resolution (using the medium sized
        // default font as size reference).
        relCornerSize = mCornerSize * (Font::get(FONT_SIZE_MEDIUM_FIXED)->getLetterHeight() *
                                       (mSharpCorners == true ? 0.0568f : 0.09f) / 2.0f);
    }

    glm::vec2 texSize {relCornerSize * 3.0f};
    mTexture = TextureResource::get(mPath, false, false, false, false, false,
                                    static_cast<size_t>(texSize.x), static_cast<size_t>(texSize.y));

    mTexture->rasterizeAt(texSize.x, texSize.y);

    if (mTexture->getSize() == glm::ivec2 {0, 0}) {
        LOG(LogError) << "NinePatchComponent has no texture";
        return;
    }

    mVertices = std::make_unique<std::vector<Renderer::Vertex>>(6 * 9);

    const float imgSizeX[3] {relCornerSize.x, mSize.x - relCornerSize.x * 2.0f, relCornerSize.x};
    const float imgSizeY[3] {relCornerSize.y, mSize.y - relCornerSize.y * 2.0f, relCornerSize.y};
    const float imgPosX[3] {0, imgSizeX[0], imgSizeX[0] + imgSizeX[1]};
    const float imgPosY[3] {0, imgSizeY[0], imgSizeY[0] + imgSizeY[1]};

    // The "1 +" in posY and "-" in sizeY is to deal with texture coordinates having a bottom
    // left corner origin vs. verticies having a top left origin.
    // clang-format off
    const float texSizeX[3] {relCornerSize.x / texSize.x,  (texSize.x - relCornerSize.x * 2.0f) / texSize.x,  relCornerSize.x / texSize.x};
    const float texSizeY[3] {-relCornerSize.y / texSize.y, -(texSize.y - relCornerSize.y * 2.0f) / texSize.y, -relCornerSize.y / texSize.y};

    const float texPosX[3] {0.0f,        texSizeX[0],        texSizeX[0] + texSizeX[1]};
    const float texPosY[3] {1.0f, 1.0f + texSizeY[0], 1.0f + texSizeY[0] + texSizeY[1]};
    // clang-format on

    int v {0};

    for (int slice {0}; slice < 9; ++slice) {
        const int sliceX {slice % 3};
        const int sliceY {slice / 3};
        const glm::vec2 imgPos {imgPosX[sliceX], imgPosY[sliceY]};
        const glm::vec2 imgSize {imgSizeX[sliceX], imgSizeY[sliceY]};
        const glm::vec2 texPos {texPosX[sliceX], texPosY[sliceY]};
        const glm::vec2 texSizeSlice {texSizeX[sliceX], texSizeY[sliceY]};

        // clang-format off
        (*mVertices)[v + 1] = {{imgPos.x            , imgPos.y            }, {texPos.x,                  texPos.y                 }, 0};
        (*mVertices)[v + 2] = {{imgPos.x            , imgPos.y + imgSize.y}, {texPos.x,                  texPos.y + texSizeSlice.y}, 0};
        (*mVertices)[v + 3] = {{imgPos.x + imgSize.x, imgPos.y            }, {texPos.x + texSizeSlice.x, texPos.y                 }, 0};
        (*mVertices)[v + 4] = {{imgPos.x + imgSize.x, imgPos.y + imgSize.y}, {texPos.x + texSizeSlice.x, texPos.y + texSizeSlice.y}, 0};
        // clang-format on

        // Round vertices.
        for (int i {1}; i < 5; ++i)
            (*mVertices)[v + i].position = glm::round((*mVertices)[v + i].position);

        // Make duplicates of first and last vertex so this can be rendered as a triangle strip.
        (*mVertices)[v + 0] = (*mVertices)[v + 1];
        (*mVertices)[v + 5] = (*mVertices)[v + 4];

        v += 6;
    }

    updateColors();
}

void NinePatchComponent::updateColors()
{
    if (mVertices == nullptr)
        return;

    for (int i {0}; i < 6 * 9; ++i)
        (*mVertices)[i].color = mFrameColor;
}