// 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"
	"fmt"
	"io"
	"strings"
	"unsafe"

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

type BlobChunked struct {
	Chunks   []string
	ChunkLen int64
}

func (blob *BlobChunked) Len() (sum int64) {
	for _, s := range blob.Chunks {
		sum += int64(len(s))
	}
	return
}

func (blob *BlobChunked) Reader() io.Reader {
	rs := make([]io.Reader, 0, len(blob.Chunks))
	for _, s := range blob.Chunks {
		rs = append(rs, strings.NewReader(s))
	}
	return io.MultiReader(rs...)
}

func (blob *BlobChunked) String() string {
	return fmt.Sprintf("BLOB(%d, %d)", blob.ChunkLen, blob.Len())
}

func (blob BlobChunked) MarshalJSON() ([]byte, error) {
	return []byte(`"` + blob.String() + `"`), nil
}

type BlobReader struct {
	R        io.Reader
	ChunkLen int64
}

func (blob *BlobReader) String() string {
	return fmt.Sprintf("BLOB(%d, ?)", blob.ChunkLen)
}

// BlobDecoder decodes a BLOB from the reader.
type BlobDecoder struct {
	d        *Decoder
	ChunkLen int64
	eof      bool
}

func NewBlobDecoder(r io.Reader, maxChunkLen int64) (*BlobDecoder, error) {
	d := NewDecoderFromReader(r, &DecodeOpts{MaxStrLen: maxChunkLen})
	t, err := d.DecodeAtom()
	if err != nil {
		return nil, err
	}
	if t != types.Blob {
		return nil, errors.New("not a BLOB")
	}
	chunkLen := d.blobChunkLens[0]
	if chunkLen > maxChunkLen {
		return nil, ErrLenTooBig
	}
	d.readBuf = make([]byte, max(chunkLen, 8))
	return &BlobDecoder{d: d, ChunkLen: chunkLen}, nil
}

func (d *BlobDecoder) SetReadBuf(buf []byte) {
	d.d.readBuf = buf
}

// Get the next chunk of the BLOB. Last one may be smaller size than the
// ChunkLen. When BLOB is finished, io.EOF is returned with nil chunk.
func (d *BlobDecoder) Next() (chunk []byte, err error) {
	if d.eof {
		return nil, io.EOF
	}
	var t types.Type
	t, err = d.d.DecodeAtom()
	if err != nil {
		return
	}
	if t != types.Bin {
		err = ErrBlobBadAtom
		return
	}
	s := d.d.strs[len(d.d.strs)-1]
	d.d.deTail()
	d.d.strs = d.d.strs[:len(d.d.strs)-1]
	chunk = unsafe.Slice(unsafe.StringData(s), len(s))
	d.d.readBuf = d.d.readBuf[:max(d.ChunkLen, 8)]
	if int64(len(s)) == d.ChunkLen {
		return chunk, nil
	}
	if int64(len(s)) > d.ChunkLen {
		return nil, ErrBlobBadChunkLen
	}
	d.eof = true
	if len(s) == 0 {
		return nil, io.EOF
	}
	return chunk, nil
}
