package parser

// Table values are generated like this:
//
//	index:  currentState << IndexStateShift | charCode
//	value:  action << TransitionActionShift | nextState
const (
	TransitionActionShift = 4
	TransitionStateMask   = 15
	IndexStateShift       = 8

	// DefaultTableSize is the default size of the transition table.
	DefaultTableSize = 4096
)

// Table is a DEC ANSI transition table.
var Table = GenerateTransitionTable()

// TransitionTable is a DEC ANSI transition table.
// https://vt100.net/emu/dec_ansi_parser
type TransitionTable []byte

// NewTransitionTable returns a new DEC ANSI transition table.
func NewTransitionTable(size int) TransitionTable {
	if size <= 0 {
		size = DefaultTableSize
	}
	return TransitionTable(make([]byte, size))
}

// SetDefault sets default transition.
func (t TransitionTable) SetDefault(action Action, state State) {
	for i := 0; i < len(t); i++ {
		t[i] = action<<TransitionActionShift | state
	}
}

// AddOne adds a transition.
func (t TransitionTable) AddOne(code byte, state State, action Action, next State) {
	idx := int(state)<<IndexStateShift | int(code)
	value := action<<TransitionActionShift | next
	t[idx] = value
}

// AddMany adds many transitions.
func (t TransitionTable) AddMany(codes []byte, state State, action Action, next State) {
	for _, code := range codes {
		t.AddOne(code, state, action, next)
	}
}

// AddRange adds a range of transitions.
func (t TransitionTable) AddRange(start, end byte, state State, action Action, next State) {
	for i := int(start); i <= int(end); i++ {
		t.AddOne(byte(i), state, action, next)
	}
}

// Transition returns the next state and action for the given state and byte.
func (t TransitionTable) Transition(state State, code byte) (State, Action) {
	index := int(state)<<IndexStateShift | int(code)
	value := t[index]
	return value & TransitionStateMask, value >> TransitionActionShift
}

// byte range macro
func r(start, end byte) []byte {
	var a []byte
	for i := int(start); i <= int(end); i++ {
		a = append(a, byte(i))
	}
	return a
}

