package scanner

import (
	"strings"
)

// Token is a lexical token of the Chai SQL language.
type Token int

// These are a comprehensive list of Chai SQL language tokens.
const (
	// ILLEGAL Token, EOF, WS are Special Chai SQL tokens.
	ILLEGAL Token = iota
	EOF
	WS
	COMMENT

	// IDENT and the following are Chai SQL literal tokens.
	IDENT           // main
	POSITIONALPARAM // $1
	NUMBER          // 12345.67
	INTEGER         // 12345
	STRING          // "abc"
	BADSTRING       // "abc
	BADESCAPE       // \q
	TRUE            // true
	FALSE           // false
	NULL            // NULL
	REGEX           // Regular expressions
	BADREGEX        // `.*
	literalEnd

	operatorBeg
	// ADD and the following are Chai SQL Operators
	ADD        // +
	SUB        // -
	MUL        // *
	DIV        // /
	MOD        // %
	BITWISEAND // &
	BITWISEOR  // |
	BITWISEXOR // ^

	AND // AND
	OR  // OR

	EQ       // =
	NEQ      // !=
	EQREGEX  // =~
	NEQREGEX // !~
	LT       // <
	LTE      // <=
	GT       // >
	GTE      // >=
	IN       // IN
	NIN      // NOT IN
	IS       // IS
	ISN      // IS NOT
	LIKE     // LIKE
	NLIKE    // NOT LIKE
	CONCAT   // ||
	BETWEEN  // BETWEEN
	operatorEnd

	LPAREN      // (
	RPAREN      // )
	LBRACKET    // {
	RBRACKET    // }
	LSBRACKET   // [
	RSBRACKET   // ]
	COMMA       // ,
	COLON       // :
	DOUBLECOLON // ::
	SEMICOLON   // ;
	DOT         // .

	keywordBeg
	// ALL and the following are Chai SQL Keywords
	ADD_KEYWORD
	ALL
	ALTER
	AS
	ASC
	BEGIN
	BY
	CACHE
	CAST
	CHECK
	COLUMN
	COMMIT
	CONFLICT
	CONSTRAINT
	CREATE
	CYCLE
	DEFAULT
	DELETE
	DESC
	DISTINCT
	DO
	DROP
	EXISTS
	EXPLAIN
	FOR
	FROM
	GROUP
	IF
	IGNORE
	INCREMENT
	INDEX
	INSERT
	INTO
	KEY
	LIMIT
	MAXVALUE
	MINVALUE
	NO
	NOT
	NOTHING
	OFFSET
	ON
	ONLY
	ORDER
	PRECISION
	PRIMARY
	READ
	REINDEX
	RENAME
	REPLACE
	RETURNING
	ROLLBACK
	SELECT
	SEQUENCE
	SET
	START
	TABLE
	TO
	TRANSACTION
	UNION
	UNIQUE
	UPDATE
	VALUES
	WITH
	WHERE
	WRITE

	// Types
	TYPEBIGINT
	TYPEBYTEA
	TYPEBOOL
	TYPEBOOLEAN
	TYPEBYTES
	TYPECHARACTER
	TYPEDOUBLE
	TYPEINT
	TYPEINT2
	TYPEINT8
	TYPEINTEGER
	TYPEMEDIUMINT
	TYPEREAL
	TYPESMALLINT
	TYPETEXT
	TYPETIMESTAMP
	TYPETINYINT
	TYPEVARCHAR

	keywordEnd
)

