package tablestore

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"net/http"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/aliyun/aliyun-tablestore-go-sdk/common"
	"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore/otsprotocol"
	"github.com/golang/protobuf/proto"
)

const (
	maxTableNameLength  = 100
	maxPrimaryKeyLength = 255
	maxPrimaryKeyNum    = 10
	maxMultiDeleteRows  = 100
)

type ColumnType int32

const (
	ColumnType_STRING   ColumnType = 1
	ColumnType_INTEGER  ColumnType = 2
	ColumnType_BOOLEAN  ColumnType = 3
	ColumnType_DOUBLE   ColumnType = 4
	ColumnType_BINARY   ColumnType = 5
	ColumnType_DATETIME ColumnType = 6
	ColumnType_TIME     ColumnType = 7
	ColumnType_DATE     ColumnType = 8
)

func (t *ColumnType) String() string {
	if t == nil {
		return "UNKNOWN"
	}
	switch *t {
	case ColumnType_STRING:
		return "STRING"
	case ColumnType_INTEGER:
		return "INTEGER"
	case ColumnType_BOOLEAN:
		return "BOOLEAN"
	case ColumnType_DOUBLE:
		return "DOUBLE"
	case ColumnType_BINARY:
		return "BINARY"
	default:
		return "UNKNOWN"
	}
}

const (
	Version          = "1.0"
	ApiVersion       = "2015-12-31"
	xOtsDateFormat   = "2006-01-02T15:04:05.123Z"
	xOtsInstanceName = "x-ots-instancename"
	xOtsRequestId    = "x-ots-requestid"
)

type ColumnValue struct {
	Type  ColumnType
	Value interface{}
}

func NewColumnValue(columnType ColumnType, value interface{}) *ColumnValue {
	return &ColumnValue{
		Type:  columnType,
		Value: value,
	}
}

func (cv *ColumnValue) writeCellValue(w io.Writer) {
	writeTag(w, TAG_CELL_VALUE)
	if cv == nil {
		WriteRawLittleEndian32(w, 1)
		writeRawByte(w, VT_AUTO_INCREMENT)
		return
	}

	switch cv.Type {
	case ColumnType_STRING:
		v := cv.Value.(string)

		WriteRawLittleEndian32(w, int32(LITTLE_ENDIAN_32_SIZE+1+len(v))) // length + type + value
		writeRawByte(w, VT_STRING)
		WriteRawLittleEndian32(w, int32(len(v)))
		writeBytes(w, []byte(v))

	case ColumnType_INTEGER:
		v := cv.Value.(int64)
		WriteRawLittleEndian32(w, int32(LITTLE_ENDIAN_64_SIZE+1))
		writeRawByte(w, VT_INTEGER)
		writeRawLittleEndian64(w, v)
	case ColumnType_BOOLEAN:
		v := cv.Value.(bool)
		WriteRawLittleEndian32(w, 2)
		writeRawByte(w, VT_BOOLEAN)
		writeBoolean(w, v)

	case ColumnType_DOUBLE:
		v := cv.Value.(float64)

		WriteRawLittleEndian32(w, LITTLE_ENDIAN_64_SIZE+1)
		writeRawByte(w, VT_DOUBLE)
		writeDouble(w, v)

	case ColumnType_BINARY:
		v := cv.Value.([]byte)

		WriteRawLittleEndian32(w, int32(LITTLE_ENDIAN_32_SIZE+1+len(v))) // length + type + value
		writeRawByte(w, VT_BLOB)
		WriteRawLittleEndian32(w, int32(len(v)))
		writeBytes(w, v)
	}
}

func (cv *ColumnValue) WriteCellValueWithoutLengthPrefix() []byte {
	var b bytes.Buffer
	w := &b
	switch cv.Type {
	case ColumnType_STRING:
		v := cv.Value.(string)

		writeRawByte(w, VT_STRING)
		WriteRawLittleEndian32(w, int32(len(v)))
		writeBytes(w, []byte(v))

	case ColumnType_INTEGER:
		v := cv.Value.(int64)
		writeRawByte(w, VT_INTEGER)
		writeRawLittleEndian64(w, v)
	case ColumnType_BOOLEAN:
		v := cv.Value.(bool)
		writeRawByte(w, VT_BOOLEAN)
		writeBoolean(w, v)

	case ColumnType_DOUBLE:
		v := cv.Value.(float64)

		writeRawByte(w, VT_DOUBLE)
		writeDouble(w, v)

	case ColumnType_BINARY:
		v := cv.Value.([]byte)

		writeRawByte(w, VT_BLOB)
		WriteRawLittleEndian32(w, int32(len(v)))
		writeBytes(w, v)
	}

	return b.Bytes()
}

