/*
 * 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 hll

import (
	"encoding/binary"
	"fmt"
)

type couponListImpl struct {
	hllSketchConfig
	hllCouponState
}

func (c *couponListImpl) GetCompositeEstimate() (float64, error) {
	return getEstimate(c)
}

func (c *couponListImpl) GetEstimate() (float64, error) {
	return getEstimate(c)
}

func (c *couponListImpl) GetHipEstimate() (float64, error) {
	return getEstimate(c)
}

func (c *couponListImpl) GetLowerBound(numStdDev int) (float64, error) {
	return getLowerBound(c, numStdDev)
}

func (c *couponListImpl) GetUpperBound(numStdDev int) (float64, error) {
	return getUpperBound(c, numStdDev)
}

func (c *couponListImpl) GetUpdatableSerializationBytes() int {
	return c.getMemDataStart() + (4 << c.getLgCouponArrInts())
}

func (c *couponListImpl) ToCompactSlice() ([]byte, error) {
	return toCouponSlice(c, true)
}

func (c *couponListImpl) ToUpdatableSlice() ([]byte, error) {
	return toCouponSlice(c, false)
}

// couponUpdate updates the couponListImpl with the given coupon.
// it returns the updated couponListImpl or a newly promoted couponHashSetImpl.
func (c *couponListImpl) couponUpdate(coupon int) (hllSketchStateI, error) {
	length := 1 << c.lgCouponArrInts
	for i := 0; i < length; i++ {
		couponAtIdx := c.couponIntArr[i]
		if couponAtIdx == empty {
			c.couponIntArr[i] = coupon //update
			c.couponCount++
			if c.couponCount >= length {
				if c.lgConfigK < 8 {
					return promoteListToHll(c) //oooFlag = false
				}
				return promoteListToSet(c) //oooFlag = true
			}
			return c, nil
		}
		//cell not empty
		if couponAtIdx == coupon {
			return c, nil //duplicate
		}
		//cell not empty & not a duplicate, continue
	}
	return nil, fmt.Errorf("array invalid: no empties & no duplicates")
}

// iterator returns an iterator over the couponListImpl.
func (c *couponListImpl) iterator() pairIterator {
	return newIntArrayPairIterator(c.couponIntArr, c.lgConfigK)
}

func (c *couponListImpl) getMemDataStart() int {
	return listIntArrStart
}

func (c *couponListImpl) getPreInts() int {
	return listPreInts
}

func (c *couponListImpl) copyAs(tgtHllType TgtHllType) (hllSketchStateI, error) {
	newC := &couponListImpl{
		hllSketchConfig: newHllSketchConfig(c.lgConfigK, tgtHllType, curModeList),
		hllCouponState:  newHllCouponState(c.lgCouponArrInts, c.couponCount, make([]int, len(c.couponIntArr))),
	}

	copy(newC.couponIntArr, c.couponIntArr)
	return newC, nil
}

func (c *couponListImpl) copy() (hllSketchStateI, error) {
	return c.copyAs(c.tgtHllType)
}

func (c *couponListImpl) mergeTo(dest HllSketch) error {
	return mergeCouponTo(c, dest)
}

// promoteListToHll move coupons to an hllArray from couponListImpl
func promoteListToHll(src *couponListImpl) (hllArray, error) {
	tgtHllArr, _ := newHllArray(src.lgConfigK, src.tgtHllType)
	srcIter := src.iterator()
	tgtHllArr.putKxQ0(float64(uint64(1) << src.lgConfigK))

	for srcIter.nextValid() {
		p, err := srcIter.getPair()
		if err != nil {
			return nil, err
		}
		_, err = tgtHllArr.couponUpdate(p)
		if err != nil {
			return nil, err
		}
	}
	est, err := src.GetEstimate()
	if err != nil {
		return nil, err
	}
	tgtHllArr.putHipAccum(est)
	tgtHllArr.putOutOfOrder(false)
	return tgtHllArr, nil
}

// promoteListToSet move coupons to a couponHashSetImpl from couponListImpl
func promoteListToSet(c *couponListImpl) (hllSketchStateI, error) {
	couponCount := c.getCouponCount()
	arr := c.couponIntArr
	chSet, err := newCouponHashSet(c.lgConfigK, c.tgtHllType)
	if err != nil {
		return nil, err
	}
	for i := 0; i < couponCount && err == nil; i++ {
		_, err = chSet.couponUpdate(arr[i])
	}

	if err != nil {
		return nil, err
	}
	return &chSet, nil
}

// newCouponList returns a new couponListImpl.
func newCouponList(lgConfigK int, tgtHllType TgtHllType, curMode curMode) (couponListImpl, error) {
	var (
		lgCouponArrInts = lgInitSetSize //SET
	)

	if curMode == curModeList {
		lgCouponArrInts = lgInitListSize
	} else if lgConfigK <= 7 {
		return couponListImpl{}, fmt.Errorf("lgConfigK must be > 7 for non-HLL mode")
	}

	couponIntArr := make([]int, 1<<lgCouponArrInts)
	couponCount := 0

	return couponListImpl{
		hllSketchConfig: newHllSketchConfig(lgConfigK, tgtHllType, curMode),
		hllCouponState:  newHllCouponState(lgCouponArrInts, couponCount, couponIntArr),
	}, nil
}

// deserializeCouponList returns a new couponListImpl from the given byte slice.
func deserializeCouponList(byteArray []byte) (hllCoupon, error) {
	lgConfigK := extractLgK(byteArray)
	tgtHllType := extractTgtHllType(byteArray)

	list, err := newCouponList(lgConfigK, tgtHllType, curModeList)
	if err != nil {
		return nil, err
	}
	couponCount := extractListCount(byteArray)
	// TODO there must be a more efficient to reinterpret the byte array as an int array
	base := listIntArrStart
	for it := 0; it < couponCount; it++ {
		list.couponIntArr[it] = int(binary.LittleEndian.Uint32(byteArray[base:]))
		base += 4
	}
	list.couponCount = couponCount
	return &list, nil
}
