package splashmenu

import (
	"cosmos/diego/groovy"
	splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
	"fmt"
	"image/color"
	"math"
	"strconv"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
	"github.com/hajimehoshi/ebiten/v2/text"
	"github.com/hajimehoshi/ebiten/v2/vector"
)

const (
	BGCOLOR       = 0xCCCCCC
	LINECOLOR     = 0x8a8a8a
	SQUARECOLOR   = 0x199a19
	RADIUSCOLOR   = 0xFF0000
	AXISCOLOR     = 0xf100c1
	SEMIAXISCOLOR = 0x178fc5 //0x1e1e1e //0xdccdFF
)

type ColorData struct {
	selectionColor color.RGBA
	squareColor    color.RGBA
	radiusColor    color.RGBA
	axisColor      color.RGBA
	semiAxisColor  color.RGBA
}

type Coords struct {
	X float32
	Y float32
}

func (c Coords) DistanceFrom(pos Coords) float32 {
	return float32(math.Sqrt(float64((pos.X-c.X)*(pos.X-c.X) + (pos.Y-c.Y)*(pos.Y-c.Y))))
}

func (c Coords) SignedDistanceFrom(pos Coords) Coords {
	return Coords{X: pos.X - c.X, Y: pos.Y - c.Y}
}

func (c Coords) Magnitude() float32 {
	return float32(math.Sqrt(float64(c.X*c.X + c.Y*c.Y)))
}

type Rays struct {
	events     map[groovy.SceneEvent]func()
	increment  int
	Dimensions groovy.Area

	coordinates Coords
	selection   Coords
	interacting bool

	bgcolor   color.RGBA
	colorData ColorData
}

func hexToRGBA(hexcolor int) color.RGBA {
	return color.RGBA{
		R: uint8(hexcolor >> 0x10 & 0xff),
		G: uint8(hexcolor >> 0x08 & 0xff),
		B: uint8(hexcolor >> 0x00 & 0xff),
		A: 0xff,
	}
}

func NewRays() Rays {
	r := Rays{
		events:      make(map[groovy.SceneEvent]func()),
		increment:   0,
		bgcolor:     hexToRGBA(BGCOLOR),
		coordinates: Coords{X: 0, Y: 0},
		selection:   Coords{X: 0, Y: 0},
	}

	r.colorData.selectionColor = hexToRGBA(LINECOLOR)
	r.colorData.squareColor = hexToRGBA(SQUARECOLOR)
	r.colorData.radiusColor = hexToRGBA(RADIUSCOLOR)
	r.colorData.axisColor = hexToRGBA(AXISCOLOR)
	r.colorData.semiAxisColor = hexToRGBA(SEMIAXISCOLOR)

	return r
}

func gimmeFloatCursor() (float32, float32) {
	x, y := ebiten.CursorPosition()

	return float32(x), float32(y)

}

func (r *Rays) Update() error {
	r.increment++

	x, y := gimmeFloatCursor()
	mouseCoords := Coords{X: x, Y: y}
	newRad := mouseCoords.DistanceFrom(r.selection)

	xMin := r.selection.X - newRad
	xMax := r.selection.X + newRad
	yMin := r.selection.Y - newRad
	yMax := r.selection.Y + newRad

	if 0 <= xMin && xMax < float32(r.Dimensions.Width) &&
		0 <= yMin && yMax < float32(r.Dimensions.Height) {
		r.coordinates = mouseCoords
	}

	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
		r.selection = mouseCoords
		r.interacting = true
	}

	if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
		//r.selection = r.coordinates
		r.interacting = false
	}

	if !r.interacting {
		r.selection = r.coordinates
	}

	return nil
}

func (r *Rays) Draw(screen *ebiten.Image) {
	screen.Fill(r.bgcolor)
	r.DrawTheAngleBits(screen)

	x, y := gimmeFloatCursor()

	text.Draw(screen, fmt.Sprintf("Mouse x, y: (%.1f, %.1f)", x, y), splashmenu.SplashFont.Menu, 20, 20, color.White)
	text.Draw(screen, fmt.Sprintf("Selection x, y: (%.1f, %.1f)", r.selection.X, r.selection.Y), splashmenu.SplashFont.Menu, 20, 40, color.White)
	text.Draw(screen, fmt.Sprintf("Radius: %.1f", r.GetPrimaryRadius()), splashmenu.SplashFont.Menu, 20, 60, color.White)
	text.Draw(screen, fmt.Sprintf("Interaction: %s", strconv.FormatBool(r.interacting)), splashmenu.SplashFont.Menu, 20, 80, color.White)

}

func (r *Rays) SetDimensions(a groovy.Area) {
	r.Dimensions = a
}

func (r *Rays) SetEventHandler(event groovy.SceneEvent, f func()) {
	r.events[event] = f
}

