package stats

import (
	"fmt"
	"github.com/omniscale/imposm3/logging"
	"time"
)

type Counter struct {
	start     time.Time
	Coords    *RpsCounter
	Nodes     *RpsCounter
	Ways      *RpsCounter
	Relations *RpsCounter
}

func (c *Counter) Tick() {
	c.Coords.Tick()
	c.Nodes.Tick()
	c.Ways.Tick()
	c.Relations.Tick()
}

func NewCounter() *Counter {
	return &Counter{
		start:     time.Now(),
		Coords:    NewRpsCounter(),
		Nodes:     NewRpsCounter(),
		Ways:      NewRpsCounter(),
		Relations: NewRpsCounter(),
	}
}

func NewCounterWithEstimate(counts ElementCounts) *Counter {
	counter := NewCounter()
	counter.Coords.total = counts.Coords.Current
	counter.Nodes.total = counts.Nodes.Current
	counter.Ways.total = counts.Ways.Current
	counter.Relations.total = counts.Relations.Current
	return counter
}

type ElementCounts struct {
	Coords, Nodes, Ways, Relations ElementCount
}

// Duration returns the duration since start with seconds precission.
func (c *Counter) CurrentCount() *ElementCounts {
	return &ElementCounts{
		Coords:    c.Coords.Count(),
		Nodes:     c.Nodes.Count(),
		Ways:      c.Ways.Count(),
		Relations: c.Relations.Count(),
	}
}

// Duration returns the duration since start with seconds precission.
func (c *Counter) Duration() time.Duration {
	return time.Duration(int64(time.Since(c.start).Seconds()) * 1000 * 1000 * 1000)
}

type Statistics struct {
	counter *Counter
	done    chan bool
}

const (
	RESET = iota
	START
	STOP
	QUIT
)

func (s *Statistics) AddCoords(n int)    { s.counter.Coords.Add(n) }
func (s *Statistics) AddNodes(n int)     { s.counter.Nodes.Add(n) }
func (s *Statistics) AddWays(n int)      { s.counter.Ways.Add(n) }
func (s *Statistics) AddRelations(n int) { s.counter.Relations.Add(n) }
func (s *Statistics) Stop() *ElementCounts {
	s.done <- true
	return s.counter.CurrentCount()
}

func NewStatsReporter() *Statistics {
	s := Statistics{}
	s.counter = NewCounter()
	s.done = make(chan bool)

	go s.loop()
	return &s
}

func NewStatsReporterWithEstimate(counts *ElementCounts) *Statistics {
	s := Statistics{}
	if counts != nil {
		s.counter = NewCounterWithEstimate(*counts)
	} else {
		s.counter = NewCounter()
	}
	s.done = make(chan bool)

	go s.loop()
	return &s
}

func (s *Statistics) loop() {
	tick := time.NewTicker(500 * time.Millisecond)
	tock := time.NewTicker(time.Minute)
	for {
		select {
		case <-s.done:
			tick.Stop()
			tock.Stop()
			s.counter.PrintStats()
			return
		case <-tock.C:
			s.counter.PrintStats()
		case <-tick.C:
			s.counter.PrintTick()
			s.counter.Tick()
		}
	}
}

func fmtPercentOrVal(progress float64, value int64) string {
	if progress == -1.0 {
		return fmt.Sprintf("%d", value)
	}
	return fmt.Sprintf("%4.1f%%", progress*100)
}

func roundInt(val float64, round int) int64 {
	return int64(val/float64(round)) * int64(round)
}

func (c *Counter) PrintTick() {
	logging.Progress(
		fmt.Sprintf("[%6s] C: %7d/s %7d/s (%s) N: %7d/s %7d/s (%s) W: %7d/s %7d/s (%s) R: %6d/s %6d/s (%s)",
			c.Duration(),
			roundInt(c.Coords.Rps(), 1000),
			roundInt(c.Coords.LastRps(), 1000),
			fmtPercentOrVal(c.Coords.Progress(), c.Coords.Value()),
			roundInt(c.Nodes.Rps(), 100),
			roundInt(c.Nodes.LastRps(), 100),
			fmtPercentOrVal(c.Nodes.Progress(), c.Nodes.Value()),
			roundInt(c.Ways.Rps(), 100),
			roundInt(c.Ways.LastRps(), 100),
			fmtPercentOrVal(c.Ways.Progress(), c.Ways.Value()),
			roundInt(c.Relations.Rps(), 10),
			roundInt(c.Relations.LastRps(), 10),
			fmtPercentOrVal(c.Relations.Progress(), c.Relations.Value()),
		))
}

func (c *Counter) PrintStats() {
	logging.Infof("[%6s] C: %7d/s (%s) N: %7d/s (%s) W: %7d/s (%s) R: %6d/s (%s)",
		c.Duration(),
		roundInt(c.Coords.Rps(), 1000),
		fmtPercentOrVal(c.Coords.Progress(), c.Coords.Value()),
		roundInt(c.Nodes.Rps(), 100),
		fmtPercentOrVal(c.Nodes.Progress(), c.Nodes.Value()),
		roundInt(c.Ways.Rps(), 100),
		fmtPercentOrVal(c.Ways.Progress(), c.Ways.Value()),
		roundInt(c.Relations.Rps(), 10),
		fmtPercentOrVal(c.Relations.Progress(), c.Relations.Value()),
	)
}
