const std = @import("std");
const builtin = @import("builtin");
const dvui = @import("dvui");
const RenderEngine = @import("RenderEngine.zig").RenderEngine;
const Document = @import("../models/Document.zig");
const basic_models = @import("../models/basic_models.zig");
const cpu_draw = @import("cpu/draw.zig");
const RenderStats = @import("RenderStats.zig");
const Size_i = basic_models.Size_i;
const Rect_i = basic_models.Rect_i;
const Allocator = std.mem.Allocator;
const Color = dvui.Color;

const CpuRenderEngine = @This();
const Type = enum {
    Gradient,
    Squares,
};

type: Type,
gradient_start: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 },
gradient_end: Color.PMA = .{ .r = 255, .g = 255, .b = 255, .a = 255 },
_allocator: Allocator,
_renderStats: RenderStats,

pub fn init(allocator: Allocator, render_type: Type) CpuRenderEngine {
    return .{
        ._allocator = allocator,
        .type = render_type,
        ._renderStats = .{
            .render_time_ns = 0,
        },
    };
}

pub fn exampleReset(self: *CpuRenderEngine) void {
    var prng = std.Random.DefaultPrng.init(@intCast(std.time.microTimestamp()));
    const random = prng.random();
    self.gradient_start = Color.PMA{ .r = random.int(u8), .g = random.int(u8), .b = random.int(u8), .a = 255 };
    self.gradient_end = Color.PMA{ .r = random.int(u8), .g = random.int(u8), .b = random.int(u8), .a = 255 };
}

fn renderGradient(self: CpuRenderEngine, pixels: []Color.PMA, width: u32, height: u32, full_w: u32, full_h: u32, visible_rect: Rect_i) void {
    var y: u32 = 0;
    while (y < height) : (y += 1) {
        var x: u32 = 0;
        while (x < width) : (x += 1) {
            const gx: u32 = visible_rect.x + x;
            const gy: u32 = visible_rect.y + y;

            const denom_x: f32 = if (full_w > 1) @as(f32, @floatFromInt(full_w - 1)) else 1;
            const denom_y: f32 = if (full_h > 1) @as(f32, @floatFromInt(full_h - 1)) else 1;
            const fx: f32 = @as(f32, @floatFromInt(gx)) / denom_x;
            const fy: f32 = @as(f32, @floatFromInt(gy)) / denom_y;
            const factor: f32 = std.math.clamp((fx + fy) / 2, 0, 1);

            const r_f: f32 = @as(f32, @floatFromInt(self.gradient_start.r)) + factor * (@as(f32, @floatFromInt(self.gradient_end.r)) - @as(f32, @floatFromInt(self.gradient_start.r)));
            const g_f: f32 = @as(f32, @floatFromInt(self.gradient_start.g)) + factor * (@as(f32, @floatFromInt(self.gradient_end.g)) - @as(f32, @floatFromInt(self.gradient_start.g)));
            const b_f: f32 = @as(f32, @floatFromInt(self.gradient_start.b)) + factor * (@as(f32, @floatFromInt(self.gradient_end.b)) - @as(f32, @floatFromInt(self.gradient_start.b)));

            const r: u8 = @intFromFloat(std.math.clamp(r_f, 0, 255));
            const g: u8 = @intFromFloat(std.math.clamp(g_f, 0, 255));
            const b: u8 = @intFromFloat(std.math.clamp(b_f, 0, 255));
            pixels[y * width + x] = .{ .r = r, .g = g, .b = b, .a = 255 };
        }
    }
}

