package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"
)

var (
	listen     = flag.String("l", "0.0.0.0:8080", "Listen address")
	rate       = flag.Int("r", 500, "Sample rate (ms)")
	timeout    = flag.Int("t", 10, "Total timeout (s)")
	minRate    = 500
	maxTimeout = 600
)

// TODO: bench test with pointer
func getMetrics() (Metric, error) {
	log.Println("Started getMetrics")
	var s = Stat{}
	m := Metric{}
	cpustats := s.CPUStats()
	m.Load1m = cpustats.LoadMin1
	log.Printf("Metric: %v\n", m)
	return m, nil
}

func (t *Test) collectData() {
	timeo := time.After(t.Timeout)
	t.Status = "STARTED"
	for {
		select {
		case tick := <-t.ticker.C:
			t.Status = "ONGOING"

			m, err := getMetrics()
			if err != nil {
				log.Printf("Cannot collect metrics: %s\n", err)
				return
			}
			t.Result = append(t.Result, m)
			log.Printf("%v: %v\n", tick, m)
			//t.Result += fmt.Sprintf("%v: Collecting data\n", tick)
		case <-t.Done:
			log.Printf("DONE\n")
			t.Status = "COMPLETED"
			t.Unlock()
			return
		case <-timeo:
			log.Printf("TIMEOUT\n")
			t.Status = "TIMEOUT"
			t.Unlock()
			return
		}
	}
	return // really needed?
}

func (t *Test) startTest(w http.ResponseWriter, req *http.Request) {
	// Check if another collectData is running (better: check sync.Mutex)
	if t.Status == "ONGOING" {
		http.Error(w, "Another test is currently ongoing, stop or try again later", 409) //better error code
		return
	}
	// Check parameters
	qRate := req.URL.Query().Get("rate")
	if qRate != "" {
		qRate, err := strconv.Atoi(qRate)
		if err != nil {
			log.Printf("Error while converting rate: %s\n", err.Error())
			http.Error(w, "Error: error while converting rate", 400)
			return
		}
		if qRate < minRate {
			log.Printf("Requested rate is too low: %d (min: %d)\n", qRate, minRate)
			http.Error(w, "Error: requested rate too low", 400)
			return
		}
		t.Rate = qRate
	}

	qTimeout := req.URL.Query().Get("timeout")
	if qTimeout != "" {
		qTimeout, err := strconv.Atoi(qTimeout)
		if err != nil {
			log.Printf("Error while converting timeout: %s\n", err.Error())
			http.Error(w, "Error: error while converting timeout", 400)
			return
		}
		if qTimeout > maxTimeout {
			log.Printf("Requested timeout is too high: %d (max: %d)\n", qTimeout, maxTimeout)
			http.Error(w, "Error: requested timeout too high", 400)
			return
		}
		t.Timeout = time.Duration(qTimeout) * time.Second
	}

	qName := req.URL.Query().Get("name")
	if qName == "" {
		t.Name = fmt.Sprintf("test-%d", time.Now().Local().Unix())
	} else {
		t.Name = qName
	}
	// Check if previous test is present
	if len(t.Result) == 0 {
		log.Println("Cleaning previous test data")
		t.Result = []Metric{}
	}
	// Lock to avoid multiple start
	t.Lock()
	fmt.Fprintf(w, "Test name: %s, rate: %d (ms), timeout: %d (s)\n", t.Name, t.Rate, t.Timeout/1000000000)
	endTime := time.Now().Local().Add(t.Timeout)
	fmt.Fprintf(w, "Test will end @%v\n", endTime.Format(time.RFC1123Z))
	go t.collectData()
}

func (t *Test) stopTest(w http.ResponseWriter, req *http.Request) {
	if t.Status != "ONGOING" {
		e := fmt.Sprintf("Cannot stop test in %s status: try start first", t.Status)
		http.Error(w, e, 400)
		return
	}

	t.ticker.Stop()
	t.Done <- true
	fmt.Fprintf(w, "Done\n")
	fmt.Fprintf(w, "%v\n", t.Result)
}

func (t *Test) testStatus(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "%v\n", t.Result)
	fmt.Fprintf(w, "Test %s, status: %s\n", t.Name, t.Status)
}

func newTest() *Test {
	log.Println("Creating new test")
	// Not checking if rate and timeout are in value range because are set on CLI
	t := Test{
		ticker:  time.NewTicker(time.Duration(*rate) * time.Millisecond),
		Done:    make(chan bool),
		Rate:    *rate,
		Timeout: time.Duration(*timeout) * time.Second,
		Status:  "CREATED",
	}
	return &t
}

func main() {

	flag.Parse()

	t := newTest()
	log.Printf("%+v\n", t)

	http.HandleFunc("/start", t.startTest)
	http.HandleFunc("/status", t.testStatus)
	http.HandleFunc("/stop", t.stopTest)

	log.Printf("Listening on %s\n", *listen)
	log.Fatal(http.ListenAndServe(*listen, nil))
}
