package main

import (
	"fmt"
	"sort"
	"sync"
	"time"
)

const (
	histShift  = 10
	histLen    = 60
	histPeriod = 5

	sliceSize  = 200
	maxSignals = 50

	graphUpdateCounter = 6
	graphUpdateTimeout = 10 * time.Second
)

type GraphTop struct {
	client *YasmHTTPApi
	tags   YasmTags

	signals2xx [][]string
	signals3xx [][]string
	signals4xx [][]string
	signals5xx [][]string
}

type GraphTopStorage struct {
	Signals2xx TemplateStringLenType
	Signals3xx TemplateStringLenType
	Signals4xx TemplateStringLenType
	Signals5xx TemplateStringLenType

	Lock sync.Mutex
}

func NewGraphTop(itype string, ctype string, prj string) (*GraphTop, error) {
	client, err := NewYasmHTTPApi()
	if err != nil {
		return nil, err
	}

	return &GraphTop{client: client, tags: YasmTags{Itype: itype, Ctype: ctype, Prj: prj}}, nil
}

func (t GraphTop) GetStats(signals [][]string) ([]string, error) {
	et := time.Now().Unix() - histShift
	st := et - histLen

	var topSingals []YasmJSONHistResponse
	for _, sl := range signals {
		r, err := t.client.RequestHist(histPeriod, st, et, sl)
		if err != nil {
			return nil, err
		}

		sort.Slice(r, func(i, j int) bool {
			medianI := r[i].values[len(r[i].values)>>1]
			medianJ := r[j].values[len(r[j].values)>>1]

			return medianI > medianJ
		})

		topSingals = append(topSingals, r[:10]...)
	}

	sort.Slice(topSingals, func(i, j int) bool {
		medianI := topSingals[i].values[len(topSingals[i].values)>>1]
		medianJ := topSingals[j].values[len(topSingals[j].values)>>1]

		return medianI > medianJ
	})

	max := maxSignals
	if len(topSingals) < maxSignals {
		max = len(topSingals)
	}

	var ret []string
	for i := 0; i < max; i++ {
		ret = append(ret, topSingals[i].signal)
	}

	return ret, nil
}

func (t GraphTop) Serve(storage *GraphTopStorage) error {
	updateCounter := int64(0)

	for {
		if updateCounter%graphUpdateCounter == 0 {
			if err := t.UpdateSignalsByPattern(); err != nil {
				return err
			}
		}
		updateCounter++

		stats2xx, err := t.GetStats(t.signals2xx)
		if err != nil {
			return err
		}

		stats3xx, err := t.GetStats(t.signals3xx)
		if err != nil {
			return err
		}

		stats4xx, err := t.GetStats(t.signals4xx)
		if err != nil {
			return err
		}

		stats5xx, err := t.GetStats(t.signals5xx)
		if err != nil {
			return err
		}

		storage.Lock.Lock()
		storage.Signals2xx = stats2xx
		storage.Signals3xx = stats3xx
		storage.Signals4xx = stats4xx
		storage.Signals5xx = stats5xx
		storage.Lock.Unlock()

		time.Sleep(graphUpdateTimeout)
	}
}

func (t GraphTop) splitAndFormatSplice(slice []string, sz int) [][]string {
	var chunks [][]string
	for {
		if len(slice) == 0 {
			break
		}
		if len(slice) < sz {
			sz = len(slice)
		}

		formatted := make([]string, 0)
		for i := 0; i < sz; i++ {
			formatted = append(formatted,
				fmt.Sprintf("itype=%s;ctype=%s;prj=%s:%s", t.tags.Itype, t.tags.Ctype, t.tags.Prj, slice[i]))
		}
		chunks = append(chunks, formatted)
		slice = slice[sz:]
	}

	return chunks
}

func (t *GraphTop) UpdateSignalsByPattern() error {
	signals2xx, err := t.client.RequestSignalsByPattern(t.tags, "2xx_summ")
	if err != nil {
		return err
	}
	t.signals2xx = t.splitAndFormatSplice(signals2xx, sliceSize)

	signals3xx, err := t.client.RequestSignalsByPattern(t.tags, "3xx_summ")
	if err != nil {
		return err
	}
	t.signals3xx = t.splitAndFormatSplice(signals3xx, sliceSize)

	signals4xx, err := t.client.RequestSignalsByPattern(t.tags, "4xx_summ")
	if err != nil {
		return err
	}
	t.signals4xx = t.splitAndFormatSplice(signals4xx, sliceSize)

	signals5xx, err := t.client.RequestSignalsByPattern(t.tags, "5xx_summ")
	if err != nil {
		return err
	}
	t.signals5xx = t.splitAndFormatSplice(signals5xx, sliceSize)

	return nil
}
