package decoder

import (
	"bytes"
	"unsafe"

	"github.com/goccy/go-json/internal/errors"
	"github.com/goccy/go-json/internal/runtime"
)

type funcDecoder struct {
	typ        *runtime.Type
	structName string
	fieldName  string
}

func newFuncDecoder(typ *runtime.Type, structName, fieldName string) *funcDecoder {
	fnDecoder := &funcDecoder{typ, structName, fieldName}
	return fnDecoder
}

func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
	s.skipWhiteSpace()
	start := s.cursor
	if err := s.skipValue(depth); err != nil {
		return err
	}
	src := s.buf[start:s.cursor]
	if len(src) > 0 {
		switch src[0] {
		case '"':
			return &errors.UnmarshalTypeError{
				Value:  "string",
				Type:   runtime.RType2Type(d.typ),
				Offset: s.totalOffset(),
			}
		case '[':
			return &errors.UnmarshalTypeError{
				Value:  "array",
				Type:   runtime.RType2Type(d.typ),
				Offset: s.totalOffset(),
			}
		case '{':
			return &errors.UnmarshalTypeError{
				Value:  "object",
				Type:   runtime.RType2Type(d.typ),
				Offset: s.totalOffset(),
			}
		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			return &errors.UnmarshalTypeError{
				Value:  "number",
				Type:   runtime.RType2Type(d.typ),
				Offset: s.totalOffset(),
			}
		case 'n':
			if err := nullBytes(s); err != nil {
				return err
			}
			*(*unsafe.Pointer)(p) = nil
			return nil
		case 't':
			if err := trueBytes(s); err == nil {
				return &errors.UnmarshalTypeError{
					Value:  "boolean",
					Type:   runtime.RType2Type(d.typ),
					Offset: s.totalOffset(),
				}
			}
		case 'f':
			if err := falseBytes(s); err == nil {
				return &errors.UnmarshalTypeError{
					Value:  "boolean",
					Type:   runtime.RType2Type(d.typ),
					Offset: s.totalOffset(),
				}
			}
		}
	}
	return errors.ErrNotAtBeginningOfValue(start)
}

func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
	buf := ctx.Buf
	cursor = skipWhiteSpace(buf, cursor)
	start := cursor
	end, err := skipValue(buf, cursor, depth)
	if err != nil {
		return 0, err
	}
	src := buf[start:end]
	if len(src) > 0 {
		switch src[0] {
		case '"':
			return 0, &errors.UnmarshalTypeError{
				Value:  "string",
				Type:   runtime.RType2Type(d.typ),
				Offset: start,
			}
		case '[':
			return 0, &errors.UnmarshalTypeError{
				Value:  "array",
				Type:   runtime.RType2Type(d.typ),
				Offset: start,
			}
		case '{':
			return 0, &errors.UnmarshalTypeError{
				Value:  "object",
				Type:   runtime.RType2Type(d.typ),
				Offset: start,
			}
		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			return 0, &errors.UnmarshalTypeError{
				Value:  "number",
				Type:   runtime.RType2Type(d.typ),
				Offset: start,
			}
		case 'n':
			if bytes.Equal(src, nullbytes) {
				*(*unsafe.Pointer)(p) = nil
				return end, nil
			}
		case 't':
			if err := validateTrue(buf, start); err == nil {
				return 0, &errors.UnmarshalTypeError{
					Value:  "boolean",
					Type:   runtime.RType2Type(d.typ),
					Offset: start,
				}
			}
		case 'f':
			if err := validateFalse(buf, start); err == nil {
				return 0, &errors.UnmarshalTypeError{
					Value:  "boolean",
					Type:   runtime.RType2Type(d.typ),
					Offset: start,
				}
			}
		}
	}
	return 0, errors.ErrNotAtBeginningOfValue(start)
}
