const Texture = @This();

const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");

id: c.GLuint,

pub inline fn active(target: c.GLenum) !void {
    glad.context.ActiveTexture.?(target);
    try errors.getError();
}

/// Enun for possible texture binding targets.
pub const Target = enum(c_uint) {
    @"1D" = c.GL_TEXTURE_1D,
    @"2D" = c.GL_TEXTURE_2D,
    @"3D" = c.GL_TEXTURE_3D,
    @"1DArray" = c.GL_TEXTURE_1D_ARRAY,
    @"2DArray" = c.GL_TEXTURE_2D_ARRAY,
    Rectangle = c.GL_TEXTURE_RECTANGLE,
    CubeMap = c.GL_TEXTURE_CUBE_MAP,
    Buffer = c.GL_TEXTURE_BUFFER,
    @"2DMultisample" = c.GL_TEXTURE_2D_MULTISAMPLE,
    @"2DMultisampleArray" = c.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
};

/// Enum for possible texture parameters.
pub const Parameter = enum(c_uint) {
    BaseLevel = c.GL_TEXTURE_BASE_LEVEL,
    CompareFunc = c.GL_TEXTURE_COMPARE_FUNC,
    CompareMode = c.GL_TEXTURE_COMPARE_MODE,
    LodBias = c.GL_TEXTURE_LOD_BIAS,
    MinFilter = c.GL_TEXTURE_MIN_FILTER,
    MagFilter = c.GL_TEXTURE_MAG_FILTER,
    MinLod = c.GL_TEXTURE_MIN_LOD,
    MaxLod = c.GL_TEXTURE_MAX_LOD,
    MaxLevel = c.GL_TEXTURE_MAX_LEVEL,
    SwizzleR = c.GL_TEXTURE_SWIZZLE_R,
    SwizzleG = c.GL_TEXTURE_SWIZZLE_G,
    SwizzleB = c.GL_TEXTURE_SWIZZLE_B,
    SwizzleA = c.GL_TEXTURE_SWIZZLE_A,
    WrapS = c.GL_TEXTURE_WRAP_S,
    WrapT = c.GL_TEXTURE_WRAP_T,
    WrapR = c.GL_TEXTURE_WRAP_R,
};

/// Internal format enum for texture images.
pub const InternalFormat = enum(c_int) {
    Red = c.GL_RED,
    RGBA = c.GL_RGBA,

    // There are so many more that I haven't filled in.
    _,
};

/// Format for texture images
pub const Format = enum(c_uint) {
    Red = c.GL_RED,
    BGRA = c.GL_BGRA,

    // There are so many more that I haven't filled in.
    _,
};

/// Data type for texture images.
pub const DataType = enum(c_uint) {
    UnsignedByte = c.GL_UNSIGNED_BYTE,

    // There are so many more that I haven't filled in.
    _,
};

pub const Binding = struct {
    target: Target,

    pub inline fn unbind(b: *Binding) void {
        glad.context.BindTexture.?(@intFromEnum(b.target), 0);
        b.* = undefined;
    }

    pub fn generateMipmap(b: Binding) void {
        glad.context.GenerateMipmap.?(@intFromEnum(b.target));
    }

    pub fn parameter(b: Binding, name: Parameter, value: anytype) !void {
        switch (@TypeOf(value)) {
            c.GLint => glad.context.TexParameteri.?(
                @intFromEnum(b.target),
                @intFromEnum(name),
                value,
            ),
            else => unreachable,
        }
    }

    pub fn image2D(
        b: Binding,
        level: c.GLint,
        internal_format: InternalFormat,
        width: c.GLsizei,
        height: c.GLsizei,
        border: c.GLint,
        format: Format,
        typ: DataType,
        data: ?*const anyopaque,
    ) !void {
        glad.context.TexImage2D.?(
            @intFromEnum(b.target),
            level,
            @intFromEnum(internal_format),
            width,
            height,
            border,
            @intFromEnum(format),
            @intFromEnum(typ),
            data,
        );
    }

    pub fn subImage2D(
        b: Binding,
        level: c.GLint,
        xoffset: c.GLint,
        yoffset: c.GLint,
        width: c.GLsizei,
        height: c.GLsizei,
        format: Format,
        typ: DataType,
        data: ?*const anyopaque,
    ) !void {
        glad.context.TexSubImage2D.?(
            @intFromEnum(b.target),
            level,
            xoffset,
            yoffset,
            width,
            height,
            @intFromEnum(format),
            @intFromEnum(typ),
            data,
        );
    }
};

/// Create a single texture.
pub inline fn create() !Texture {
    var id: c.GLuint = undefined;
    glad.context.GenTextures.?(1, &id);
    return Texture{ .id = id };
}

/// glBindTexture
pub inline fn bind(v: Texture, target: Target) !Binding {
    glad.context.BindTexture.?(@intFromEnum(target), v.id);
    try errors.getError();
    return Binding{ .target = target };
}

pub inline fn destroy(v: Texture) void {
    glad.context.DeleteTextures.?(1, &v.id);
}
