package main

import (
	"image/color"
	"math/rand"
	"strconv"
	"time"

	"tinygo.org/x/tinydraw"
	"tinygo.org/x/tinyfont"
	"tinygo.org/x/tinyfont/freesans"
)

const (
	reflexStateWait = iota
	reflexStateReady
	reflexStateGo
	reflexStateResult
)

type ReflexGame struct {
	state       int
	direction   int // 0=Up 1=Down 2=Left 3=Right
	score       int
	round       int
	startTime   time.Time
	reactionMs  int64
	bestTime    int64
	splashShown bool
}

func NewReflexGame() *ReflexGame {
	return &ReflexGame{
		state:    reflexStateWait,
		bestTime: 999999,
	}
}

func (r *ReflexGame) Loop() {
	r.state = reflexStateWait
	r.score = 0
	r.round = 0
	r.bestTime = 999999
	r.splashShown = false

	for {
		getInput()
		switch r.state {
		case reflexStateWait:
			r.showInstructions()
			if !buttonsOldState[buttonA] && buttonsState[buttonA] {
				r.state = reflexStateReady
				time.Sleep(300 * time.Millisecond)
			}
			if goBack() {
				return
			}

		case reflexStateReady:
			r.showGetReady()
			delay := time.Duration(1000+rand.Int31n(2000)) * time.Millisecond
			deadline := time.Now().Add(delay)
			for time.Now().Before(deadline) {
				getInput()
				if goBack() {
					return
				}
				time.Sleep(50 * time.Millisecond)
			}
			r.state = reflexStateGo

		case reflexStateGo:
			r.round++
			r.direction = int(rand.Int31n(4))
			r.startTime = time.Now()
			r.showTarget()
			pressed := false
			for !pressed {
				getInput()
				if goBack() {
					return
				}
				switch r.direction {
				case 0:
					pressed = buttonsState[buttonUp]
				case 1:
					pressed = buttonsState[buttonDown]
				case 2:
					pressed = buttonsState[buttonLeft]
				case 3:
					pressed = buttonsState[buttonRight]
				}
				time.Sleep(10 * time.Millisecond)
			}
			r.reactionMs = time.Since(r.startTime).Milliseconds()
			if r.reactionMs < r.bestTime {
				r.bestTime = r.reactionMs
			}
			if points := int(1000 - r.reactionMs); points > 0 {
				r.score += points
			}
			r.state = reflexStateResult

		case reflexStateResult:
			r.showResult()
			r.feedbackLEDs()
			for i := 0; i < 40; i++ {
				getInput()
				if goBack() {
					r.clearLEDs()
					return
				}
				time.Sleep(50 * time.Millisecond)
			}
			r.clearLEDs()
			if r.round >= 5 {
				r.showFinalScore()
				for {
					getInput()
					if goBack() {
						return
					}
					if !buttonsOldState[buttonA] && buttonsState[buttonA] {
						break
					}
					time.Sleep(50 * time.Millisecond)
				}
				r.state = reflexStateWait
				r.score = 0
				r.round = 0
				r.bestTime = 999999
				r.splashShown = false
			} else {
				r.state = reflexStateReady
			}
		}
		time.Sleep(50 * time.Millisecond)
	}
}

func (r *ReflexGame) showInstructions() {
	if r.splashShown {
		return
	}
	display.FillScreen(colorBlack)
	if displayHeight >= 160 {
		tinyfont.WriteLine(&display, &freesans.Bold18pt7b, 40, 50, "REFLEX", colorYellow)
		tinyfont.WriteLine(&display, &freesans.Bold18pt7b, 40, 80, "TESTER", colorYellow)
		tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 130, "Press matching", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 155, "direction button", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 180, "when arrow appears!", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 60, 215, "[A] START", colorMellonGreen)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 60, 235, "[B] BACK", colorRed)
	} else {
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 25, "REFLEX TESTER", colorYellow)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 55, "Press matching", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 70, "direction button", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 85, "when arrow appears!", colorWhite)
		tinyfont.WriteLine(&display, &freesans.Bold9pt7b, 10, 110, "[A] START", colorMellonGreen)
		tinyfont.WriteLine(&display, &freesans.Bold9pt7b, 10, 125, "[B] BACK", colorRed)
	}
	r.splashShown = true
}

func (r *ReflexGame) showGetReady() {
	display.FillScreen(colorBlack)
	if displayHeight >= 160 {
		tinyfont.WriteLine(&display, &freesans.Bold24pt7b, 20, displayHeight/2+15, "GET READY!", colorOrange)
	} else {
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 20, displayHeight/2+8, "GET READY!", colorOrange)
	}
}

func (r *ReflexGame) showTarget() {
	display.FillScreen(colorBlack)
	roundText := "Round " + strconv.Itoa(r.round) + "/5"
	if displayHeight >= 160 {
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 20, roundText, colorText)
	} else {
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 5, 14, roundText, colorText)
	}
	cx := int16(displayWidth / 2)
	cy := int16(displayHeight / 2)
	switch r.direction {
	case 0:
		r.drawArrow(cx, cy, colorPurple, 0)
	case 1:
		r.drawArrow(cx, cy, colorMellonGreen, 1)
	case 2:
		r.drawArrow(cx, cy, colorOrange, 2)
	case 3:
		r.drawArrow(cx, cy, colorRed, 3)
	}
}

