const std = @import("std");
const c = @import("c.zig");
const errors = @import("errors.zig");
const Library = @import("Library.zig");
const Error = errors.Error;
const intToError = errors.intToError;

pub const Face = struct {
    handle: c.FT_Face,

    pub fn deinit(self: Face) void {
        _ = c.FT_Done_Face(self.handle);
    }

    /// A macro that returns true whenever a face object contains some
    /// embedded bitmaps. See the available_sizes field of the FT_FaceRec structure.
    pub fn hasFixedSizes(self: Face) bool {
        return c.FT_HAS_FIXED_SIZES(self.handle);
    }

    /// A macro that returns true whenever a face object contains tables for
    /// color glyphs.
    pub fn hasColor(self: Face) bool {
        return c.FT_HAS_COLOR(self.handle);
    }

    /// A macro that returns true whenever a face object contains some
    /// multiple masters.
    pub fn hasMultipleMasters(self: Face) bool {
        return c.FT_HAS_MULTIPLE_MASTERS(self.handle);
    }

    /// A macro that returns true whenever a face object contains a scalable
    /// font face (true for TrueType, Type 1, Type 42, CID, OpenType/CFF,
    /// and PFR font formats).
    pub fn isScalable(self: Face) bool {
        return c.FT_IS_SCALABLE(self.handle);
    }

    /// Select a given charmap by its encoding tag (as listed in freetype.h).
    pub fn selectCharmap(self: Face, encoding: Encoding) Error!void {
        return intToError(c.FT_Select_Charmap(self.handle, @intFromEnum(encoding)));
    }

    /// Call FT_Request_Size to request the nominal size (in points).
    pub fn setCharSize(
        self: Face,
        char_width: i32,
        char_height: i32,
        horz_resolution: u16,
        vert_resolution: u16,
    ) Error!void {
        return intToError(c.FT_Set_Char_Size(
            self.handle,
            char_width,
            char_height,
            horz_resolution,
            vert_resolution,
        ));
    }

    /// Select a bitmap strike. To be more precise, this function sets the
    /// scaling factors of the active FT_Size object in a face so that bitmaps
    /// from this particular strike are taken by FT_Load_Glyph and friends.
    pub fn selectSize(self: Face, idx: i32) Error!void {
        return intToError(c.FT_Select_Size(self.handle, idx));
    }

    /// Return the glyph index of a given character code. This function uses
    /// the currently selected charmap to do the mapping.
    pub fn getCharIndex(self: Face, char: u32) ?u32 {
        const i = c.FT_Get_Char_Index(self.handle, char);
        return if (i == 0) null else i;
    }

    /// Load a glyph into the glyph slot of a face object.
    pub fn loadGlyph(self: Face, glyph_index: u32, load_flags: LoadFlags) Error!void {
        return intToError(c.FT_Load_Glyph(
            self.handle,
            glyph_index,
            @bitCast(load_flags),
        ));
    }

    /// Return a pointer to a given SFNT table stored within a face.
    pub fn getSfntTable(self: Face, comptime tag: SfntTag) ?*tag.DataType() {
        return @ptrCast(@alignCast(c.FT_Get_Sfnt_Table(
            self.handle,
            @intFromEnum(tag),
        )));
    }

    /// Retrieve the number of name strings in the SFNT ‘name’ table.
    pub fn getSfntNameCount(self: Face) usize {
        return @intCast(c.FT_Get_Sfnt_Name_Count(self.handle));
    }

    /// Retrieve a string of the SFNT ‘name’ table for a given index.
    pub fn getSfntName(self: Face, i: usize) Error!c.FT_SfntName {
        var name: c.FT_SfntName = undefined;
        const res = c.FT_Get_Sfnt_Name(self.handle, @intCast(i), &name);
        return if (intToError(res)) |_| name else |err| err;
    }

    /// Retrieve the font variation descriptor for a font.
    pub fn getMMVar(self: Face) Error!*c.FT_MM_Var {
        var result: *c.FT_MM_Var = undefined;
        const res = c.FT_Get_MM_Var(self.handle, @ptrCast(&result));
        return if (intToError(res)) |_| result else |err| err;
    }

    /// Get the design coordinates of the currently selected interpolated font.
    pub fn getVarDesignCoordinates(self: Face, coords: []c.FT_Fixed) Error!void {
        const res = c.FT_Get_Var_Design_Coordinates(
            self.handle,
            @intCast(coords.len),
            coords.ptr,
        );
        return intToError(res);
    }

    /// Choose an interpolated font design through design coordinates.
    pub fn setVarDesignCoordinates(self: Face, coords: []c.FT_Fixed) Error!void {
        const res = c.FT_Set_Var_Design_Coordinates(
            self.handle,
            @intCast(coords.len),
            coords.ptr,
        );
        return intToError(res);
    }
};

