// Copyright (C) 2022  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

package json

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"strconv"

	"git.lukeshu.com/go/lowmemjson"
)

type (
	Number     = json.Number
	Marshaler  = json.Marshaler
	RawMessage = json.RawMessage

	// low-level decode errors
	//SyntaxError        = json.SyntaxError // duplicated to access a private field
	UnmarshalTypeError = json.UnmarshalTypeError

	// high-level decode errors
	InvalidUnmarshalError = json.InvalidUnmarshalError

	// marshal errors
	//MarshalerError        = json.MarshalerError // duplicated to access a private field
	UnsupportedTypeError  = json.UnsupportedTypeError
	UnsupportedValueError = json.UnsupportedValueError
)

// Encode wrappers ///////////////////////////////////////////////////

func convertEncodeError(err error) error {
	if me, ok := err.(*lowmemjson.EncodeMethodError); ok {
		err = &MarshalerError{
			Type:       me.Type,
			Err:        me.Err,
			sourceFunc: me.SourceFunc,
		}
	}
	return err
}

func marshal(v any, formatter *lowmemjson.ReEncoder) ([]byte, error) {
	var buf bytes.Buffer
	formatter.Out = &buf
	if err := convertEncodeError(lowmemjson.Encode(formatter, v)); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
	return marshal(v, &lowmemjson.ReEncoder{
		Indent: indent,
		Prefix: prefix,
	})
}

func Marshal(v any) ([]byte, error) {
	return marshal(v, &lowmemjson.ReEncoder{
		Compact: true,
	})
}

type Encoder struct {
	encoder   *lowmemjson.Encoder
	formatter lowmemjson.ReEncoder
}

func NewEncoder(w io.Writer) *Encoder {
	ret := &Encoder{
		formatter: lowmemjson.ReEncoder{
			Out: w,

			AllowMultipleValues: true,

			Compact:               true,
			ForceTrailingNewlines: true,
		},
	}
	ret.encoder = lowmemjson.NewEncoder(&ret.formatter)
	return ret
}

func (enc *Encoder) Encode(v any) error {
	return convertEncodeError(enc.encoder.Encode(v))
}

func (enc *Encoder) SetEscapeHTML(on bool) {
	var escaper lowmemjson.BackslashEscaper
	if !on {
		escaper = lowmemjson.EscapeDefaultNonHTMLSafe
	}
	enc.formatter.BackslashEscape = escaper
}

func (enc *Encoder) SetIndent(prefix, indent string) {
	enc.formatter.Compact = prefix == "" && indent == ""
	enc.formatter.Prefix = prefix
	enc.formatter.Indent = indent
}

// ReEncode wrappers /////////////////////////////////////////////////

func convertReEncodeError(err error) error {
	if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok {
		err = &SyntaxError{
			msg:    se.Err.Error(),
			Offset: se.Offset + 1,
		}
		if errors.Is(se.Err, io.ErrUnexpectedEOF) {
			err.(*SyntaxError).msg = "unexpected end of JSON input"
		}
	}
	return err
}

func HTMLEscape(dst *bytes.Buffer, src []byte) {
	formatter := &lowmemjson.ReEncoder{
		Out: dst,
	}
	_, _ = formatter.Write(src)
}

func reencode(src []byte, formatter *lowmemjson.ReEncoder) error {
	_, err := formatter.Write(src)
	if err == nil {
		err = formatter.Close()
	}
	return convertReEncodeError(err)
}

func Compact(dst *bytes.Buffer, src []byte) error {
	return reencode(src, &lowmemjson.ReEncoder{
		Out:             dst,
		Compact:         true,
		BackslashEscape: lowmemjson.EscapePreserve,
	})
}

func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
	return reencode(src, &lowmemjson.ReEncoder{
		Out:             dst,
		Indent:          indent,
		Prefix:          prefix,
		BackslashEscape: lowmemjson.EscapePreserve,
	})
}

func Valid(data []byte) bool {
	formatter := &lowmemjson.ReEncoder{
		Out:     io.Discard,
		Compact: true,
	}
	_, err := formatter.Write(data)
	return err == nil
}

// Decode wrappers ///////////////////////////////////////////////////

func convertDecodeError(err error) error {
	if derr, ok := err.(*lowmemjson.DecodeError); ok {
		switch terr := derr.Err.(type) {
		case *lowmemjson.DecodeSyntaxError:
			switch {
			case errors.Is(terr.Err, io.EOF):
				err = io.EOF
			case errors.Is(terr.Err, io.ErrUnexpectedEOF):
				err = &SyntaxError{
					msg:    "unexpected end of JSON input",
					Offset: terr.Offset,
				}
			default:
				err = &SyntaxError{
					msg:    terr.Err.Error(),
					Offset: terr.Offset,
				}
			}
		case *lowmemjson.DecodeTypeError:
			if typeErr, ok := terr.Err.(*json.UnmarshalTypeError); ok {
				err = &UnmarshalTypeError{
					Value:  typeErr.Value,
					Type:   typeErr.Type,
					Offset: typeErr.Offset,
					Struct: derr.FieldParent,
					Field:  derr.FieldName,
				}
			} else if _, isArgErr := terr.Err.(*lowmemjson.DecodeArgumentError); terr.Err != nil &&
				!isArgErr &&
				!errors.Is(terr.Err, lowmemjson.ErrDecodeNonEmptyInterface) &&
				!errors.Is(terr.Err, strconv.ErrSyntax) &&
				!errors.Is(terr.Err, strconv.ErrRange) {
				err = terr.Err
			} else {
				err = &UnmarshalTypeError{
					Value:  terr.JSONType,
					Type:   terr.GoType,
					Offset: terr.Offset,
					Struct: derr.FieldParent,
					Field:  derr.FieldName,
				}
			}
		}
	}
	return err
}

func Unmarshal(data []byte, ptr any) error {
	return convertDecodeError(lowmemjson.DecodeThenEOF(bytes.NewReader(data), ptr))
}

type Decoder struct {
	*lowmemjson.Decoder
	buf *bufio.Reader
}

func NewDecoder(r io.Reader) *Decoder {
	br, ok := r.(*bufio.Reader)
	if !ok {
		br = bufio.NewReader(r)
	}
	return &Decoder{
		Decoder: lowmemjson.NewDecoder(br),
		buf:     br,
	}
}

func (dec *Decoder) Decode(ptr any) error {
	return convertDecodeError(dec.Decoder.Decode(ptr))
}

func (dec *Decoder) Buffered() io.Reader {
	dat, _ := dec.buf.Peek(dec.buf.Buffered())
	return bytes.NewReader(dat)
}

//func (dec *Decoder) Token() (Token, error)