package enc

import (
	"fmt"
	"math"

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

type ReverseInstantDeltaDecompressor struct {
	buf        []byte
	pos        int
	bound      int
	firstValue float64
	lastValue  float64
	length     uint16
	coef       float64
	idxOf8     uint
	s8         byte
}

func NewReverseInstantDeltaDecompressor(buf []byte, fracDigits byte) *ReverseInstantDeltaDecompressor {
	var coef float64 = 1
	if fracDigits > 0 {
		coef = math.Pow(10, float64(fracDigits))
	}
	return &ReverseInstantDeltaDecompressor{
		buf:  buf,
		coef: coef,
		pos:  len(buf),
	}
}

func (s *ReverseInstantDeltaDecompressor) NextValue() (value float64, done bool) {
	if s.length > 0 {
		s.length--
		return s.lastValue, false
	}
	if s.pos < s.bound {
		return 0, true
	}

	if s.pos == len(s.buf) {
		u64, n, err := bin.GetVarInt64(s.buf)
		if err != nil {
			panic(err)
		}
		s.firstValue = float64(u64) / s.coef
		s.bound = n
		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--
		}
		return s.lastValue, false
	}

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

func (s *ReverseInstantDeltaDecompressor) readVar() {
	i64, n, err := bin.ReverseGetVarInt64(s.buf[:s.pos+1])
	if err != nil {
		panic(err)
	}
	s.pos -= n
	s.lastValue = s.firstValue + float64(i64)/s.coef

	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 *ReverseInstantDeltaDecompressor) 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
}

func GetValueBounds(valuesBuf []byte, metricType octopus.MetricType, fracDigits byte) (sinceValue, untilValue float64) {
	var decompressor octopus.ValueDecompressor
	switch metricType {
	case octopus.Instant:
		decompressor = NewReverseInstantDeltaDecompressor(valuesBuf, fracDigits)
	case octopus.Cumulative:
		decompressor = NewReverseCumulativeDeltaDecompressor(valuesBuf, fracDigits)
	default:
		panic(fmt.Sprintf("unknown metricType %d", metricType))
	}
	value, done := decompressor.NextValue()
	if done {
		return
	}

	sinceValue = value
	untilValue = value
	for {
		value, done = decompressor.NextValue()
		if done {
			return
		}
		sinceValue = value
	}
}