func (cv *ColumnValue) getCheckSum(crc byte) byte {
	if cv == nil {
		return crc8Byte(crc, VT_AUTO_INCREMENT)
	}

	switch cv.Type {
	case ColumnType_STRING:
		v := cv.Value.(string)
		crc = crc8Byte(crc, VT_STRING)
		crc = crc8Int32(crc, int32(len(v)))
		crc = crc8Bytes(crc, []byte(v))
	case ColumnType_INTEGER:
		v := cv.Value.(int64)
		crc = crc8Byte(crc, VT_INTEGER)
		crc = crc8Int64(crc, v)
	case ColumnType_BOOLEAN:
		v := cv.Value.(bool)
		crc = crc8Byte(crc, VT_BOOLEAN)
		if v {
			crc = crc8Byte(crc, 0x1)
		} else {
			crc = crc8Byte(crc, 0x0)
		}

	case ColumnType_DOUBLE:
		v := cv.Value.(float64)
		crc = crc8Byte(crc, VT_DOUBLE)
		crc = crc8Int64(crc, int64(math.Float64bits(v)))
	case ColumnType_BINARY:
		v := cv.Value.([]byte)
		crc = crc8Byte(crc, VT_BLOB)
		crc = crc8Int32(crc, int32(len(v)))
		crc = crc8Bytes(crc, v)
	}

	return crc
}

type Column struct {
	Name         []byte
	Value        ColumnValue
	Type         byte
	Timestamp    int64
	HasType      bool
	HasTimestamp bool
	IgnoreValue  bool
}

func NewColumn(name []byte, value interface{}) *Column {

	v := &Column{}
	v.Name = name

	if value != nil {
		t := reflect.TypeOf(value)
		switch t.Kind() {
		case reflect.String:
			v.Value.Type = ColumnType_STRING

		case reflect.Int64:
			v.Value.Type = ColumnType_INTEGER

		case reflect.Bool:
			v.Value.Type = ColumnType_BOOLEAN

		case reflect.Float64:
			v.Value.Type = ColumnType_DOUBLE

		case reflect.Slice:
			v.Value.Type = ColumnType_BINARY
		default:
			panic(errInvalidInput)
		}

		v.Value.Value = value
	}

	return v
}

func (c *Column) toPlainBufferCell(ignoreValue bool) *PlainBufferCell {
	cell := &PlainBufferCell{}
	cell.cellName = c.Name
	cell.ignoreValue = ignoreValue
	if ignoreValue == false {
		cell.cellValue = &c.Value
	}

	if c.HasType {
		cell.hasCellType = c.HasType
		cell.cellType = byte(c.Type)
	}

	if c.HasTimestamp {
		cell.hasCellTimestamp = c.HasTimestamp
		cell.cellTimestamp = c.Timestamp
	}

	return cell
}

type PrimaryKeyColumnInner struct {
	Name  []byte
	Type  otsprotocol.PrimaryKeyType
	Value interface{}
}

func NewPrimaryKeyColumnINF_MAX(name []byte) *PrimaryKeyColumnInner {
	v := &PrimaryKeyColumnInner{}
	v.Name = name
	v.Type = 0
	v.Value = "INF_MAX"

	return v
}

func NewPrimaryKeyColumnINF_MIN(name []byte) *PrimaryKeyColumnInner {
	v := &PrimaryKeyColumnInner{}
	v.Name = name
	v.Type = 0
	v.Value = "INF_MIN"

	return v
}

func NewPrimaryKeyColumnAuto_Increment(name []byte) *PrimaryKeyColumnInner {
	v := &PrimaryKeyColumnInner{}
	v.Name = name
	v.Type = 0
	v.Value = "AUTO_INCRMENT"
	return v
}

func NewPrimaryKeyColumn(name []byte, value interface{}, option PrimaryKeyOption) *PrimaryKeyColumnInner {

	if option == NONE {
		v := &PrimaryKeyColumnInner{}
		v.Name = name

		t := reflect.TypeOf(value)
		switch t.Kind() {
		case reflect.String:
			v.Type = otsprotocol.PrimaryKeyType_STRING

		case reflect.Int64:
			v.Type = otsprotocol.PrimaryKeyType_INTEGER

		case reflect.Slice:
			v.Type = otsprotocol.PrimaryKeyType_BINARY

		default:
			panic(errInvalidInput)
		}

		v.Value = value

		return v
	} else if option == AUTO_INCREMENT {
		return NewPrimaryKeyColumnAuto_Increment(name)
	} else if option == MIN {
		return NewPrimaryKeyColumnINF_MIN(name)
	} else {
		return NewPrimaryKeyColumnINF_MAX(name)
	}
}

func (pkc *PrimaryKeyColumnInner) toColumnValue() *ColumnValue {
	switch pkc.Type {
	case otsprotocol.PrimaryKeyType_INTEGER:
		return &ColumnValue{ColumnType_INTEGER, pkc.Value}
	case otsprotocol.PrimaryKeyType_STRING:
		return &ColumnValue{ColumnType_STRING, pkc.Value}
	case otsprotocol.PrimaryKeyType_BINARY:
		return &ColumnValue{ColumnType_BINARY, pkc.Value}
	}

	return nil
}

func (pkc *PrimaryKeyColumnInner) toPlainBufferCell() *PlainBufferCell {
	cell := &PlainBufferCell{}
	cell.cellName = pkc.Name
	cell.cellValue = pkc.toColumnValue()
	return cell
}

func (pkc *PrimaryKeyColumnInner) isInfMin() bool {
	if pkc.Type == 0 && pkc.Value.(string) == "INF_MIN" {
		return true
	}

	return false
}

