#ifndef SPLITTER_DRAFT_SRC_L2_MARIE_RASTERIZATION_H
#define SPLITTER_DRAFT_SRC_L2_MARIE_RASTERIZATION_H

#include "../../../gen/l1/geom.h"
#include "../../l1/marie/geom_alg_utils.h"

typedef struct {
    /* guest, x, y, attribute (custom) */
    void (*fn)(void*, S32, S32, MarieVertAttr);
    void* guest;
} FnMarieRasterizerCallback;

typedef struct {
    float c1;
    float c0;
} MarieLinearFun;


/* f(y) = (y + 0.5 - a.y) / (b.y - a.y) * (b.x - a.x) + a.x - 0.5 */
MarieLinearFun marie_gen_scanline_borderline(vec2 a, vec2 b) {
    return (MarieLinearFun){.c1 = (b.x - a.x) / (b.y - a.y),
        .c0 = (0.5f - a.y) * (b.x - a.x) / (b.y - a.y) + a.x - 0.5f };
}

/* Utility function, that is used by rasterization function.
 * Don't use in your code*/
void marie_h_rasterize_only_one_line_of_triangle(
    vec2 v0pos, vec2 v1pos, vec2 v2pos, S32 L, S32 R, MarieVertAttr P0, MarieVertAttr P1, MarieVertAttr P2,
    float S, S32 my, FnMarieRasterizerCallback cb
    ) {
    float E01 = marie_surface(v0pos, v1pos, (vec2){(float)L + 0.5f, (float)my + 0.5f});
    float E12 = marie_surface(v1pos, v2pos, (vec2){(float)L + 0.5f, (float)my + 0.5f});
    float E20 = marie_surface(v2pos, v0pos, (vec2){(float)L + 0.5f, (float)my + 0.5f});
    for (S32 x = L; x <= R; x++) {
        if (marie_same_dir3(E01, E12, E20, S)) {
            cb.fn(cb.guest, x, my, vec4_add_vec4(vec4_mul_scal(P0, E12),
                vec4_add_vec4(vec4_mul_scal(P1, E20), vec4_mul_scal(P2, E01))));
        }
        E01 += (v0pos.y - v1pos.y);
        E12 += (v1pos.y - v2pos.y);
        E20 += (v2pos.y - v0pos.y);
    }
}

/* Utility function, that is used by rasterization function.
 * Don't use in your code*/
void marie_h_rasterize_half_of_triangle(
    vec2 v0pos, vec2 v1pos, vec2 v2pos, S32 below, S32 above, MarieVertAttr P0, MarieVertAttr P1, MarieVertAttr P2,
    MarieLinearFun left_border, MarieLinearFun right_border, FnMarieRasterizerCallback cb
    ) {
    for (S32 my = below + 1; my < above; my++) {
        S32 left = (S32)ceilf(left_border.c1 * (float)my + left_border.c0);
        S32 right = (S32)floorf(right_border.c1 * (float)my + right_border.c0);
        MarieVertAttr Q0 = vec4_mul_scal(P0, marie_surface(v1pos, v2pos, (vec2){(float)left + 0.5f, (float)my + 0.5f}));
        MarieVertAttr Q1 = vec4_mul_scal(P1, marie_surface(v2pos, v0pos, (vec2){(float)left + 0.5f, (float)my + 0.5f}));
        MarieVertAttr Q2 = vec4_mul_scal(P2, marie_surface(v0pos, v1pos, (vec2){(float)left + 0.5f, (float)my + 0.5f}));
        MarieVertAttr dq0 = vec4_mul_scal(P0, v1pos.y - v2pos.y);
        MarieVertAttr dq1 = vec4_mul_scal(P1, v2pos.y - v0pos.y);
        MarieVertAttr dq2 = vec4_mul_scal(P2, v0pos.y - v1pos.y);
        for (S32 x = left; x <= right; x++) {
            cb.fn(cb.guest, x, my, vec4_add_vec4(Q0, vec4_add_vec4(Q1, Q2)));
            Q0 = vec4_add_vec4(Q0, dq0);
            Q1 = vec4_add_vec4(Q1, dq1);
            Q2 = vec4_add_vec4(Q2, dq2);
        }
    }
}

