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

/// The possible reference locations for a point. When someone says "(42, 80)"
/// in the context of a terminal, that could mean multiple things: it is in the
/// current visible viewport? the current active area of the screen where the
/// cursor is? the entire scrollback history? etc.
///
/// This tag is used to differentiate those cases.
pub const Tag = lib.Enum(lib.target, &.{
    // Top-left is part of the active area where a running program can
    // jump the cursor and make changes. The active area is the "editable"
    // part of the screen.
    //
    // The bottom-right of the active tag differs from all other tags
    // because it includes the full height (rows) of the screen, including
    // rows that may not be written yet. This is required because the active
    // area is fully "addressable" by the running program (see below) whereas
    // the other tags are used primarily for reading/modifying past-written
    // data so they can't address unwritten rows.
    //
    // Note for those less familiar with terminal functionality: there
    // are escape sequences to move the cursor to any position on
    // the screen, but it is limited to the size of the viewport and
    // the bottommost part of the screen. Terminal programs can't --
    // with sequences at the time of writing this comment -- modify
    // anything in the scrollback, visible viewport (if it differs
    // from the active area), etc.
    "active",

    // Top-left is the visible viewport. This means that if the user
    // has scrolled in any direction, top-left changes. The bottom-right
    // is the last written row from the top-left.
    "viewport",

    // Top-left is the furthest back in the scrollback history
    // supported by the screen and the bottom-right is the bottom-right
    // of the last written row. Note this last point is important: the
    // bottom right is NOT necessarily the same as "active" because
    // "active" always allows referencing the full rows tall of the
    // screen whereas "screen" only contains written rows.
    "screen",

    // The top-left is the same as "screen" but the bottom-right is
    // the line just before the top of "active". This contains only
    // the scrollback history.
    "history",
});

/// An x/y point in the terminal for some definition of location (tag).
pub const Point = union(Tag) {
    active: Coordinate,
    viewport: Coordinate,
    screen: Coordinate,
    history: Coordinate,

    pub inline fn coord(self: Point) Coordinate {
        return switch (self) {
            .active,
            .viewport,
            .screen,
            .history,
            => |v| v,
        };
    }

    const c_union = lib.TaggedUnion(
        lib.target,
        @This(),
        // Padding: largest variant is Coordinate (u16 + u32 = 6 bytes).
        // Use [2]u64 (16 bytes) for future expansion.
        .{
            .padding = [2]u64,
            .field_renames = .{
                .active = "coordinate",
                .viewport = "coordinate",
                .screen = "coordinate",
                .history = "coordinate",
            },
        },
    );
    pub const C = c_union.C;
    pub const CValue = c_union.CValue;
    pub const cval = c_union.cval;

    /// Convert a C ABI point into the native Zig tagged union.
    pub fn fromC(pt: C) Point {
        return switch (pt.tag) {
            .active => .{ .active = pt.value.active },
            .viewport => .{ .viewport = pt.value.viewport },
            .screen => .{ .screen = pt.value.screen },
            .history => .{ .history = pt.value.history },
        };
    }
};

pub const Coordinate = extern struct {
    /// x can use size.CellCountInt because the number of columns
    /// can't ever be more than a valid number of columns in a Page.
    x: size.CellCountInt = 0,

    /// y does not use size.CellCountInt because certain coordinate
    /// usage such as screen/history can have more rows than are possible
    /// in a single page.
    y: u32 = 0,

    pub fn eql(self: Coordinate, other: Coordinate) bool {
        return self.x == other.x and self.y == other.y;
    }
};
