//External includes
#include "SDL_surface.h"

//Project includes
#include "Renderer.h"
#include "Maths.h"

using namespace dae;

Renderer::Renderer(SDL_Window *pWindow) : m_pWindow(pWindow) {
    //Initialize
    SDL_GetWindowSize(pWindow, &m_Width, &m_Height);

    //Create Buffers
    m_pFrontBuffer = SDL_GetWindowSurface(pWindow);
    m_pBackBuffer = SDL_CreateRGBSurface(0, m_Width, m_Height, 32, 0, 0, 0, 0);
    m_pBackBufferPixels = (uint32_t *) m_pBackBuffer->pixels;

    m_pDepthBufferPixels = new float[m_Width * m_Height];

    //Initialize Camera
    m_Camera.Initialize(60.f, {.0f, .0f, -10.f});

    //Initialize Camera
    m_Camera.Initialize(60.f, {.0f, .0f, -10.f});
    m_aspectRatio = static_cast<float>(m_Width) / static_cast<float>(m_Height);
}

Renderer::~Renderer() {
    delete[] m_pDepthBufferPixels;
}

void Renderer::Update(Timer *pTimer) {
    m_Camera.Update(pTimer);
}

void Renderer::Render() {
    //@START
    //Lock BackBuffer
    //Random color
    SDL_FillRect(m_pBackBuffer, nullptr, m_ClearColor);
    SDL_LockSurface(m_pBackBuffer);
    std::fill_n(m_pDepthBufferPixels, m_Width * m_Height, std::numeric_limits<float>::max());

    //Clear
    SDL_FillRect(m_pBackBuffer, nullptr, m_ClearColor);


    ColorRGB finalColor{};
    constexpr int numVerticies = 3;

    std::vector<Vertex> verticiesScreenSpace{};
    VertexTransformationFunction(m_verticiesWorld, verticiesScreenSpace);
    const int numTriangles{static_cast<int>(m_verticiesWorld.size()) / numVerticies};

    for (int triangleIndex{}; triangleIndex < numTriangles; ++triangleIndex) {

        const Vector3 &vertexPos0{verticiesScreenSpace[triangleIndex * numVerticies].position};
        const Vector3 &vertexPos1{verticiesScreenSpace[triangleIndex * numVerticies + 1].position};
        const Vector3 &vertexPos2{verticiesScreenSpace[triangleIndex * numVerticies + 2].position};

        const float minX{std::min(vertexPos0.x, std::min(vertexPos1.x, vertexPos2.x))};
        const float minY{std::min(vertexPos0.y, std::min(vertexPos1.y, vertexPos2.y))};

        const float maxX{std::max(vertexPos0.x, std::max(vertexPos1.x, vertexPos2.x))};
        const float maxY{std::max(vertexPos0.y, std::max(vertexPos1.y, vertexPos2.y))};

        const int indexOffset{triangleIndex * numVerticies};

        const Vector3 side1{
                verticiesScreenSpace[indexOffset + 1].position - verticiesScreenSpace[indexOffset].position};
        const Vector3 side2{
                verticiesScreenSpace[indexOffset + 2].position - verticiesScreenSpace[indexOffset].position};

        const float area{0.5f * Vector3::Cross(side1, side2).z};

        const int startX = std::max(0, static_cast<int>(minX));
        const int endX = std::min(m_Width - 1, static_cast<int>(maxX));

        const int startY = std::max(0, static_cast<int>(minY));
        const int endY = std::min(m_Height - 1, static_cast<int>(maxY));

        for (int px{startX}; px < endX; ++px) {
            for (int py{startY}; py < endY; ++py) {

                Vector3 P{px + 0.5f, py + 0.5f, 1};

                bool inTriangle{true};
                float currDepth{0.0f};
                ColorRGB pointColor{};

                for (int vertexIndex{0}; vertexIndex < numVerticies; ++vertexIndex) {
                    const Vertex &nextVert{verticiesScreenSpace[(vertexIndex + 1) % numVerticies + indexOffset]};
                    const Vector3 &currentVecPos{verticiesScreenSpace[vertexIndex + indexOffset].position};

                    const Vector3 eVec{nextVert.position - currentVecPos};
                    const Vector3 pVec{P - currentVecPos};

                    const float crossResult{Vector3::Cross(eVec, pVec).z};

                    if (crossResult < 0) {
                        inTriangle = false;
                        break;
                    }

                    currDepth +=
                            verticiesScreenSpace[((vertexIndex + 2) % numVerticies + indexOffset)].position.z *
                            ((crossResult * 0.5f) / area);
                    pointColor += verticiesScreenSpace[((vertexIndex + 2) % numVerticies + indexOffset)].color *
                                  ((crossResult * 0.5f) / area);
                }

                if (!inTriangle) {
                    continue;
                }

                const int depthBufferIndex{px + (py * m_Width)};

                if (m_pDepthBufferPixels[depthBufferIndex] >= currDepth) {
                    m_pDepthBufferPixels[depthBufferIndex] = currDepth;

                    finalColor = pointColor;
                    finalColor.MaxToOne();

                    m_pBackBufferPixels[px + (py * m_Width)] = SDL_MapRGB(m_pBackBuffer->format,
                                                                          static_cast<uint8_t>(finalColor.r * 255),
                                                                          static_cast<uint8_t>(finalColor.g * 255),
                                                                          static_cast<uint8_t>(finalColor.b * 255));
                }
            }
        }
    }

    //RENDER LOGIC

    SDL_UnlockSurface(m_pBackBuffer);
    SDL_BlitSurface(m_pBackBuffer, nullptr, m_pFrontBuffer, nullptr);
    SDL_UpdateWindowSurface(m_pWindow);
}

void Renderer::VertexTransformationFunction(const std::vector<Vertex> &vertices_in,
                                            std::vector<Vertex> &vertices_out) const {
    for (const Vertex &vert: vertices_in) {
        Vector3 vertPos{m_Camera.invViewMatrix.TransformPoint(vert.position)};

        vertPos.x = (vertPos.x / vertPos.z) / (m_aspectRatio * m_Camera.fov);
        vertPos.y = (vertPos.y / vertPos.z) / m_Camera.fov;

        vertPos.x = (vertPos.x + 1) / 2 * static_cast<float>(m_Width);
        vertPos.y = (1 - vertPos.y) / 2 * static_cast<float>(m_Height);
        vertices_out.push_back(Vertex{vertPos, vert.color});
    }
}

bool Renderer::SaveBufferToImage() const {
    return SDL_SaveBMP(m_pBackBuffer, "Rasterizer_ColorBuffer.bmp");
}