var tokens = [...]string{
	ILLEGAL: "ILLEGAL",
	EOF:     "EOF",
	WS:      "WS",

	IDENT:           "IDENT",
	POSITIONALPARAM: "?",
	NUMBER:          "NUMBER",
	STRING:          "STRING",
	BADSTRING:       "BADSTRING",
	BADESCAPE:       "BADESCAPE",
	TRUE:            "TRUE",
	FALSE:           "FALSE",
	REGEX:           "REGEX",
	NULL:            "NULL",

	ADD:        "+",
	SUB:        "-",
	MUL:        "*",
	DIV:        "/",
	MOD:        "%",
	BITWISEAND: "&",
	BITWISEOR:  "|",
	BITWISEXOR: "^",
	BETWEEN:    "BETWEEN",

	AND: "AND",
	OR:  "OR",

	EQ:       "=",
	NEQ:      "!=",
	EQREGEX:  "=~",
	NEQREGEX: "!~",
	LT:       "<",
	LTE:      "<=",
	GT:       ">",
	GTE:      ">=",
	IN:       "IN",
	IS:       "IS",
	LIKE:     "LIKE",

	LPAREN:      "(",
	RPAREN:      ")",
	LBRACKET:    "{",
	RBRACKET:    "}",
	LSBRACKET:   "[",
	RSBRACKET:   "]",
	COMMA:       ",",
	COLON:       ":",
	DOUBLECOLON: "::",
	SEMICOLON:   ";",
	DOT:         ".",

	ADD_KEYWORD: "ADD",
	ALL:         "ALL",
	ALTER:       "ALTER",
	AS:          "AS",
	ASC:         "ASC",
	BEGIN:       "BEGIN",
	BY:          "BY",
	CACHE:       "CACHE",
	CAST:        "CAST",
	CHECK:       "CHECK",
	COLUMN:      "COLUMN",
	COMMIT:      "COMMIT",
	CONFLICT:    "CONFLICT",
	CONSTRAINT:  "CONSTRAINT",
	CREATE:      "CREATE",
	CYCLE:       "CYCLE",
	DO:          "DO",
	DEFAULT:     "DEFAULT",
	DELETE:      "DELETE",
	DESC:        "DESC",
	DISTINCT:    "DISTINCT",
	DROP:        "DROP",
	EXISTS:      "EXISTS",
	EXPLAIN:     "EXPLAIN",
	GROUP:       "GROUP",
	KEY:         "KEY",
	FOR:         "FOR",
	FROM:        "FROM",
	IF:          "IF",
	IGNORE:      "IGNORE",
	INCREMENT:   "INCREMENT",
	INDEX:       "INDEX",
	INSERT:      "INSERT",
	INTO:        "INTO",
	LIMIT:       "LIMIT",
	MAXVALUE:    "MAXVALUE",
	MINVALUE:    "MINVALUE",
	NO:          "NO",
	NOT:         "NOT",
	NOTHING:     "NOTHING",
	OFFSET:      "OFFSET",
	ON:          "ON",
	ONLY:        "ONLY",
	ORDER:       "ORDER",
	PRECISION:   "PRECISION",
	PRIMARY:     "PRIMARY",
	READ:        "READ",
	REINDEX:     "REINDEX",
	RENAME:      "RENAME",
	RETURNING:   "RETURNING",
	REPLACE:     "REPLACE",
	ROLLBACK:    "ROLLBACK",
	START:       "START",
	SELECT:      "SELECT",
	SET:         "SET",
	SEQUENCE:    "SEQUENCE",
	TABLE:       "TABLE",
	TO:          "TO",
	TRANSACTION: "TRANSACTION",
	UNION:       "UNION",
	UNIQUE:      "UNIQUE",
	UPDATE:      "UPDATE",
	VALUES:      "VALUES",
	WITH:        "WITH",
	WHERE:       "WHERE",
	WRITE:       "WRITE",

	TYPEBIGINT:    "BIGINT",
	TYPEBYTEA:     "BYTEA",
	TYPEBOOL:      "BOOL",
	TYPEBOOLEAN:   "BOOLEAN",
	TYPEBYTES:     "BYTES",
	TYPECHARACTER: "CHARACTER",
	TYPEDOUBLE:    "DOUBLE",
	TYPEINT:       "INT",
	TYPEINT2:      "INT2",
	TYPEINT8:      "INT8",
	TYPEINTEGER:   "INTEGER",
	TYPEMEDIUMINT: "MEDIUMINT",
	TYPEREAL:      "REAL",
	TYPESMALLINT:  "SMALLINT",
	TYPETEXT:      "TEXT",
	TYPETIMESTAMP: "TIMESTAMP",
	TYPETINYINT:   "TINYINT",
	TYPEVARCHAR:   "VARCHAR",
}

var keywords map[string]Token

// String returns the string representation of the token.
func (tok Token) String() string {
	if tok >= 0 && tok < Token(len(tokens)) {
		return tokens[tok]
	}
	return ""
}

// Precedence returns the operator precedence of the binary operator token.
func (tok Token) Precedence() int {
	switch tok {
	case OR:
		return 1
	case AND:
		return 2
	case NOT:
		return 3
	case EQ, NEQ, IS, ISN, IN, NIN, LIKE, NLIKE, EQREGEX, NEQREGEX, BETWEEN:
		return 4
	case LT, LTE, GT, GTE:
		return 5
	case BITWISEOR, BITWISEXOR, BITWISEAND:
		return 6
	case ADD, SUB:
		return 7
	case MUL, DIV, MOD:
		return 8
	case CONCAT:
		return 9
	}
	return 0
}

// IsOperator returns true for operator tokens.
func (tok Token) IsOperator() bool { return tok > operatorBeg && tok < operatorEnd }

// Tokstr returns a literal if provided, otherwise returns the token string.
func Tokstr(tok Token, lit string) string {
	if lit != "" {
		return lit
	}
	return tok.String()
}

// lookup returns the token associated with a given string.
func lookup(ident string) Token {
	if tok, ok := keywords[strings.ToLower(ident)]; ok {
		return tok
	}
	return IDENT
}

// Pos specifies the line and character position of a token.
// The Char and Line are both zero-based indexes.
type Pos struct {
	Line int
	Char int
}

// AllKeywords returns all defined tokens corresponding to keywords.
func AllKeywords() []Token {
	tokens := make([]Token, 0, len(keywords))
	for _, tok := range keywords {
		tokens = append(tokens, tok)
	}
	return tokens
}
