package atree

import (
	"fmt"

	octopus "gordenko.dev/dima/diploma"
)

type IterateAllCumulativeByTreeCursorReq struct {
	FracDigits     byte
	PageNo         uint32
	EndTimestamp   uint32
	EndValue       float64
	ResponseWriter *CumulativeMeasureWriter
}

func (s *Atree) IterateAllCumulativeByTreeCursor(req IterateAllCumulativeByTreeCursorReq) error {
	buf, err := s.fetchDataPage(req.PageNo)
	if err != nil {
		return err
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.PageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Cumulative,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	var (
		endTimestamp = req.EndTimestamp
		endValue     = req.EndValue
	)

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			err := req.ResponseWriter.WriteMeasure(CumulativeMeasure{
				Timestamp: endTimestamp,
				Value:     endValue,
				Total:     endValue,
			})
			if err != nil {
				return err
			}
			return nil
		}
		err = req.ResponseWriter.WriteMeasure(CumulativeMeasure{
			Timestamp: endTimestamp,
			Value:     endValue,
			Total:     endValue - value,
		})
		if err != nil {
			return err
		}
		endTimestamp = timestamp
		endValue = value
	}
}

type ContinueIterateCumulativeByTreeCursorReq struct {
	FracDigits     byte
	Since          uint32
	Until          uint32
	LastPageNo     uint32
	EndTimestamp   uint32
	EndValue       float64
	ResponseWriter *CumulativeMeasureWriter
}

func (s *Atree) ContinueIterateCumulativeByTreeCursor(req ContinueIterateCumulativeByTreeCursorReq) error {
	buf, err := s.fetchDataPage(req.LastPageNo)
	if err != nil {
		return fmt.Errorf("fetchDataPage(%d): %s", req.LastPageNo, err)
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.LastPageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Cumulative,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	var (
		endTimestamp = req.EndTimestamp
		endValue     = req.EndValue
	)

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			err := req.ResponseWriter.WriteMeasure(CumulativeMeasure{
				Timestamp: endTimestamp,
				Value:     endValue,
				Total:     endValue,
			})
			if err != nil {
				return err
			}
			return nil
		}

		if timestamp <= req.Until {
			err := req.ResponseWriter.WriteMeasure(CumulativeMeasure{
				Timestamp: endTimestamp,
				Value:     endValue,
				Total:     endValue - value,
			})
			if err != nil {
				return err
			}
			if timestamp < req.Since {
				return nil
			}
		} else {
			// bug panic
			panic("continue cumulative but timestamp > req.Until")
		}
	}
}

type FindAndIterateCumulativeByTreeCursorReq struct {
	FracDigits     byte
	Since          uint32
	Until          uint32
	RootPageNo     uint32
	ResponseWriter *CumulativeMeasureWriter
}

func (s *Atree) FindAndIterateCumulativeByTreeCursor(req FindAndIterateCumulativeByTreeCursorReq) error {
	pageNo, buf, err := s.findDataPage(req.RootPageNo, req.Until)
	if err != nil {
		return err
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     pageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Cumulative,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	var (
		endTimestamp uint32
		endValue     float64
	)

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			if endTimestamp > 0 {
				err := req.ResponseWriter.WriteMeasure(CumulativeMeasure{
					Timestamp: endTimestamp,
					Value:     endValue,
					Total:     endValue,
				})
				if err != nil {
					return err
				}
			}
			return nil
		}

		if timestamp > req.Until {
			continue
		}

		if endTimestamp > 0 {
			err := req.ResponseWriter.WriteMeasure(CumulativeMeasure{
				Timestamp: endTimestamp,
				Value:     endValue,
				Total:     endValue - value,
			})
			if err != nil {
				return err
			}
		}
		endTimestamp = timestamp
		endValue = value

		if timestamp < req.Since {
			return nil
		}
	}
}

type IterateAllInstantByTreeCursorReq struct {
	FracDigits     byte
	PageNo         uint32
	ResponseWriter *InstantMeasureWriter
}

func (s *Atree) IterateAllInstantByTreeCursor(req IterateAllInstantByTreeCursorReq) error {
	buf, err := s.fetchDataPage(req.PageNo)
	if err != nil {
		return err
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.PageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Instant,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			return nil
		}

		err = req.ResponseWriter.WriteMeasure(InstantMeasure{
			Timestamp: timestamp,
			Value:     value,
		})
		if err != nil {
			return err
		}
	}
}

type ContinueIterateInstantByTreeCursorReq struct {
	FracDigits     byte
	Since          uint32
	Until          uint32
	LastPageNo     uint32
	ResponseWriter *InstantMeasureWriter
}

func (s *Atree) ContinueIterateInstantByTreeCursor(req ContinueIterateInstantByTreeCursorReq) error {
	buf, err := s.fetchDataPage(req.LastPageNo)
	if err != nil {
		return fmt.Errorf("fetchDataPage(%d): %s", req.LastPageNo, err)
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.LastPageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Instant,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			// - записи закончились;
			return nil
		}

		if timestamp > req.Until {
			panic("continue instant timestamp > req.Until")
		}

		if timestamp < req.Since {
			return nil
		}

		err = req.ResponseWriter.WriteMeasure(InstantMeasure{
			Timestamp: timestamp,
			Value:     value,
		})
		if err != nil {
			return err
		}
	}
}

