/* Implementation of a simple rasterized 3D renderer in pure C without external dependencies.
   This application is monothreaded and draws only triangles to the terminal.
   Shades are determined by brightness related to a light source and drawn as ASCII characters.
   Colors are fully supported through the extended palette of the terminal.
   This program only uses standard ISO C99 code. */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>



#define WIDTH 80
#define HEIGHT 40

#define ZNEAR 0.1f
#define ZFAR 100.0f

#define FOV 90.0f

/* Aspect ration correction is needed, as we display on a 80x40 terminal, with characters being taller than they are wide */
#define ASPECT_RATIO_CORRECTION 0.5f


/* Types and basic structures */
typedef unsigned char u8_t;
typedef float f32_t;
typedef int i32_t;
typedef unsigned int u32_t;

typedef struct { u8_t r, g, b, a; } col_t;
typedef struct { f32_t x, y, z; } vec3_t;
typedef struct { f32_t m[3][3]; } mat3_t;
typedef struct { vec3_t p; col_t c; } vert_t;
typedef struct { vert_t a, b, c; } tri_t;
typedef struct { vec3_t p; u8_t a; } light_t;
typedef struct { i32_t x, y; f32_t z; } screen_vert_t;
typedef struct { screen_vert_t a, b, c; } screen_tri_t;


/* Shades are determined by brightness related to a light source. By default, the camera emits a light of intensity 1.0, for simplicity */
const u8_t *shades[] = {" ",  ".",  ":",  "-",  "=",  "+",  "*",  "#",  "%",  "@", "█"};
#define NSHADES (sizeof(shades) / sizeof(shades[0]))


/* Canvas to draw on screen */
col_t fb[HEIGHT][WIDTH]; // fb
f32_t zb[HEIGHT][WIDTH]; // z buffer


/* Utility functions */
const u8_t *get_shade(f32_t a) {
    u8_t shade = a / 255.0f * (NSHADES - 1);
    return shades[shade];
}
void set_color(col_t c) { printf("\x1b[38;2;%d;%d;%dm%s", c.r, c.g, c.b, get_shade(c.a)); }
void next_line() { printf("\x1b[0m\n"); }
void clear_screen(){
    printf("\x1b[2J"); // Clear screen
    printf("\x1b[H");  // Move cursor to home position
}


void clear_fb() {
    for(u32_t i = 0; i < HEIGHT * WIDTH; i++) {
        ((col_t*)fb)[i] = (col_t){0, 0, 0, 255}; // Black background with full alpha
        ((f32_t*)zb)[i] = ZFAR;
    }
}



// SECOND PART OF THE CODE. I WANT YOU TO CLEAN WHAT'S IN THAT SECOND PART AND MAKE IT USE THE CODE FROM THE FIRST PART, APPLYING THE SAME WRITING RULES AND NAMING CONVENTIONS AS ABOVE.
// USE SCREEN TRIANGLE WHEN IT IS NOT USED AND WE INSTANTIATE 3 SCREEN VERTICES, FOR EXAMPLE.
// REMOVE ANY DEAD CODE THAT YOU SEE.

// Example triangle
tri_t triangle = {
    .a = { .p = { -0.5f, -0.5f, 1.0f }, .c = { 255, 0, 0, 255 } }, // Red
    .b = { .p = { 0.5f, -0.5f, 1.0f }, .c = { 0, 255, 0, 255 } },  // Green
    .c = { .p = { 0.0f, 0.5f, 1.0f }, .c = { 0, 0, 255, 255 } }    // Blue
};

// Function to project 3D point to 2D screen space
typedef struct {
    int x, y;
    f32_t z;
} screen_vertex_t;

// Simple orthographic projection
screen_vertex_t project_vertex(vec3_t p) {
    screen_vertex_t sv;
    sv.x = (int)((p.x + 1.0f) * 0.5f * WIDTH);
    sv.y = (int)((1.0f - (p.y + 1.0f) * 0.5f) * HEIGHT); // Invert Y for screen space
    sv.z = p.z;
    return sv;
}

