package optdec

import (
	"encoding"
	"encoding/json"
	"math"
	"reflect"
	"unsafe"

	"github.com/bytedance/sonic/internal/rt"
)

/** Decoder for most common map types: map[string]interface{}, map[string]string **/

type mapEfaceDecoder struct {
}

func (d *mapEfaceDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*map[string]interface{})(vp) = nil
		return nil
	}

	return node.AsMapEface(ctx, vp)
}

type mapStringDecoder struct {
}

func (d *mapStringDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*map[string]string)(vp) = nil
		return nil
	}

	return node.AsMapString(ctx, vp)
}

/** Decoder for map with string key **/

type mapStrKeyDecoder struct {
	mapType *rt.GoMapType
	elemDec decFunc
	assign  rt.MapStrAssign
	typ 	reflect.Type
}

func (d *mapStrKeyDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return error_mismatch(node, ctx, d.mapType.Pack())
	}

	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	var gerr error
	next := obj.Children()
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		key, _ := keyn.AsStr(ctx)

		valn := NewNode(PtrOffset(next, 1))
		valp := d.assign(d.mapType, m, key)
		err := d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}

/** Decoder for map with int32 or int64 key **/

type mapI32KeyDecoder struct {
	mapType *rt.GoMapType
	elemDec decFunc
	assign rt.Map32Assign
}

func (d *mapI32KeyDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return error_mismatch(node, ctx, d.mapType.Pack())
	}

	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	next := obj.Children()
	var gerr error
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		k, ok := keyn.ParseI64(ctx)
		if !ok || k > math.MaxInt32 || k < math.MinInt32 {
			if gerr == nil {
				gerr = error_mismatch(keyn, ctx, d.mapType.Pack())
			}
			valn := NewNode(PtrOffset(next, 1))
			next = valn.Next()
			continue
		}

		key := int32(k)
		ku32 := *(*uint32)(unsafe.Pointer(&key))
		valn := NewNode(PtrOffset(next, 1))
		valp := d.assign(d.mapType, m, ku32)
		err := d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}

		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}

type mapI64KeyDecoder struct {
	mapType *rt.GoMapType
	elemDec decFunc
	assign rt.Map64Assign
}

func (d *mapI64KeyDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return error_mismatch(node, ctx, d.mapType.Pack())
	}

	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	var gerr error
	next := obj.Children()
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		key, ok := keyn.ParseI64(ctx)

		if !ok {
			if gerr == nil {
				gerr = error_mismatch(keyn, ctx, d.mapType.Pack())
			}
			valn := NewNode(PtrOffset(next, 1))
			next = valn.Next()
			continue
		}

		ku64 := *(*uint64)(unsafe.Pointer(&key))
		valn := NewNode(PtrOffset(next, 1))
		valp := d.assign(d.mapType, m, ku64)
		err := d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}

/** Decoder for map with unt32 or uint64 key **/

type mapU32KeyDecoder struct {
	mapType *rt.GoMapType
	elemDec decFunc
	assign  rt.Map32Assign
}

func (d *mapU32KeyDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return error_mismatch(node, ctx, d.mapType.Pack())
	}

	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	var gerr error
	next := obj.Children()
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		k, ok := keyn.ParseU64(ctx)
		if !ok || k > math.MaxUint32 {
			if gerr == nil {
				gerr = error_mismatch(keyn, ctx, d.mapType.Pack())
			}
			valn := NewNode(PtrOffset(next, 1))
			next = valn.Next()
			continue
		}

		key := uint32(k)
		valn := NewNode(PtrOffset(next, 1))
		valp := d.assign(d.mapType, m, key)
		err := d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}

type mapU64KeyDecoder struct {
	mapType *rt.GoMapType
	elemDec decFunc
	assign rt.Map64Assign
}

func (d *mapU64KeyDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return  error_mismatch(node, ctx, d.mapType.Pack())
	}
	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	var gerr error
	next := obj.Children()
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		key, ok := keyn.ParseU64(ctx)
		if !ok {
			if gerr == nil {
				gerr = error_mismatch(keyn, ctx, d.mapType.Pack())
			}
			valn := NewNode(PtrOffset(next, 1))
			next = valn.Next()
			continue
		}

		valn := NewNode(PtrOffset(next, 1))
		valp := d.assign(d.mapType, m, key)
		err := d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}

