const std = @import("std");
const c = @import("c.zig");
const common = @import("common.zig");
const Error = @import("errors.zig").Error;
const Direction = common.Direction;
const Script = common.Script;
const Language = common.Language;

/// Buffers serve a dual role in HarfBuzz; before shaping, they hold the
/// input characters that are passed to hb_shape(), and after shaping they
/// hold the output glyphs.
pub const Buffer = struct {
    handle: *c.hb_buffer_t,

    /// Creates a new hb_buffer_t with all properties to defaults.
    pub fn create() Error!Buffer {
        const handle = c.hb_buffer_create() orelse return Error.HarfbuzzFailed;
        return Buffer{ .handle = handle };
    }

    /// Deallocate the buffer . Decreases the reference count on buffer by one.
    /// If the result is zero, then buffer and all associated resources are
    /// freed. See hb_buffer_reference().
    pub fn destroy(self: *Buffer) void {
        c.hb_buffer_destroy(self.handle);
    }

    /// Resets the buffer to its initial status, as if it was just newly
    /// created with hb_buffer_create().
    pub fn reset(self: Buffer) void {
        c.hb_buffer_reset(self.handle);
    }

    /// Returns the number of items in the buffer.
    pub fn getLength(self: Buffer) u32 {
        return c.hb_buffer_get_length(self.handle);
    }

    /// Sets the type of buffer contents. Buffers are either empty, contain
    /// characters (before shaping), or contain glyphs (the result of shaping).
    pub fn setContentType(self: Buffer, ct: ContentType) void {
        c.hb_buffer_set_content_type(self.handle, @intFromEnum(ct));
    }

    /// Fetches the type of buffer contents. Buffers are either empty, contain
    /// characters (before shaping), or contain glyphs (the result of shaping).
    pub fn getContentType(self: Buffer) ContentType {
        return @enumFromInt(c.hb_buffer_get_content_type(self.handle));
    }

    /// Appends a character with the Unicode value of codepoint to buffer,
    /// and gives it the initial cluster value of cluster . Clusters can be
    /// any thing the client wants, they are usually used to refer to the
    /// index of the character in the input text stream and are output in
    /// hb_glyph_info_t.cluster field.
    ///
    /// This function does not check the validity of codepoint, it is up to
    /// the caller to ensure it is a valid Unicode code point.
    pub fn add(self: Buffer, cp: u32, cluster: u32) void {
        c.hb_buffer_add(self.handle, cp, cluster);
    }

    /// Appends characters from text array to buffer . The item_offset is the
    /// position of the first character from text that will be appended, and
    /// item_length is the number of character. When shaping part of a larger
    /// text (e.g. a run of text from a paragraph), instead of passing just
    /// the substring corresponding to the run, it is preferable to pass the
    /// whole paragraph and specify the run start and length as item_offset and
    /// item_length , respectively, to give HarfBuzz the full context to be
    /// able, for example, to do cross-run Arabic shaping or properly handle
    /// combining marks at stat of run.
    ///
    /// This function does not check the validity of text , it is up to the
    /// caller to ensure it contains a valid Unicode code points.
    pub fn addCodepoints(self: Buffer, text: []const u32) void {
        c.hb_buffer_add_codepoints(
            self.handle,
            text.ptr,
            @intCast(text.len),
            0,
            @intCast(text.len),
        );
    }

    /// See hb_buffer_add_codepoints().
    ///
    /// Replaces invalid UTF-32 characters with the buffer replacement code
    /// point, see hb_buffer_set_replacement_codepoint().
    pub fn addUTF32(self: Buffer, text: []const u32) void {
        c.hb_buffer_add_utf32(
            self.handle,
            text.ptr,
            @intCast(text.len),
            0,
            @intCast(text.len),
        );
    }

    /// See hb_buffer_add_codepoints().
    ///
    /// Replaces invalid UTF-16 characters with the buffer replacement code
    /// point, see hb_buffer_set_replacement_codepoint().
    pub fn addUTF16(self: Buffer, text: []const u16) void {
        c.hb_buffer_add_utf16(
            self.handle,
            text.ptr,
            @intCast(text.len),
            0,
            @intCast(text.len),
        );
    }

    /// See hb_buffer_add_codepoints().
    ///
    /// Replaces invalid UTF-8 characters with the buffer replacement code
    /// point, see hb_buffer_set_replacement_codepoint().
    pub fn addUTF8(self: Buffer, text: []const u8) void {
        c.hb_buffer_add_utf8(
            self.handle,
            text.ptr,
            @intCast(text.len),
            0,
            @intCast(text.len),
        );
    }

    /// Similar to hb_buffer_add_codepoints(), but allows only access to first
    /// 256 Unicode code points that can fit in 8-bit strings.
    pub fn addLatin1(self: Buffer, text: []const u8) void {
        c.hb_buffer_add_latin1(
            self.handle,
            text.ptr,
            @intCast(text.len),
            0,
            @intCast(text.len),
        );
    }

    /// Set the text flow direction of the buffer. No shaping can happen
    /// without setting buffer direction, and it controls the visual direction
    /// for the output glyphs; for RTL direction the glyphs will be reversed.
    /// Many layout features depend on the proper setting of the direction,
    /// for example, reversing RTL text before shaping, then shaping with LTR
    /// direction is not the same as keeping the text in logical order and
    /// shaping with RTL direction.
    pub fn setDirection(self: Buffer, dir: Direction) void {
        c.hb_buffer_set_direction(self.handle, @intFromEnum(dir));
    }

    /// See hb_buffer_set_direction()
    pub fn getDirection(self: Buffer) Direction {
        return @enumFromInt(c.hb_buffer_get_direction(self.handle));
    }

    /// Sets the script of buffer to script.
    ///
    /// Script is crucial for choosing the proper shaping behaviour for
    /// scripts that require it (e.g. Arabic) and the which OpenType features
    /// defined in the font to be applied.
    ///
    /// You can pass one of the predefined hb_script_t values, or use
    /// hb_script_from_string() or hb_script_from_iso15924_tag() to get the
    /// corresponding script from an ISO 15924 script tag.
    pub fn setScript(self: Buffer, script: Script) void {
        c.hb_buffer_set_script(self.handle, @intFromEnum(script));
    }

    /// See hb_buffer_set_script()
    pub fn getScript(self: Buffer) Script {
        return @enumFromInt(c.hb_buffer_get_script(self.handle));
    }

    /// Sets the language of buffer to language .
    ///
    /// Languages are crucial for selecting which OpenType feature to apply to
    /// the buffer which can result in applying language-specific behaviour.
    /// Languages are orthogonal to the scripts, and though they are related,
    /// they are different concepts and should not be confused with each other.
    ///
    /// Use hb_language_from_string() to convert from BCP 47 language tags to
    /// hb_language_t.
    pub fn setLanguage(self: Buffer, language: Language) void {
        c.hb_buffer_set_language(self.handle, language.handle);
    }

    /// See hb_buffer_set_language()
    pub fn getLanguage(self: Buffer) Language {
        return Language{ .handle = c.hb_buffer_get_language(self.handle) };
    }

    /// Returns buffer glyph information array. Returned pointer is valid as
    /// long as buffer contents are not modified.
    pub fn getGlyphInfos(self: Buffer) []GlyphInfo {
        var length: u32 = 0;
        const ptr: [*c]GlyphInfo = @ptrCast(c.hb_buffer_get_glyph_infos(self.handle, &length));
        return ptr[0..length];
    }

    /// Returns buffer glyph position array. Returned pointer is valid as
    /// long as buffer contents are not modified.
    ///
    /// If buffer did not have positions before, the positions will be
    /// initialized to zeros, unless this function is called from within a
    /// buffer message callback (see hb_buffer_set_message_func()), in which
    /// case NULL is returned.
    pub fn getGlyphPositions(self: Buffer) ?[]GlyphPosition {
        var length: u32 = 0;

        if (c.hb_buffer_get_glyph_positions(self.handle, &length)) |positions| {
            const ptr: [*]GlyphPosition = @ptrCast(positions);
            return ptr[0..length];
        }

        return null;
    }

    /// Sets unset buffer segment properties based on buffer Unicode contents.
    /// If buffer is not empty, it must have content type
    /// HB_BUFFER_CONTENT_TYPE_UNICODE.
    ///
    /// If buffer script is not set (ie. is HB_SCRIPT_INVALID), it will be set
    /// to the Unicode script of the first character in the buffer that has a
    /// script other than HB_SCRIPT_COMMON, HB_SCRIPT_INHERITED, and
    /// HB_SCRIPT_UNKNOWN.
    ///
    /// Next, if buffer direction is not set (ie. is HB_DIRECTION_INVALID), it
    /// will be set to the natural horizontal direction of the buffer script as
    /// returned by hb_script_get_horizontal_direction(). If
    /// hb_script_get_horizontal_direction() returns HB_DIRECTION_INVALID,
    /// then HB_DIRECTION_LTR is used.
    ///
    /// Finally, if buffer language is not set (ie. is HB_LANGUAGE_INVALID), it
    /// will be set to the process's default language as returned by
    /// hb_language_get_default(). This may change in the future by taking
    /// buffer script into consideration when choosing a language. Note that
    /// hb_language_get_default() is NOT threadsafe the first time it is
    /// called. See documentation for that function for details.
    pub fn guessSegmentProperties(self: Buffer) void {
        c.hb_buffer_guess_segment_properties(self.handle);
    }
};

