const sdl = @import("./sdl.zig").c;
const std = @import("std");
const GameState = @import("./game_state.zig").GameState;
const Offset = @import("./utils/offset.zig").Offset;
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
const FONT_SIZE = 24;

var font: ?*sdl.TTF_Font = null;
var text_init = false;

pub const GameTextFactoryController = struct {
    texts: std.ArrayList(GameText),

    pub fn init(allocator: std.mem.Allocator) !GameTextFactoryController {
        try initFont();
        return .{
            .texts = std.ArrayList(GameText).init(allocator),
        };
    }

    fn initFont() !void {
        const loaded_font = sdl.TTF_OpenFont("./assets/fonts/DepartureMonoNF-Regular.ttf", FONT_SIZE) orelse {
            sdl.SDL_Log("Error loading ttf font: %s\n", sdl.TTF_GetError());
            return error.FailedToLoadFont;
        };

        font = loaded_font;
        text_init = true;
    }

    pub fn deinit(self: *GameTextFactoryController) void {
        self.texts.deinit();
        deinitFont();
    }

    fn deinitFont() void {
        sdl.TTF_CloseFont(font);
        text_init = false;
    }

    pub fn addText(self: *GameTextFactoryController, text_ctx: GameTextContext) !void {
        try self.texts.append(GameText.initFromContext(text_ctx));
    }

    pub fn render(self: *GameTextFactoryController, renderer: *sdl.SDL_Renderer, game_state: *const GameState) !void {
        _ = game_state;
        for (self.texts.items) |text| {
            const c_text: [*c]const u8 = @ptrCast(text.text);
            // TODO dont create new surface if unchanged
            const text_surface: [*c]sdl.SDL_Surface = sdl.TTF_RenderText_Solid(font.?, c_text, text.color) orelse {
                sdl.SDL_Log("Error loading text surface: %s\n", sdl.SDL_GetError());
                return error.FailedToRenderSurface;
            };
            defer sdl.SDL_FreeSurface(text_surface);

            if (!text_init) {
                return error.TextNotInitialized;
            }

            // TODO dont create new texture if unchanged
            const text_texture = sdl.SDL_CreateTextureFromSurface(renderer, text_surface) orelse {
                sdl.SDL_Log("Error loading text texture: %s\n", sdl.SDL_GetError());
                return error.FailedToRenderTexture;
            };
            defer sdl.SDL_DestroyTexture(text_texture);

            const w: c_int = @intCast(text_surface.*.w);
            const h: c_int = @intCast(text_surface.*.h);
            const srcr = sdl.SDL_Rect{ .x = 0, .y = 0, .w = w, .h = h };
            const destr = sdl.SDL_Rect{ .x = @intCast(text.offset.x), .y = @intCast(text.offset.y), .w = w, .h = h };
            _ = sdl.SDL_RenderCopy(renderer, text_texture, &srcr, &destr);
        }
    }

    pub fn spawnFactory(self: *GameTextFactoryController, allocator: std.mem.Allocator) void {
        const texts = self.texts.items;
        var texts_copy = try allocator.alloc(GameText, texts.len);
        errdefer allocator.free(texts_copy);

        std.mem.copyForwards(GameText, texts_copy, texts);

        return .{
            .texts = &texts_copy,
        };
    }
};

pub const GameTextFactory = struct {
    texts: []GameText,
    text_surfaces: [*c]sdl.SDL_Surface,
};

pub const GameTextContext = struct {
    text: [*:0]const u8,
    color: sdl.SDL_Color = RGBAColor.whiteSmoke().tosdl(),
    // TODO Constraints??
    offset: Offset = .{ .x = 0, .y = 0 },
};

pub const GameText = struct {
    text: [*:0]const u8,
    color: sdl.SDL_Color,
    offset: Offset,

    // pub fn deinit(self: *GameText) void {
    //     sdl.SDL_FreeSurface(self.text_surface);
    // }

    pub fn setBlendMode(self: *GameText, blend: sdl.SDL_BlendMode) !void {
        _ = self;
        _ = blend;
        return error.Unimplemented;
    }

    pub fn setAlpha(self: *GameText, alpha: u8) !void {
        _ = self;
        _ = alpha;
        return error.Unimplemented;
    }

    pub fn initFromContext(ctx: GameTextContext) GameText {
        return .{
            .text = ctx.text,
            .color = ctx.color,
            .offset = ctx.offset,
        };
    }
};