func (pkc *PrimaryKeyColumnInner) isInfMax() bool {
	if pkc.Type == 0 && pkc.Value.(string) == "INF_MAX" {
		return true
	}

	return false
}

func (pkc *PrimaryKeyColumnInner) isAutoInc() bool {
	if pkc.Type == 0 && pkc.Value.(string) == "AUTO_INCRMENT" {
		return true
	}
	return false
}

func (pkc *PrimaryKeyColumnInner) getCheckSum(crc byte) byte {
	if pkc.isInfMin() {
		return crc8Byte(crc, VT_INF_MIN)
	}
	if pkc.isInfMax() {
		return crc8Byte(crc, VT_INF_MAX)
	}
	if pkc.isAutoInc() {
		return crc8Byte(crc, VT_AUTO_INCREMENT)
	}

	return pkc.toColumnValue().getCheckSum(crc)
}

func (pkc *PrimaryKeyColumnInner) writePrimaryKeyColumn(w io.Writer) {
	writeTag(w, TAG_CELL)
	writeCellName(w, []byte(pkc.Name))
	if pkc.isInfMin() {
		writeTag(w, TAG_CELL_VALUE)
		WriteRawLittleEndian32(w, 1)
		writeRawByte(w, VT_INF_MIN)
		return
	}
	if pkc.isInfMax() {
		writeTag(w, TAG_CELL_VALUE)
		WriteRawLittleEndian32(w, 1)
		writeRawByte(w, VT_INF_MAX)
		return
	}
	if pkc.isAutoInc() {
		writeTag(w, TAG_CELL_VALUE)
		WriteRawLittleEndian32(w, 1)
		writeRawByte(w, VT_AUTO_INCREMENT)
		return
	}
	pkc.toColumnValue().writeCellValue(w)
}

type PrimaryKey2 struct {
	primaryKey []*PrimaryKeyColumnInner
}

func (pk *PrimaryKey) Build(isDelete bool) []byte {
	var b bytes.Buffer
	writeHeader(&b)
	writeTag(&b, TAG_ROW_PK)

	rowChecksum := byte(0x0)
	var cellChecksum byte

	for _, column := range pk.PrimaryKeys {
		primaryKeyColumn := NewPrimaryKeyColumn([]byte(column.ColumnName), column.Value, column.PrimaryKeyOption)

		cellChecksum = crc8Bytes(byte(0x0), []byte(primaryKeyColumn.Name))
		cellChecksum = primaryKeyColumn.getCheckSum(cellChecksum)
		rowChecksum = crc8Byte(rowChecksum, cellChecksum)
		primaryKeyColumn.writePrimaryKeyColumn(&b)

		writeTag(&b, TAG_CELL_CHECKSUM)
		writeRawByte(&b, cellChecksum)
	}

	// 没有deleteMarker, 要与0x0做crc.
	if isDelete {
		writeTag(&b, TAG_DELETE_ROW_MARKER)
		rowChecksum = crc8Byte(rowChecksum, byte(0x1))
	} else {
		rowChecksum = crc8Byte(rowChecksum, byte(0x0))
	}
	writeTag(&b, TAG_ROW_CHECKSUM)
	writeRawByte(&b, rowChecksum)

	return b.Bytes()
}

type RowPutChange struct {
	primaryKey   []*PrimaryKeyColumnInner
	columnsToPut []*Column
}

type RowUpdateChange struct {
	primaryKey      []*PrimaryKeyColumnInner
	columnsToUpdate []*Column
}

func (rpc *RowPutChange) Build() []byte {
	pkCells := make([]*PlainBufferCell, len(rpc.primaryKey))
	for i, pkc := range rpc.primaryKey {
		pkCells[i] = pkc.toPlainBufferCell()
	}

	cells := make([]*PlainBufferCell, len(rpc.columnsToPut))
	for i, c := range rpc.columnsToPut {
		cells[i] = c.toPlainBufferCell(false)
	}

	row := &PlainBufferRow{
		primaryKey: pkCells,
		cells:      cells}
	var b bytes.Buffer
	row.writeRowWithHeader(&b)

	return b.Bytes()
}

func (ruc *RowUpdateChange) Build() []byte {
	pkCells := make([]*PlainBufferCell, len(ruc.primaryKey))
	for i, pkc := range ruc.primaryKey {
		pkCells[i] = pkc.toPlainBufferCell()
	}

	cells := make([]*PlainBufferCell, len(ruc.columnsToUpdate))
	for i, c := range ruc.columnsToUpdate {
		cells[i] = c.toPlainBufferCell(c.IgnoreValue)
	}

	row := &PlainBufferRow{
		primaryKey: pkCells,
		cells:      cells}
	var b bytes.Buffer
	row.writeRowWithHeader(&b)

	return b.Bytes()
}

const (
	MaxValue = "_get_range_max"
	MinValue = "_get_range_min"
)

