package column

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/kshvakov/clickhouse/lib/binary"
)

type Enum struct {
	iv map[string]interface{}
	vi map[interface{}]string
	base
	baseType interface{}
}

func (enum *Enum) Read(decoder *binary.Decoder) (interface{}, error) {
	var (
		err   error
		ident interface{}
	)
	switch enum.baseType.(type) {
	case int16:
		if ident, err = decoder.Int16(); err != nil {
			return nil, err
		}
	default:
		if ident, err = decoder.Int8(); err != nil {
			return nil, err
		}
	}
	if ident, found := enum.vi[ident]; found {
		return ident, nil
	}
	return nil, fmt.Errorf("invalid Enum value: %v", ident)
}

func (enum *Enum) Write(encoder *binary.Encoder, v interface{}) error {
	switch v := v.(type) {
	case string:
		ident, found := enum.iv[v]
		if !found {
			return fmt.Errorf("invalid Enum ident: %s", v)
		}
		switch ident := ident.(type) {
		case int8:
			return encoder.Int8(ident)
		case int16:
			return encoder.Int16(ident)
		}
	case uint8:
		if _, ok := enum.baseType.(int8); ok {
			return encoder.Int8(int8(v))
		}
	case int8:
		if _, ok := enum.baseType.(int8); ok {
			return encoder.Int8(v)
		}
	case uint16:
		if _, ok := enum.baseType.(int16); ok {
			return encoder.Int16(int16(v))
		}
	case int16:
		if _, ok := enum.baseType.(int16); ok {
			return encoder.Int16(v)
		}
	case int64:
		switch enum.baseType.(type) {
		case int8:
			return encoder.Int8(int8(v))
		case int16:
			return encoder.Int16(int16(v))
		}
	}
	return &ErrUnexpectedType{
		T:      v,
		Column: enum,
	}
}

func (enum *Enum) defaultValue() interface{} {
	return enum.baseType
}

func parseEnum(name, chType string) (*Enum, error) {
	var (
		data     string
		isEnum16 bool
	)
	if len(chType) < 8 {
		return nil, fmt.Errorf("invalid Enum format: %s", chType)
	}
	switch {
	case strings.HasPrefix(chType, "Enum8"):
		data = chType[6:]
	case strings.HasPrefix(chType, "Enum16"):
		data = chType[7:]
		isEnum16 = true
	default:
		return nil, fmt.Errorf("'%s' is not Enum type", chType)
	}
	enum := Enum{
		base: base{
			name:    name,
			chType:  chType,
			valueOf: baseTypes[string("")],
		},
		iv: make(map[string]interface{}),
		vi: make(map[interface{}]string),
	}
	for _, block := range strings.Split(data[:len(data)-1], ",") {
		parts := strings.Split(block, "=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid Enum format: %s", chType)
		}
		var (
			ident      = strings.TrimSpace(parts[0])
			value, err = strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 16)
		)
		if err != nil {
			return nil, fmt.Errorf("invalid Enum value: %v", chType)
		}
		{
			var (
				ident             = ident[1 : len(ident)-1]
				value interface{} = int16(value)
			)
			if !isEnum16 {
				value = int8(value.(int16))
			}
			if enum.baseType == nil {
				enum.baseType = value
			}
			enum.iv[ident] = value
			enum.vi[value] = ident
		}
	}
	return &enum, nil
}