const std = @import("std");

// const gl = @import("zopengl").wrapper;
const zgl = @import("zgl");
const zm = @import("zmath");

const Shader = @This();

id: zgl.Program,

pub fn initSource(vertex_source: []const u8, fragment_source: []const u8) !Shader {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const vertex = zgl.Shader.create(.vertex);
    vertex.source(1, &vertex_source);
    vertex.compile();

    const fragment = zgl.Shader.create(.fragment);
    fragment.source(1, &fragment_source);
    fragment.compile();

    const program = zgl.Program.create();
    program.attach(vertex);
    program.attach(fragment);
    program.link();

    return .{ .id = program };
}

pub fn initFile(vertex_path: []const u8, fragment_path: []const u8) !Shader {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const vertex_source = try readFromFile(arena.allocator(), vertex_path);
    const fragment_source = try readFromFile(arena.allocator(), fragment_path);
    return initSource(vertex_source, fragment_source);
}

pub fn use(self: Shader) void {
    self.id.use();
}

pub fn updateUniformMatrix(self: Shader, name: [:0]const u8, matrices: []const zm.Mat) void {
    self.id.uniformMatrix4(self.id.uniformLocation(name), false, matrices);
}

pub fn updateUniformVec4(self: Shader, name: [:0]const u8, vectors: []const zm.Vec) void {
    if (self.id.uniformLocation(name)) |location| {
        zgl.binding.programUniform4fv(@intFromEnum(self.id), @intCast(location), @intCast(vectors.len), @ptrCast(vectors));
    }
}

pub fn updateUniformVec3(self: Shader, name: [:0]const u8, vectors: []const [3]f32) void {
    if (self.id.uniformLocation(name)) |location| {
        zgl.binding.programUniform3fv(@intFromEnum(self.id), @intCast(location), @intCast(vectors.len), @ptrCast(vectors));
    }
    // zgl.uniform3fv(self.id.uniformLocation(name), vectors);
}

pub fn updateUniformInt(self: Shader, name: [:0]const u8, value: i32) void {
    self.id.uniform1i(self.id.uniformLocation(name), value);
}

pub fn delete(self: Shader) void {
    self.id.delete();
}

/// The caller is responsible for freeing the content.
fn readFromFile(allocator: std.mem.Allocator, file_path: []const u8) ![:0]u8 {
    return std.fs.cwd().readFileAllocOptions(allocator, file_path, std.math.maxInt(usize), null, .@"1", 0);
}
