//go:build (386 && !appengine) || (amd64 && !appengine) || (arm && !appengine) || (arm64 && !appengine) || (ppc64le && !appengine) || (mipsle && !appengine) || (mips64le && !appengine) || (mips64p32le && !appengine) || (wasm && !appengine)
// +build 386,!appengine amd64,!appengine arm,!appengine arm64,!appengine ppc64le,!appengine mipsle,!appengine mips64le,!appengine mips64p32le,!appengine wasm,!appengine

package roaring

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"unsafe"
)

func (ac *arrayContainer) writeTo(stream io.Writer) (int, error) {
	buf := uint16SliceAsByteSlice(ac.content)
	return stream.Write(buf)
}

func (bc *bitmapContainer) writeTo(stream io.Writer) (int, error) {
	if bc.cardinality <= arrayDefaultMaxSize {
		return 0, errors.New("refusing to write bitmap container with cardinality of array container")
	}
	buf := uint64SliceAsByteSlice(bc.bitmap)
	return stream.Write(buf)
}

func uint64SliceAsByteSlice(slice []uint64) []byte {
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	const size = unsafe.Sizeof(uint64(0))
	return unsafe.Slice(((*byte)(unsafe.Pointer(ptr))), int(size)*len(slice))
}

func uint16SliceAsByteSlice(slice []uint16) []byte {
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	const size = unsafe.Sizeof(uint16(0))
	return unsafe.Slice(((*byte)(unsafe.Pointer(ptr))), int(size)*len(slice))
}

func interval16SliceAsByteSlice(slice []interval16) []byte {
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	const size = unsafe.Sizeof(interval16{})
	return unsafe.Slice(((*byte)(unsafe.Pointer(ptr))), int(size)*len(slice))
}

func (bc *bitmapContainer) asLittleEndianByteSlice() []byte {
	return uint64SliceAsByteSlice(bc.bitmap)
}

// Deserialization code follows

// //
// These methods (byteSliceAsUint16Slice,...) do not make copies,
// they are pointer-based (unsafe). The caller is responsible to
// ensure that the input slice does not get garbage collected, deleted
// or modified while you hold the returned slince.
// //
func byteSliceAsUint16Slice(slice []byte) (result []uint16) { // here we create a new slice holder
	const sz = int(unsafe.Sizeof(uint16(0)))
	if len(slice)%sz != 0 {
		panic(fmt.Sprintf("Slice size should be divisible by %d", sz))
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*uint16)(unsafe.Pointer(ptr)), len(slice)/sz)
}

func byteSliceAsUint64Slice(slice []byte) (result []uint64) {
	const sz = int(unsafe.Sizeof(uint64(0)))
	if len(slice)%sz != 0 {
		panic(fmt.Sprintf("Slice size should be divisible by %d", sz))
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*uint64)(unsafe.Pointer(ptr)), len(slice)/sz)
}

func byteSliceAsInterval16Slice(slice []byte) (result []interval16) {
	const sz = int(unsafe.Sizeof(interval16{}))
	if len(slice)%sz != 0 {
		panic(fmt.Sprintf("Slice size should be divisible by %d", sz))
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*interval16)(unsafe.Pointer(ptr)), len(slice)/sz)
}

func byteSliceAsContainerSlice(slice []byte) (result []container) {
	var c container
	containerSize := int(unsafe.Sizeof(c))

	if len(slice)%containerSize != 0 {
		panic("Slice size should be divisible by unsafe.Sizeof(container)")
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*container)(unsafe.Pointer(ptr)), len(slice)/containerSize)
}

func byteSliceAsBitsetSlice(slice []byte) (result []bitmapContainer) {
	const bitsetSize = int(unsafe.Sizeof(bitmapContainer{}))
	if len(slice)%bitsetSize != 0 {
		panic("Slice size should be divisible by unsafe.Sizeof(bitmapContainer)")
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*bitmapContainer)(unsafe.Pointer(ptr)), len(slice)/bitsetSize)
}