type FindAndIterateInstantByTreeCursorReq struct {
	FracDigits     byte
	Since          uint32
	Until          uint32
	RootPageNo     uint32
	ResponseWriter *InstantMeasureWriter
}

func (s *Atree) FindAndIterateInstantByTreeCursor(req FindAndIterateInstantByTreeCursorReq) error {
	pageNo, buf, err := s.findDataPage(req.RootPageNo, req.Until)
	if err != nil {
		return err
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     pageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Instant,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done {
			return nil
		}

		if timestamp > req.Until {
			continue
		}

		if timestamp < req.Since {
			return nil
		}

		err = req.ResponseWriter.WriteMeasure(InstantMeasure{
			Timestamp: timestamp,
			Value:     value,
		})
		if err != nil {
			return err
		}
	}
}

type ContinueCollectInstantPeriodsReq struct {
	FracDigits     byte
	Aggregator     *InstantAggregator
	ResponseWriter *InstantPeriodsWriter
	LastPageNo     uint32
	Since          uint32
	Until          uint32
}

func (s *Atree) ContinueCollectInstantPeriods(req ContinueCollectInstantPeriodsReq) error {
	buf, err := s.fetchDataPage(req.LastPageNo)
	if err != nil {
		return fmt.Errorf("fetchDataPage(%d): %s", req.LastPageNo, err)
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.LastPageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Instant,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	var period InstantPeriod

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done || timestamp < req.Since {
			isCompleted := req.Aggregator.FillPeriod(timestamp, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if timestamp <= req.Until {
			isCompleted := req.Aggregator.Feed(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
		}
	}
}

type FindInstantPeriodsReq struct {
	FracDigits     byte
	ResponseWriter *InstantPeriodsWriter
	RootPageNo     uint32
	Since          uint32
	Until          uint32
	GroupBy        octopus.GroupBy
	FirstHourOfDay int
}

func (s *Atree) FindInstantPeriods(req FindInstantPeriodsReq) error {
	pageNo, buf, err := s.findDataPage(req.RootPageNo, req.Until)
	if err != nil {
		return err
	}

	aggregator, err := NewInstantAggregator(InstantAggregatorOptions{
		GroupBy:        req.GroupBy,
		FirstHourOfDay: req.FirstHourOfDay,
	})
	if err != nil {
		return err
	}

	cursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     pageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Instant,
	})
	if err != nil {
		return err
	}
	defer cursor.Close()

	var period InstantPeriod

	for {
		timestamp, value, done, err := cursor.Prev()
		if err != nil {
			return err
		}

		if done || timestamp < req.Since {
			isCompleted := aggregator.FillPeriod(timestamp, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if timestamp <= req.Until {
			isCompleted := aggregator.Feed(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
		}
	}
}

type FindCumulativePeriodsReq struct {
	FracDigits     byte
	ResponseWriter *CumulativePeriodsWriter
	RootPageNo     uint32
	Since          uint32
	Until          uint32
	GroupBy        octopus.GroupBy
	FirstHourOfDay int
}

func (s *Atree) FindCumulativePeriods(req FindCumulativePeriodsReq) error {
	pageNo, buf, err := s.findDataPage(req.RootPageNo, req.Until)
	if err != nil {
		return err
	}

	aggregator, err := NewCumulativeAggregator(CumulativeAggregatorOptions{
		GroupBy:        req.GroupBy,
		FirstHourOfDay: req.FirstHourOfDay,
	})
	if err != nil {
		return err
	}

	cursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     pageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Cumulative,
	})
	if err != nil {
		return err
	}
	defer cursor.Close()

	var period CumulativePeriod

	for {
		timestamp, value, done, err := cursor.Prev()
		if err != nil {
			return err
		}

		if done || timestamp < req.Since {
			isCompleted := aggregator.FillPeriod(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if timestamp <= req.Until {
			isCompleted := aggregator.Feed(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
		}
	}
}

type ContinueCollectCumulativePeriodsReq struct {
	FracDigits     byte
	Aggregator     *CumulativeAggregator
	ResponseWriter *CumulativePeriodsWriter
	LastPageNo     uint32
	Since          uint32
	Until          uint32
}

func (s *Atree) ContinueCollectCumulativePeriods(req ContinueCollectCumulativePeriodsReq) error {
	buf, err := s.fetchDataPage(req.LastPageNo)
	if err != nil {
		return fmt.Errorf("fetchDataPage(%d): %s", req.LastPageNo, err)
	}

	treeCursor, err := NewBackwardCursor(BackwardCursorOptions{
		PageNo:     req.LastPageNo,
		PageData:   buf,
		Atree:      s,
		FracDigits: req.FracDigits,
		MetricType: octopus.Cumulative,
	})
	if err != nil {
		return err
	}
	defer treeCursor.Close()

	var period CumulativePeriod

	for {
		timestamp, value, done, err := treeCursor.Prev()
		if err != nil {
			return err
		}

		if done || timestamp < req.Since {
			isCompleted := req.Aggregator.FillPeriod(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if timestamp <= req.Until {
			isCompleted := req.Aggregator.Feed(timestamp, value, &period)
			if isCompleted {
				err := req.ResponseWriter.WritePeriod(period)
				if err != nil {
					return err
				}
			}
		}
	}
}
