//! The primary export of this file is "table", which contains a
//! comptime-generated state transition table for VT emulation.
//!
//! This is based on the vt100.net state machine:
//! https://vt100.net/emu/dec_ansi_parser
//! But has some modifications:
//!
//!   * utf8 state introduced to detect UTF8-encoded sequences. The
//!     actual handling back OUT of the utf8 state is done manually in the
//!     parser.
//!
//!   * csi_param accepts the colon character (':') since the SGR command
//!     accepts colon as a valid parameter value.
//!

const std = @import("std");
const builtin = @import("builtin");
const parser = @import("Parser.zig");
const State = parser.State;
const Action = parser.TransitionAction;

/// The state transition table. The type is [u8][State]Transition but
/// comptime-generated to be exactly-sized.
pub const table = genTable();

/// Table is the type of the state table. This is dynamically (comptime)
/// generated to be exactly sized.
pub const Table = genTableType(false);

/// OptionalTable is private to this file. We use this to accumulate and
/// detect invalid transitions created.
const OptionalTable = genTableType(true);

// Transition is the transition to take within the table
pub const Transition = struct {
    state: State,
    action: Action,
};

/// Table is the type of the state transition table.
fn genTableType(comptime optional: bool) type {
    const max_u8 = std.math.maxInt(u8);
    const stateInfo = @typeInfo(State);
    const max_state = stateInfo.Enum.fields.len;
    const Elem = if (optional) ?Transition else Transition;
    return [max_u8 + 1][max_state]Elem;
}