/// The type of hb_buffer_t contents.
pub const ContentType = enum(u2) {
    /// Initial value for new buffer.
    invalid = c.HB_BUFFER_CONTENT_TYPE_INVALID,

    /// The buffer contains input characters (before shaping).
    unicode = c.HB_BUFFER_CONTENT_TYPE_UNICODE,

    /// The buffer contains output glyphs (after shaping).
    glyphs = c.HB_BUFFER_CONTENT_TYPE_GLYPHS,
};

/// The hb_glyph_info_t is the structure that holds information about the
/// glyphs and their relation to input text.
pub const GlyphInfo = extern struct {
    /// either a Unicode code point (before shaping) or a glyph index (after shaping).
    codepoint: u32,
    _mask: u32,

    /// the index of the character in the original text that corresponds to
    /// this hb_glyph_info_t, or whatever the client passes to hb_buffer_add().
    /// More than one hb_glyph_info_t can have the same cluster value, if they
    /// resulted from the same character (e.g. one to many glyph substitution),
    /// and when more than one character gets merged in the same glyph (e.g.
    /// many to one glyph substitution) the hb_glyph_info_t will have the
    /// smallest cluster value of them. By default some characters are merged
    /// into the same cluster (e.g. combining marks have the same cluster as
    /// their bases) even if they are separate glyphs, hb_buffer_set_cluster_level()
    /// allow selecting more fine-grained cluster handling.
    cluster: u32,
    _var1: u32,
    _var2: u32,
};

