const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;

pub const FontDescriptor = opaque {
    pub fn createWithNameAndSize(name: *foundation.String, size: f64) Allocator.Error!*FontDescriptor {
        return @as(
            ?*FontDescriptor,
            @ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateWithNameAndSize(@ptrCast(name), size))),
        ) orelse Allocator.Error.OutOfMemory;
    }

    pub fn createWithAttributes(dict: *foundation.Dictionary) Allocator.Error!*FontDescriptor {
        return @as(
            ?*FontDescriptor,
            @ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateWithAttributes(@ptrCast(dict)))),
        ) orelse Allocator.Error.OutOfMemory;
    }

    pub fn createCopyWithAttributes(
        original: *FontDescriptor,
        dict: *foundation.Dictionary,
    ) Allocator.Error!*FontDescriptor {
        return @as(
            ?*FontDescriptor,
            @ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateCopyWithAttributes(
                @ptrCast(original),
                @ptrCast(dict),
            ))),
        ) orelse Allocator.Error.OutOfMemory;
    }

    pub fn createCopyWithVariation(
        original: *FontDescriptor,
        id: *foundation.Number,
        value: f64,
    ) Allocator.Error!*FontDescriptor {
        return @as(
            ?*FontDescriptor,
            @ptrCast(@constCast(c.CTFontDescriptorCreateCopyWithVariation(
                @ptrCast(original),
                @ptrCast(id),
                value,
            ))),
        ) orelse Allocator.Error.OutOfMemory;
    }

    pub fn retain(self: *FontDescriptor) void {
        _ = c.CFRetain(self);
    }

    pub fn release(self: *FontDescriptor) void {
        c.CFRelease(self);
    }

    pub fn copyAttribute(self: *const FontDescriptor, comptime attr: FontAttribute) ?attr.Value() {
        return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute(
            @ptrCast(self),
            @ptrCast(attr.key()),
        )));
    }

    pub fn copyAttributes(self: *const FontDescriptor) *foundation.Dictionary {
        return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttributes(
            @ptrCast(self),
        )));
    }
};

pub const FontAttribute = enum {
    url,
    name,
    display_name,
    family_name,
    style_name,
    traits,
    variation,
    size,
    matrix,
    cascade_list,
    character_set,
    languages,
    baseline_adjust,
    macintosh_encodings,
    features,
    feature_settings,
    fixed_advance,
    orientation,
    format,
    registration_scope,
    priority,
    enabled,
    downloadable,
    downloaded,

    // https://developer.apple.com/documentation/coretext/core_text_constants?language=objc
    variation_axes,

    pub fn key(self: FontAttribute) *foundation.String {
        return @as(*foundation.String, @ptrFromInt(@intFromPtr(switch (self) {
            .url => c.kCTFontURLAttribute,
            .name => c.kCTFontNameAttribute,
            .display_name => c.kCTFontDisplayNameAttribute,
            .family_name => c.kCTFontFamilyNameAttribute,
            .style_name => c.kCTFontStyleNameAttribute,
            .traits => c.kCTFontTraitsAttribute,
            .variation => c.kCTFontVariationAttribute,
            .size => c.kCTFontSizeAttribute,
            .matrix => c.kCTFontMatrixAttribute,
            .cascade_list => c.kCTFontCascadeListAttribute,
            .character_set => c.kCTFontCharacterSetAttribute,
            .languages => c.kCTFontLanguagesAttribute,
            .baseline_adjust => c.kCTFontBaselineAdjustAttribute,
            .macintosh_encodings => c.kCTFontMacintoshEncodingsAttribute,
            .features => c.kCTFontFeaturesAttribute,
            .feature_settings => c.kCTFontFeatureSettingsAttribute,
            .fixed_advance => c.kCTFontFixedAdvanceAttribute,
            .orientation => c.kCTFontOrientationAttribute,
            .format => c.kCTFontFormatAttribute,
            .registration_scope => c.kCTFontRegistrationScopeAttribute,
            .priority => c.kCTFontPriorityAttribute,
            .enabled => c.kCTFontEnabledAttribute,
            .downloadable => c.kCTFontDownloadableAttribute,
            .downloaded => c.kCTFontDownloadedAttribute,
            .variation_axes => c.kCTFontVariationAxesAttribute,
        })));
    }

    pub fn Value(comptime self: FontAttribute) type {
        return switch (self) {
            .url => *foundation.URL,
            .name => *foundation.String,
            .display_name => *foundation.String,
            .family_name => *foundation.String,
            .style_name => *foundation.String,
            .traits => *foundation.Dictionary,
            .variation => *foundation.Dictionary,
            .size => *foundation.Number,
            .matrix => *anyopaque, // CFDataRef
            .cascade_list => *foundation.Array,
            .character_set => *anyopaque, // CFCharacterSetRef
            .languages => *foundation.Array,
            .baseline_adjust => *foundation.Number,
            .macintosh_encodings => *foundation.Number,
            .features => *foundation.Array,
            .feature_settings => *foundation.Array,
            .fixed_advance => *foundation.Number,
            .orientation => *foundation.Number,
            .format => *foundation.Number,
            .registration_scope => *foundation.Number,
            .priority => *foundation.Number,
            .enabled => *foundation.Number,
            .downloadable => *anyopaque, // CFBoolean
            .downloaded => *anyopaque, // CFBoolean
            .variation_axes => *foundation.Array,
        };
    }
};

