package chunkenc

import (
	"fmt"

	"gordenko.dev/dima/qb/bin"
	"gordenko.dev/dima/qb/conbuf"
)

// REVERSE

const (
	lastUnixtimeIdx = 0
	baseDeltaIdx    = 4
)

type ReverseTimeDeltaOfDeltaCompressor struct {
	buf              *conbuf.ContinuousBuffer
	pos              int
	baseDelta        uint32
	lastUnixtime     uint32
	lastDeltaOfDelta int64
	length           uint16
	numIdx           int
}

func NewReverseTimeDeltaOfDeltaCompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaOfDeltaCompressor {
	s := &ReverseTimeDeltaOfDeltaCompressor{
		buf: buf,
		pos: size,
	}
	if size > 0 {
		s.restoreState()
	}
	return s
}

func (s *ReverseTimeDeltaOfDeltaCompressor) restoreState() {
	s.lastUnixtime = s.buf.GetUint32(lastUnixtimeIdx)
	if s.pos > 4 {
		u64, _, err := s.buf.GetVarUint64(baseDeltaIdx)
		if err != nil {
			panic(fmt.Sprintf("bug: get base delta: %s", err))
		}
		s.baseDelta = uint32(u64)

		pos := s.pos - 1
		idxOf8 := uint(8 - s.buf.GetByte(pos))
		pos--
		s8 := s.buf.GetByte(pos)
		pos--

		var n int
		s.lastDeltaOfDelta, n, err = s.buf.ReverseGetVarInt64(pos)
		if err != nil {
			panic(err)
		}
		pos -= n
		s.numIdx = pos + 1
		var flag byte = 1 << idxOf8
		if (s8 & flag) == flag {
			s.length, _ = s.buf.DecodeRunLength(pos)
		}
	}
}

func (s *ReverseTimeDeltaOfDeltaCompressor) Size() int {
	return s.pos
}

func (s *ReverseTimeDeltaOfDeltaCompressor) CalcRequiredSpace(unixtime uint32) int {
	if s.pos == 0 {
		return 4
	}

	if s.baseDelta == 0 {
		baseDelta := unixtime - s.lastUnixtime
		n := bin.CalcVarUint64Length(uint64(baseDelta))
		return n + 3
	}

	deltaOfDelta := int64(unixtime-s.lastUnixtime) - int64(s.baseDelta)
	if deltaOfDelta == s.lastDeltaOfDelta {
		if s.length == 0 {
			return 1
		} else {
			newLength := s.length + 1
			if newLength < 130 {
				return 0
			} else if newLength == 130 {
				return 1
			} else {
				if newLength < 32769 {
					return 0
				} else {
					n := bin.CalcVarInt64Length(deltaOfDelta)
					n += 2
					s8q := s.buf.GetByte(s.pos - 1)
					if s8q == 8 {
						n -= 1
					} else {
						n -= 2
					}
					return n
				}
			}
		}
	} else {
		n := bin.CalcVarInt64Length(deltaOfDelta)
		n += 2
		s8q := s.buf.GetByte(s.pos - 1)
		if s8q == 8 {
			n -= 1
		} else {
			n -= 2
		}
		return n
	}
}

func (s *ReverseTimeDeltaOfDeltaCompressor) Append(unixtime uint32) {
	if s.pos == 0 {
		s.lastUnixtime = unixtime
		s.buf.PutUint32(lastUnixtimeIdx, unixtime)
		s.pos += 4
		return
	}

	if s.baseDelta == 0 {
		s.baseDelta = unixtime - s.lastUnixtime
		s.lastDeltaOfDelta = 0
		s.lastUnixtime = unixtime
		s.buf.PutUint32(lastUnixtimeIdx, unixtime)

		n := s.buf.PutVarUint64(s.pos, uint64(s.baseDelta))
		s.pos += n
		s.encodeNewDeltaOfDelta(0, 0, 1)
		return
	}

	deltaOfDelta := int64(unixtime-s.lastUnixtime) - int64(s.baseDelta)
	s.lastUnixtime = unixtime
	s.buf.PutUint32(lastUnixtimeIdx, unixtime)

	if deltaOfDelta == s.lastDeltaOfDelta {
		if s.length == 0 {
			s.length = 2
			s.shiftOnePosToRight()
			s.buf.SetByte(s.numIdx-1, 0)
			s8q := s.buf.GetByte(s.pos - 1)
			s.buf.SetFlag(s.pos-2, 1<<(8-s8q))
		} else {
			s.length++
			if s.length < 130 {
				s.buf.SetByte(s.numIdx-1, byte(s.length-2))
			} else if s.length == 130 {
				s.shiftOnePosToRight()
				s.encode2bLength()
			} else {
				if s.length < 32769 {
					s.encode2bLength()
				} else {
					s.appendNewDeltaOfDelta(deltaOfDelta)
				}
			}
		}
	} else {
		s.appendNewDeltaOfDelta(deltaOfDelta)
	}
}

