#pragma once
#include "../GPU/Primitives/primitive_poly_types.hpp"
#include "../GPU/gpu_types.hpp"
#include "../../math.hpp"
#include "../../stdio.hpp"

namespace JabyEngine {
    namespace GTE {
        namespace internal {
            template<typename T>
            struct VECTOR {
                T x;
                T y;
                T z;
                T pad;

                static constexpr VECTOR create() {
                    return VECTOR::create(0, 0, 0);
                }

                static constexpr VECTOR create(T x, T y, T z) {
                    return VECTOR{.x = x, .y = y, .z = z, .pad = 0};
                }

                static constexpr VECTOR create(gte_float x, gte_float y, gte_float z) {
                    return VECTOR{.x = static_cast<T>(x), .y = static_cast<T>(y), .z = static_cast<T>(z)};
                }

                template<typename S>
                static constexpr VECTOR from(const GPU::Position<S>& pos) {
                    return VECTOR::create(static_cast<T>(pos.x), static_cast<T>(pos.y), 0);
                }

                template<typename S>
                constexpr S to() const {
                    return S::create(static_cast<S::PrimitiveType>(this->x), static_cast<S::PrimitiveType>(this->y));
                }
            };
        }
        using VECTOR  = internal::VECTOR<int32_t>;
        using SVECTOR = internal::VECTOR<int16_t>;

        struct ROTMATRIX {
            int16_t matrix[3][3];

            static constexpr ROTMATRIX identity() {
                return ROTMATRIX{.matrix = {
                        {static_cast<int16_t>(gte_float::one()), 0, 0},
                        {0, static_cast<int16_t>(gte_float::one()), 0},
                        {0, 0, static_cast<int16_t>(gte_float::one())}
                    }
                };
            } 
            
            static constexpr ROTMATRIX scaled(gte_float sx = 1.0_gf, gte_float sy = 1.0_gf, gte_float sz = 1.0_gf) {
                return ROTMATRIX{.matrix = {
                        {static_cast<int16_t>(sx), 0, 0},
                        {0, static_cast<int16_t>(sy), 0},
                        {0, 0, static_cast<int16_t>(sz)}
                    }
                };                
            }

            static ROTMATRIX rotated(deg_t x = 0.0_deg, deg_t y = 0.0_deg, deg_t z = 0.0_deg);
        };

        struct TRANSFERVECTOR : public VECTOR {
            static constexpr TRANSFERVECTOR identity() {
                return TRANSFERVECTOR::translated();
            }

            static constexpr TRANSFERVECTOR translated(int32_t x = 0, int32_t y = 0, int32_t z = 0) {
                return TRANSFERVECTOR{{.x = x, .y = y, .z = z}};
            }
        };
        
        struct MATRIX {
            ROTMATRIX      rotation;
            TRANSFERVECTOR transfer;

            static constexpr MATRIX identity() {
                return MATRIX{.rotation = ROTMATRIX::identity(), .transfer = TRANSFERVECTOR::identity()};
            }

            static constexpr MATRIX translated(int32_t x = 0, int32_t y = 0, int32_t z = 0) {
                return MATRIX{.rotation = ROTMATRIX::identity(), .transfer = TRANSFERVECTOR::translated(x, y, z)};
            }

            static constexpr MATRIX scaled(gte_float sx = 1.0_gf, gte_float sy = 1.0_gf, gte_float sz = 1.0_gf) {
                return MATRIX{.rotation = ROTMATRIX::scaled(sx, sy, sz), .transfer = TRANSFERVECTOR::identity()};
            }

            static MATRIX rotated(deg_t x = 0.0_deg, deg_t y = 0.0_deg, deg_t z = 0.0_deg) {
                return MATRIX{.rotation = ROTMATRIX::rotated(x, y, z), .transfer = TRANSFERVECTOR::identity()};
            }

            static MATRIX comp(MATRIX new_matrix, const MATRIX& matrix) {
                new_matrix.comp(matrix);
                return new_matrix;
            }

            void dump() const {
                printf("---\n");
                printf("|%i|%i|%i|\n", this->rotation.matrix[0][0], this->rotation.matrix[0][1], this->rotation.matrix[0][2]);
                printf("|%i|%i|%i|\n", this->rotation.matrix[1][0], this->rotation.matrix[1][1], this->rotation.matrix[1][2]);
                printf("|%i|%i|%i|\n", this->rotation.matrix[2][0], this->rotation.matrix[2][1], this->rotation.matrix[2][2]);
                printf("~~~\n");
                printf("|%i|%i|%i|\n", this->transfer.x, this->transfer.y, this->transfer.z);
                printf("---\n");
            }

            MATRIX& comp(const MATRIX& matrix);
            MATRIX& translate(int32_t x = 0, int32_t y = 0, int32_t z = 0) {
                return MATRIX::comp(MATRIX::translated(x, y, z));
            }

            MATRIX& scale(gte_float sx = 1.0_gf, gte_float sy = 1.0_gf, gte_float sz = 1.0_gf) {
                return MATRIX::comp(MATRIX::scaled(sx, sy, sz));
            }

            MATRIX& rotate(deg_t x = 0.0_deg, deg_t y = 0.0_deg, deg_t z = 0.0_deg) {
                return MATRIX::comp(MATRIX::rotated(x, y, z));
            }

            GPU::Vertex& apply_to(GPU::Vertex& vertex)       const;
            GPU::Vertex  apply_to(const GPU::Vertex& vertex) const;

            template<typename T>
            T& apply_to_area(T& poly, const GPU::AreaI16& area) const {
                poly.vertex0 = MATRIX::apply_to(GPU::POLY_G4::vertex0_from(area));
                poly.vertex1 = MATRIX::apply_to(GPU::POLY_G4::vertex1_from(area));
                poly.vertex2 = MATRIX::apply_to(GPU::POLY_G4::vertex2_from(area));
                poly.vertex3 = MATRIX::apply_to(GPU::POLY_G4::vertex3_from(area));
                return poly;
            }
        };
    }
}