func byteSliceAsArraySlice(slice []byte) (result []arrayContainer) {
	const arraySize = int(unsafe.Sizeof(arrayContainer{}))
	if len(slice)%arraySize != 0 {
		panic("Slice size should be divisible by unsafe.Sizeof(arrayContainer)")
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*arrayContainer)(unsafe.Pointer(ptr)), len(slice)/arraySize)
}

func byteSliceAsRun16Slice(slice []byte) (result []runContainer16) {
	const run16Size = int(unsafe.Sizeof(runContainer16{}))
	if len(slice)%run16Size != 0 {
		panic("Slice size should be divisible by unsafe.Sizeof(runContainer16)")
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*runContainer16)(unsafe.Pointer(ptr)), len(slice)/run16Size)
}

func byteSliceAsBoolSlice(slice []byte) (result []bool) {
	const boolSize = int(unsafe.Sizeof(true))
	if len(slice)%boolSize != 0 {
		panic("Slice size should be divisible by unsafe.Sizeof(bool)")
	}
	ptr := unsafe.SliceData(slice)
	if ptr == nil {
		return nil
	}
	return unsafe.Slice((*bool)(unsafe.Pointer(ptr)), len(slice)/boolSize)
}

// FrozenView creates a static view of a serialized bitmap stored in buf.
// It uses CRoaring's frozen bitmap format.
//
// The format specification is available here:
// https://github.com/RoaringBitmap/CRoaring/blob/2c867e9f9c9e2a3a7032791f94c4c7ae3013f6e0/src/roaring.c#L2756-L2783
//
// The provided byte array (buf) is expected to be a constant.
// The function makes the best effort attempt not to copy data.
// Only little endian is supported. The function will err if it detects a big
// endian serialized file.
// You should take care not to modify buff as it will likely result in
// unexpected program behavior.
// If said buffer comes from a memory map, it's advisable to give it read
// only permissions, either at creation or by calling Mprotect from the
// golang.org/x/sys/unix package.
//
// Resulting bitmaps are effectively immutable in the following sense:
// a copy-on-write marker is used so that when you modify the resulting
// bitmap, copies of selected data (containers) are made.
// You should *not* change the copy-on-write status of the resulting
// bitmaps (SetCopyOnWrite).
//
// If buf becomes unavailable, then a bitmap created with
// FromBuffer would be effectively broken. Furthermore, any
// bitmap derived from this bitmap (e.g., via Or, And) might
// also be broken. Thus, before making buf unavailable, you should
// call CloneCopyOnWriteContainers on all such bitmaps.
func (rb *Bitmap) FrozenView(buf []byte) error {
	return rb.highlowcontainer.frozenView(buf)
}

func (rb *Bitmap) MustFrozenView(buf []byte) error {
	if err := rb.FrozenView(buf); err != nil {
		return err
	}
	err := rb.Validate()

	return err
}

/* Verbatim specification from CRoaring.
 *
 * FROZEN SERIALIZATION FORMAT DESCRIPTION
 *
 * -- (beginning must be aligned by 32 bytes) --
 * <bitset_data> uint64_t[BITSET_CONTAINER_SIZE_IN_WORDS * num_bitset_containers]
 * <run_data>    rle16_t[total number of rle elements in all run containers]
 * <array_data>  uint16_t[total number of array elements in all array containers]
 * <keys>        uint16_t[num_containers]
 * <counts>      uint16_t[num_containers]
 * <typecodes>   uint8_t[num_containers]
 * <header>      uint32_t
 *
 * <header> is a 4-byte value which is a bit union of frozenCookie (15 bits)
 * and the number of containers (17 bits).
 *
 * <counts> stores number of elements for every container.
 * Its meaning depends on container type.
 * For array and bitset containers, this value is the container cardinality minus one.
 * For run container, it is the number of rle_t elements (n_runs).
 *
 * <bitset_data>,<array_data>,<run_data> are flat arrays of elements of
 * all containers of respective type.
 *
 * <*_data> and <keys> are kept close together because they are not accessed
 * during deserilization. This may reduce IO in case of large mmaped bitmaps.
 * All members have their native alignments during deserilization except <header>,
 * which is not guaranteed to be aligned by 4 bytes.
 */
