package optdec

import (
	"reflect"
	"unsafe"

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

type sliceDecoder struct {
	elemType *rt.GoType
	elemDec  decFunc
	typ      reflect.Type
}

var (
	emptyPtr = &struct{}{}
)

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

	arr, ok := node.AsArr()
	if !ok {
		return error_mismatch(node, ctx, d.typ)
	}

	slice := rt.MakeSlice(vp, d.elemType, arr.Len())
	elems := slice.Ptr
	next := arr.Children()

	var gerr error
	for i := 0; i < arr.Len(); i++ {
		val := NewNode(next)
		elem := unsafe.Pointer(uintptr(elems) + uintptr(i)*d.elemType.Size)
		err := d.elemDec.FromDom(elem, val, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = val.Next()
	}

	*(*rt.GoSlice)(vp) = *slice
	return gerr
}

type arrayDecoder struct {
	len      int
	elemType *rt.GoType
	elemDec  decFunc
	typ   	reflect.Type
}

//go:nocheckptr
func (d *arrayDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) error {
	if node.IsNull() {
		return nil
	}

	arr, ok := node.AsArr()
	if !ok {
		return error_mismatch(node, ctx, d.typ)
	}

	next := arr.Children()
	i := 0

	var gerr error
	for ; i < d.len && i < arr.Len(); i++ {
		elem := unsafe.Pointer(uintptr(vp) + uintptr(i)*d.elemType.Size)
		val := NewNode(next)
		err := d.elemDec.FromDom(elem, val, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = val.Next()
	}

	/* zero rest of array */
	ptr := unsafe.Pointer(uintptr(vp) + uintptr(i)*d.elemType.Size)
	n := uintptr(d.len-i) * d.elemType.Size
	rt.ClearMemory(d.elemType, ptr, n)
	return gerr
}

type sliceEfaceDecoder struct {
}

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

	return node.AsSliceEface(ctx, vp)
}

type sliceI32Decoder struct {
}

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

	return node.AsSliceI32(ctx, vp)
}

type sliceI64Decoder struct {
}

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

	return node.AsSliceI64(ctx, vp)
}

type sliceU32Decoder struct {
}

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

	return node.AsSliceU32(ctx, vp)
}

type sliceU64Decoder struct {
}

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

	return node.AsSliceU64(ctx, vp)
}

type sliceStringDecoder struct {
}

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

	return node.AsSliceString(ctx, vp)
}

type sliceBytesDecoder struct {
}

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

	s, err := node.AsSliceBytes(ctx)
	if err != nil {
		return err
	}

	*(*[]byte)(vp) = s
	return nil
}

type sliceBytesUnmarshalerDecoder struct {
	elemType *rt.GoType
	elemDec  decFunc
	typ reflect.Type
}

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

	/* parse JSON string into `[]byte` */
	if node.IsStr() {
		slice, err := node.AsSliceBytes(ctx)
		if err != nil {
			return err
		}
		*(*[]byte)(vp) = slice
		return nil
	}

	/* parse JSON array into `[]byte` */
	arr, ok := node.AsArr()
	if !ok {
		return error_mismatch(node, ctx, d.typ)
	}

	slice := rt.MakeSlice(vp, d.elemType, arr.Len())
	elems := slice.Ptr

	var gerr error
	next := arr.Children()
	for i := 0; i < arr.Len(); i++ {
		child := NewNode(next)
		elem := unsafe.Pointer(uintptr(elems) + uintptr(i)*d.elemType.Size)
		err := d.elemDec.FromDom(elem, child, ctx)
		if gerr == nil && err != nil {
			gerr = err
		}
		next = child.Next()
	}

	*(*rt.GoSlice)(vp) = *slice
	return gerr
}
