package mysql

import (
	"fmt"
	"strconv"

	"github.com/ngaut/log"
	"github.com/pingcap/tidb/model"
	"github.com/pingcap/tidb/types"
	"github.com/siddontang/go-mysql/schema"

	"github.com/pingcap/wormhole/pkg/whpb"
)

// ToTableInfo converts *schema.Table to *model.TableInfo.
func ToTableInfo(table *schema.Table) *model.TableInfo {
	var flag uint
	var elems []string

	columns := make([]*model.ColumnInfo, 0, len(table.Columns))
	for _, col := range table.Columns {
		if col.IsUnsigned {
			flag |= UnsignedFlag
		}
		if col.IsAuto {
			flag |= AutoIncrementFlag
		}
		if len(col.EnumValues) > 0 {
			flag |= EnumFlag
			elems = col.EnumValues
		}
		if len(col.SetValues) > 0 {
			flag |= SetFlag
			elems = col.SetValues
		}

		column := &model.ColumnInfo{
			Name: model.NewCIStr(col.Name),
			FieldType: types.FieldType{
				Tp:    ConvertTypeToByte(col.Type),
				Flag:  flag,
				Elems: elems,
			},
		}
		columns = append(columns, column)
	}

	tableInfo := &model.TableInfo{
		Name:    model.NewCIStr(table.Name),
		Columns: columns,
		// Indices: need index?
	}

	return tableInfo
}

// ToRowData converts rowData of binlog RowEvent to []*whpb.RowData.
func ToRowData(rowsData [][]interface{}, cols []*model.ColumnInfo) []*whpb.RowData {
	rows := make([]*whpb.RowData, 0, len(rowsData))
	for _, rowData := range rowsData {
		row := &whpb.RowData{
			Row: make([]string, 0, len(rowData)),
		}

		// aliyun RDS add implicit primary key for table that lack of primary key or unique key.
		// implicit column is the last column in the table.
		if len(rowData) != len(cols) {
			log.Warn("[mysql] found len(rowData) != len(cols). strip aliyun rds implicit primary key")
			rowData = rowData[0:len(cols)]
		}
		for i, d := range rowData {
			isUnsigned := HasUnsignedFlag(cols[i].Flag)
			row.Row = append(row.Row, columnValue(d, isUnsigned))
		}
		rows = append(rows, row)
	}
	return rows
}

func columnValue(value interface{}, unsigned bool) string {
	castValue := castUnsigned(value, unsigned)

	var data string
	switch v := castValue.(type) {
	case nil:
		data = "null"
	case bool:
		if v {
			data = "1"
		} else {
			data = "0"
		}
	case int:
		data = strconv.FormatInt(int64(v), 10)
	case int8:
		data = strconv.FormatInt(int64(v), 10)
	case int16:
		data = strconv.FormatInt(int64(v), 10)
	case int32:
		data = strconv.FormatInt(int64(v), 10)
	case int64:
		data = strconv.FormatInt(int64(v), 10)
	case uint8:
		data = strconv.FormatUint(uint64(v), 10)
	case uint16:
		data = strconv.FormatUint(uint64(v), 10)
	case uint32:
		data = strconv.FormatUint(uint64(v), 10)
	case uint64:
		data = strconv.FormatUint(uint64(v), 10)
	case float32:
		data = strconv.FormatFloat(float64(v), 'f', -1, 32)
	case float64:
		data = strconv.FormatFloat(float64(v), 'f', -1, 64)
	case string:
		data = v
	case []byte:
		data = string(v)
	default:
		data = fmt.Sprintf("%v", v)
	}

	return data
}

func castUnsigned(data interface{}, unsigned bool) interface{} {
	if !unsigned {
		return data
	}

	switch v := data.(type) {
	case int:
		return uint(v)
	case int8:
		return uint8(v)
	case int16:
		return uint16(v)
	case int32:
		return uint32(v)
	case int64:
		return strconv.FormatUint(uint64(v), 10)
	}

	return data
}

// ToColumns converts []model.ColumnInfo to []*whpb.Column.
func ToColumns(cols []*model.ColumnInfo) []*whpb.Column {
	metaCols := make([]*whpb.Column, 0, len(cols))
	for idx, col := range cols {
		metaCol := &whpb.Column{
			Idx:   int32(idx),
			Name:  col.Name.String(),
			IsKey: HasPriKeyFlag(col.Flag) || HasUniKeyFlag(col.Flag), // we only care about primary key and unique key.
			Type:  int32(col.Tp),                                      // type defines in  github.com/pingcap/tidb/mysql/type.go
		}
		metaCols = append(metaCols, metaCol)
	}
	return metaCols
}