/* Utility function, that is used by rasterization function.
 * Don't use in your code*/
void marie_h_rasterize_triangle_with_attr_sorted(
    MariePlaneVertAttr v0, MariePlaneVertAttr v1, MariePlaneVertAttr v2, FnMarieRasterizerCallback cb
    ) {
    float S = marie_surface(v0.pos, v1.pos, v2.pos);
    S32 sx0 = (S32)roundf(v0.pos.x - 0.5f);
    S32 sy0 = (S32)roundf(v0.pos.y - 0.5f);
    S32 sx1 = (S32)roundf(v1.pos.x - 0.5f);
    S32 sy1 = (S32)roundf(v1.pos.y - 0.5f);
    S32 sx2 = (S32)roundf(v2.pos.x - 0.5f);
    S32 sy2 = (S32)roundf(v2.pos.y - 0.5f);

    S32 L = MIN_S32(sx0, MIN_S32(sx1, sx2));
    S32 R = MAX_S32(sx0, MAX_S32(sx1, sx2));

    MarieVertAttr P0 = vec4_div_by_scal(v0.attr, S);
    MarieVertAttr P1 = vec4_div_by_scal(v1.attr, S);
    MarieVertAttr P2 = vec4_div_by_scal(v2.attr, S);

    /* I compile with -fno-trapping-math, so I hope nothing bad would happen */
    MarieLinearFun line_01 = marie_gen_scanline_borderline(v0.pos, v1.pos);
    MarieLinearFun line_12 = marie_gen_scanline_borderline(v1.pos, v2.pos);
    MarieLinearFun line_20 = marie_gen_scanline_borderline(v2.pos, v0.pos);

    marie_h_rasterize_only_one_line_of_triangle(v0.pos, v1.pos, v2.pos, L, R, P0, P1, P2, S, sy0, cb);
    if (sy0 + 1 < sy1) {
        MarieLinearFun left_border = S > 0 ? line_20 : line_01;
        MarieLinearFun right_border = S > 0 ? line_01 : line_20;
        marie_h_rasterize_half_of_triangle(v0.pos, v1.pos, v2.pos, sy0, sy1, P0, P1, P2, left_border, right_border, cb);
    }
    if (sy1 > sy0) {
        marie_h_rasterize_only_one_line_of_triangle(v0.pos, v1.pos, v2.pos, L, R, P0, P1, P2, S, sy1, cb);
    }
    if (sy1 + 1 < sy2) {
        MarieLinearFun left_border = S > 0 ? line_20 : line_12;
        MarieLinearFun right_border = S > 0 ? line_12 : line_20;
        marie_h_rasterize_half_of_triangle(v0.pos, v1.pos, v2.pos, sy1, sy2, P0, P1, P2, left_border, right_border, cb);
    }
    if (sy2 > sy1) {
        marie_h_rasterize_only_one_line_of_triangle(v0.pos, v1.pos, v2.pos, L, R, P0, P1, P2, S, sy2, cb);
    }
}
void marie_rasterize_triangle_with_attr(
    MariePlaneVertAttr a, MariePlaneVertAttr b, MariePlaneVertAttr c, FnMarieRasterizerCallback cb
    ) {
    if (a.pos.y < b.pos.y) {
        if (b.pos.y < c.pos.y) {
            marie_h_rasterize_triangle_with_attr_sorted(a, b, c, cb);
        } else {
            if (a.pos.y < c.pos.y) {
                marie_h_rasterize_triangle_with_attr_sorted(a, c, b, cb);
            } else {
                marie_h_rasterize_triangle_with_attr_sorted(c, a, b, cb);
            }
        }
    } else {
        if (a.pos.y < c.pos.y) {
            marie_h_rasterize_triangle_with_attr_sorted(b, a, c, cb);
        } else {
            if (b.pos.y < c.pos.y) {
                marie_h_rasterize_triangle_with_attr_sorted(b, c, a, cb);
            } else {
                marie_h_rasterize_triangle_with_attr_sorted(c, b, a, cb);
            }
        }
    }
}

#endif