func (s *ReverseTimeDeltaOfDeltaCompressor) appendNewDeltaOfDelta(deltaOfDelta int64) {
	s.length = 0
	s8 := s.buf.GetByte(s.pos - 2)
	s8q := s.buf.GetByte(s.pos - 1)

	if s8q == 8 {
		s.pos -= 1
		s8 = 0
		s8q = 1
	} else {
		s.pos -= 2
		s8q++
	}

	s.encodeNewDeltaOfDelta(deltaOfDelta, s8, s8q)
}

func (s *ReverseTimeDeltaOfDeltaCompressor) encodeNewDeltaOfDelta(deltaOfDelta int64, s8 byte, s8q byte) {
	s.lastDeltaOfDelta = deltaOfDelta
	s.numIdx = s.pos
	n := s.buf.ReversePutVarInt64(s.pos, deltaOfDelta)
	s.pos += n
	s.buf.SetByte(s.pos, s8)
	s.pos++
	s.buf.SetByte(s.pos, s8q)
	s.pos++
}

func (s *ReverseTimeDeltaOfDeltaCompressor) shiftOnePosToRight() {
	s.buf.ShiftOnePosToRight(s.numIdx, s.pos)
	s.pos++
	s.numIdx++
}

func (s *ReverseTimeDeltaOfDeltaCompressor) encode2bLength() {
	num := s.length - 2
	s.buf.SetByte(s.numIdx-1, byte(num&127)|128)
	s.buf.SetByte(s.numIdx-2, byte(num>>7))
}

func (s *ReverseTimeDeltaOfDeltaCompressor) DeleteLast() {
	var (
		s8q       = s.buf.GetByte(s.pos - 1)
		s8        = s.buf.GetByte(s.pos - 2)
		flag byte = 1 << uint(8-s8q)
	)

	if s.length > 0 {
		if s.length == 2 {
			s.length = 0
			s.buf.UnsetFlag(s.pos-2, flag)
			s.buf.ShiftOnePosToLeft(s.numIdx, s.pos)
			s.numIdx--
			s.pos--
		} else if s.length < 130 {
			s.length--
			s.buf.SetByte(s.numIdx-1, byte(s.length)-2)
		} else if s.length == 130 {
			s.length--
			s.buf.ShiftOnePosToLeft(s.numIdx, s.pos)
			s.numIdx--
			s.pos--
			s.buf.SetByte(s.numIdx-1, byte(s.length)-2)
		} else {
			s.length--
			s.encode2bLength()
		}
	} else {
		if s8q > 1 {
			s8q--
			flag = 1 << uint(8-s8q)
			s.pos = s.numIdx + 2
			s.buf.SetByte(s.pos-2, s8)
			s.buf.SetByte(s.pos-1, s8q)
		} else {
			s.pos = s.numIdx + 1
			s.buf.SetByte(s.pos-1, 8)
			s8 = s.buf.GetByte(s.pos - 2)
			flag = 1
		}
		var (
			pos = s.pos - 3
			n   int
			err error
		)
		s.lastDeltaOfDelta, n, err = s.buf.ReverseGetVarInt64(pos)
		if err != nil {
			panic(err)
		}
		s.numIdx = pos - n
		if (s8 & flag) == flag {
			s.length, _ = s.buf.DecodeRunLength(s.numIdx - 1)
		}
	}

	delta := int64(s.baseDelta) + s.lastDeltaOfDelta
	s.lastUnixtime = uint32(int64(s.lastUnixtime) - delta)
	s.buf.PutUint32(lastUnixtimeIdx, s.lastUnixtime)
}

type ReverseTimeDeltaOfDeltaDecompressor struct {
	step             byte
	buf              *conbuf.ContinuousBuffer
	pos              int
	bound            int
	lastUnixtime     uint32
	baseDelta        uint32
	lastDeltaOfDelta int64
	length           uint16
	idxOf8           uint
	s8               byte
}

func NewReverseTimeDeltaOfDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaOfDeltaDecompressor {
	return &ReverseTimeDeltaOfDeltaDecompressor{
		buf: buf,
		pos: size,
	}
}

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

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

		s.pos--
		s.idxOf8 = uint(8 - s.buf.GetByte(s.pos))
		s.pos--
		s.s8 = s.buf.GetByte(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.GetByte(s.pos)
		s.pos--
	}
	s.readVar()
	if s.length > 0 {
		s.length--
	}
	return s.lastUnixtime, false
}

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

	s.lastDeltaOfDelta, n, err = s.buf.ReverseGetVarInt64(s.pos)
	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.length, n = s.buf.DecodeRunLength(s.pos)
		s.pos -= n
	}
	if s.idxOf8 == 7 {
		s.idxOf8 = 0
	} else {
		s.idxOf8++
	}
}