/// Function to generate the full state transition table for VT emulation.
fn genTable() Table {
    @setEvalBranchQuota(20000);

    // We accumulate using an "optional" table so we can detect duplicates.
    var result: OptionalTable = undefined;
    for (0..result.len) |i| {
        for (0..result[0].len) |j| {
            result[i][j] = null;
        }
    }

    // anywhere transitions
    const stateInfo = @typeInfo(State);
    inline for (stateInfo.Enum.fields) |field| {
        const source: State = @enumFromInt(field.value);

        // anywhere => ground
        single(&result, 0x18, source, .ground, .execute);
        single(&result, 0x1A, source, .ground, .execute);
        range(&result, 0x80, 0x8F, source, .ground, .execute);
        range(&result, 0x91, 0x97, source, .ground, .execute);
        single(&result, 0x99, source, .ground, .execute);
        single(&result, 0x9A, source, .ground, .execute);
        single(&result, 0x9C, source, .ground, .none);

        // anywhere => escape
        single(&result, 0x1B, source, .escape, .none);

        // anywhere => sos_pm_apc_string
        single(&result, 0x98, source, .sos_pm_apc_string, .none);
        single(&result, 0x9E, source, .sos_pm_apc_string, .none);
        single(&result, 0x9F, source, .sos_pm_apc_string, .none);

        // anywhere => csi_entry
        single(&result, 0x9B, source, .csi_entry, .none);

        // anywhere => dcs_entry
        single(&result, 0x90, source, .dcs_entry, .none);

        // anywhere => osc_string
        single(&result, 0x9D, source, .osc_string, .none);
    }

    // ground
    {
        const source = State.ground;

        // events
        single(&result, 0x19, .ground, .ground, .execute);
        range(&result, 0, 0x17, .ground, .ground, .execute);
        range(&result, 0x1C, 0x1F, .ground, .ground, .execute);
        range(&result, 0x20, 0x7F, .ground, .ground, .print);

        // => utf8
        range(&result, 0xC2, 0xDF, source, .utf8, .collect);
        range(&result, 0xE0, 0xEF, source, .utf8, .collect);
        range(&result, 0xF0, 0xF4, source, .utf8, .collect);
    }

    // escape_intermediate
    {
        const source = State.escape_intermediate;

        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        range(&result, 0x20, 0x2F, source, source, .collect);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x30, 0x7E, source, .ground, .esc_dispatch);
    }

    // sos_pm_apc_string
    {
        const source = State.sos_pm_apc_string;

        // events
        single(&result, 0x19, source, source, .apc_put);
        range(&result, 0, 0x17, source, source, .apc_put);
        range(&result, 0x1C, 0x1F, source, source, .apc_put);
        range(&result, 0x20, 0x7F, source, source, .apc_put);
    }

    // escape
    {
        const source = State.escape;

        // events
        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x30, 0x4F, source, .ground, .esc_dispatch);
        range(&result, 0x51, 0x57, source, .ground, .esc_dispatch);
        range(&result, 0x60, 0x7E, source, .ground, .esc_dispatch);
        single(&result, 0x59, source, .ground, .esc_dispatch);
        single(&result, 0x5A, source, .ground, .esc_dispatch);
        single(&result, 0x5C, source, .ground, .esc_dispatch);

        // => escape_intermediate
        range(&result, 0x20, 0x2F, source, .escape_intermediate, .collect);

        // => sos_pm_apc_string
        single(&result, 0x58, source, .sos_pm_apc_string, .none);
        single(&result, 0x5E, source, .sos_pm_apc_string, .none);
        single(&result, 0x5F, source, .sos_pm_apc_string, .none);

        // => dcs_entry
        single(&result, 0x50, source, .dcs_entry, .none);

        // => csi_entry
        single(&result, 0x5B, source, .csi_entry, .none);

        // => osc_string
        single(&result, 0x5D, source, .osc_string, .none);
    }

    // dcs_entry
    {
        const source = State.dcs_entry;

        // events
        single(&result, 0x19, source, source, .ignore);
        range(&result, 0, 0x17, source, source, .ignore);
        range(&result, 0x1C, 0x1F, source, source, .ignore);
        single(&result, 0x7F, source, source, .ignore);

        // => dcs_intermediate
        range(&result, 0x20, 0x2F, source, .dcs_intermediate, .collect);

        // => dcs_ignore
        single(&result, 0x3A, source, .dcs_ignore, .none);

        // => dcs_param
        range(&result, 0x30, 0x39, source, .dcs_param, .param);
        single(&result, 0x3B, source, .dcs_param, .param);
        range(&result, 0x3C, 0x3F, source, .dcs_param, .collect);

        // => dcs_passthrough
        range(&result, 0x40, 0x7E, source, .dcs_passthrough, .none);
    }

    // dcs_intermediate
    {
        const source = State.dcs_intermediate;

        // events
        single(&result, 0x19, source, source, .ignore);
        range(&result, 0, 0x17, source, source, .ignore);
        range(&result, 0x1C, 0x1F, source, source, .ignore);
        range(&result, 0x20, 0x2F, source, source, .collect);
        single(&result, 0x7F, source, source, .ignore);

        // => dcs_ignore
        range(&result, 0x30, 0x3F, source, .dcs_ignore, .none);

        // => dcs_passthrough
        range(&result, 0x40, 0x7E, source, .dcs_passthrough, .none);
    }

    // dcs_ignore
    {
        const source = State.dcs_ignore;

        // events
        single(&result, 0x19, source, source, .ignore);
        range(&result, 0, 0x17, source, source, .ignore);
        range(&result, 0x1C, 0x1F, source, source, .ignore);
    }

    // dcs_param
    {
        const source = State.dcs_param;

        // events
        single(&result, 0x19, source, source, .ignore);
        range(&result, 0, 0x17, source, source, .ignore);
        range(&result, 0x1C, 0x1F, source, source, .ignore);
        range(&result, 0x30, 0x39, source, source, .param);
        single(&result, 0x3B, source, source, .param);
        single(&result, 0x7F, source, source, .ignore);

        // => dcs_ignore
        single(&result, 0x3A, source, .dcs_ignore, .none);
        range(&result, 0x3C, 0x3F, source, .dcs_ignore, .none);

        // => dcs_intermediate
        range(&result, 0x20, 0x2F, source, .dcs_intermediate, .collect);

        // => dcs_passthrough
        range(&result, 0x40, 0x7E, source, .dcs_passthrough, .none);
    }

    // dcs_passthrough
    {
        const source = State.dcs_passthrough;

        // events
        single(&result, 0x19, source, source, .put);
        range(&result, 0, 0x17, source, source, .put);
        range(&result, 0x1C, 0x1F, source, source, .put);
        range(&result, 0x20, 0x7E, source, source, .put);
        single(&result, 0x7F, source, source, .ignore);
    }

    // csi_param
    {
        const source = State.csi_param;

        // events
        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        range(&result, 0x30, 0x39, source, source, .param);
        single(&result, 0x3A, source, source, .param);
        single(&result, 0x3B, source, source, .param);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x40, 0x7E, source, .ground, .csi_dispatch);

        // => csi_ignore
        range(&result, 0x3C, 0x3F, source, .csi_ignore, .none);

        // => csi_intermediate
        range(&result, 0x20, 0x2F, source, .csi_intermediate, .collect);
    }

    // csi_ignore
    {
        const source = State.csi_ignore;

        // events
        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        range(&result, 0x20, 0x3F, source, source, .ignore);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x40, 0x7E, source, .ground, .none);
    }

    // csi_intermediate
    {
        const source = State.csi_intermediate;

        // events
        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        range(&result, 0x20, 0x2F, source, source, .collect);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x40, 0x7E, source, .ground, .csi_dispatch);

        // => csi_ignore
        range(&result, 0x30, 0x3F, source, .csi_ignore, .none);
    }

    // csi_entry
    {
        const source = State.csi_entry;

        // events
        single(&result, 0x19, source, source, .execute);
        range(&result, 0, 0x17, source, source, .execute);
        range(&result, 0x1C, 0x1F, source, source, .execute);
        single(&result, 0x7F, source, source, .ignore);

        // => ground
        range(&result, 0x40, 0x7E, source, .ground, .csi_dispatch);

        // => csi_ignore
        single(&result, 0x3A, source, .csi_ignore, .none);

        // => csi_intermediate
        range(&result, 0x20, 0x2F, source, .csi_intermediate, .collect);

        // => csi_param
        range(&result, 0x30, 0x39, source, .csi_param, .param);
        single(&result, 0x3B, source, .csi_param, .param);
        range(&result, 0x3C, 0x3F, source, .csi_param, .collect);
    }

    // osc_string
    {
        const source = State.osc_string;

        // events
        single(&result, 0x19, source, source, .ignore);
        range(&result, 0, 0x06, source, source, .ignore);
        range(&result, 0x08, 0x17, source, source, .ignore);
        range(&result, 0x1C, 0x1F, source, source, .ignore);
        range(&result, 0x20, 0xFF, source, source, .osc_put);

        // XTerm accepts either BEL  or ST  for terminating OSC
        // sequences, and when returning information, uses the same
        // terminator used in a query.
        single(&result, 0x07, source, .ground, .none);
    }

    // Create our immutable version
    var final: Table = undefined;
    for (0..final.len) |i| {
        for (0..final[0].len) |j| {
            final[i][j] = result[i][j] orelse transition(@enumFromInt(j), .none);
        }
    }

    return final;
}

fn single(t: *OptionalTable, c: u8, s0: State, s1: State, a: Action) void {
    const s0_int = @intFromEnum(s0);

    // TODO: enable this but it thinks we're in runtime right now
    // if (t[c][s0_int]) |existing| {
    //     @compileLog(c);
    //     @compileLog(s0);
    //     @compileLog(s1);
    //     @compileLog(existing);
    //     @compileError("transition set multiple times");
    // }

    t[c][s0_int] = transition(s1, a);
}

fn range(t: *OptionalTable, from: u8, to: u8, s0: State, s1: State, a: Action) void {
    var i = from;
    while (i <= to) : (i += 1) {
        single(t, i, s0, s1, a);
        // If 'to' is 0xFF, our next pass will overflow. Return early to prevent
        // the loop from executing it's continue expression
        if (i == to) break;
    }
}

fn transition(state: State, action: Action) Transition {
    return .{ .state = state, .action = action };
}

test {
    // This forces comptime-evaluation of table, so we're just testing
    // that it succeeds in creation.
    _ = table;
}
