// KEKS -- Go KEKS codec implementation
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package keks

import (
	"errors"
	"math/big"

	"go.cypherpunks.su/keks/types"
)

var (
	ErrMapBadKey       = errors.New("map bad key")
	ErrMapUnordered    = errors.New("map unordered")
	ErrBlobBadAtom     = errors.New("blob unexpected atom")
	ErrBlobBadChunkLen = errors.New("blob bad chunk len")
	ErrEOCUnexpected   = errors.New("unexpected EOC")
	ErrTooDeep         = errors.New("too deep structure")
	ErrFloatBadInt     = errors.New("non-int in float")
)

func (ctx *Decoder) deTail() {
	ctx.types = ctx.types[:len(ctx.types)-1]
	ctx.depths = ctx.depths[:len(ctx.depths)-1]
	if ctx.opts != nil && ctx.opts.SaveOffsets {
		ctx.offsets = ctx.offsets[:len(ctx.offsets)-1]
	}
}

func (ctx *Decoder) popIntForFloat() (b *big.Int, err error) {
	var sub types.Type
	sub, err = ctx.DecodeAtom()
	if err != nil {
		return
	}
	switch sub {
	case types.Int:
		b = big.NewInt(ctx.ints[len(ctx.ints)-1])
		ctx.ints = ctx.ints[:len(ctx.ints)-1]
	case types.UInt:
		b = big.NewInt(0).SetUint64(ctx.uints[len(ctx.uints)-1])
		ctx.uints = ctx.uints[:len(ctx.uints)-1]
	case types.BigInt:
		b = ctx.bigints[len(ctx.bigints)-1]
		ctx.bigints = ctx.bigints[:len(ctx.bigints)-1]
	default:
		err = ErrFloatBadInt
		return
	}
	ctx.deTail()
	return
}

func (ctx *Decoder) parse() (t types.Type, err error) {
	t, err = ctx.DecodeAtom()
	if err != nil {
		return
	}
	switch t {
	case types.Float:
		f := ctx.floats[len(ctx.floats)-1]
		if f.NaN || f.Pinf || f.Ninf {
			return
		}
		f.M, err = ctx.popIntForFloat()
		if err != nil {
			return
		}
		f.E, err = ctx.popIntForFloat()
		if err != nil {
			return
		}
	case types.List:
		ctx.depth++
		if ctx.depth < 0 {
			err = ErrTooDeep
			return
		}
		ctx.lens = append(ctx.lens, 0)
		idx := len(ctx.lens) - 1
		var count int
		var sub types.Type
		for {
			sub, err = ctx.parse()
			if err != nil {
				return
			}
			if sub == types.EOC {
				ctx.deTail()
				break
			}
			count++
			if ctx.opts != nil &&
				ctx.opts.MaxContLen > 0 &&
				int64(count) >= ctx.opts.MaxContLen {
				err = ErrLenTooBig
				return
			}
		}
		ctx.lens[idx] = count
		ctx.depth--
	case types.Map:
		ctx.depth++
		if ctx.depth < 0 {
			err = ErrTooDeep
			return
		}
		ctx.lens = append(ctx.lens, 0)
		idx := len(ctx.lens) - 1
		var count int
		var sub types.Type
		var keyPrev string
		for {
			sub, err = ctx.DecodeAtom()
			if err != nil {
				return
			}
			if sub == types.EOC {
				ctx.deTail()
				break
			}
			if sub != types.Str {
				err = ErrMapBadKey
				return
			}
			{
				s := ctx.strs[len(ctx.strs)-1]
				if len(s) == 0 {
					err = ErrMapBadKey
					return
				}
				if len(s) < len(keyPrev) {
					err = ErrMapUnordered
					return
				} else if (len(s) == len(keyPrev)) && s <= keyPrev {
					err = ErrMapUnordered
					return
				}
				keyPrev = s
			}
			sub, err = ctx.parse()
			if err != nil {
				return
			}
			if sub == types.EOC {
				err = ErrEOCUnexpected
				return
			}
			count++
			if ctx.opts != nil &&
				ctx.opts.MaxContLen > 0 &&
				int64(count) >= ctx.opts.MaxContLen {
				err = ErrLenTooBig
				return
			}
		}
		ctx.lens[idx] = count
		ctx.depth--
	case types.Blob:
		chunkLen := ctx.blobChunkLens[len(ctx.blobChunkLens)-1]
		if ctx.opts != nil && ctx.opts.MaxStrLen != 0 && chunkLen > ctx.opts.MaxStrLen {
			err = ErrLenTooBig
			return
		}
		var chunks []string
		var sub types.Type
		var s string
		for {
			sub, err = ctx.DecodeAtom()
			if err != nil {
				return
			}
			if sub != types.Bin {
				err = ErrBlobBadAtom
				return
			}
			s = ctx.strs[len(ctx.strs)-1]
			ctx.deTail()
			ctx.strs = ctx.strs[:len(ctx.strs)-1]
			if int64(len(s)) == chunkLen {
				chunks = append(chunks, s)
			} else if int64(len(s)) < chunkLen {
				if len(s) != 0 {
					chunks = append(chunks, s)
				}
				break
			} else {
				err = ErrBlobBadChunkLen
				return
			}
		}
		ctx.blobChunkses = append(ctx.blobChunkses, chunks)
		return
	}
	return
}

// Parse raw data into internal structures.
// t is the type of the decoded element.
// You will use ctx.Iter or ctx.Unmarshal functions after that.
func (ctx *Decoder) Parse() (t types.Type, err error) {
	t, err = ctx.parse()
	if t == types.EOC {
		err = ErrEOCUnexpected
	}
	return
}
