package ss13_se

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"sort"
	"time"

	chart "github.com/wcharczuk/go-chart"
)

// TODO BUG:
// - gonna have to fix the funky Y range ticks:
//   High pop servers will have Y ranges between min/max values while
//   low pop servers will show multiple ticks with same values (like 3,3,3,2,2,1,1,1,0)
//   Needs to be standardized (from 0 to max, with sane increments in between).
//
// - Top of bars in barcharts doesn't align well to the Y range ticks
//
// - I'm also sure I fixed the bug with one day missing randomly from the charts..

var weekDaysOrder = []time.Weekday{
	time.Monday,
	time.Tuesday,
	time.Wednesday,
	time.Thursday,
	time.Friday,
	time.Saturday,
	time.Sunday,
}

type renderableChart interface {
	Render(chart.RendererProvider, io.Writer) error
}

func (a *App) renderChart(w http.ResponseWriter, c renderableChart) error {
	buf := &bytes.Buffer{}
	err := c.Render(chart.PNG, buf)

	if err != nil {
		//a.Log("Error while rendering chart: %s", err)
		return HttpError{
			Status: http.StatusInternalServerError,
			Err:    fmt.Errorf("error while rendering chart"),
		}
	}

	w.Header().Add("Content-Type", "image/png")
	_, err = io.Copy(w, buf)
	if err != nil {
		a.Log("Error while sending chart: %s", err)
		return HttpError{
			Status: http.StatusInternalServerError,
			Err:    fmt.Errorf("error while sending chart"),
		}
	}

	return nil
}

func makeHistoryChart(points []ServerPoint, showLegend bool) chart.Chart {
	var xVals []time.Time
	var yVals []float64
	for _, p := range points {
		xVals = append(xVals, p.Time)
		yVals = append(yVals, float64(p.Players))
	}

	series := chart.TimeSeries{
		Name:    "Players",
		XValues: xVals,
		YValues: yVals,
	}
	lr := &chart.LinearRegressionSeries{
		Name:        "Linear regression",
		InnerSeries: series,
	}
	sma := &chart.SMASeries{
		Name:        "Simple moving avg.",
		InnerSeries: series,
	}

	c := chart.Chart{
		Background: chart.Style{
			Padding: chart.Box{
				Top: 40,
			},
		},
		XAxis: chart.XAxis{
			Style: chart.StyleShow(),
			ValueFormatter: func(v interface{}) string {
				t := int64(v.(float64))
				return time.Unix(0, t).Format("Jan 02 15:04")
			},
		},
		YAxis: chart.YAxis{
			Style: chart.StyleShow(),
			ValueFormatter: func(v interface{}) string {
				return fmt.Sprintf("%.0f", v)
			},
		},
		Series: []chart.Series{
			series,
			lr,
			sma,
		},
	}
	if showLegend {
		c.Elements = []chart.Renderable{
			chart.LegendThin(&c),
		}
	}
	return c
}

// NOTE: The chart won't be renderable unless we've got at least two days/hours of history
func makeAverageChart(values map[int][]int, fnFormat func(int, float64) string, fnSort func([]int) []int) chart.BarChart {
	var keys []int
	avg := make(map[int]float64)
	for i, vl := range values {
		sum := 0
		for _, v := range vl {
			sum += v
		}
		avg[i] = float64(sum / len(vl))
		keys = append(keys, i)
	}

	var bars []chart.Value
	for _, k := range fnSort(keys) {
		bars = append(bars, chart.Value{
			Label: fnFormat(k, avg[k]),
			Value: avg[k],
			Style: chart.Style{
				StrokeColor: chart.ColorBlue,
				FillColor:   chart.ColorBlue,
			},
		})
	}

	barW, barS := 50, 100
	if len(avg) > 7 {
		barW, barS = 20, 20
	}
	s := chart.Style{
		Show:        true,
		StrokeWidth: 1,
	}
	return chart.BarChart{
		BarWidth:   barW,
		BarSpacing: barS,
		XAxis:      s,
		YAxis: chart.YAxis{
			Style: s,
			ValueFormatter: func(v interface{}) string {
				return fmt.Sprintf("%.0f", v)
			},
		},
		Bars: bars,
	}
}

// Shortcut/helper func for the calling handler
func avgDailyChart(points []ServerPoint) chart.BarChart {
	days := make(map[int][]int)
	for _, p := range points {
		d := int(p.Time.Weekday())
		days[d] = append(days[d], p.Players)
	}
	now := time.Now()
	formatter := func(i int, f float64) string {
		d := time.Weekday(i)
		extra := ""
		if d == now.Weekday() {
			extra = "*"
		}
		return fmt.Sprintf("%s%s", d, extra)
	}
	sorter := func(keys []int) []int {
		sort.Slice(keys, func(i, j int) bool {
			return keys[i] < keys[j]
		})
		// Fucking wankers and their fucking sundays
		if keys[0] == int(time.Sunday) {
			keys = append(keys[1:], int(time.Sunday))
		}
		return keys
	}
	return makeAverageChart(days, formatter, sorter)
}

// Shortcut/helper func for the calling handler
func avgHourlyChart(points []ServerPoint) chart.BarChart {
	hours := make(map[int][]int)
	for _, p := range points {
		h := p.Time.Hour()
		hours[h] = append(hours[h], p.Players)
	}
	now := time.Now()
	formatter := func(i int, f float64) string {
		extra := ""
		if i == now.Hour() {
			extra = "*"
		}
		return fmt.Sprintf("%02d%s", i, extra)
	}
	sorter := func(keys []int) []int {
		sort.Slice(keys, func(i, j int) bool {
			return keys[i] < keys[j]
		})
		return keys
	}
	return makeAverageChart(hours, formatter, sorter)
}