package encoding

import (
	"fmt"
	"math"
	"reflect"

	"github.com/shamaton/msgpack/v2/def"
	"github.com/shamaton/msgpack/v2/internal/common"
)

type encoder struct {
	d       []byte
	asArray bool
	common.Common
	mk map[uintptr][]reflect.Value
	mv map[uintptr][]reflect.Value
}

// Encode returns the MessagePack-encoded byte array of v.
func Encode(v interface{}, asArray bool) (b []byte, err error) {
	e := encoder{asArray: asArray}
	/*
		defer func() {
			e := recover()
			if e != nil {
				b = nil
				err = fmt.Errorf("unexpected error!! \n%s", stackTrace())
			}
		}()
	*/

	rv := reflect.ValueOf(v)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
		if rv.Kind() == reflect.Ptr {
			rv = rv.Elem()
		}
	}
	size, err := e.calcSize(rv)
	if err != nil {
		return nil, err
	}

	e.d = make([]byte, size)
	last := e.create(rv, 0)
	if size != last {
		return nil, fmt.Errorf("%w size=%d, lastIdx=%d", def.ErrNotMatchLastIndex, size, last)
	}
	return e.d, err
}

//func stackTrace() string {
//	msg := ""
//	for depth := 0; ; depth++ {
//		_, file, line, ok := runtime.Caller(depth)
//		if !ok {
//			break
//		}
//		msg += fmt.Sprintln(depth, ": ", file, ":", line)
//	}
//	return msg
//}

func (e *encoder) calcSize(rv reflect.Value) (int, error) {
	switch rv.Kind() {
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
		v := rv.Uint()
		return e.calcUint(v), nil

	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		v := rv.Int()
		return e.calcInt(int64(v)), nil

	case reflect.Float32:
		return e.calcFloat32(0), nil

	case reflect.Float64:
		return e.calcFloat64(0), nil

	case reflect.String:
		return e.calcString(rv.String()), nil

	case reflect.Bool:
		return def.Byte1, nil

	case reflect.Complex64:
		return e.calcComplex64(), nil

	case reflect.Complex128:
		return e.calcComplex128(), nil

	case reflect.Slice:
		if rv.IsNil() {
			return def.Byte1, nil
		}
		// bin format
		if e.isByteSlice(rv) {
			size, err := e.calcByteSlice(rv.Len())
			if err != nil {
				return 0, err
			}
			return size, nil
		}

		if size, find := e.calcFixedSlice(rv); find {
			return size, nil
		}

		// func
		elem := rv.Type().Elem()
		var f structCalcFunc
		if elem.Kind() == reflect.Struct {
			f = e.getStructCalc(elem)
		} else {
			f = e.calcSize
		}

		l := rv.Len()
		size, err := e.calcLength(l)
		if err != nil {
			return 0, err
		}

		// objects size
		for i := 0; i < l; i++ {
			s, err := f(rv.Index(i))
			if err != nil {
				return 0, err
			}
			size += s
		}
		return size, nil

	case reflect.Array:
		// bin format
		if e.isByteSlice(rv) {
			size, err := e.calcByteSlice(rv.Len())
			if err != nil {
				return 0, err
			}
			return size, nil
		}

		// func
		elem := rv.Type().Elem()
		var f structCalcFunc
		if elem.Kind() == reflect.Struct {
			f = e.getStructCalc(elem)
		} else {
			f = e.calcSize
		}

		l := rv.Len()
		size, err := e.calcLength(l)
		if err != nil {
			return 0, err
		}

		// objects size
		for i := 0; i < l; i++ {
			s, err := f(rv.Index(i))
			if err != nil {
				return 0, err
			}
			size += s
		}
		return size, nil

	case reflect.Map:
		if rv.IsNil() {
			return def.Byte1, nil
		}

		if size, find := e.calcFixedMap(rv); find {
			return size, nil
		}

		if e.mk == nil {
			e.mk = map[uintptr][]reflect.Value{}
			e.mv = map[uintptr][]reflect.Value{}
		}

		keys := rv.MapKeys()
		size, err := e.calcLength(len(keys))
		if err != nil {
			return 0, err
		}

		// key-value
		mv := make([]reflect.Value, len(keys))
		i := 0
		for _, k := range keys {
			keySize, err := e.calcSize(k)
			if err != nil {
				return 0, err
			}
			value := rv.MapIndex(k)
			valueSize, err := e.calcSize(value)
			if err != nil {
				return 0, err
			}
			size += keySize + valueSize
			mv[i] = value
			i++
		}
		e.mk[rv.Pointer()], e.mv[rv.Pointer()] = keys, mv
		return size, nil

	case reflect.Struct:
		size, err := e.calcStruct(rv)
		if err != nil {
			return 0, err
		}
		return size, nil

	case reflect.Ptr:
		if rv.IsNil() {
			return def.Byte1, nil
		}
		size, err := e.calcSize(rv.Elem())
		if err != nil {
			return 0, err
		}
		return size, nil

	case reflect.Interface:
		size, err := e.calcSize(rv.Elem())
		if err != nil {
			return 0, err
		}
		return size, nil

	case reflect.Invalid:
		// do nothing (return nil)
		return def.Byte1, nil

	default:
		return 0, fmt.Errorf("%v is %w type", rv.Kind(), def.ErrUnsupportedType)
	}
}