func (comparatorType *ComparatorType) ConvertToPbComparatorType() otsprotocol.ComparatorType {
	switch *comparatorType {
	case CT_EQUAL:
		return otsprotocol.ComparatorType_CT_EQUAL
	case CT_NOT_EQUAL:
		return otsprotocol.ComparatorType_CT_NOT_EQUAL
	case CT_GREATER_THAN:
		return otsprotocol.ComparatorType_CT_GREATER_THAN
	case CT_GREATER_EQUAL:
		return otsprotocol.ComparatorType_CT_GREATER_EQUAL
	case CT_LESS_THAN:
		return otsprotocol.ComparatorType_CT_LESS_THAN
	default:
		return otsprotocol.ComparatorType_CT_LESS_EQUAL
	}
}

func (columnType DefinedColumnType) ConvertToPbDefinedColumnType() otsprotocol.DefinedColumnType {
	switch columnType {
	case DefinedColumn_INTEGER:
		return otsprotocol.DefinedColumnType_DCT_INTEGER
	case DefinedColumn_DOUBLE:
		return otsprotocol.DefinedColumnType_DCT_DOUBLE
	case DefinedColumn_BOOLEAN:
		return otsprotocol.DefinedColumnType_DCT_BOOLEAN
	case DefinedColumn_STRING:
		return otsprotocol.DefinedColumnType_DCT_STRING
	default:
		return otsprotocol.DefinedColumnType_DCT_BLOB
	}
}

func ConvertPbDefinedColumnType(columnType otsprotocol.DefinedColumnType) DefinedColumnType {
	switch columnType {
	case otsprotocol.DefinedColumnType_DCT_INTEGER:
		return DefinedColumn_INTEGER
	case otsprotocol.DefinedColumnType_DCT_DOUBLE:
		return DefinedColumn_DOUBLE
	case otsprotocol.DefinedColumnType_DCT_BOOLEAN:
		return DefinedColumn_BOOLEAN
	case otsprotocol.DefinedColumnType_DCT_STRING:
		return DefinedColumn_STRING
	default:
		return DefinedColumn_BINARY
	}
}

func (loType *LogicalOperator) ConvertToPbLoType() otsprotocol.LogicalOperator {
	switch *loType {
	case LO_NOT:
		return otsprotocol.LogicalOperator_LO_NOT
	case LO_AND:
		return otsprotocol.LogicalOperator_LO_AND
	default:
		return otsprotocol.LogicalOperator_LO_OR
	}
}

func ConvertToPbCastType(variantType VariantType) *otsprotocol.VariantType {
	switch variantType {
	case Variant_INTEGER:
		return otsprotocol.VariantType_VT_INTEGER.Enum()
	case Variant_DOUBLE:
		return otsprotocol.VariantType_VT_DOUBLE.Enum()
	case Variant_STRING:
		return otsprotocol.VariantType_VT_STRING.Enum()
	default:
		panic("invalid VariantType")
	}
}

func NewValueTransferRule(regex string, vt VariantType) *ValueTransferRule {
	return &ValueTransferRule{Regex: regex, Cast_type: vt}
}

func NewSingleColumnValueRegexFilter(columnName string, comparator ComparatorType, rule *ValueTransferRule, value interface{}) *SingleColumnCondition {
	return &SingleColumnCondition{ColumnName: &columnName, Comparator: &comparator, ColumnValue: value, TransferRule: rule}
}

func NewSingleColumnValueFilter(condition *SingleColumnCondition) *otsprotocol.SingleColumnValueFilter {
	filter := new(otsprotocol.SingleColumnValueFilter)

	comparatorType := condition.Comparator.ConvertToPbComparatorType()
	filter.Comparator = &comparatorType
	filter.ColumnName = condition.ColumnName
	col := NewColumn([]byte(*condition.ColumnName), condition.ColumnValue)
	filter.ColumnValue = col.toPlainBufferCell(false).cellValue.WriteCellValueWithoutLengthPrefix()
	filter.FilterIfMissing = proto.Bool(condition.FilterIfMissing)
	filter.LatestVersionOnly = proto.Bool(condition.LatestVersionOnly)
	if condition.TransferRule != nil {
		filter.ValueTransRule = &otsprotocol.ValueTransferRule{Regex: proto.String(condition.TransferRule.Regex), CastType: ConvertToPbCastType(condition.TransferRule.Cast_type)}
	}
	return filter
}

func NewCompositeFilter(filters []ColumnFilter, lo LogicalOperator) *otsprotocol.CompositeColumnValueFilter {
	ccvfilter := new(otsprotocol.CompositeColumnValueFilter)
	combinator := lo.ConvertToPbLoType()
	ccvfilter.Combinator = &combinator
	for _, cf := range filters {
		filter := cf.ToFilter()
		ccvfilter.SubFilters = append(ccvfilter.SubFilters, filter)
	}

	return ccvfilter
}

func NewPaginationFilter(filter *PaginationFilter) *otsprotocol.ColumnPaginationFilter {
	pageFilter := new(otsprotocol.ColumnPaginationFilter)
	pageFilter.Offset = proto.Int32(filter.Offset)
	pageFilter.Limit = proto.Int32(filter.Limit)
	return pageFilter
}

func rawHttpToOtsError(code int, body []byte, reqId string) *OtsError {
	oerr := &OtsError{
		Message:        string(body),
		RequestId:      reqId,
		HttpStatusCode: code,
	}
	if code >= 500 && code < 600 {
		oerr.Code = SERVER_UNAVAILABLE
	} else {
		oerr.Code = OTS_CLIENT_UNKNOWN
	}
	return oerr
}