const frozenCookie = 13766

var (
	// ErrFrozenBitmapInvalidCookie is returned when the header does not contain the frozenCookie.
	ErrFrozenBitmapInvalidCookie = errors.New("header does not contain the frozenCookie")
	// ErrFrozenBitmapBigEndian is returned when the header is big endian.
	ErrFrozenBitmapBigEndian = errors.New("loading big endian frozen bitmaps is not supported")
	// ErrFrozenBitmapIncomplete is returned when the buffer is too small to contain a frozen bitmap.
	ErrFrozenBitmapIncomplete = errors.New("input buffer too small to contain a frozen bitmap")
	// ErrFrozenBitmapOverpopulated is returned when the number of containers is too large.
	ErrFrozenBitmapOverpopulated = errors.New("too many containers")
	// ErrFrozenBitmapUnexpectedData is returned when the buffer contains unexpected data.
	ErrFrozenBitmapUnexpectedData = errors.New("spurious data in input")
	// ErrFrozenBitmapInvalidTypecode is returned when the typecode is invalid.
	ErrFrozenBitmapInvalidTypecode = errors.New("unrecognized typecode")
	// ErrFrozenBitmapBufferTooSmall is returned when the buffer is too small.
	ErrFrozenBitmapBufferTooSmall = errors.New("buffer too small")
)