func (e *encoder) calcLength(l int) (int, error) {
	if l <= 0x0f {
		return def.Byte1, nil
	} else if l <= math.MaxUint16 {
		return def.Byte1 + def.Byte2, nil
	} else if uint(l) <= math.MaxUint32 {
		return def.Byte1 + def.Byte4, nil
	}
	// not supported error
	return 0, fmt.Errorf("array length %d is %w", l, def.ErrUnsupportedLength)
}

func (e *encoder) create(rv reflect.Value, offset int) int {
	switch rv.Kind() {
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
		v := rv.Uint()
		offset = e.writeUint(v, offset)

	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		v := rv.Int()
		offset = e.writeInt(v, offset)

	case reflect.Float32:
		offset = e.writeFloat32(rv.Float(), offset)

	case reflect.Float64:
		offset = e.writeFloat64(rv.Float(), offset)

	case reflect.Bool:
		offset = e.writeBool(rv.Bool(), offset)

	case reflect.String:
		offset = e.writeString(rv.String(), offset)

	case reflect.Complex64:
		offset = e.writeComplex64(complex64(rv.Complex()), offset)

	case reflect.Complex128:
		offset = e.writeComplex128(rv.Complex(), offset)

	case reflect.Slice:
		if rv.IsNil() {
			return e.writeNil(offset)
		}

		// bin format
		if e.isByteSlice(rv) {
			offset = e.writeByteSliceLength(rv.Len(), offset)
			offset = e.setBytes(rv.Bytes(), offset)
			return offset
		}

		if offset, find := e.writeFixedSlice(rv, offset); find {
			return offset
		}

		// func
		elem := rv.Type().Elem()
		var f structWriteFunc
		if elem.Kind() == reflect.Struct {
			f = e.getStructWriter(elem)
		} else {
			f = e.create
		}

		// objects
		l := rv.Len()
		offset = e.writeSliceLength(l, offset)
		for i := 0; i < l; i++ {
			offset = f(rv.Index(i), offset)
		}

	case reflect.Array:
		l := rv.Len()
		// bin format
		if e.isByteSlice(rv) {
			offset = e.writeByteSliceLength(l, offset)
			// objects
			for i := 0; i < l; i++ {
				offset = e.setByte1Uint64(rv.Index(i).Uint(), offset)
			}
			return offset
		}

		// format
		offset = e.writeSliceLength(l, offset)

		// func
		elem := rv.Type().Elem()
		var f structWriteFunc
		if elem.Kind() == reflect.Struct {
			f = e.getStructWriter(elem)
		} else {
			f = e.create
		}

		// objects
		for i := 0; i < l; i++ {
			offset = f(rv.Index(i), offset)
		}

	case reflect.Map:
		if rv.IsNil() {
			return e.writeNil(offset)
		}

		l := rv.Len()
		offset = e.writeMapLength(l, offset)

		if offset, find := e.writeFixedMap(rv, offset); find {
			return offset
		}

		// key-value
		p := rv.Pointer()
		for i := range e.mk[p] {
			offset = e.create(e.mk[p][i], offset)
			offset = e.create(e.mv[p][i], offset)
		}

	case reflect.Struct:
		offset = e.writeStruct(rv, offset)

	case reflect.Ptr:
		if rv.IsNil() {
			return e.writeNil(offset)
		}

		offset = e.create(rv.Elem(), offset)

	case reflect.Interface:
		offset = e.create(rv.Elem(), offset)

	case reflect.Invalid:
		return e.writeNil(offset)

	}
	return offset
}