// Function to compute barycentric coordinates
void compute_barycentric(int px, int py, screen_vertex_t v0, screen_vertex_t v1, screen_vertex_t v2, f32_t *u, f32_t *v, f32_t *w) {
    f32_t denom = (f32_t)((v1.y - v2.y)*(v0.x - v2.x) + (v2.x - v1.x)*(v0.y - v2.y));
    if (denom == 0) { // Degenerate triangle
        *u = *v = *w = -1;
        return;
    }
    *u = ((v1.y - v2.y)*(px - v2.x) + (v2.x - v1.x)*(py - v2.y)) / denom;
    *v = ((v2.y - v0.y)*(px - v2.x) + (v0.x - v2.x)*(py - v2.y)) / denom;
    *w = 1.0f - *u - *v;
}

// Function to rasterize a single triangle
void rasterize_triangle(tri_t tri) {
    // Project vertices to screen space
    screen_vertex_t sv0 = project_vertex(tri.a.p);
    screen_vertex_t sv1 = project_vertex(tri.b.p);
    screen_vertex_t sv2 = project_vertex(tri.c.p);

    // Compute bounding box
    int minX = sv0.x < sv1.x ? (sv0.x < sv2.x ? sv0.x : sv2.x) : (sv1.x < sv2.x ? sv1.x : sv2.x);
    int maxX = sv0.x > sv1.x ? (sv0.x > sv2.x ? sv0.x : sv2.x) : (sv1.x > sv2.x ? sv1.x : sv2.x);
    int minY = sv0.y < sv1.y ? (sv0.y < sv2.y ? sv0.y : sv2.y) : (sv1.y < sv2.y ? sv1.y : sv2.y);
    int maxY = sv0.y > sv1.y ? (sv0.y > sv2.y ? sv0.y : sv2.y) : (sv1.y > sv2.y ? sv1.y : sv2.y);

    // Clamp to screen dimensions
    if(minX < 0) minX = 0;
    if(maxX >= WIDTH) maxX = WIDTH -1;
    if(minY < 0) minY = 0;
    if(maxY >= HEIGHT) maxY = HEIGHT -1;

    // Iterate over the bounding box
    for(int y = minY; y <= maxY; y++) {
        for(int x = minX; x <= maxX; x++) {
            f32_t u, v, w;
            compute_barycentric(x + 0.5f, y + 0.5f, sv0, sv1, sv2, &u, &v, &w);

            // Check if inside the triangle
            if(u >= 0 && v >= 0 && w >= 0) {
                // Interpolate depth
                f32_t depth = u * sv0.z + v * sv1.z + w * sv2.z;

                // Z-buffer test
                if(depth < zb[y][x]) {
                    zb[y][x] = depth;

                    // Interpolate color
                    col_t color;
                    color.r = (u * tri.a.c.r) + (v * tri.b.c.r) + (w * tri.c.c.r);
                    color.g = (u * tri.a.c.g) + (v * tri.b.c.g) + (w * tri.c.c.g);
                    color.b = (u * tri.a.c.b) + (v * tri.b.c.b) + (w * tri.c.c.b);
                    color.a = 255; // Full alpha

                    // Assign to fb
                    fb[y][x] = color;
                }
            }
        }
    }
}

// Function to render the fb to the terminal using brightness-based shading
void render_fb() {
    for(int y = 0; y < HEIGHT; y++) {
        for(int x = 0; x < WIDTH; x++) {
            col_t color = fb[y][x];
            // Calculate brightness
            f32_t brightness = (0.299f * color.r + 0.587f * color.g + 0.114f * color.b) / 255.0f;
            //int shade_idx = (int)(brightness * (NSHADES - 1));
            //if(shade_idx >= NSHADES) shade_idx = NSHADES -1;
            //const u8_t *shade = shades[shade_idx];
            //color.a = shade;
            color.a = brightness;
            set_color(color);
        }
        next_line();
    }
    // Reset color at the end
    printf("\x1b[0m");
}

// Alternative render function using fixed character
void render_fb_fixed_char() {
    for(int y = 0; y < HEIGHT; y++) {
        for(int x = 0; x < WIDTH; x++) {
            col_t color = fb[y][x];
            color.a = 255;
            set_color(color);
        }
        next_line();
    }
    // Reset color at the end
    printf("\x1b[0m");
}

int main() {
    // Clear the screen
    clear_screen();

    // Clear fb and zb
    clear_fb();

    // Rasterize the triangle
    rasterize_triangle(triangle);

    // Render the fb to terminal
    render_fb_fixed_char(); // Use this for fixed character rendering
    // render_fb(); // Use this for brightness-based shading

    return 0;
}