func pbErrToOtsError(statusCode int, pbErr *otsprotocol.Error, reqId string) *OtsError {
	return &OtsError{
		Code:           pbErr.GetCode(),
		Message:        pbErr.GetMessage(),
		RequestId:      reqId,
		HttpStatusCode: statusCode,
	}
}

func getRequestId(response *http.Response) string {
	if response == nil || response.Header == nil {
		return ""
	}

	return response.Header.Get(xOtsRequestId)
}

func buildRowPutChange(primarykey *PrimaryKey, columns []AttributeColumn) *RowPutChange {
	row := new(RowPutChange)
	row.primaryKey = make([]*PrimaryKeyColumnInner, len(primarykey.PrimaryKeys))
	for i, p := range primarykey.PrimaryKeys {
		row.primaryKey[i] = NewPrimaryKeyColumn([]byte(p.ColumnName), p.Value, p.PrimaryKeyOption)
	}

	row.columnsToPut = make([]*Column, len(columns))
	for i, p := range columns {
		row.columnsToPut[i] = NewColumn([]byte(p.ColumnName), p.Value)
		if p.Timestamp != 0 {
			row.columnsToPut[i].HasTimestamp = true
			row.columnsToPut[i].Timestamp = p.Timestamp
		}
	}

	return row
}

func buildRowUpdateChange(primarykey *PrimaryKey, columns []ColumnToUpdate) *RowUpdateChange {
	row := new(RowUpdateChange)
	row.primaryKey = make([]*PrimaryKeyColumnInner, len(primarykey.PrimaryKeys))
	for i, p := range primarykey.PrimaryKeys {
		row.primaryKey[i] = NewPrimaryKeyColumn([]byte(p.ColumnName), p.Value, p.PrimaryKeyOption)
	}

	row.columnsToUpdate = make([]*Column, len(columns))
	for i, p := range columns {
		row.columnsToUpdate[i] = NewColumn([]byte(p.ColumnName), p.Value)
		row.columnsToUpdate[i].HasTimestamp = p.HasTimestamp
		row.columnsToUpdate[i].HasType = p.HasType
		row.columnsToUpdate[i].Type = p.Type
		row.columnsToUpdate[i].Timestamp = p.Timestamp
		row.columnsToUpdate[i].IgnoreValue = p.IgnoreValue
	}

	return row
}

func (condition *RowCondition) buildCondition() *otsprotocol.RowExistenceExpectation {
	switch condition.RowExistenceExpectation {
	case RowExistenceExpectation_IGNORE:
		return otsprotocol.RowExistenceExpectation_IGNORE.Enum()
	case RowExistenceExpectation_EXPECT_EXIST:
		return otsprotocol.RowExistenceExpectation_EXPECT_EXIST.Enum()
	case RowExistenceExpectation_EXPECT_NOT_EXIST:
		return otsprotocol.RowExistenceExpectation_EXPECT_NOT_EXIST.Enum()
	}

	panic(errInvalidInput)
}

// build primary key for create table, put row, delete row and update row
// value only support int64,string,[]byte or you will get panic
func buildPrimaryKey(primaryKeyName string, value interface{}) *PrimaryKeyColumn {
	// Todo: validate the input
	return &PrimaryKeyColumn{ColumnName: primaryKeyName, Value: value, PrimaryKeyOption: NONE}
}

