package glossary

import (
	"image/color"
	"math/rand"
	"sync"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/imdraw"
)

// -------------------------------------------------------------------------
// Reusable modified starfiled
// - Original: "github.com/faiface/pixel-examples/community/starfield"
// - Encapsulated by nanitefactory

// -------------------------------------------------------------------------
// Galaxy

type star struct {
	Pos pixel.Vec  // x, y
	Z   float64    // z
	P   float64    // prev z
	C   color.RGBA // color
}

// Galaxy is an imd of stars.
type Galaxy struct {
	imd   *imdraw.IMDraw // shared variable
	mutex sync.Mutex     // synchronize
	//
	width  float64
	height float64
	speed  float64
	stars  [1024]*star
}

// NewGalaxy is a constructor.
func NewGalaxy(_width, _height, _speed float64) *Galaxy {
	return &Galaxy{
		width:  _width,
		height: _height,
		speed:  _speed,
	}
}

// Speed is a getter.
// Pass lock by value warning from (galaxy Galaxy) should be ignored,
// because a galaxy here is just passed as a read only argument.
func (galaxy Galaxy) Speed() float64 {
	return galaxy.speed
}

// SetSpeed is a setter.
func (galaxy *Galaxy) SetSpeed(_speed float64) {
	galaxy.speed = _speed
}

// Draw guarantees the thread safety, though it's not a necessary condition.
// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
func (galaxy *Galaxy) Draw(t pixel.Target) {
	galaxy.mutex.Lock()
	defer galaxy.mutex.Unlock()

	if galaxy.imd == nil { // isInvisible set to true.
		return // An empty image is drawn.
	}

	galaxy.imd.Draw(t)
}

// Update animates a galaxy.
func (galaxy *Galaxy) Update(dt float64) {
	// random()
	random := func(min, max float64) float64 {
		return rand.Float64()*(max-min) + min
	}

	// newStar()
	newStar := func() *star {
		starColors := []color.RGBA{
			color.RGBA{157, 180, 255, 255},
			color.RGBA{162, 185, 255, 255},
			color.RGBA{167, 188, 255, 255},
			color.RGBA{170, 191, 255, 255},
			color.RGBA{175, 195, 255, 255},
			color.RGBA{186, 204, 255, 255},
			color.RGBA{192, 209, 255, 255},
			color.RGBA{202, 216, 255, 255},
			color.RGBA{228, 232, 255, 255},
			color.RGBA{237, 238, 255, 255},
			color.RGBA{251, 248, 255, 255},
			color.RGBA{255, 249, 249, 255},
			color.RGBA{255, 245, 236, 255},
			color.RGBA{255, 244, 232, 255},
			color.RGBA{255, 241, 223, 255},
			color.RGBA{255, 235, 209, 255},
			color.RGBA{255, 215, 174, 255},
			color.RGBA{255, 198, 144, 255},
			color.RGBA{255, 190, 127, 255},
			color.RGBA{255, 187, 123, 255},
			color.RGBA{255, 187, 123, 255},
		} // Colors based on stellar types listed at // http://www.vendian.org/mncharity/dir3/starcolor/
		return &star{
			Pos: pixel.V(random(-galaxy.width, galaxy.width), random(-galaxy.height, galaxy.height)),
			Z:   random(0, galaxy.width),
			P:   0,
			C:   starColors[rand.Intn(len(starColors))],
		}
	}

	// lock before imdraw update
	galaxy.mutex.Lock()
	defer galaxy.mutex.Unlock()

	// imdraw (a state machine)
	if galaxy.imd == nil { // lazy creation
		galaxy.imd = imdraw.New(nil)
		galaxy.imd.SetMatrix(pixel.IM.Moved(pixel.V(galaxy.width/2, galaxy.height/2)))
	}
	imd := galaxy.imd
	imd.Clear()
	imd.Precision = 7

	// now update all stars in this galaxy
	for i, s := range galaxy.stars {
		if s == nil {
			galaxy.stars[i] = newStar()
			s = galaxy.stars[i]
		}

		scale := func(unscaledNum, min, max, minAllowed, maxAllowed float64) float64 {
			return (maxAllowed-minAllowed)*(unscaledNum-min)/(max-min) + minAllowed
		}

		s.P = s.Z
		s.Z -= dt * galaxy.speed

		if s.Z < 0 {
			s.Pos.X = random(-galaxy.width, galaxy.width)
			s.Pos.Y = random(-galaxy.height, galaxy.height)
			s.Z = galaxy.width
			s.P = s.Z
		}

		p := pixel.V(
			scale(s.Pos.X/s.Z, 0, 1, 0, galaxy.width),
			scale(s.Pos.Y/s.Z, 0, 1, 0, galaxy.height),
		)

		o := pixel.V(
			scale(s.Pos.X/s.P, 0, 1, 0, galaxy.width),
			scale(s.Pos.Y/s.P, 0, 1, 0, galaxy.height),
		)

		r := scale(s.Z, 0, galaxy.width, 11, 0)

		galaxy.imd.Color = s.C
		if p.Sub(o).Len() > 6 {
			galaxy.imd.Push(p, o)
			galaxy.imd.Line(r)
		}
		galaxy.imd.Push(p)
		galaxy.imd.Circle(r, 0)
	}
}