pub const FontTraitKey = enum {
    symbolic,
    weight,
    width,
    slant,

    pub fn key(self: FontTraitKey) *foundation.String {
        return @as(*foundation.String, @ptrFromInt(@intFromPtr(switch (self) {
            .symbolic => c.kCTFontSymbolicTrait,
            .weight => c.kCTFontWeightTrait,
            .width => c.kCTFontWidthTrait,
            .slant => c.kCTFontSlantTrait,
        })));
    }

    pub fn Value(self: FontTraitKey) type {
        return switch (self) {
            .symbolic => *foundation.Number,
            .weight => *foundation.Number,
            .width => *foundation.Number,
            .slant => *foundation.Number,
        };
    }
};

// https://developer.apple.com/documentation/coretext/ctfont/font_variation_axis_dictionary_keys?language=objc
pub const FontVariationAxisKey = enum {
    identifier,
    minimum_value,
    maximum_value,
    default_value,
    name,
    hidden,

    pub fn key(self: FontVariationAxisKey) *foundation.String {
        return @as(*foundation.String, @ptrFromInt(@intFromPtr(switch (self) {
            .identifier => c.kCTFontVariationAxisIdentifierKey,
            .minimum_value => c.kCTFontVariationAxisMinimumValueKey,
            .maximum_value => c.kCTFontVariationAxisMaximumValueKey,
            .default_value => c.kCTFontVariationAxisDefaultValueKey,
            .name => c.kCTFontVariationAxisNameKey,
            .hidden => c.kCTFontVariationAxisHiddenKey,
        })));
    }

    pub fn Value(comptime self: FontVariationAxisKey) type {
        return switch (self) {
            .identifier => foundation.Number,
            .minimum_value => foundation.Number,
            .maximum_value => foundation.Number,
            .default_value => foundation.Number,
            .name => foundation.String,
            .hidden => foundation.Number,
        };
    }
};

pub const FontSymbolicTraits = packed struct(u32) {
    italic: bool = false,
    bold: bool = false,
    _unused1: u3 = 0,
    expanded: bool = false,
    condensed: bool = false,
    _unused2: u3 = 0,
    monospace: bool = false,
    vertical: bool = false,
    ui_optimized: bool = false,
    color_glyphs: bool = false,
    composite: bool = false,
    _padding: u17 = 0,

    pub fn init(num: *foundation.Number) FontSymbolicTraits {
        var raw: i32 = undefined;
        _ = num.getValue(.sint32, &raw);
        return @as(FontSymbolicTraits, @bitCast(raw));
    }

    test {
        try std.testing.expectEqual(
            @bitSizeOf(c.CTFontSymbolicTraits),
            @bitSizeOf(FontSymbolicTraits),
        );
    }

    test "bitcast" {
        const actual: c.CTFontSymbolicTraits = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded;
        const expected: FontSymbolicTraits = .{
            .monospace = true,
            .expanded = true,
        };

        try std.testing.expectEqual(actual, @as(c.CTFontSymbolicTraits, @bitCast(expected)));
    }

    test "number" {
        const raw: i32 = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded;
        const num = try foundation.Number.create(.sint32, &raw);
        defer num.release();

        const expected: FontSymbolicTraits = .{ .monospace = true, .expanded = true };
        const actual = FontSymbolicTraits.init(num);
        try std.testing.expect(std.meta.eql(expected, actual));
    }
};

test {
    @import("std").testing.refAllDecls(@This());
}

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

    const name = try foundation.String.createWithBytes("foo", .utf8, false);
    defer name.release();

    const v = try FontDescriptor.createWithNameAndSize(name, 12);
    defer v.release();

    const copy_name = v.copyAttribute(.name).?;
    defer copy_name.release();

    {
        var buf: [128]u8 = undefined;
        const cstr = copy_name.cstring(&buf, .utf8).?;
        try testing.expectEqualStrings("foo", cstr);
    }
}