// value only support int64,string,bool,float64,[]byte. other type will get panic
func (rowchange *PutRowChange) AddColumn(columnName string, value interface{}) {
	// Todo: validate the input
	column := &AttributeColumn{ColumnName: columnName, Value: value}
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (rowchange *PutRowChange) SetReturnPk() {
	rowchange.ReturnType = ReturnType(ReturnType_RT_PK)
}

func (rowchange *UpdateRowChange) SetReturnIncrementValue() {
	rowchange.ReturnType = ReturnType(ReturnType_RT_AFTER_MODIFY)
}

func (rowchange *UpdateRowChange) AppendIncrementColumnToReturn(name string) {
	rowchange.ColumnNamesToReturn = append(rowchange.ColumnNamesToReturn, name)
}

// value only support int64,string,bool,float64,[]byte. other type will get panic
func (rowchange *PutRowChange) AddColumnWithTimestamp(columnName string, value interface{}, timestamp int64) {
	// Todo: validate the input
	column := &AttributeColumn{ColumnName: columnName, Value: value}
	column.Timestamp = timestamp
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (pk *PrimaryKey) AddPrimaryKeyColumn(primaryKeyName string, value interface{}) {
	pk.PrimaryKeys = append(pk.PrimaryKeys, buildPrimaryKey(primaryKeyName, value))
}

func (pk *PrimaryKey) AddPrimaryKeyColumnWithAutoIncrement(primaryKeyName string) {
	pk.PrimaryKeys = append(pk.PrimaryKeys, &PrimaryKeyColumn{ColumnName: primaryKeyName, PrimaryKeyOption: AUTO_INCREMENT})
}

func (pk *PrimaryKey) AddPrimaryKeyColumnWithMinValue(primaryKeyName string) {
	pk.PrimaryKeys = append(pk.PrimaryKeys, &PrimaryKeyColumn{ColumnName: primaryKeyName, PrimaryKeyOption: MIN})
}

// Only used for range query
func (pk *PrimaryKey) AddPrimaryKeyColumnWithMaxValue(primaryKeyName string) {
	pk.PrimaryKeys = append(pk.PrimaryKeys, &PrimaryKeyColumn{ColumnName: primaryKeyName, PrimaryKeyOption: MAX})
}

func (rowchange *PutRowChange) SetCondition(rowExistenceExpectation RowExistenceExpectation) {
	rowchange.Condition = &RowCondition{RowExistenceExpectation: rowExistenceExpectation}
}

func (rowchange *DeleteRowChange) SetCondition(rowExistenceExpectation RowExistenceExpectation) {
	rowchange.Condition = &RowCondition{RowExistenceExpectation: rowExistenceExpectation}
}

func (Criteria *SingleRowQueryCriteria) SetFilter(filter ColumnFilter) {
	Criteria.Filter = filter
}

func (Criteria *MultiRowQueryCriteria) SetFilter(filter ColumnFilter) {
	Criteria.Filter = filter
}

func NewSingleColumnCondition(columnName string, comparator ComparatorType, value interface{}) *SingleColumnCondition {
	return &SingleColumnCondition{ColumnName: &columnName, Comparator: &comparator, ColumnValue: value}
}

func NewCompositeColumnCondition(lo LogicalOperator) *CompositeColumnValueFilter {
	return &CompositeColumnValueFilter{Operator: lo}
}

func (rowchange *PutRowChange) SetColumnCondition(condition ColumnFilter) {
	rowchange.Condition.ColumnCondition = condition
}

func (rowchange *UpdateRowChange) SetCondition(rowExistenceExpectation RowExistenceExpectation) {
	rowchange.Condition = &RowCondition{RowExistenceExpectation: rowExistenceExpectation}
}

func (rowchange *UpdateRowChange) SetColumnCondition(condition ColumnFilter) {
	rowchange.Condition.ColumnCondition = condition
}

func (rowchange *DeleteRowChange) SetColumnCondition(condition ColumnFilter) {
	rowchange.Condition.ColumnCondition = condition
}

func (meta *TableMeta) AddPrimaryKeyColumn(name string, keyType PrimaryKeyType) {
	meta.SchemaEntry = append(meta.SchemaEntry, &PrimaryKeySchema{Name: &name, Type: &keyType})
}

func (meta *TableMeta) AddPrimaryKeyColumnOption(name string, keyType PrimaryKeyType, keyOption PrimaryKeyOption) {
	meta.SchemaEntry = append(meta.SchemaEntry, &PrimaryKeySchema{Name: &name, Type: &keyType, Option: &keyOption})
}

// value only support int64,string,bool,float64,[]byte. other type will get panic
func (rowchange *UpdateRowChange) PutColumn(columnName string, value interface{}) {
	// Todo: validate the input
	column := &ColumnToUpdate{ColumnName: columnName, Value: value}
	rowchange.Columns = append(rowchange.Columns, *column)
}

// value only support int64,string,bool,float64,[]byte. other type will get panic
func (rowchange *UpdateRowChange) PutColumnWithTimestamp(columnName string, value interface{}, timestamp int64) {
	column := &ColumnToUpdate{ColumnName: columnName, Value: value}
	column.Timestamp = timestamp
	column.HasTimestamp = true
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (rowchange *UpdateRowChange) DeleteColumn(columnName string) {
	// Todo: validate the input
	column := &ColumnToUpdate{ColumnName: columnName, Value: nil, Type: DELETE_ALL_VERSION, HasType: true, IgnoreValue: true}
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (rowchange *UpdateRowChange) DeleteColumnWithTimestamp(columnName string, timestamp int64) {
	// Todo: validate the input
	column := &ColumnToUpdate{ColumnName: columnName, Value: nil, Type: DELETE_ONE_VERSION, HasType: true, HasTimestamp: true, Timestamp: timestamp, IgnoreValue: true}
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (rowchange *UpdateRowChange) IncrementColumn(columnName string, value int64) {
	// Todo: validate the input
	column := &ColumnToUpdate{ColumnName: columnName, Value: value, Type: INCREMENT, HasType: true, IgnoreValue: false}
	rowchange.Columns = append(rowchange.Columns, *column)
}

func (rowchange *DeleteRowChange) Serialize() []byte {
	return rowchange.PrimaryKey.Build(true)
}

func (rowchange *PutRowChange) Serialize() []byte {
	row := buildRowPutChange(rowchange.PrimaryKey, rowchange.Columns)
	return row.Build()
}

func (rowchange *UpdateRowChange) Serialize() []byte {
	row := buildRowUpdateChange(rowchange.PrimaryKey, rowchange.Columns)
	return row.Build()
}

func (rowchange *DeleteRowChange) GetTableName() string {
	return rowchange.TableName
}

func (rowchange *PutRowChange) GetTableName() string {
	return rowchange.TableName
}

func (rowchange *UpdateRowChange) GetTableName() string {
	return rowchange.TableName
}

func (rowchange *DeleteRowChange) getOperationType() otsprotocol.OperationType {
	return otsprotocol.OperationType_DELETE
}

func (rowchange *PutRowChange) getOperationType() otsprotocol.OperationType {
	return otsprotocol.OperationType_PUT
}

func (rowchange *UpdateRowChange) getOperationType() otsprotocol.OperationType {
	return otsprotocol.OperationType_UPDATE
}

func (rowchange *DeleteRowChange) getCondition() *otsprotocol.Condition {
	condition := new(otsprotocol.Condition)
	condition.RowExistence = rowchange.Condition.buildCondition()
	if rowchange.Condition.ColumnCondition != nil {
		condition.ColumnCondition = rowchange.Condition.ColumnCondition.Serialize()
	}
	return condition
}

func (rowchange *UpdateRowChange) getCondition() *otsprotocol.Condition {
	condition := new(otsprotocol.Condition)
	condition.RowExistence = rowchange.Condition.buildCondition()
	if rowchange.Condition.ColumnCondition != nil {
		condition.ColumnCondition = rowchange.Condition.ColumnCondition.Serialize()
	}
	return condition
}

func (rowchange *PutRowChange) getCondition() *otsprotocol.Condition {
	condition := new(otsprotocol.Condition)
	condition.RowExistence = rowchange.Condition.buildCondition()
	if rowchange.Condition.ColumnCondition != nil {
		condition.ColumnCondition = rowchange.Condition.ColumnCondition.Serialize()
	}
	return condition
}

func (request *BatchWriteRowRequest) AddRowChange(change RowChange) {
	if request.RowChangesGroupByTable == nil {
		request.RowChangesGroupByTable = make(map[string][]RowChange)
	}
	request.RowChangesGroupByTable[change.GetTableName()] = append(request.RowChangesGroupByTable[change.GetTableName()], change)
}

func (direction Direction) ToDirection() otsprotocol.Direction {
	if direction == FORWARD {
		return otsprotocol.Direction_FORWARD
	} else {
		return otsprotocol.Direction_BACKWARD
	}
}

func (columnMap *ColumnMap) GetRange(start int, count int) ([]*AttributeColumn, error) {
	columns := []*AttributeColumn{}

	end := start + count
	if len(columnMap.columnsKey) < end {
		return nil, fmt.Errorf("invalid arugment")
	}

	for i := start; i < end; i++ {
		subColumns := columnMap.Columns[columnMap.columnsKey[i]]
		for _, column := range subColumns {
			columns = append(columns, column)
		}
	}

	return columns, nil
}

func (response *GetRowResponse) GetColumnMap() *ColumnMap {
	if response == nil {
		return nil
	}
	if response.columnMap != nil {
		return response.columnMap
	} else {
		response.columnMap = &ColumnMap{}
		response.columnMap.Columns = make(map[string][]*AttributeColumn)

		if len(response.Columns) == 0 {
			return response.columnMap
		} else {
			for _, column := range response.Columns {
				if _, ok := response.columnMap.Columns[column.ColumnName]; ok {
					response.columnMap.Columns[column.ColumnName] = append(response.columnMap.Columns[column.ColumnName], column)
				} else {
					response.columnMap.columnsKey = append(response.columnMap.columnsKey, column.ColumnName)
					value := []*AttributeColumn{}
					value = append(value, column)
					response.columnMap.Columns[column.ColumnName] = value
				}
			}

			sort.Strings(response.columnMap.columnsKey)
			return response.columnMap
		}
	}
}

func Assert(cond bool, msg string) {
	if !cond {
		panic(msg)
	}
}

func (meta *TableMeta) AddDefinedColumn(name string, definedType DefinedColumnType) {
	meta.DefinedColumns = append(meta.DefinedColumns, &DefinedColumnSchema{Name: name, ColumnType: definedType})
}

func (meta *IndexMeta) AddDefinedColumn(name string) {
	meta.DefinedColumns = append(meta.DefinedColumns, name)
}

func (meta *IndexMeta) AddPrimaryKeyColumn(name string) {
	meta.Primarykey = append(meta.Primarykey, name)
}

func (meta *IndexMeta) SetAsGlobalIndex() {
	meta.IndexType = IT_GLOBAL_INDEX
}

func (meta *IndexMeta) SetAsLocalIndex() {
	meta.IndexType = IT_LOCAL_INDEX
}

func (request *CreateTableRequest) AddIndexMeta(meta *IndexMeta) {
	request.IndexMetas = append(request.IndexMetas, meta)
}

func (meta *IndexMeta) ConvertToPbIndexMeta() *otsprotocol.IndexMeta {
	return &otsprotocol.IndexMeta{
		Name:            &meta.IndexName,
		PrimaryKey:      meta.Primarykey,
		DefinedColumn:   meta.DefinedColumns,
		IndexUpdateMode: ConvertIndexTypeToPBIndexUpdateMode(meta.IndexType).Enum(),
		IndexType:       ConvertIndexTypeToPBIndexType(meta.IndexType).Enum(),
	}
}

func ConvertIndexTypeToPBIndexType(indexType IndexType) otsprotocol.IndexType {
	switch indexType {
	case IT_LOCAL_INDEX:
		return otsprotocol.IndexType_IT_LOCAL_INDEX
	default:
		return otsprotocol.IndexType_IT_GLOBAL_INDEX
	}
}

func ConvertIndexTypeToPBIndexUpdateMode(indexType IndexType) otsprotocol.IndexUpdateMode {
	switch indexType {
	case IT_LOCAL_INDEX:
		return otsprotocol.IndexUpdateMode_IUM_SYNC_INDEX
	default:
		return otsprotocol.IndexUpdateMode_IUM_ASYNC_INDEX
	}
}

func ConvertPbIndexTypeToIndexType(indexType *otsprotocol.IndexType) IndexType {
	switch *indexType {
	case otsprotocol.IndexType_IT_GLOBAL_INDEX:
		return IT_GLOBAL_INDEX
	default:
		return IT_LOCAL_INDEX
	}
}

func ConvertPbIndexMetaToIndexMeta(meta *otsprotocol.IndexMeta) *IndexMeta {
	indexmeta := &IndexMeta{
		IndexName:      *meta.Name,
		IndexType:      ConvertPbIndexTypeToIndexType(meta.IndexType),
		IndexSyncPhase: (*SyncPhase)(meta.IndexSyncPhase),
	}

	for _, pk := range meta.PrimaryKey {
		indexmeta.Primarykey = append(indexmeta.Primarykey, pk)
	}

	for _, col := range meta.DefinedColumn {
		indexmeta.DefinedColumns = append(indexmeta.DefinedColumns, col)
	}

	return indexmeta
}

func (request *AddDefinedColumnRequest) AddDefinedColumn(name string, definedType DefinedColumnType) {
	request.DefinedColumns = append(request.DefinedColumns, &DefinedColumnSchema{Name: name, ColumnType: definedType})
}

// implement sortedMap : map[string]string
func SortedMapString(Map map[string]string) ([]string, []string) {
	n := len(Map)
	keys := make([]string, 0, n)
	values := make([]string, 0, n)
	for tag_key, _ := range Map {
		keys = append(keys, tag_key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		values = append(values, Map[key])
	}

	return keys, values
}

// implement sorted map[string]ColumnValue
func SortedMapColumnValue(Map map[string]*ColumnValue) ([]string, []*ColumnValue) {
	n := len(Map)
	keys := make([]string, 0, n)
	values := make([]*ColumnValue, 0, n)

	for fieldKey, _ := range Map {
		keys = append(keys, fieldKey)
	}
	sort.Strings(keys)
	for i := 0; i < n; i++ {
		values = append(values, Map[keys[i]])
	}
	return keys, values
}

func CheckTagKeyOrValue(s string) error {
	if s == "" {
		return fmt.Errorf("Tag or attribute key/value is empty")
	}

	for i := 0; i < len(s); i++ {
		if s[i] == '"' || s[i] == '=' {
			return fmt.Errorf("Tag or attribute key/value [%v] include illegal character", s)
		}
	}
	return nil
}

func BuildTags(tags map[string]string) ([]*otsprotocol.TimeseriesTag, error) {
	var tagsPB []*otsprotocol.TimeseriesTag
	for key, value := range tags {
		err := CheckTagKeyOrValue(key)
		if err != nil {
			return nil, err
		}
		err = CheckTagKeyOrValue(value)
		if err != nil {
			return nil, err
		}
		tagsPB = append(tagsPB, &otsprotocol.TimeseriesTag{Name: proto.String(key), Value: proto.String(value)})
	}
	sort.Slice(tagsPB, func(i, j int) bool {
		return tagsPB[i].GetName() < tagsPB[j].GetName()
	})
	return tagsPB, nil
}

func BuildTagString(tags map[string]string) (string, error) {
	var capacity int = 2
	n := len(tags)
	keys := make([]string, 0, n)
	for key, value := range tags {
		err := CheckTagKeyOrValue(key)
		if err != nil {
			return "", err
		}
		err = CheckTagKeyOrValue(value)
		if err != nil {
			return "", err
		}
		keys = append(keys, key)
		capacity += len(key) + len(value) + 3
	}

	sort.Strings(keys)

	sb := strings.Builder{}
	sb.Grow(capacity)
	sb.WriteByte('[')
	for i := 0; i < len(keys); i++ {
		key, value := keys[i], tags[keys[i]]
		sb.WriteByte('"')
		sb.WriteString(key)
		sb.WriteByte('=')
		sb.WriteString(value)
		sb.WriteByte('"')

		if i != len(keys)-1 {
			sb.WriteByte(',')
		}
	}
	sb.WriteByte(']')
	return sb.String(), nil
}

// SetCredentialsProvider sets funciton for get the user's ak
func SetCredentialsProvider(provider common.CredentialsProvider) ClientOption {
	return func(client *TableStoreClient) {
		client.credentialsProvider = provider
	}
}

func fromUnixMicrosTimestamp(timestamp int64) time.Time {
	return time.Unix(timestamp/1e6, (timestamp%1e6)*1e3)
}