// GenerateTransitionTable generates a DEC ANSI transition table compatible
// with the VT500-series of terminals. This implementation includes a few
// modifications that include:
//   - A new Utf8State is introduced to handle UTF8 sequences.
//   - Osc and Dcs data accept UTF8 sequences by extending the printable range
//     to 0xFF and 0xFE respectively.
//   - We don't ignore 0x3A (':') when building Csi and Dcs parameters and
//     instead use it to denote sub-parameters.
//   - Support dispatching SosPmApc sequences.
//   - The DEL (0x7F) character is executed in the Ground state.
//   - The DEL (0x7F) character is collected in the DcsPassthrough string state.
//   - The ST C1 control character (0x9C) is executed and not ignored.
func GenerateTransitionTable() TransitionTable {
	table := NewTransitionTable(DefaultTableSize)
	table.SetDefault(NoneAction, GroundState)

	// Anywhere
	for _, state := range r(GroundState, Utf8State) {
		// Anywhere -> Ground
		table.AddMany([]byte{0x18, 0x1a, 0x99, 0x9a}, state, ExecuteAction, GroundState)
		table.AddRange(0x80, 0x8F, state, ExecuteAction, GroundState)
		table.AddRange(0x90, 0x97, state, ExecuteAction, GroundState)
		table.AddOne(0x9C, state, ExecuteAction, GroundState)
		// Anywhere -> Escape
		table.AddOne(0x1B, state, ClearAction, EscapeState)
		// Anywhere -> SosStringState
		table.AddOne(0x98, state, StartAction, SosStringState)
		// Anywhere -> PmStringState
		table.AddOne(0x9E, state, StartAction, PmStringState)
		// Anywhere -> ApcStringState
		table.AddOne(0x9F, state, StartAction, ApcStringState)
		// Anywhere -> CsiEntry
		table.AddOne(0x9B, state, ClearAction, CsiEntryState)
		// Anywhere -> DcsEntry
		table.AddOne(0x90, state, ClearAction, DcsEntryState)
		// Anywhere -> OscString
		table.AddOne(0x9D, state, StartAction, OscStringState)
		// Anywhere -> Utf8
		table.AddRange(0xC2, 0xDF, state, CollectAction, Utf8State) // UTF8 2 byte sequence
		table.AddRange(0xE0, 0xEF, state, CollectAction, Utf8State) // UTF8 3 byte sequence
		table.AddRange(0xF0, 0xF4, state, CollectAction, Utf8State) // UTF8 4 byte sequence
	}

	// Ground
	table.AddRange(0x00, 0x17, GroundState, ExecuteAction, GroundState)
	table.AddOne(0x19, GroundState, ExecuteAction, GroundState)
	table.AddRange(0x1C, 0x1F, GroundState, ExecuteAction, GroundState)
	table.AddRange(0x20, 0x7E, GroundState, PrintAction, GroundState)
	table.AddOne(0x7F, GroundState, ExecuteAction, GroundState)

	// EscapeIntermediate
	table.AddRange(0x00, 0x17, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
	table.AddOne(0x19, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
	table.AddRange(0x1C, 0x1F, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
	table.AddRange(0x20, 0x2F, EscapeIntermediateState, CollectAction, EscapeIntermediateState)
	table.AddOne(0x7F, EscapeIntermediateState, IgnoreAction, EscapeIntermediateState)
	// EscapeIntermediate -> Ground
	table.AddRange(0x30, 0x7E, EscapeIntermediateState, DispatchAction, GroundState)

	// Escape
	table.AddRange(0x00, 0x17, EscapeState, ExecuteAction, EscapeState)
	table.AddOne(0x19, EscapeState, ExecuteAction, EscapeState)
	table.AddRange(0x1C, 0x1F, EscapeState, ExecuteAction, EscapeState)
	table.AddOne(0x7F, EscapeState, IgnoreAction, EscapeState)
	// Escape -> Ground
	table.AddRange(0x30, 0x4F, EscapeState, DispatchAction, GroundState)
	table.AddRange(0x51, 0x57, EscapeState, DispatchAction, GroundState)
	table.AddOne(0x59, EscapeState, DispatchAction, GroundState)
	table.AddOne(0x5A, EscapeState, DispatchAction, GroundState)
	table.AddOne(0x5C, EscapeState, DispatchAction, GroundState)
	table.AddRange(0x60, 0x7E, EscapeState, DispatchAction, GroundState)
	// Escape -> Escape_intermediate
	table.AddRange(0x20, 0x2F, EscapeState, CollectAction, EscapeIntermediateState)
	// Escape -> Sos_pm_apc_string
	table.AddOne('X', EscapeState, StartAction, SosStringState) // SOS
	table.AddOne('^', EscapeState, StartAction, PmStringState)  // PM
	table.AddOne('_', EscapeState, StartAction, ApcStringState) // APC
	// Escape -> Dcs_entry
	table.AddOne('P', EscapeState, ClearAction, DcsEntryState)
	// Escape -> Csi_entry
	table.AddOne('[', EscapeState, ClearAction, CsiEntryState)
	// Escape -> Osc_string
	table.AddOne(']', EscapeState, StartAction, OscStringState)

	// Sos_pm_apc_string
	for _, state := range r(SosStringState, ApcStringState) {
		table.AddRange(0x00, 0x17, state, PutAction, state)
		table.AddOne(0x19, state, PutAction, state)
		table.AddRange(0x1C, 0x1F, state, PutAction, state)
		table.AddRange(0x20, 0x7F, state, PutAction, state)
		// ESC, ST, CAN, and SUB terminate the sequence
		table.AddOne(0x1B, state, DispatchAction, EscapeState)
		table.AddOne(0x9C, state, DispatchAction, GroundState)
		table.AddMany([]byte{0x18, 0x1A}, state, IgnoreAction, GroundState)
	}

	// Dcs_entry
	table.AddRange(0x00, 0x07, DcsEntryState, IgnoreAction, DcsEntryState)
	table.AddRange(0x0E, 0x17, DcsEntryState, IgnoreAction, DcsEntryState)
	table.AddOne(0x19, DcsEntryState, IgnoreAction, DcsEntryState)
	table.AddRange(0x1C, 0x1F, DcsEntryState, IgnoreAction, DcsEntryState)
	table.AddOne(0x7F, DcsEntryState, IgnoreAction, DcsEntryState)
	// Dcs_entry -> Dcs_intermediate
	table.AddRange(0x20, 0x2F, DcsEntryState, CollectAction, DcsIntermediateState)
	// Dcs_entry -> Dcs_param
	table.AddRange(0x30, 0x3B, DcsEntryState, ParamAction, DcsParamState)
	table.AddRange(0x3C, 0x3F, DcsEntryState, MarkerAction, DcsParamState)
	// Dcs_entry -> Dcs_passthrough
	table.AddRange(0x08, 0x0D, DcsEntryState, PutAction, DcsStringState) // Follows ECMA-48 § 8.3.27
	// XXX: allows passing ESC (not a ECMA-48 standard) this to allow for
	// passthrough of ANSI sequences like in Screen or Tmux passthrough mode.
	table.AddOne(0x1B, DcsEntryState, PutAction, DcsStringState)
	table.AddRange(0x40, 0x7E, DcsEntryState, StartAction, DcsStringState)

	// Dcs_intermediate
	table.AddRange(0x00, 0x17, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
	table.AddOne(0x19, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
	table.AddRange(0x1C, 0x1F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
	table.AddRange(0x20, 0x2F, DcsIntermediateState, CollectAction, DcsIntermediateState)
	table.AddOne(0x7F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
	// Dcs_intermediate -> Dcs_passthrough
	table.AddRange(0x30, 0x3F, DcsIntermediateState, StartAction, DcsStringState)
	table.AddRange(0x40, 0x7E, DcsIntermediateState, StartAction, DcsStringState)

	// Dcs_param
	table.AddRange(0x00, 0x17, DcsParamState, IgnoreAction, DcsParamState)
	table.AddOne(0x19, DcsParamState, IgnoreAction, DcsParamState)
	table.AddRange(0x1C, 0x1F, DcsParamState, IgnoreAction, DcsParamState)
	table.AddRange(0x30, 0x3B, DcsParamState, ParamAction, DcsParamState)
	table.AddOne(0x7F, DcsParamState, IgnoreAction, DcsParamState)
	table.AddRange(0x3C, 0x3F, DcsParamState, IgnoreAction, DcsParamState)
	// Dcs_param -> Dcs_intermediate
	table.AddRange(0x20, 0x2F, DcsParamState, CollectAction, DcsIntermediateState)
	// Dcs_param -> Dcs_passthrough
	table.AddRange(0x40, 0x7E, DcsParamState, StartAction, DcsStringState)

	// Dcs_passthrough
	table.AddRange(0x00, 0x17, DcsStringState, PutAction, DcsStringState)
	table.AddOne(0x19, DcsStringState, PutAction, DcsStringState)
	table.AddRange(0x1C, 0x1F, DcsStringState, PutAction, DcsStringState)
	table.AddRange(0x20, 0x7E, DcsStringState, PutAction, DcsStringState)
	table.AddOne(0x7F, DcsStringState, PutAction, DcsStringState)
	table.AddRange(0x80, 0xFF, DcsStringState, PutAction, DcsStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF
	// ST, CAN, SUB, and ESC terminate the sequence
	table.AddOne(0x1B, DcsStringState, DispatchAction, EscapeState)
	table.AddOne(0x9C, DcsStringState, DispatchAction, GroundState)
	table.AddMany([]byte{0x18, 0x1A}, DcsStringState, IgnoreAction, GroundState)

	// Csi_param
	table.AddRange(0x00, 0x17, CsiParamState, ExecuteAction, CsiParamState)
	table.AddOne(0x19, CsiParamState, ExecuteAction, CsiParamState)
	table.AddRange(0x1C, 0x1F, CsiParamState, ExecuteAction, CsiParamState)
	table.AddRange(0x30, 0x3B, CsiParamState, ParamAction, CsiParamState)
	table.AddOne(0x7F, CsiParamState, IgnoreAction, CsiParamState)
	table.AddRange(0x3C, 0x3F, CsiParamState, IgnoreAction, CsiParamState)
	// Csi_param -> Ground
	table.AddRange(0x40, 0x7E, CsiParamState, DispatchAction, GroundState)
	// Csi_param -> Csi_intermediate
	table.AddRange(0x20, 0x2F, CsiParamState, CollectAction, CsiIntermediateState)

	// Csi_intermediate
	table.AddRange(0x00, 0x17, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
	table.AddOne(0x19, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
	table.AddRange(0x1C, 0x1F, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
	table.AddRange(0x20, 0x2F, CsiIntermediateState, CollectAction, CsiIntermediateState)
	table.AddOne(0x7F, CsiIntermediateState, IgnoreAction, CsiIntermediateState)
	// Csi_intermediate -> Ground
	table.AddRange(0x40, 0x7E, CsiIntermediateState, DispatchAction, GroundState)
	// Csi_intermediate -> Csi_ignore
	table.AddRange(0x30, 0x3F, CsiIntermediateState, IgnoreAction, GroundState)

	// Csi_entry
	table.AddRange(0x00, 0x17, CsiEntryState, ExecuteAction, CsiEntryState)
	table.AddOne(0x19, CsiEntryState, ExecuteAction, CsiEntryState)
	table.AddRange(0x1C, 0x1F, CsiEntryState, ExecuteAction, CsiEntryState)
	table.AddOne(0x7F, CsiEntryState, IgnoreAction, CsiEntryState)
	// Csi_entry -> Ground
	table.AddRange(0x40, 0x7E, CsiEntryState, DispatchAction, GroundState)
	// Csi_entry -> Csi_intermediate
	table.AddRange(0x20, 0x2F, CsiEntryState, CollectAction, CsiIntermediateState)
	// Csi_entry -> Csi_param
	table.AddRange(0x30, 0x3B, CsiEntryState, ParamAction, CsiParamState)
	table.AddRange(0x3C, 0x3F, CsiEntryState, MarkerAction, CsiParamState)

	// Osc_string
	table.AddRange(0x00, 0x06, OscStringState, IgnoreAction, OscStringState)
	table.AddRange(0x08, 0x17, OscStringState, IgnoreAction, OscStringState)
	table.AddOne(0x19, OscStringState, IgnoreAction, OscStringState)
	table.AddRange(0x1C, 0x1F, OscStringState, IgnoreAction, OscStringState)
	table.AddRange(0x20, 0xFF, OscStringState, PutAction, OscStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF

	// ST, CAN, SUB, ESC, and BEL terminate the sequence
	table.AddOne(0x1B, OscStringState, DispatchAction, EscapeState)
	table.AddOne(0x07, OscStringState, DispatchAction, GroundState)
	table.AddOne(0x9C, OscStringState, DispatchAction, GroundState)
	table.AddMany([]byte{0x18, 0x1A}, OscStringState, IgnoreAction, GroundState)

	return table
}