fn renderSquares(pixels: []Color.PMA, canvas_size: Size_i, visible_rect: Rect_i) void {
    const colors = [_]Color.PMA{
        .{ .r = 255, .g = 0, .b = 0, .a = 255 },
        .{ .r = 255, .g = 165, .b = 0, .a = 255 },
        .{ .r = 255, .g = 255, .b = 0, .a = 255 },
        .{ .r = 0, .g = 255, .b = 0, .a = 255 },
        .{ .r = 0, .g = 255, .b = 255, .a = 255 },
        .{ .r = 0, .g = 0, .b = 255, .a = 255 },
    };

    const squares_num = 5;
    var thikness: u32 = @intFromFloat(@as(f32, @floatFromInt(canvas_size.w + canvas_size.h)) / 2 * 0.03);
    if (thikness == 0) thikness = 1;

    const squares_sum_w = canvas_size.w - thikness * (squares_num + 1);
    const base_w = squares_sum_w / squares_num;
    const extra_w = squares_sum_w % squares_num;
    const squares_sum_h = canvas_size.h - thikness * (squares_num + 1);
    const base_h = squares_sum_h / squares_num;
    const extra_h = squares_sum_h % squares_num;

    var x_pos: [6]u32 = undefined;
    x_pos[0] = 0;
    for (1..squares_num + 1) |i| {
        const w = base_w + if (i - 1 < extra_w) @as(u32, 1) else 0;
        x_pos[i] = x_pos[i - 1] + thikness + w;
    }

    var y_pos: [6]u32 = undefined;
    y_pos[0] = 0;
    for (1..squares_num + 1) |i| {
        const h = base_h + if (i - 1 < extra_h) @as(u32, 1) else 0;
        y_pos[i] = y_pos[i - 1] + thikness + h;
    }

    var y: u32 = 0;
    while (y < visible_rect.h) : (y += 1) {
        const canvas_y = y + visible_rect.y;
        if (canvas_y >= canvas_size.h) continue;
        var x: u32 = 0;
        while (x < visible_rect.w) : (x += 1) {
            const canvas_x = x + visible_rect.x;
            if (canvas_x >= canvas_size.w) continue;

            var vertical_index: ?u32 = null;
            for (0..x_pos.len) |i| {
                if (canvas_x >= x_pos[i] and canvas_x < x_pos[i] + thikness) {
                    vertical_index = @intCast(i);
                    break;
                }
            }

            var horizontal_index: ?u32 = null;
            for (0..y_pos.len) |i| {
                if (canvas_y >= y_pos[i] and canvas_y < y_pos[i] + thikness) {
                    horizontal_index = @intCast(i);
                    break;
                }
            }

            if (vertical_index) |idx| {
                pixels[y * visible_rect.w + x] = colors[idx];
            } else if (horizontal_index) |idx| {
                pixels[y * visible_rect.w + x] = colors[idx];
            } else {
                var square_x: u32 = 0;
                for (0..squares_num) |i| {
                    if (canvas_x >= x_pos[i] + thikness and canvas_x < x_pos[i + 1]) {
                        square_x = @intCast(i);
                        break;
                    }
                }
                var square_y: u32 = 0;
                for (0..squares_num) |i| {
                    if (canvas_y >= y_pos[i] + thikness and canvas_y < y_pos[i + 1]) {
                        square_y = @intCast(i);
                        break;
                    }
                }
                if (square_x % 2 == square_y % 2) {
                    pixels[y * visible_rect.w + x] = .{ .r = 255, .g = 255, .b = 255, .a = 255 };
                } else {
                    pixels[y * visible_rect.w + x] = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
                }
            }
        }
    }
}

pub fn example(self: CpuRenderEngine, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
    const full_w = canvas_size.w;
    const full_h = canvas_size.h;

    const width = visible_rect.w;
    const height = visible_rect.h;

    const pixels = try self._allocator.alloc(Color.PMA, @as(usize, width) * height);
    defer self._allocator.free(pixels);

    switch (self.type) {
        .Gradient => self.renderGradient(pixels, width, height, full_w, full_h, visible_rect),
        .Squares => renderSquares(pixels, canvas_size, visible_rect),
    }

    return try dvui.textureCreate(pixels, width, height, .nearest, .abgr_8_8_8_8);
}

pub fn renderEngine(self: *CpuRenderEngine) RenderEngine {
    return .{ .cpu = self };
}

/// Растеризует документ в текстуру (фон + фигуры через конвейер).
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
    const width = visible_rect.w;
    const height = visible_rect.h;
    const pixels = try self._allocator.alloc(Color.PMA, @as(usize, width) * height);
    defer self._allocator.free(pixels);

    for (pixels) |*p| p.* = .{ .r = 255, .g = 255, .b = 255, .a = 255 };
    var t = try std.time.Timer.start();
    try cpu_draw.drawDocument(pixels, width, height, visible_rect, document, canvas_size, self._allocator);
    self._renderStats.render_time_ns = t.read();

    return try dvui.textureCreate(pixels, width, height, .nearest, .abgr_8_8_8_8);
}

pub fn getStats(self: CpuRenderEngine) RenderStats {
    return self._renderStats;
}