/// The hb_glyph_position_t is the structure that holds the positions of the
/// glyph in both horizontal and vertical directions. All positions in
/// hb_glyph_position_t are relative to the current point.
pub const GlyphPosition = extern struct {
    /// how much the line advances after drawing this glyph when setting text
    /// in horizontal direction.
    x_advance: i32,

    /// how much the line advances after drawing this glyph when setting text
    /// in vertical direction.
    y_advance: i32,

    /// how much the glyph moves on the X-axis before drawing it, this should
    /// not affect how much the line advances.
    x_offset: i32,

    /// how much the glyph moves on the Y-axis before drawing it, this should
    /// not affect how much the line advances.
    y_offset: i32,

    _var: u32,
};

test "create" {
    const testing = std.testing;

    var buffer = try Buffer.create();
    defer buffer.destroy();
    buffer.reset();

    // Content type
    buffer.setContentType(.unicode);
    try testing.expectEqual(ContentType.unicode, buffer.getContentType());

    // Try add functions
    buffer.add('🥹', 27);
    var utf32 = [_]u32{ 'A', 'B', 'C' };
    var utf16 = [_]u16{ 'A', 'B', 'C' };
    var utf8 = [_]u8{ 'A', 'B', 'C' };
    buffer.addCodepoints(&utf32);
    buffer.addUTF32(&utf32);
    buffer.addUTF16(&utf16);
    buffer.addUTF8(&utf8);
    buffer.addLatin1(&utf8);

    // Guess properties first
    buffer.guessSegmentProperties();

    // Try to set properties
    buffer.setDirection(.ltr);
    try testing.expectEqual(Direction.ltr, buffer.getDirection());

    buffer.setScript(.arabic);
    try testing.expectEqual(Script.arabic, buffer.getScript());

    buffer.setLanguage(Language.fromString("en"));
}
