package main

import (
	"image/color"
	"log"
	"math"
	"math/rand"
	"time"

	"github.com/hajimehoshi/ebiten/v2"
)

const (
	screenWidth  = 800
	screenHeight = 600
	gravity      = 0.08
	maxSparks    = 150
)

// A reusable 2x2 white square used as the base graphic for all particles
var particleImage *ebiten.Image

func init() {
	particleImage = ebiten.NewImage(3, 3)
	particleImage.Fill(color.White)
	
	// Seed random number generator for Go versions < 1.20
	rand.Seed(time.Now().UnixNano()) 
}

// Particle represents the firework shell going up, or a single spark from an explosion
type Particle struct {
	x, y    float64
	vx, vy  float64
	life    int
	maxLife int
	color   color.RGBA
	size    float64
}

// Firework manages the life cycle of a single firework event
type Firework struct {
	shell    *Particle
	sparks   []*Particle
	exploded bool
}

// App implements the ebiten.Game interface
type App struct {
	fireworks []*Firework
}

// Update proceeds the game state by one tick (default 60 FPS)
func (a *App) Update() error {
	// 1. Randomly launch new fireworks (throttle max concurrent fireworks)
	if rand.Float64() < 0.04 && len(a.fireworks) < 8 {
		a.fireworks = append(a.fireworks, newFirework())
	}

	// 2. Update physics
	var activeFireworks []*Firework

	for _, fw := range a.fireworks {
		if !fw.exploded {
			// Update shell going up
			fw.shell.y += fw.shell.vy
			fw.shell.x += fw.shell.vx
			fw.shell.vy += gravity

			// Explode when vertical velocity reaches peak, OR if it gets too close to the top edge
			if fw.shell.vy >= -0.5 || fw.shell.y <= 40 {
				fw.explode()
			}
			activeFireworks = append(activeFireworks, fw)
		} else {
			// Update sparks
			var activeSparks []*Particle
			for _, s := range fw.sparks {
				s.x += s.vx
				s.y += s.vy
				s.vy += gravity
				s.vx *= 0.98 // Air drag
				s.vy *= 0.98 // Air drag
				s.life--

				if s.life > 0 {
					activeSparks = append(activeSparks, s)
				}
			}
			fw.sparks = activeSparks

			// Keep firework alive if it still has visible sparks
			if len(fw.sparks) > 0 {
				activeFireworks = append(activeFireworks, fw)
			}
		}
	}

	a.fireworks = activeFireworks
	return nil
}

// Draw renders the game screen
func (a *App) Draw(screen *ebiten.Image) {
	// Background clears to black automatically in Ebiten
	
	for _, fw := range a.fireworks {
		if !fw.exploded {
			drawParticle(screen, fw.shell)
		} else {
			for _, s := range fw.sparks {
				drawParticle(screen, s)
			}
		}
	}
}

// Layout sets the internal screen size
func (a *App) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}

// Helper Functions

func newFirework() *Firework {
	c := randomBrightColor()
	startX := float64(rand.Intn(screenWidth-100) + 50)
	
	return &Firework{
		shell: &Particle{
			x:     startX,
			y:     screenHeight,
			vx:    (rand.Float64() - 0.5) * 2, // Slight wind drift
			vy:    -(rand.Float64()*3 + 5.5),
			color: c,
			size:  1.5,
			life:  1,
			maxLife: 1,
		},
		exploded: false,
	}
}

func (fw *Firework) explode() {
	fw.exploded = true
	numSparks := rand.Intn(maxSparks/2) + maxSparks/2

	for i := 0; i < numSparks; i++ {
		angle := rand.Float64() * 2 * math.Pi
		velocity := rand.Float64() * 6 // Explosion force
		
		life := rand.Intn(40) + 40

		fw.sparks = append(fw.sparks, &Particle{
			x:       fw.shell.x,
			y:       fw.shell.y,
			vx:      math.Cos(angle) * velocity,
			vy:      math.Sin(angle) * velocity,
			life:    life,
			maxLife: life,
			color:   fw.shell.color,
			size:    1.0,
		})
	}
}

func drawParticle(screen *ebiten.Image, p *Particle) {
	op := &ebiten.DrawImageOptions{}
	
	// Scale the particle
	op.GeoM.Scale(p.size, p.size)
	
	// Translate to x,y coordinates
	op.GeoM.Translate(p.x, p.y)
	
	// Calculate alpha for a smooth fade out
	alpha := float32(p.life) / float32(p.maxLife)
	
	// Apply color and alpha
	op.ColorScale.ScaleWithColor(p.color)
	op.ColorScale.ScaleAlpha(alpha)

	screen.DrawImage(particleImage, op)
}

func randomBrightColor() color.RGBA {
	colors := []color.RGBA{
		{255, 50, 50, 255},   // Red
		{50, 255, 50, 255},   // Green
		{50, 150, 255, 255},  // Blue
		{255, 255, 50, 255},  // Yellow
		{255, 50, 255, 255},  // Magenta
		{50, 255, 255, 255},  // Cyan
		{255, 150, 50, 255},  // Orange
	}
	return colors[rand.Intn(len(colors))]
}

func main() {
	ebiten.SetWindowSize(screenWidth, screenHeight)
	ebiten.SetWindowTitle("Ebiten Fireworks")
	
	app := &App{}
	
	if err := ebiten.RunGame(app); err != nil {
		log.Fatal(err)
	}
}