/// An enumeration to specify indices of SFNT tables loaded and parsed by
/// FreeType during initialization of an SFNT font. Used in the
/// FT_Get_Sfnt_Table API function.
pub const SfntTag = enum(c_int) {
    head = c.FT_SFNT_HEAD,
    maxp = c.FT_SFNT_MAXP,
    os2 = c.FT_SFNT_OS2,
    hhea = c.FT_SFNT_HHEA,
    vhea = c.FT_SFNT_VHEA,
    post = c.FT_SFNT_POST,
    pclt = c.FT_SFNT_PCLT,

    /// The data type for a given sfnt tag.
    pub fn DataType(comptime self: SfntTag) type {
        return switch (self) {
            .os2 => c.TT_OS2,
            else => unreachable, // As-needed...
        };
    }
};

/// An enumeration to specify character sets supported by charmaps. Used in the
/// FT_Select_Charmap API function.
pub const Encoding = enum(u31) {
    none = c.FT_ENCODING_NONE,
    ms_symbol = c.FT_ENCODING_MS_SYMBOL,
    unicode = c.FT_ENCODING_UNICODE,
    sjis = c.FT_ENCODING_SJIS,
    prc = c.FT_ENCODING_PRC,
    big5 = c.FT_ENCODING_BIG5,
    wansung = c.FT_ENCODING_WANSUNG,
    johab = c.FT_ENCODING_JOHAB,
    adobe_standard = c.FT_ENCODING_ADOBE_STANDARD,
    adobe_expert = c.FT_ENCODING_ADOBE_EXPERT,
    adobe_custom = c.FT_ENCODING_ADOBE_CUSTOM,
    adobe_latin_1 = c.FT_ENCODING_ADOBE_LATIN_1,
    old_latin_2 = c.FT_ENCODING_OLD_LATIN_2,
    apple_roman = c.FT_ENCODING_APPLE_ROMAN,
};

/// A list of bit field constants for FT_Load_Glyph to indicate what kind of
/// operations to perform during glyph loading.
pub const LoadFlags = packed struct {
    no_scale: bool = false,
    no_hinting: bool = false,
    render: bool = false,
    no_bitmap: bool = false,
    vertical_layout: bool = false,
    force_autohint: bool = false,
    crop_bitmap: bool = false,
    pedantic: bool = false,
    ignore_global_advance_with: bool = false,
    no_recurse: bool = false,
    ignore_transform: bool = false,
    monochrome: bool = false,
    linear_design: bool = false,
    no_autohint: bool = false,
    _padding1: u1 = 0,
    target_normal: bool = false,
    target_light: bool = false,
    target_mono: bool = false,
    target_lcd: bool = false,
    target_lcd_v: bool = false,
    color: bool = false,
    compute_metrics: bool = false,
    bitmap_metrics_only: bool = false,
    _padding2: u9 = 0,

    test {
        // This must always be an i32 size so we can bitcast directly.
        const testing = std.testing;
        try testing.expectEqual(@sizeOf(i32), @sizeOf(LoadFlags));
    }

    test "bitcast" {
        const testing = std.testing;
        const cval: i32 = c.FT_LOAD_RENDER | c.FT_LOAD_PEDANTIC | c.FT_LOAD_COLOR;
        const flags = @as(LoadFlags, @bitCast(cval));
        try testing.expect(!flags.no_hinting);
        try testing.expect(flags.render);
        try testing.expect(flags.pedantic);
        try testing.expect(flags.color);
    }
};

test "loading memory font" {
    const testing = std.testing;
    const font_data = @import("test.zig").font_regular;

    var lib = try Library.init();
    defer lib.deinit();
    var face = try lib.initMemoryFace(font_data, 0);
    defer face.deinit();

    // Try APIs
    try face.selectCharmap(.unicode);
    try testing.expect(!face.hasFixedSizes());
    try face.setCharSize(12, 0, 0, 0);

    // Try loading
    const idx = face.getCharIndex('A').?;
    try face.loadGlyph(idx, .{});

    // Try getting a truetype table
    const os2 = face.getSfntTable(.os2);
    try testing.expect(os2 != null);
}
