package atree

import (
	"hash/crc32"

	"gordenko.dev/dima/diploma/bin"
)

type ValueAtComparator struct {
	buf       []byte
	timestamp uint32
}

func (s ValueAtComparator) CompareTo(elemIdx int) int {
	var (
		pos  = elemIdx * timestampSize
		elem = bin.GetUint32(s.buf[pos:])
	)

	if s.timestamp < elem {
		return -1
	} else if s.timestamp > elem {
		return 1
	} else {
		return 0
	}
}

func BinarySearch(qty int, keyComparator bin.KeyComparator) (elemIdx int, isFound bool) {
	if qty == 0 {
		return
	}
	a := 0
	b := qty - 1
	for {
		var (
			elemIdx = (b-a)/2 + a
			code    = keyComparator.CompareTo(elemIdx)
		)
		if code == 1 {
			a = elemIdx + 1
			if a > b {
				return elemIdx, false // +1
			}
		} else if code == -1 {
			b = elemIdx - 1
			if b < a {
				if elemIdx == 0 {
					return 0, false
				} else {
					return elemIdx - 1, false
				}
			}
		} else {
			return elemIdx, true
		}
	}
}

type chunksToDataPageReq struct {
	PrevPageNo       uint32
	TimestampsChunks [][]byte
	TimestampsSize   uint16
	ValuesChunks     [][]byte
	ValuesSize       uint16
}

func chunksToDataPage(buf []byte, req chunksToDataPageReq) {
	bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
	bin.PutUint16(buf[timestampsSizeIdx:], req.TimestampsSize)
	bin.PutUint16(buf[valuesSizeIdx:], req.ValuesSize)

	var (
		remainingSize = int(req.TimestampsSize)
		pos           = 0
	)
	for _, chunk := range req.TimestampsChunks {
		if remainingSize >= len(chunk) {
			copy(buf[pos:], chunk)
			remainingSize -= len(chunk)
			pos += len(chunk)
		} else {
			copy(buf[pos:], chunk[:remainingSize])
			break
		}
	}

	remainingSize = int(req.ValuesSize)
	pos = int(req.TimestampsSize)

	for _, chunk := range req.ValuesChunks {
		if remainingSize >= len(chunk) {
			copy(buf[pos:], chunk)
			remainingSize -= len(chunk)
			pos += len(chunk)
		} else {
			copy(buf[pos:], chunk[:remainingSize])
			break
		}
	}
	bin.PutUint32(buf[dataCRC32Idx:], crc32.ChecksumIEEE(buf[:dataCRC32Idx]))
}

func setPrevPageNo(buf []byte, pageNo uint32) {
	bin.PutUint32(buf[prevPageIdx:], pageNo)
}

func getPrevPageNo(buf []byte) uint32 {
	return bin.GetUint32(buf[prevPageIdx:])
}

func findPageNo(buf []byte, timestamp uint32) (pageNo uint32) {
	var (
		qty        = bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
		comparator = ValueAtComparator{
			buf:       buf,
			timestamp: timestamp,
		}
	)
	elemIdx, _ := BinarySearch(qty, comparator)
	pos := indexFooterIdx - (elemIdx+1)*PageNoSize
	return bin.GetUint32(buf[pos:])
}

func findPageNoIdx(buf []byte, timestamp uint32) (idx int) {
	var (
		qty        = bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
		comparator = ValueAtComparator{
			buf:       buf,
			timestamp: timestamp,
		}
	)
	elemIdx, _ := BinarySearch(qty, comparator)
	return elemIdx
}

func findPageNoForDeleteSince(buf []byte, since uint32) (uint32, int, int) {
	var (
		qty        = bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
		comparator = ValueAtComparator{
			buf:       buf,
			timestamp: since,
		}
	)
	elemIdx, _ := BinarySearch(qty, comparator)
	pos := elemIdx * timestampSize
	timestamp := bin.GetUint32(buf[pos:])

	if timestamp == since {
		if elemIdx == 0 {
			return 0, qty, -1
		}
		elemIdx--
	}
	pos = indexFooterIdx - (elemIdx+1)*PageNoSize
	return bin.GetUint32(buf[pos:]), qty, elemIdx
}

func getLastPageNo(buf []byte) (pageNo uint32) {
	qty := bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
	pos := indexFooterIdx - qty*PageNoSize
	return bin.GetUint32(buf[pos:])
}

func getPageNo(buf []byte, idx int) (pageNo uint32) {
	pos := indexFooterIdx - (idx+1)*PageNoSize
	return bin.GetUint32(buf[pos:])
}

func listPageNumbers(buf []byte) (pageNumbers []uint32) {
	qty := bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
	pos := indexFooterIdx - PageNoSize
	for range qty {
		pageNumbers = append(pageNumbers, bin.GetUint32(buf[pos:]))
		pos -= PageNoSize
	}
	return
}

// include since timestamp
func listPageNumbersSince(buf []byte, timestamp uint32) (pageNumbers []uint32) {
	var (
		qty        = bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
		comparator = ValueAtComparator{
			buf:       buf,
			timestamp: timestamp,
		}
	)
	elemIdx, _ := BinarySearch(qty, comparator)
	pos := indexFooterIdx - (elemIdx+1)*PageNoSize
	for range qty {
		pageNumbers = append(pageNumbers, bin.GetUint32(buf[pos:]))
		pos -= PageNoSize
	}
	return
}

func getSince(buf []byte) uint32 {
	return bin.GetUint32(buf[0:])
}

func appendPair(buf []byte, timestamp uint32, pageNo uint32) bool {
	qty := bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
	free := indexFooterIdx - qty*pairSize
	if free < pairSize {
		return false
	}
	pos := qty * timestampSize
	bin.PutUint32(buf[pos:], timestamp)
	pos = indexFooterIdx - (qty+1)*PageNoSize
	bin.PutUint32(buf[pos:], pageNo)
	bin.PutIntAsUint16(buf[indexRecordsQtyIdx:], qty+1)
	return true
}
