/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tuple

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"

	"github.com/apache/datasketches-go/internal"
	"github.com/apache/datasketches-go/theta"
)

// SummaryReader reads and returns a summary from the reader.
// Implementations should read the format written by a corresponding SummaryWriter.
//
// If the summary contains string values and the caller cares about
// cross-language compatibility, it is the caller's responsibility to ensure
// that the serialized string data is encoded as valid UTF-8.
type SummaryReader[S Summary] func(r io.Reader) (S, error)

// Decoder decodes a compact sketch from the given reader.
type Decoder[S Summary] struct {
	seed uint64
	read SummaryReader[S]
}

// NewDecoder creates a new decoder.
func NewDecoder[S Summary](seed uint64, read SummaryReader[S]) Decoder[S] {
	return Decoder[S]{
		seed: seed,
		read: read,
	}
}

// Decode decodes a compact sketch from the given reader.
func (dec *Decoder[S]) Decode(r io.Reader) (*CompactSketch[S], error) {
	var preambleLongs uint8
	if err := binary.Read(r, binary.LittleEndian, &preambleLongs); err != nil {
		return nil, err
	}

	var serialVersion uint8
	if err := binary.Read(r, binary.LittleEndian, &serialVersion); err != nil {
		return nil, err
	}

	var family uint8
	if err := binary.Read(r, binary.LittleEndian, &family); err != nil {
		return nil, err
	}

	var sketchType uint8
	if err := binary.Read(r, binary.LittleEndian, &sketchType); err != nil {
		return nil, err
	}

	var unused uint8
	if err := binary.Read(r, binary.LittleEndian, &unused); err != nil {
		return nil, err
	}

	var flags uint8
	if err := binary.Read(r, binary.LittleEndian, &flags); err != nil {
		return nil, err
	}

	var seedHash uint16
	if err := binary.Read(r, binary.LittleEndian, &seedHash); err != nil {
		return nil, err
	}

	if serialVersion != SerialVersion && serialVersion != SerialVersionLegacy {
		return nil, fmt.Errorf("serial version mismatch: expected %d, actual %d", SerialVersion, serialVersion)
	}

	if err := theta.CheckSketchFamilyEqual(family, uint8(internal.FamilyEnum.Tuple.Id)); err != nil {
		return nil, err
	}

	if sketchType != uint8(SketchTypeCompactTuple) && sketchType != uint8(SketchTypeCompactTupleLegacy) {
		return nil, fmt.Errorf("sketch type mismatch: expected %d, actual %d", SketchTypeCompactTuple, sketchType)
	}

	isEmpty := (flags & (1 << flagIsEmpty)) != 0
	if !isEmpty {
		expectedSeedHash, err := internal.ComputeSeedHash(int64(dec.seed))
		if err != nil {
			return nil, err
		}
		if err := theta.CheckSeedHashEqual(seedHash, uint16(expectedSeedHash)); err != nil {
			return nil, err
		}
	}

	thetaVal := theta.MaxTheta
	numEntries := uint32(0)
	if !isEmpty {
		if preambleLongs == 1 {
			numEntries = 1
		} else {
			if err := binary.Read(r, binary.LittleEndian, &numEntries); err != nil {
				return nil, err
			}

			unused := uint32(0)
			if err := binary.Read(r, binary.LittleEndian, &unused); err != nil {
				return nil, err
			}

			if preambleLongs > 2 {
				if err := binary.Read(r, binary.LittleEndian, &thetaVal); err != nil {
					return nil, err
				}
			}
		}
	}

	entries := make([]entry[S], numEntries)
	for i := uint32(0); i < numEntries; i++ {
		var hash uint64
		if err := binary.Read(r, binary.LittleEndian, &hash); err != nil {
			return nil, err
		}

		summary, err := dec.read(r)
		if err != nil {
			return nil, err
		}

		entries[i] = entry[S]{Hash: hash, Summary: summary}
	}

	isOrdered := (flags & (1 << flagIsOrdered)) != 0
	return newCompactSketch[S](
		isEmpty, isOrdered, seedHash, thetaVal, entries,
	), nil
}

// Decode reconstructs a CompactSketch from a byte slice using a specified seed and read function.
func Decode[S Summary](b []byte, seed uint64, read SummaryReader[S]) (*CompactSketch[S], error) {
	decoder := NewDecoder[S](seed, read)
	return decoder.Decode(bytes.NewReader(b))
}
