
#ifndef RENDERER_H
#define RENDERER_H

#include "fastmath.hpp"
#include "model.hpp"
#include "polygon.hpp"
#include "rendertarget.hpp"
#include <cstring>
#include <memory.h>

#define SCREEN_SPACE_SIZE 2.0

class Renderer {

  public:
    Rendertarget *target;

    bool clearTarget = true;
    vec3 sunDir = vec3(1.0, -1.0, 1.0).normalize();

    void toScreenSpace(vec3 *np, mat4 matrix) {
        vec4 tp = (matrix * vec4(*np, decimal(1.0f)));
        tp.x() = tp.x() / tp.z() * decimal(SCREEN_SPACE_SIZE) +
                 decimal(SCREEN_SPACE_SIZE);
        tp.y() = tp.y() / tp.z() * decimal(SCREEN_SPACE_SIZE) +
                 decimal(SCREEN_SPACE_SIZE);
        *np = vec3(tp.x(), tp.y(), tp.z());
    }

    void render(const model *model, const mat4 matrix) {
        decimal widthScale =
            decimal(SCREEN_SPACE_SIZE * 2) / decimal((float)target->width);
        decimal heightScale =
            decimal(SCREEN_SPACE_SIZE * 2) / decimal((float)target->height);

        decimal invWidthScale =
            decimal((float)(target->width / SCREEN_SPACE_SIZE / 2));
        decimal invHeightScale =
            decimal((float)(target->height / SCREEN_SPACE_SIZE / 2));

        // TODO clear target with memset

        vec3 verts[model->verts.size()] = {};

        std::copy(model->verts.begin(), model->verts.end(), verts);

        for (int i = 0; i < model->verts.size(); i++) {
            toScreenSpace(verts + i, matrix);
        }

        vec3 normals[model->normals.size()] = {};
        mat3 normalMatrix = matrix.cutTo<mat3>();
        for (int i = 0; i < model->normals.size(); i++) {
            normals[i] = normalMatrix * model->normals[i];
        }

        polygon testP;

        for (int f = 0; f < model->faces.size(); f += 3) {
            testP.small = false;
            for (int p = 0; p < 3; p++) {
                testP.points[p] = verts[std::get<0>(model->faces[f + p])];
                testP.colors[p] =
                    model->colors[std::get<0>(model->faces[f + p])];
                testP.normals[p] = normals[std::get<1>(model->faces[f + p])];
            }
            if ((testP.avgNormal() * vec3(0.0, 0.0, 1.0)) > decimal(0.))
                continue;

            testP.calcDelta();

            int startX = std::max<int>(
                (testP.bounding[0] * invWidthScale).i >> SHIFT_AMOUNT, 0);
            int endX = std::max<int>(
                std::min<int>((testP.bounding[1] * invWidthScale).i >>
                                  SHIFT_AMOUNT,
                              (uint32_t)target->width - 1),
                0);
            int startY = std::max<int>(
                (testP.bounding[2] * invHeightScale).i >> SHIFT_AMOUNT, 0);
            int endY = std::min<int>((testP.bounding[3] * invHeightScale).i >>
                                         SHIFT_AMOUNT,
                                     target->height - 1);

            std::cout << "Polys:\n" << testP.baryFactor << "\n";
            for (int i = 0; i < 3; i++) {
                std::cout << testP.points[i];
            }
            std::cout << "Boundings:\n";
            std::cout << testP.bounding[0] << " " << startX << "\n";
            std::cout << testP.bounding[1] << " " << endX << "\n";
            std::cout << testP.bounding[2] << " " << startY << "\n";
            std::cout << testP.bounding[3] << " " << endY << "\n";

            vec3 pos = vec3(testP.bounding[0], testP.bounding[2], 0.0);
            for (int x = 0; x < target->width; x++) {
                for (int y = 0; y < target->height; y++) {
                    // for (int x = startX; x < endX; x++) {
                    //     for (int y = startY; y < endY; y++) {

                    testP.calcBarycentric(pos);
                    if (testP.contains(pos)) {

                        // testP.calcBarycentric(pos);
                        decimal depth = testP.calcDepth();
                        if (depth < target->getDepth(x, y)) {
                            // std::cout << factors << std::endl;
                            vec3 normal = testP.calcNormal();
                            vec3 color = testP.calcColor();
                            decimal lightFac =
                                std::max(normal * (-sunDir), decimal(0.0)) +
                                decimal(0.5);
                            ;
                            target->setDepth(x, y, depth);
                            // target->set(x, y,
                            //             (color * decimal(120.0)) * lightFac);

                            // target->set(x, y,
                            // vec3(lightFac * decimal(200.0), 0, 0));
                            // target->set(x, y,
                            //             (normal + vec3(1.0, 1.0, 1.0)) *
                            //                 decimal(120.0));
                            // target->set(
                            //     x, y,
                            //     (testP.avgNormal() + vec3(1.0, 1.0, 1.0)) *
                            //         decimal(120.0));
                            target->set(x, y,
                                        testP.barycentrics * decimal(200.0));
                            // if (!factors.isSmall())
                            //   target->set(x, y, vec3(0., 255.0, 0.));
                        }
                        // target->set(x, y, vec3(0.0, 255.0, 0.0));
                    }
                    pos.y() += heightScale;
                }
                pos.y() = decimal(testP.bounding[2]);
                pos.x() += widthScale;
            }
        }
    }
};

#endif
