//! Wrapper for handling textures.
const Self = @This();

const std = @import("std");
const Allocator = std.mem.Allocator;
const gl = @import("opengl");

const OpenGL = @import("../OpenGL.zig");

const log = std.log.scoped(.opengl);

/// Options for initializing a texture.
pub const Options = struct {
    format: gl.Texture.Format,
    internal_format: gl.Texture.InternalFormat,
    target: gl.Texture.Target,
    min_filter: gl.Texture.MinFilter,
    mag_filter: gl.Texture.MagFilter,
    wrap_s: gl.Texture.Wrap,
    wrap_t: gl.Texture.Wrap,
};

texture: gl.Texture,

/// The width of this texture.
width: usize,
/// The height of this texture.
height: usize,

/// Format for this texture.
format: gl.Texture.Format,

/// Target for this texture.
target: gl.Texture.Target,

pub const Error = error{
    /// An OpenGL API call failed.
    OpenGLFailed,
};

/// Initialize a texture
pub fn init(
    opts: Options,
    width: usize,
    height: usize,
    data: ?[]const u8,
) Error!Self {
    const tex = gl.Texture.create() catch return error.OpenGLFailed;
    errdefer tex.destroy();
    {
        const texbind = tex.bind(opts.target) catch return error.OpenGLFailed;
        defer texbind.unbind();
        texbind.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed;
        texbind.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed;
        texbind.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed;
        texbind.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed;
        texbind.image2D(
            0,
            opts.internal_format,
            @intCast(width),
            @intCast(height),
            opts.format,
            .UnsignedByte,
            if (data) |d| @ptrCast(d.ptr) else null,
        ) catch return error.OpenGLFailed;
    }

    return .{
        .texture = tex,
        .width = width,
        .height = height,
        .format = opts.format,
        .target = opts.target,
    };
}

pub fn deinit(self: Self) void {
    self.texture.destroy();
}

/// Replace a region of the texture with the provided data.
///
/// Does NOT check the dimensions of the data to ensure correctness.
pub fn replaceRegion(
    self: Self,
    x: usize,
    y: usize,
    width: usize,
    height: usize,
    data: []const u8,
) Error!void {
    const texbind = self.texture.bind(self.target) catch return error.OpenGLFailed;
    defer texbind.unbind();
    texbind.subImage2D(
        0,
        @intCast(x),
        @intCast(y),
        @intCast(width),
        @intCast(height),
        self.format,
        .UnsignedByte,
        data.ptr,
    ) catch return error.OpenGLFailed;
}
