//! Branch Drawing Characters | U+F5D0...U+F60D
//!
//! Branch drawing character set, used for drawing git-like
//! graphs in the terminal. Originally implemented in Kitty.
//! Ref:
//! - https://github.com/kovidgoyal/kitty/pull/7681
//! - https://github.com/kovidgoyal/kitty/pull/7805
//! NOTE: Kitty is GPL licensed, and its code was not referenced
//!       for these characters, only the loose specification of
//!       the character set in the pull request descriptions.
//!
//!                
//!                
//!                
//!              
//!

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

const common = @import("common.zig");
const Thickness = common.Thickness;
const Shade = common.Shade;
const Edge = common.Edge;
const hlineMiddle = common.hlineMiddle;
const vlineMiddle = common.vlineMiddle;

const arc = @import("box.zig").arc;

const font = @import("../../main.zig");

/// Specification of a branch drawing node, which consists of a
/// circle which is either empty or filled, and lines connecting
/// optionally between the circle and each of the 4 edges.
const BranchNode = packed struct(u5) {
    up: bool = false,
    right: bool = false,
    down: bool = false,
    left: bool = false,
    filled: bool = false,
};

pub fn drawF5D0_F60D(
    cp: u32,
    canvas: *font.sprite.Canvas,
    width: u32,
    height: u32,
    metrics: font.Metrics,
) !void {
    _ = width;
    _ = height;

    switch (cp) {
        // ''
        0x0f5d0 => hlineMiddle(metrics, canvas, .light),
        // ''
        0x0f5d1 => vlineMiddle(metrics, canvas, .light),
        // ''
        0x0f5d2 => fadingLine(metrics, canvas, .right, .light),
        // ''
        0x0f5d3 => fadingLine(metrics, canvas, .left, .light),
        // ''
        0x0f5d4 => fadingLine(metrics, canvas, .bottom, .light),
        // ''
        0x0f5d5 => fadingLine(metrics, canvas, .top, .light),
        // ''
        0x0f5d6 => try arc(metrics, canvas, .br, .light),
        // ''
        0x0f5d7 => try arc(metrics, canvas, .bl, .light),
        // ''
        0x0f5d8 => try arc(metrics, canvas, .tr, .light),
        // ''
        0x0f5d9 => try arc(metrics, canvas, .tl, .light),
        // ''
        0x0f5da => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tr, .light);
        },
        // ''
        0x0f5db => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5dc => {
            try arc(metrics, canvas, .tr, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5dd => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tl, .light);
        },
        // ''
        0x0f5de => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .bl, .light);
        },
        // ''
        0x0f5df => {
            try arc(metrics, canvas, .tl, .light);
            try arc(metrics, canvas, .bl, .light);
        },

        // ''
        0x0f5e0 => {
            try arc(metrics, canvas, .bl, .light);
            hlineMiddle(metrics, canvas, .light);
        },
        // ''
        0x0f5e1 => {
            try arc(metrics, canvas, .br, .light);
            hlineMiddle(metrics, canvas, .light);
        },
        // ''
        0x0f5e2 => {
            try arc(metrics, canvas, .br, .light);
            try arc(metrics, canvas, .bl, .light);
        },
        // ''
        0x0f5e3 => {
            try arc(metrics, canvas, .tl, .light);
            hlineMiddle(metrics, canvas, .light);
        },
        // ''
        0x0f5e4 => {
            try arc(metrics, canvas, .tr, .light);
            hlineMiddle(metrics, canvas, .light);
        },
        // ''
        0x0f5e5 => {
            try arc(metrics, canvas, .tr, .light);
            try arc(metrics, canvas, .tl, .light);
        },
        // ''
        0x0f5e6 => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tl, .light);
            try arc(metrics, canvas, .tr, .light);
        },
        // ''
        0x0f5e7 => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .bl, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5e8 => {
            hlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .bl, .light);
            try arc(metrics, canvas, .tl, .light);
        },
        // ''
        0x0f5e9 => {
            hlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tr, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5ea => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tl, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5eb => {
            vlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tr, .light);
            try arc(metrics, canvas, .bl, .light);
        },
        // ''
        0x0f5ec => {
            hlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tl, .light);
            try arc(metrics, canvas, .br, .light);
        },
        // ''
        0x0f5ed => {
            hlineMiddle(metrics, canvas, .light);
            try arc(metrics, canvas, .tr, .light);
            try arc(metrics, canvas, .bl, .light);
        },
        // ''
        0x0f5ee => branchNode(metrics, canvas, .{ .filled = true }, .light),
        // ''
        0x0f5ef => branchNode(metrics, canvas, .{}, .light),

        // ''
        0x0f5f0 => branchNode(metrics, canvas, .{
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5f1 => branchNode(metrics, canvas, .{
            .right = true,
        }, .light),
        // ''
        0x0f5f2 => branchNode(metrics, canvas, .{
            .left = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5f3 => branchNode(metrics, canvas, .{
            .left = true,
        }, .light),
        // ''
        0x0f5f4 => branchNode(metrics, canvas, .{
            .left = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5f5 => branchNode(metrics, canvas, .{
            .left = true,
            .right = true,
        }, .light),
        // ''
        0x0f5f6 => branchNode(metrics, canvas, .{
            .down = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5f7 => branchNode(metrics, canvas, .{
            .down = true,
        }, .light),
        // ''
        0x0f5f8 => branchNode(metrics, canvas, .{
            .up = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5f9 => branchNode(metrics, canvas, .{
            .up = true,
        }, .light),
        // ''
        0x0f5fa => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5fb => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
        }, .light),
        // ''
        0x0f5fc => branchNode(metrics, canvas, .{
            .right = true,
            .down = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5fd => branchNode(metrics, canvas, .{
            .right = true,
            .down = true,
        }, .light),
        // ''
        0x0f5fe => branchNode(metrics, canvas, .{
            .left = true,
            .down = true,
            .filled = true,
        }, .light),
        // ''
        0x0f5ff => branchNode(metrics, canvas, .{
            .left = true,
            .down = true,
        }, .light),

        // ''
        0x0f600 => branchNode(metrics, canvas, .{
            .up = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f601 => branchNode(metrics, canvas, .{
            .up = true,
            .right = true,
        }, .light),
        // ''
        0x0f602 => branchNode(metrics, canvas, .{
            .up = true,
            .left = true,
            .filled = true,
        }, .light),
        // ''
        0x0f603 => branchNode(metrics, canvas, .{
            .up = true,
            .left = true,
        }, .light),
        // ''
        0x0f604 => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f605 => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .right = true,
        }, .light),
        // ''
        0x0f606 => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .left = true,
            .filled = true,
        }, .light),
        // ''
        0x0f607 => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .left = true,
        }, .light),
        // ''
        0x0f608 => branchNode(metrics, canvas, .{
            .down = true,
            .left = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f609 => branchNode(metrics, canvas, .{
            .down = true,
            .left = true,
            .right = true,
        }, .light),
        // ''
        0x0f60a => branchNode(metrics, canvas, .{
            .up = true,
            .left = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f60b => branchNode(metrics, canvas, .{
            .up = true,
            .left = true,
            .right = true,
        }, .light),
        // ''
        0x0f60c => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .left = true,
            .right = true,
            .filled = true,
        }, .light),
        // ''
        0x0f60d => branchNode(metrics, canvas, .{
            .up = true,
            .down = true,
            .left = true,
            .right = true,
        }, .light),

        else => unreachable,
    }
}

fn branchNode(
    metrics: font.Metrics,
    canvas: *font.sprite.Canvas,
    node: BranchNode,
    comptime thickness: Thickness,
) void {
    const thick_px = thickness.height(metrics.box_thickness);
    const float_width: f64 = @floatFromInt(metrics.cell_width);
    const float_height: f64 = @floatFromInt(metrics.cell_height);
    const float_thick: f64 = @floatFromInt(thick_px);

    // Top of horizontal strokes
    const h_top = (metrics.cell_height -| thick_px) / 2;
    // Bottom of horizontal strokes
    const h_bottom = h_top +| thick_px;
    // Left of vertical strokes
    const v_left = (metrics.cell_width -| thick_px) / 2;
    // Right of vertical strokes
    const v_right = v_left +| thick_px;

    // We calculate the center of the circle this way
    // to ensure it aligns with box drawing characters
    // since the lines are sometimes off center to
    // make sure they aren't split between pixels.
    const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2;
    const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2;
    // The radius needs to be the smallest distance from the center to an edge.
    const r: f64 = @min(
        @min(cx, cy),
        @min(float_width - cx, float_height - cy),
    );

    var ctx = canvas.getContext();
    defer ctx.deinit();
    ctx.setSource(.{ .opaque_pattern = .{
        .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
    } });
    ctx.setLineWidth(float_thick);

    // These @intFromFloat casts shouldn't ever fail since r can never
    // be greater than cx or cy, so when subtracting it from them the
    // result can never be negative.
    if (node.up) canvas.box(
        @intCast(v_left),
        0,
        @intCast(v_right),
        @intFromFloat(@ceil(cy - r + float_thick / 2)),
        .on,
    );
    if (node.right) canvas.box(
        @intFromFloat(@floor(cx + r - float_thick / 2)),
        @intCast(h_top),
        @intCast(metrics.cell_width),
        @intCast(h_bottom),
        .on,
    );
    if (node.down) canvas.box(
        @intCast(v_left),
        @intFromFloat(@floor(cy + r - float_thick / 2)),
        @intCast(v_right),
        @intCast(metrics.cell_height),
        .on,
    );
    if (node.left) canvas.box(
        0,
        @intCast(h_top),
        @intFromFloat(@ceil(cx - r + float_thick / 2)),
        @intCast(h_bottom),
        .on,
    );

    if (node.filled) {
        ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return;
        ctx.closePath() catch return;
        ctx.fill() catch return;
    } else {
        ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return;
        ctx.closePath() catch return;
        ctx.stroke() catch return;
    }
}

fn fadingLine(
    metrics: font.Metrics,
    canvas: *font.sprite.Canvas,
    comptime to: Edge,
    comptime thickness: Thickness,
) void {
    const thick_px = thickness.height(metrics.box_thickness);
    const float_width: f64 = @floatFromInt(metrics.cell_width);
    const float_height: f64 = @floatFromInt(metrics.cell_height);

    // Top of horizontal strokes
    const h_top = (metrics.cell_height -| thick_px) / 2;
    // Bottom of horizontal strokes
    const h_bottom = h_top +| thick_px;
    // Left of vertical strokes
    const v_left = (metrics.cell_width -| thick_px) / 2;
    // Right of vertical strokes
    const v_right = v_left +| thick_px;

    // If we're fading to the top or left, we start with 0.0
    // and increment up as we progress, otherwise we start
    // at 255.0 and increment down (negative).
    var color: f64 = switch (to) {
        .top, .left => 0.0,
        .bottom, .right => 255.0,
    };
    const inc: f64 = 255.0 / switch (to) {
        .top => float_height,
        .bottom => -float_height,
        .left => float_width,
        .right => -float_width,
    };

    switch (to) {
        .top, .bottom => {
            for (0..metrics.cell_height) |y| {
                for (v_left..v_right) |x| {
                    canvas.pixel(
                        @intCast(x),
                        @intCast(y),
                        @enumFromInt(@as(u8, @intFromFloat(@round(color)))),
                    );
                }
                color += inc;
            }
        },
        .left, .right => {
            for (0..metrics.cell_width) |x| {
                for (h_top..h_bottom) |y| {
                    canvas.pixel(
                        @intCast(x),
                        @intCast(y),
                        @enumFromInt(@as(u8, @intFromFloat(@round(color)))),
                    );
                }
                color += inc;
            }
        },
    }
}
