package enc

import (
	"gordenko.dev/dima/qb/bin"
)

// REVERSE

const (
	lastUnixtimeIdx = 0
	baseDeltaIdx    = 4
)

type ReverseTimeDeltaOfDeltaDecompressor struct {
	step             byte
	buf              []byte
	pos              int
	bound            int
	lastUnixtime     uint32
	baseDelta        uint32
	lastDeltaOfDelta int64
	length           uint16
	idxOf8           uint
	s8               byte
}

func NewReverseTimeDeltaOfDeltaDecompressor(buf []byte) *ReverseTimeDeltaOfDeltaDecompressor {
	return &ReverseTimeDeltaOfDeltaDecompressor{
		buf: buf,
		pos: len(buf),
	}
}

func (s *ReverseTimeDeltaOfDeltaDecompressor) NextValue() (value uint32, done bool) {
	if s.step == 0 {
		if s.pos == 0 {
			return 0, true
		}
		s.lastUnixtime = bin.GetUint32(s.buf[lastUnixtimeIdx:])
		s.step = 1
		return s.lastUnixtime, false
	}

	if s.step == 1 {
		if s.pos == baseDeltaIdx {
			return 0, true
		}
		u64, n, err := bin.GetVarUint64(s.buf[baseDeltaIdx:])
		if err != nil {
			panic("EOF")
		}
		s.bound = baseDeltaIdx + n
		s.baseDelta = uint32(u64)

		s.pos--
		s.idxOf8 = uint(8 - s.buf[s.pos])
		s.pos--
		s.s8 = s.buf[s.pos]
		s.pos--

		s.readVar()
		if s.length > 0 {
			s.length--
		}
		s.step = 2
		return s.lastUnixtime, false
	}

	if s.length > 0 {
		s.length--
		delta := int64(s.baseDelta) + s.lastDeltaOfDelta
		s.lastUnixtime = uint32(int64(s.lastUnixtime) - delta)
		return s.lastUnixtime, false
	}

	if s.pos < s.bound {
		return 0, true
	}
	if s.idxOf8 == 0 {
		s.s8 = s.buf[s.pos]
		s.pos--
	}
	s.readVar()
	if s.length > 0 {
		s.length--
	}
	return s.lastUnixtime, false
}

func GetTimeRange(timestampsBuf []byte) (since, until uint32) {
	decompressor := NewReverseTimeDeltaOfDeltaDecompressor(timestampsBuf)
	value, done := decompressor.NextValue()
	if done {
		return
	}

	since = value
	until = value
	for {
		value, done = decompressor.NextValue()
		if done {
			return
		}
		since = value
	}
}

func (s *ReverseTimeDeltaOfDeltaDecompressor) readVar() {
	var (
		n   int
		err error
	)

	s.lastDeltaOfDelta, n, err = bin.ReverseGetVarInt64(s.buf[:s.pos+1])
	if err != nil {
		panic(err)
	}
	s.pos -= n

	delta := int64(s.baseDelta) + s.lastDeltaOfDelta
	s.lastUnixtime = uint32(int64(s.lastUnixtime) - delta)

	var flag byte = 1 << s.idxOf8
	if (s.s8 & flag) == flag {
		s.decodeLength()
	}
	if s.idxOf8 == 7 {
		s.idxOf8 = 0
	} else {
		s.idxOf8++
	}
}

func (s *ReverseTimeDeltaOfDeltaDecompressor) decodeLength() {
	b1 := s.buf[s.pos]
	s.pos--
	if b1 < 128 {
		s.length = uint16(b1)
	} else {
		b2 := s.buf[s.pos]
		s.pos--
		s.length = uint16(b1&127) | (uint16(b2) << 7)
	}
	s.length += 2
}
