package main

import (
	"errors"
	"github.com/gofiber/fiber/v2"
	"log"
	"math/rand"
	"time"
)

type TestCommand struct {
	Command    string `json:"cmd"`
	SequenceId int64  `json:"sequenceId"`
}

type TestData struct {
	State         string  `json:"state"`
	ExceptionType int     `json:"exceptionType,omitempty"`
	ExceptionDesc string  `json:"exceptionDesc,omitempty"`
	Image         string  `json:"image,omitempty"`
	DistanceCM    int     `json:"distanceCM,omitempty"`
	Counts        int     `json:"counts,omitempty"`
	DurationSec   float32 `json:"durationSec,omitempty"`
}

type TestStat struct {
	Scene      string   `json:"scene"`
	Timestamp  int64    `json:"timestamp"`
	SequenceId int64    `json:"sequenceId"`
	TestData   TestData `json:"sceneData"`
}

type TestException struct {
	Type int
	Desc string
}

var currentTest *TestCommand

var random *rand.Rand
var testExceptChannel chan TestException
var testStopChannel chan bool
var testDurations map[string][2]int

func setTestCmd(c *fiber.Ctx) error {
	var testCmd TestCommand
	if err := c.BodyParser(&testCmd); nil != err {
		log.Println("ERROR:", c.Body())
		return failureResponse(c, "-1", err.Error(), 200)
	} else if nil == currentScene {
		return failureResponse(c, "1", "not in a working scene", 200)
	} else {
		switch testCmd.Command {
		case "START":
			log.Println("START ...")
			if nil != currentTest {
				return failureResponse(c, "2", "A test already started", 200)
			}
			if e := recogTaskProc(&testCmd); nil != e {
				return failureResponse(c, "3", e.Error(), 200)
			}
		case "STOP":
			if nil != testStopChannel {
				testStopChannel <- true
				log.Println("STOP ...")
			}

		case "CANCEL":
			if nil != testStopChannel {
				testStopChannel <- true
				log.Println("CANCEL ...")
			}
		default:
			log.Println("UNKNOWN ...")
		}
		return successResponse(c, "Updated successfully.")
	}
}

func pullUpTask(seq int64) error {
	var tk = time.NewTicker(time.Millisecond * 200)
	defer func() {
		tk.Stop()
	}()
	time.Sleep(time.Millisecond * 300)
	go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", false, false)
	var started = false
	for {
		select {
		case <-testStopChannel:
			go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", true, true)
			return nil
		case except := <-testExceptChannel:
			if except.Type != 0 {
				go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, true)
			}
		case <-tk.C:
			log.Println("===")
			if !started {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", true, true)
				started = true
			} else {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "COUNT", true, true)
			}
		}
	}
}

func standJumpTask(seq int64) error {
	var tk = time.NewTicker(time.Millisecond * 100)
	defer func() {
		tk.Stop()
	}()
	time.Sleep(time.Millisecond * 500)
	go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", false, false)
	var started = false
	for {
		select {
		case <-testStopChannel:
			go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", true, true)
			return nil
		case except := <-testExceptChannel:
			if except.Type != 0 {
				go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, true)
			}
		case <-tk.C:
			log.Println("===")
			if !started {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", true, true)
				started = true
			}
		}
	}
}

func sitUpsTask(seq int64) error {
	var tk = time.NewTicker(time.Millisecond * 200)
	defer func() {
		tk.Stop()
	}()
	time.Sleep(time.Millisecond * 600)
	go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", false, false)
	var started = false
	for {
		select {
		case <-testStopChannel:
			go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", true, true)
			return nil
		case except := <-testExceptChannel:
			if except.Type != 0 {
				go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, true)
			}
		case <-tk.C:
			log.Println("===")
			if !started {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", true, true)
				started = true
			} else {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "COUNT", true, true)
			}
		}
	}
}

func raceTask(seq int64) error {
	var tk = time.NewTicker(time.Millisecond * 100)
	defer func() {
		tk.Stop()
	}()
	time.Sleep(time.Millisecond * 800)
	go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", false, false)
	var started = false
	for {
		select {
		case <-testStopChannel:
			go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST END", true, true)
			return nil
		case except := <-testExceptChannel:
			if except.Type != 0 {
				go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, true)
			}
		case <-tk.C:
			log.Println("===")
			if !started {
				go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", true, true)
				started = true
			}
		}
	}
}

func pushException(url string, scene string, seq int64, tp int, desc string, img bool) {
	var st = TestStat{
		Scene:      scene,
		Timestamp:  time.Now().Unix(),
		SequenceId: seq,
		TestData: TestData{
			State:         "EXCEPT",
			ExceptionType: tp,
			ExceptionDesc: desc,
		},
	}
	if img {
		st.TestData.Image = IMAGES[random.Intn(len(IMAGES))]
	}
	httpPostEx(url, st, 3)
}

func pushEvent(url string, scene string, seq int64, state string, img bool, val bool) {
	var st = TestStat{
		Scene:      scene,
		Timestamp:  time.Now().Unix(),
		SequenceId: seq,
		TestData: TestData{
			State: state,
		},
	}
	if img {
		st.TestData.Image = IMAGES[random.Intn(len(IMAGES))]
	}
	if val {
		switch scene {
		case "pullUp":
			st.TestData.Counts = random.Intn(20)
		case "standJump":
			st.TestData.DistanceCM = random.Intn(300)
		case "sitUps":
			st.TestData.Counts = random.Intn(20)
		case "race":
			st.TestData.DurationSec = random.Float32()
		default:

		}
	}
	httpPostEx(url, st, 3)
}

func recogTaskProc(cmd *TestCommand) error {
	testStopChannel = make(chan bool)
	testExceptChannel = make(chan TestException)
	random = rand.New(rand.NewSource(time.Now().UnixNano()))
	if nil == currentScene {
		return errors.New("invalid scene state")
	}
	if nil == currentScene.proc {
		return errors.New("invalid scene mode processor")
	}
	currentTest = cmd
	go func() {
		defer func() {
			close(testStopChannel)
			close(testExceptChannel)
			testStopChannel = nil
			testExceptChannel = nil
			currentTest = nil
		}()
		if e := currentScene.proc(cmd.SequenceId); nil != e {
			log.Println(">>>>", e)
		}
	}()
	go func() {
		var min = testDurations[currentScene.Scene][0]
		var max = testDurations[currentScene.Scene][1]
		for i := 0; i < random.Intn(max-min)+min; i++ {
			time.Sleep(time.Second)
		}
		testStopChannel <- true
	}()
	return nil
}

func init() {
	random = rand.New(rand.NewSource(time.Now().UnixNano()))
	currentTest = nil
	testExceptChannel = nil
	testStopChannel = nil
	testDurations = map[string][2]int{
		"pullUp":    [2]int{30, 80},
		"standJump": [2]int{10, 30},
		"sitUps":    [2]int{60, 70},
		"race":      [2]int{9, 15},
	}
}