func (r *ReflexGame) showResult() {
	display.FillScreen(colorBlack)

	timeText := strconv.FormatInt(r.reactionMs, 10) + " ms"
	var feedbackText string
	var feedbackColor color.RGBA
	switch {
	case r.reactionMs < 250:
		feedbackText = "AMAZING!"
		feedbackColor = colorMellonGreen
	case r.reactionMs < 400:
		feedbackText = "GREAT!"
		feedbackColor = colorMellonGreen
	case r.reactionMs < 550:
		feedbackText = "GOOD"
		feedbackColor = colorOrange
	case r.reactionMs < 750:
		feedbackText = "OK"
		feedbackColor = colorOrange
	default:
		feedbackText = "SLOW"
		feedbackColor = colorRed
	}
	scoreText := "Score: " + strconv.Itoa(r.score)

	if displayHeight >= 160 {
		w, _ := tinyfont.LineWidth(&freesans.Bold24pt7b, timeText)
		tinyfont.WriteLine(&display, &freesans.Bold24pt7b, (displayWidth-int16(w))/2, 80, timeText, colorYellow)
		w, _ = tinyfont.LineWidth(&freesans.Bold18pt7b, feedbackText)
		tinyfont.WriteLine(&display, &freesans.Bold18pt7b, (displayWidth-int16(w))/2, 125, feedbackText, feedbackColor)
		tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 80, 165, scoreText, colorWhite)
	} else {
		w, _ := tinyfont.LineWidth(&freesans.Bold12pt7b, timeText)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, (displayWidth-int16(w))/2, 45, timeText, colorYellow)
		w, _ = tinyfont.LineWidth(&freesans.Bold9pt7b, feedbackText)
		tinyfont.WriteLine(&display, &freesans.Bold9pt7b, (displayWidth-int16(w))/2, 70, feedbackText, feedbackColor)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 95, scoreText, colorWhite)
	}
}

func (r *ReflexGame) showFinalScore() {
	display.FillScreen(colorBlack)

	scoreText := "Final: " + strconv.Itoa(r.score)
	bestText := "Best: " + strconv.FormatInt(r.bestTime, 10) + " ms"

	avgTime := int64(5000-r.score) / 5
	var rating string
	switch {
	case avgTime < 300:
		rating = "LIGHTNING FAST!"
	case avgTime < 450:
		rating = "SUPER QUICK!"
	case avgTime < 600:
		rating = "PRETTY GOOD!"
	default:
		rating = "KEEP PRACTICING!"
	}

	if displayHeight >= 160 {
		tinyfont.WriteLine(&display, &freesans.Bold18pt7b, 40, 50, "GAME OVER!", colorYellow)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 40, 100, scoreText, colorWhite)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 40, 130, bestText, colorMellonGreen)
		tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 180, rating, colorOrange)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 40, displayHeight-15, "Press A to retry", colorText)
	} else {
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 25, "GAME OVER!", colorYellow)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 55, scoreText, colorWhite)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 70, bestText, colorMellonGreen)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 90, rating, colorOrange)
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, displayHeight-15, "Press A to retry", colorText)
	}
}

// drawArrow draws a direction arrow centered at (cx, cy).
// dir: 0=Up, 1=Down, 2=Left, 3=Right
func (r *ReflexGame) drawArrow(cx, cy int16, c color.RGBA, dir int) {
	half := int16(displayHeight) / 4   // distance from center to tip/tail
	headW := int16(displayHeight) / 8  // half-width of arrowhead base
	shaftW := int16(displayHeight) / 20 // half-width of shaft

	switch dir {
	case 0: // Up: tip at top, shaft extends down
		tinydraw.FilledTriangle(&display, cx, cy-half, cx-headW, cy, cx+headW, cy, c)
		display.FillRectangle(cx-shaftW, cy, 2*shaftW, half, c)
	case 1: // Down: tip at bottom, shaft extends up
		display.FillRectangle(cx-shaftW, cy-half, 2*shaftW, half, c)
		tinydraw.FilledTriangle(&display, cx, cy+half, cx-headW, cy, cx+headW, cy, c)
	case 2: // Left: tip at left, shaft extends right
		tinydraw.FilledTriangle(&display, cx-half, cy, cx, cy-headW, cx, cy+headW, c)
		display.FillRectangle(cx, cy-shaftW, half, 2*shaftW, c)
	case 3: // Right: tip at right, shaft extends left
		display.FillRectangle(cx-half, cy-shaftW, half, 2*shaftW, c)
		tinydraw.FilledTriangle(&display, cx+half, cy, cx, cy-headW, cx, cy+headW, c)
	}
}

func (r *ReflexGame) feedbackLEDs() {
	ledColors := make([]color.RGBA, numLEDs)
	var c color.RGBA
	switch {
	case r.reactionMs < 250:
		c = color.RGBA{0, 255, 0, 255}
	case r.reactionMs < 400:
		c = color.RGBA{0, 200, 0, 255}
	case r.reactionMs < 550:
		c = color.RGBA{150, 200, 0, 255}
	case r.reactionMs < 750:
		c = colorOrange
	default:
		c = colorRed
	}
	for i := range ledColors {
		ledColors[i] = c
	}
	leds.WriteColors(ledColors)
}

func (r *ReflexGame) clearLEDs() {
	ledColors := make([]color.RGBA, numLEDs)
	for i := range ledColors {
		ledColors[i] = colorBlackLED
	}
	leds.WriteColors(ledColors)
}
