package mqe

import (
	"sync"
	"time"

	"gordenko.dev/dima/qx"
)

type GroupBy string

// Для парсинга since и until
const (
	NoAggregateFunc byte = 1
	AggregateMin    byte = 2
	AggregateMax    byte = 4
	AggregateAvg    byte = 8

	ByHour  GroupBy = "h"
	ByDay   GroupBy = "d"
	ByMonth GroupBy = "m"

	hourPeriodLayout  = "2006010215"
	dayPeriodLayout   = "20060102"
	monthPeriodLayout = "200601"

	dateLayout = "2006-01-02"
)

type MetricMeasuresFilter struct {
	MetricID int64 `json:"metricID"`
	Since    int64 `json:"since"` // уже учтен firstHourOfDay
	Until    int64 `json:"until"` // уже учтен firstHourOfDay
}

type MeasureQueryEngine struct {
	db                     *qx.Db
	location               *time.Location
	cumulativeCh           chan *CumulativeTask
	instantCh              chan *InstantTask
	aggregatedCumulativeCh chan *AggregatedCumulativeTask
	aggregatedInstantCh    chan *AggregatedInstantTask
	cumulativeTotalCh      chan *CumulativeTotalTask
	metricReadingsCh       chan *MetricReadingsTask
}

type Options struct {
	Db         *qx.Db
	Location   *time.Location
	MinWorkers int
	QueueSize  int
}

func New(opt Options) *MeasureQueryEngine {
	if opt.Db == nil {
		panic("Db option is required")
	}

	if opt.Location == nil {
		panic("Location option is required")
	}

	if opt.MinWorkers <= 0 {
		panic("MinWorkers option is required")
	}

	if opt.QueueSize <= 0 {
		panic("QueueSize option is required")
	}

	s := new(MeasureQueryEngine)
	s.db = opt.Db
	s.location = opt.Location
	//
	s.cumulativeCh = make(chan *CumulativeTask, opt.QueueSize)
	s.instantCh = make(chan *InstantTask, opt.QueueSize)
	s.aggregatedCumulativeCh = make(chan *AggregatedCumulativeTask, opt.QueueSize)
	s.aggregatedInstantCh = make(chan *AggregatedInstantTask, opt.QueueSize)
	s.cumulativeTotalCh = make(chan *CumulativeTotalTask, opt.QueueSize)
	s.metricReadingsCh = make(chan *MetricReadingsTask, opt.QueueSize)

	//
	for i := 0; i < opt.MinWorkers; i++ {
		go s.worker()
	}
	return s
}

func (s *MeasureQueryEngine) worker() {
	for {
		select {
		case task := <-s.cumulativeCh:
			task.Result, task.Err = s.listCumulativeMeasures(task.In)
			task.WaitGroup.Done()

		case task := <-s.instantCh:
			task.Result, task.Err = s.listInstantMeasures(task.In)
			task.WaitGroup.Done()

		case task := <-s.aggregatedCumulativeCh:
			task.Result, task.Err = s.listAggregatedCumulativeMeasures(task.In)
			task.WaitGroup.Done()

		case task := <-s.aggregatedInstantCh:
			task.Result, task.Err = s.listAggregatedInstantMeasures(task.In)
			task.WaitGroup.Done()

		case task := <-s.cumulativeTotalCh:
			task.Result, task.Err = s.getCumulativeTotal(task.In)
			task.WaitGroup.Done()

		case task := <-s.metricReadingsCh:
			task.Result, task.Err = s.listMetricReadings(task.In)
			task.WaitGroup.Done()
		}
	}
}

//

type CumulativeTask struct {
	In        MetricMeasuresFilter
	WaitGroup *sync.WaitGroup
	Result    []Measure
	Err       error
}

type InstantTask struct {
	In        MetricMeasuresFilter
	WaitGroup *sync.WaitGroup
	Result    []Measure
	Err       error
}

type AggregatedCumulativeTask struct {
	In        AggregatedMeasuresFilter
	WaitGroup *sync.WaitGroup
	Result    []AggregatedMeasure
	Err       error
}

type AggregatedInstantTask struct {
	In        AggregatedInstantMeasuresIn
	WaitGroup *sync.WaitGroup
	Result    []AggregatedMeasure
	Err       error
}

type MetricReadingsTask struct {
	In        MetricMeasuresFilter
	WaitGroup *sync.WaitGroup
	Result    MetricReadings
	Err       error
}

type CumulativeTotalTask struct {
	In        getCumulativeTotalFilter
	WaitGroup *sync.WaitGroup
	Result    *RangeTotal
	Err       error
}
