package sqlparser

import (
	vtparser "github.com/blastrain/vitess-sqlparser/sqlparser"
)

// Identifier the type for sharding key
type Identifier int64

const (
	// UnknownID the identifier of default sharding key
	UnknownID Identifier = -1
)

// QueryType the type of SQL/DDL ( Select, Insert, Update, Delet, ...)
type QueryType int

const (
	// Unknown undefined query type
	Unknown QueryType = iota
	// Select 'SELECT' query type
	Select
	// Insert 'INSERT' query type
	Insert
	// Update 'UPDATE' query type
	Update
	// Delete 'DELETE' query type
	Delete
	// Drop 'DROP' query type
	Drop
	// CreateTable 'CREATE TABLE' query type
	CreateTable
	// TruncateTable 'TRUNCATE TABLE' query type
	TruncateTable
	// Show 'SHOW' query type
	Show
)

func (t QueryType) IsWriteQuery() bool {
	return t == Insert || t == Update || t == Delete
}

func (t QueryType) String() string {
	switch t {
	case Unknown:
		return "UNKNOWN"
	case Select:
		return "SELECT"
	case Insert:
		return "INSERT"
	case Update:
		return "UPDATE"
	case Delete:
		return "DELETE"
	case Drop:
		return "DROP"
	case CreateTable:
		return "CREATE TABLE"
	case TruncateTable:
		return "TRUNCATE TABLE"
	}
	return ""
}

// Query the interface that must be implemented by each query.
type Query interface {
	// Table returns a table name
	Table() string
	// QueryType returns a type of SQL/DDL
	QueryType() QueryType
}

// NewQueryBase creates instance of QueryBase structure,
// this is used by query that excluded INSERT or DELETE.
func NewQueryBase(stmt vtparser.Statement, query string, args []interface{}) *QueryBase {
	return &QueryBase{
		Text:       query,
		Args:       args,
		Stmt:       stmt,
		ShardKeyID: UnknownID,
	}
}

// QueryBase a implementation of Query interface.
type QueryBase struct {
	Text                       string
	Args                       []interface{}
	Type                       QueryType
	TableName                  string
	ShardKeyID                 Identifier
	ShardKeyIDPlaceholderIndex int
	Stmt                       vtparser.Statement
}

// Table returns table name
func (q *QueryBase) Table() string {
	return q.TableName
}

// QueryType returns a type of SQL/DDL
func (q *QueryBase) QueryType() QueryType {
	return q.Type
}

// IsNotFoundShardKeyID returns whether sharding key is found in SQL
func (q *QueryBase) IsNotFoundShardKeyID() bool {
	return q.ShardKeyID == UnknownID
}

// InsertQuery a implementation of Query interface.
type InsertQuery struct {
	*QueryBase
	Stmt           *vtparser.Insert
	ColumnValues   []func() *vtparser.SQLVal
	nextSequenceID Identifier
}

// NewInsertQuery creates instance of InsertQuery structure.
func NewInsertQuery(queryBase *QueryBase, stmt *vtparser.Insert) *InsertQuery {
	values := stmt.Rows.(vtparser.Values)
	return &InsertQuery{
		QueryBase:    queryBase,
		Stmt:         stmt,
		ColumnValues: make([]func() *vtparser.SQLVal, len(values[0])),
	}
}

// NextSequenceID get next unique id value generated by sequencer.
func (q *InsertQuery) NextSequenceID() Identifier {
	return q.nextSequenceID
}

// SetNextSequenceID set unique id value generated by sequencer.
func (q *InsertQuery) SetNextSequenceID(id int64) {
	q.nextSequenceID = Identifier(id)
}

// String returns formatted text.
// If insert query includes variable like placeholder, replace it.
func (q *InsertQuery) String() string {
	values := q.Stmt.Rows.(vtparser.Values)
	for idx, columnValue := range q.ColumnValues {
		if columnValue == nil {
			continue
		}
		values[0][idx] = columnValue()
	}
	return vtparser.String(q.Stmt)
}

// DeleteQuery a implementation of Query interface.
type DeleteQuery struct {
	*QueryBase
	Stmt            *vtparser.Delete
	IsDeleteTable   bool
	IsAllShardQuery bool
}

// NewDeleteQuery creates instance of DeleteQuery structure.
func NewDeleteQuery(queryBase *QueryBase, stmt *vtparser.Delete) *DeleteQuery {
	return &DeleteQuery{
		QueryBase: queryBase,
		Stmt:      stmt,
	}
}

func (q *DeleteQuery) setStateAfterParsing() {
	q.IsDeleteTable = q.IsNotFoundShardKeyID() &&
		q.Stmt.Where == nil && q.Stmt.OrderBy == nil && q.Stmt.Limit == nil
	q.IsAllShardQuery = q.IsNotFoundShardKeyID() &&
		(q.Stmt.Where != nil || q.Stmt.OrderBy != nil || q.Stmt.Limit != nil)
}