func (r *Rays) DrawTheAngleBits(screen *ebiten.Image) {

	if r.interacting {

		r.DrawTrigElementAxis(screen, r.selection, r.coordinates, r.colorData)
		r.DrawTrigElement(screen, r.selection, r.coordinates, r.colorData)
		r.DrawCircleElement(screen, r.selection, r.coordinates, r.colorData)

		dist := r.coordinates.SignedDistanceFrom(r.selection)
		angle := math.Atan2(float64(dist.Y), float64(dist.X))

		for i := 0; i < 4; i++ {
			var xSpot Coords
			xSpot.X = r.selection.X + float32(math.Cos(angle))*dist.Magnitude()*float32(i)
			xSpot.Y = r.selection.Y + float32(math.Sin(angle))*dist.Magnitude()*float32(i)

			var nSpot Coords

			//our limit is pi/2, but we want to stretch this out over 30 frames
			u := (math.Pi / 2) * 30

			v := float64(r.increment)

			//our angular frequency is simply the remainder of our current frame count / our limit
			w := (v/u - math.Floor(v/u))

			//now scale the circles
			nSpot.X = xSpot.X + float32(math.Cos(angle))*dist.Magnitude()*float32(math.Sin(w))
			nSpot.Y = xSpot.Y + float32(math.Sin(angle))*dist.Magnitude()*float32(math.Sin(w))
			r.DrawCircleElement(screen, xSpot, nSpot, r.colorData)
		}

	}

}

// Returns the primary circle radius value of the current scene
func (r *Rays) GetPrimaryRadius() float32 {
	return r.coordinates.DistanceFrom(r.selection)
}

// draws circle
func (r *Rays) DrawCircleElement(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
	dist := dest.SignedDistanceFrom(origin)
	vector.StrokeCircle(img, origin.X, origin.Y, dist.Magnitude(), 2, colorData.selectionColor, true)
}

// draws circle trig relationships with the x/y axis of the circles origin and a radius indicator
func (r *Rays) DrawTrigElement(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
	dist := dest.SignedDistanceFrom(origin)

	//circle and radius marker
	vector.StrokeCircle(img, origin.X, origin.Y, dist.Magnitude(), 2, colorData.selectionColor, true)
	vector.StrokeLine(img, origin.X, origin.Y, dest.X, dest.Y, 1, colorData.radiusColor, true)

	//draw rectangle from origin to dest
	vector.StrokeRect(img, origin.X, origin.Y, dest.X-origin.X, dest.Y-origin.Y, 1, colorData.squareColor, true)

	//compute directionality (1)
	dirX := dist.X / float32(math.Abs(float64(dist.X)))
	dirY := dist.Y / float32(math.Abs(float64(dist.Y)))

	//semi-axis within circle
	vector.StrokeLine(img, origin.X, origin.Y, origin.X-dirX*dist.Magnitude(), origin.Y, 1, colorData.axisColor, true)
	vector.StrokeLine(img, origin.X, origin.Y, origin.X, origin.Y-dirY*dist.Magnitude(), 1, colorData.axisColor, true)

}

func (r *Rays) DrawTrigElementAxis(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
	dist := dest.SignedDistanceFrom(origin)

	//origin to BORDER
	//	case 1. origin.x = destination.x, means intercept should be vertical line
	//	case 2. we have a slope
	if dist.X == 0 {
		vector.StrokeLine(img, origin.X, 0, origin.X, float32(r.Dimensions.Height), 1, colorData.semiAxisColor, true)
	} else {
		m := dist.Y / dist.X

		/*
				y - y1 = m(x - x1)
				y = m(x - x1) + y1
				x = (y - y1)/m + x1
			find the intercepts:
					x = Dimensions.Width
					x = 0

					y = 0
					y = Dimensions.Height
		*/

		//find our y intercept for the start of our segment
		var xZero Coords
		xZero.X = 0
		xZero.Y = m*(xZero.X-origin.X) + origin.Y

		//in the even our point lies outside the visible screen boundaries,
		//we can save some pixel operations by restricting to the bounds
		if xZero.Y > float32(r.Dimensions.Height) {
			xZero.Y = float32(r.Dimensions.Height)
			xZero.X = (xZero.Y-origin.Y)/m + origin.X
		}

		if xZero.Y < 0 {
			xZero.Y = 0
			xZero.X = (xZero.Y-origin.Y)/m + origin.X
		}

		var xMax Coords
		xMax.X = float32(r.Dimensions.Width)
		xMax.Y = m*(xMax.X-origin.X) + origin.Y

		if xMax.Y > float32(r.Dimensions.Height) {
			xMax.Y = float32(r.Dimensions.Height)
			xMax.X = (xMax.Y-origin.Y)/m + origin.X
		}

		if xMax.Y < 0 {
			xMax.Y = 0
			xMax.X = (xMax.Y-origin.Y)/m + origin.X
		}

		var slashLineOrigin Coords
		var slashLineDest Coords

		slashLineOrigin = xZero
		slashLineDest = xMax

		var xSpot Coords
		angle := math.Atan2(float64(dist.Y), float64(dist.X))
		xSpot.X = origin.X + float32(math.Cos(angle))*dist.Magnitude()
		xSpot.Y = origin.Y + float32(math.Sin(angle))*dist.Magnitude()

		vector.StrokeLine(img, slashLineOrigin.X, slashLineOrigin.Y, slashLineDest.X, slashLineDest.Y, 1, colorData.semiAxisColor, true)
		text.Draw(img, fmt.Sprintf("%0.1f : (%0.1f, %0.1f) (%0.1f, %0.1f)", m, slashLineOrigin.X, slashLineOrigin.Y, slashLineDest.X, slashLineDest.Y), splashmenu.SplashFont.Menu, 20, 100, color.White)

		//text.Draw(img, "x", splashmenu.SplashFont.Title, int(xSpot.X), int(xSpot.Y), color.Black)
		vector.DrawFilledCircle(img, xSpot.X, xSpot.Y, 5, color.Black, true)
		vector.DrawFilledCircle(img, origin.X, origin.Y, 5, colorData.radiusColor, true)

	}
}