func (ra *roaringArray) frozenView(buf []byte) error {
	if len(buf) < 4 {
		return ErrFrozenBitmapIncomplete
	}

	headerBE := binary.BigEndian.Uint32(buf[len(buf)-4:])
	if headerBE&0x7fff == frozenCookie {
		return ErrFrozenBitmapBigEndian
	}

	header := binary.LittleEndian.Uint32(buf[len(buf)-4:])
	buf = buf[:len(buf)-4]

	if header&0x7fff != frozenCookie {
		return ErrFrozenBitmapInvalidCookie
	}

	nCont := int(header >> 15)
	if nCont > (1 << 16) {
		return ErrFrozenBitmapOverpopulated
	}

	// 1 byte per type, 2 bytes per key, 2 bytes per count.
	if len(buf) < 5*nCont {
		return ErrFrozenBitmapIncomplete
	}

	types := buf[len(buf)-nCont:]
	buf = buf[:len(buf)-nCont]

	counts := byteSliceAsUint16Slice(buf[len(buf)-2*nCont:])
	buf = buf[:len(buf)-2*nCont]

	keys := byteSliceAsUint16Slice(buf[len(buf)-2*nCont:])
	buf = buf[:len(buf)-2*nCont]

	nBitmap, nArray, nRun := 0, 0, 0
	nArrayEl, nRunEl := 0, 0
	for i, t := range types {
		switch t {
		case 1:
			nBitmap++
		case 2:
			nArray++
			nArrayEl += int(counts[i]) + 1
		case 3:
			nRun++
			nRunEl += int(counts[i])
		default:
			return ErrFrozenBitmapInvalidTypecode
		}
	}

	if len(buf) < (1<<13)*nBitmap+4*nRunEl+2*nArrayEl {
		return ErrFrozenBitmapIncomplete
	}

	bitsetsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBitmap])
	buf = buf[(1<<13)*nBitmap:]

	runsArena := byteSliceAsInterval16Slice(buf[:4*nRunEl])
	buf = buf[4*nRunEl:]

	arraysArena := byteSliceAsUint16Slice(buf[:2*nArrayEl])
	buf = buf[2*nArrayEl:]

	if len(buf) != 0 {
		return ErrFrozenBitmapUnexpectedData
	}

	var c container
	containersSz := int(unsafe.Sizeof(c)) * nCont
	bitsetsSz := int(unsafe.Sizeof(bitmapContainer{})) * nBitmap
	arraysSz := int(unsafe.Sizeof(arrayContainer{})) * nArray
	runsSz := int(unsafe.Sizeof(runContainer16{})) * nRun
	needCOWSz := int(unsafe.Sizeof(true)) * nCont

	bitmapArenaSz := containersSz + bitsetsSz + arraysSz + runsSz + needCOWSz
	bitmapArena := make([]byte, bitmapArenaSz)

	containers := byteSliceAsContainerSlice(bitmapArena[:containersSz])
	bitmapArena = bitmapArena[containersSz:]

	bitsets := byteSliceAsBitsetSlice(bitmapArena[:bitsetsSz])
	bitmapArena = bitmapArena[bitsetsSz:]

	arrays := byteSliceAsArraySlice(bitmapArena[:arraysSz])
	bitmapArena = bitmapArena[arraysSz:]

	runs := byteSliceAsRun16Slice(bitmapArena[:runsSz])
	bitmapArena = bitmapArena[runsSz:]

	needCOW := byteSliceAsBoolSlice(bitmapArena)

	iBitset, iArray, iRun := 0, 0, 0
	for i, t := range types {
		needCOW[i] = true

		switch t {
		case 1:
			containers[i] = &bitsets[iBitset]
			bitsets[iBitset].cardinality = int(counts[i]) + 1
			bitsets[iBitset].bitmap = bitsetsArena[:1024]
			bitsetsArena = bitsetsArena[1024:]
			iBitset++
		case 2:
			containers[i] = &arrays[iArray]
			sz := int(counts[i]) + 1
			arrays[iArray].content = arraysArena[:sz]
			arraysArena = arraysArena[sz:]
			iArray++
		case 3:
			containers[i] = &runs[iRun]
			runs[iRun].iv = runsArena[:counts[i]]
			runsArena = runsArena[counts[i]:]
			iRun++
		}
	}

	// Not consuming the full input is a bug.
	if iBitset != nBitmap || len(bitsetsArena) != 0 ||
		iArray != nArray || len(arraysArena) != 0 ||
		iRun != nRun || len(runsArena) != 0 {
		panic("we missed something")
	}

	ra.keys = keys
	ra.containers = containers
	ra.needCopyOnWrite = needCOW
	ra.copyOnWrite = true

	return nil
}

// GetFrozenSizeInBytes returns the size in bytes of the frozen bitmap.
func (rb *Bitmap) GetFrozenSizeInBytes() uint64 {
	nBits, nArrayEl, nRunEl := uint64(0), uint64(0), uint64(0)
	for _, c := range rb.highlowcontainer.containers {
		switch v := c.(type) {
		case *bitmapContainer:
			nBits++
		case *arrayContainer:
			nArrayEl += uint64(len(v.content))
		case *runContainer16:
			nRunEl += uint64(len(v.iv))
		}
	}
	return 4 + 5*uint64(len(rb.highlowcontainer.containers)) +
		(nBits << 13) + 2*nArrayEl + 4*nRunEl
}

// Freeze serializes the bitmap in the CRoaring's frozen format.
func (rb *Bitmap) Freeze() ([]byte, error) {
	sz := rb.GetFrozenSizeInBytes()
	buf := make([]byte, sz)
	_, err := rb.FreezeTo(buf)
	return buf, err
}