/** Decoder for generic cases */

type decKey func(dec *mapDecoder, raw string, ctx *context) (interface{}, error)

func decodeKeyU8(dec *mapDecoder, raw string, ctx *context) (interface{}, error) {
	key, err := Unquote(raw)
	if err != nil {
		return nil, err
	}
	ret, err := ParseU64(key)
	if err != nil {
		return nil, err
	}
	if ret > math.MaxUint8 {
		return nil, error_value(key, dec.mapType.Key.Pack())
	}
	return uint8(ret), nil
}

func decodeKeyU16(dec *mapDecoder, raw string, ctx *context) (interface{}, error) {
	key, err := Unquote(raw)
	if err != nil {
		return nil, err
	}
	ret, err := ParseU64(key)
	if err != nil {
		return nil, err
	}
	if ret > math.MaxUint16 {
		return nil, error_value(key, dec.mapType.Key.Pack())
	}
	return uint16(ret), nil
}

func decodeKeyI8(dec *mapDecoder, raw string, ctx *context) (interface{}, error) {
	key, err := Unquote(raw)
	if err != nil {
		return nil, err
	}
	ret, err := ParseI64(key)
	if err != nil {
		return nil, err
	}
	if ret > math.MaxInt8 || ret < math.MinInt8 {
		return nil, error_value(key, dec.mapType.Key.Pack())
	}
	return int8(ret), nil
}

func decodeKeyI16(dec *mapDecoder, raw string, ctx *context) (interface{}, error) {
	key, err := Unquote(raw)
	if err != nil {
		return nil, err
	}
	ret, err := ParseI64(key)
	if err != nil {
		return nil, err
	}
	if ret > math.MaxInt16 || ret < math.MinInt16 {
		return nil, error_value(key, dec.mapType.Key.Pack())
	}
	return int16(ret), nil
}

func decodeKeyJSONUnmarshaler(dec *mapDecoder, raw string, _ *context) (interface{}, error) {
	ret := reflect.New(dec.mapType.Key.Pack()).Interface()
	err := ret.(json.Unmarshaler).UnmarshalJSON([]byte(raw))
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func decodeKeyTextUnmarshaler(dec *mapDecoder, raw string, ctx *context) (interface{}, error) {
	key, err := Unquote(raw)
	if err != nil {
		return nil, err
	}
	ret := reflect.New(dec.mapType.Key.Pack()).Interface()
	err = ret.(encoding.TextUnmarshaler).UnmarshalText([]byte(key))
	if err != nil {
		return nil, err
	}
	return ret, nil
}

type mapDecoder struct {
	mapType *rt.GoMapType
	keyDec  decKey
	elemDec decFunc
}

func (d *mapDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		*(*unsafe.Pointer)(vp) = nil
		return nil
	}

	obj, ok := node.AsObj()
	if !ok {
		return	error_mismatch(node, ctx, d.mapType.Pack())
	}

	// allocate map
	m := *(*unsafe.Pointer)(vp)
	if m == nil {
		m = rt.Makemap(&d.mapType.GoType, obj.Len())
	}

	next := obj.Children()
	var gerr error
	for i := 0; i < obj.Len(); i++ {
		keyn := NewNode(next)
		raw := keyn.AsRaw(ctx)
		key, err := d.keyDec(d, raw, ctx)
		if err != nil {
			if gerr == nil {
				gerr = error_mismatch(keyn, ctx, d.mapType.Pack())
			}
			valn := NewNode(PtrOffset(next, 1))
			next = valn.Next()
			continue
		}

		valn := NewNode(PtrOffset(next, 1))
		keyp := rt.UnpackEface(key).Value
		valp := rt.Mapassign(d.mapType, m, keyp)
		err = d.elemDec.FromDom(valp, valn, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}

		next = valn.Next()
	}

	*(*unsafe.Pointer)(vp) = m
	return gerr
}