// FreezeTo serializes the bitmap in the CRoaring's frozen format.
func (rb *Bitmap) FreezeTo(buf []byte) (int, error) {
	containers := rb.highlowcontainer.containers
	nCont := len(containers)

	nBits, nArrayEl, nRunEl := 0, 0, 0
	for _, c := range containers {
		switch v := c.(type) {
		case *bitmapContainer:
			nBits++
		case *arrayContainer:
			nArrayEl += len(v.content)
		case *runContainer16:
			nRunEl += len(v.iv)
		}
	}

	serialSize := 4 + 5*nCont + (1<<13)*nBits + 4*nRunEl + 2*nArrayEl
	if len(buf) < serialSize {
		return 0, ErrFrozenBitmapBufferTooSmall
	}

	bitsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBits])
	buf = buf[(1<<13)*nBits:]

	runsArena := byteSliceAsInterval16Slice(buf[:4*nRunEl])
	buf = buf[4*nRunEl:]

	arraysArena := byteSliceAsUint16Slice(buf[:2*nArrayEl])
	buf = buf[2*nArrayEl:]

	keys := byteSliceAsUint16Slice(buf[:2*nCont])
	buf = buf[2*nCont:]

	counts := byteSliceAsUint16Slice(buf[:2*nCont])
	buf = buf[2*nCont:]

	types := buf[:nCont]
	buf = buf[nCont:]

	header := uint32(frozenCookie | (nCont << 15))
	binary.LittleEndian.PutUint32(buf[:4], header)

	copy(keys, rb.highlowcontainer.keys[:])

	for i, c := range containers {
		switch v := c.(type) {
		case *bitmapContainer:
			copy(bitsArena, v.bitmap)
			bitsArena = bitsArena[1024:]
			counts[i] = uint16(v.cardinality - 1)
			types[i] = 1
		case *arrayContainer:
			copy(arraysArena, v.content)
			arraysArena = arraysArena[len(v.content):]
			elems := len(v.content)
			counts[i] = uint16(elems - 1)
			types[i] = 2
		case *runContainer16:
			copy(runsArena, v.iv)
			runs := len(v.iv)
			runsArena = runsArena[runs:]
			counts[i] = uint16(runs)
			types[i] = 3
		}
	}

	return serialSize, nil
}

// WriteFrozenTo serializes the bitmap in the CRoaring's frozen format.
func (rb *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) {
	// FIXME: this is a naive version that iterates 4 times through the
	// containers and allocates 3*len(containers) bytes; it's quite likely
	// it can be done more efficiently.
	containers := rb.highlowcontainer.containers
	written := 0

	for _, c := range containers {
		c, ok := c.(*bitmapContainer)
		if !ok {
			continue
		}
		n, err := wr.Write(uint64SliceAsByteSlice(c.bitmap))
		written += n
		if err != nil {
			return written, err
		}
	}

	for _, c := range containers {
		c, ok := c.(*runContainer16)
		if !ok {
			continue
		}
		n, err := wr.Write(interval16SliceAsByteSlice(c.iv))
		written += n
		if err != nil {
			return written, err
		}
	}

	for _, c := range containers {
		c, ok := c.(*arrayContainer)
		if !ok {
			continue
		}
		n, err := wr.Write(uint16SliceAsByteSlice(c.content))
		written += n
		if err != nil {
			return written, err
		}
	}

	n, err := wr.Write(uint16SliceAsByteSlice(rb.highlowcontainer.keys))
	written += n
	if err != nil {
		return written, err
	}

	countTypeBuf := make([]byte, 3*len(containers))
	counts := byteSliceAsUint16Slice(countTypeBuf[:2*len(containers)])
	types := countTypeBuf[2*len(containers):]

	for i, c := range containers {
		switch c := c.(type) {
		case *bitmapContainer:
			counts[i] = uint16(c.cardinality - 1)
			types[i] = 1
		case *arrayContainer:
			elems := len(c.content)
			counts[i] = uint16(elems - 1)
			types[i] = 2
		case *runContainer16:
			runs := len(c.iv)
			counts[i] = uint16(runs)
			types[i] = 3
		}
	}

	n, err = wr.Write(countTypeBuf)
	written += n
	if err != nil {
		return written, err
	}

	header := uint32(frozenCookie | (len(containers) << 15))
	if err := binary.Write(wr, binary.LittleEndian, header); err != nil {
		return written, err
	}
	written += 4

	return written, nil
}
