package screens

import (
	"fmt"
	"image"
	"image/color"
	"math"
	"math/rand/v2"
	"mover/assets"
	"mover/elements"
	"mover/fonts"
	"mover/gamedata"

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

const (
	MOVER_WIDTH  = 48
	MOVER_HEIGHT = 48
	sampleRate   = 44100
)

type Game struct {
	events            map[ScreenManagerEvent]func()
	background        *ebiten.Image
	collisionMask     *ebiten.Image
	projectileMask    *ebiten.Image
	heroCollisionMask *ebiten.Image
	heroCollisionCpy  *ebiten.Image

	dimensions       gamedata.Area
	Pos              gamedata.Coordinates
	Paused           bool
	initialized      bool
	gameover         bool
	reset            bool
	musicInitialized bool
	runtime          float64
	hero             *elements.Hero
	projectiles      map[int]*elements.Projectile
	explosion        *elements.Explosion
	audioplayer      *audio.Player

	score   int
	counter int
	timer   int
	targets []*elements.Mover
	boss    *elements.Boss
}

var (
	audioContext = audio.NewContext(sampleRate)
)

func NewGame() *Game {
	g := &Game{
		events:           make(map[ScreenManagerEvent]func()),
		musicInitialized: false,
		boss:             elements.NewBoss(),
	}

	return g
}

func (g *Game) Initialize() {

	if !g.musicInitialized {
		s := audio.NewInfiniteLoop(assets.SoundBank[assets.MainLoop], assets.SoundBank[assets.MainLoop].Length())
		g.audioplayer, _ = audioContext.NewPlayer(s)
		g.audioplayer.Play()
		g.musicInitialized = true
	}

	origin := gamedata.Coordinates{X: 640 / 2, Y: 480 / 2}

	g.ConstructBackground()
	g.hero = elements.NewHero()
	g.hero.SetOrigin(origin)
	g.hero.ToggleRotate()

	g.gameover = false

	g.collisionMask = ebiten.NewImage(g.dimensions.Width, g.dimensions.Height)
	g.projectileMask = ebiten.NewImage(g.dimensions.Width, g.dimensions.Height)
	g.heroCollisionMask = ebiten.NewImage(MOVER_WIDTH, MOVER_HEIGHT)
	g.heroCollisionCpy = ebiten.NewImage(MOVER_WIDTH, MOVER_HEIGHT)

	g.explosion = elements.NewExplosion()
	g.explosion.SetOrigin(origin)
	g.score = 0
	g.reset = false

	//clean up all targets
	for j := 0; j < len(g.targets); j++ {
		g.targets[j] = nil
	}
	g.targets = g.targets[:0]

	g.score = 0
	g.counter = 0
	g.timer = 0
	g.runtime = 0.

	g.boss.Reset()

	g.projectiles = make(map[int]*elements.Projectile)
	g.initialized = true
	g.reset = false

}

func (g *Game) Update() error {
	if !g.initialized || g.reset {
		g.Initialize()
	} else {
		g.StepGame()
	}

	g.timer++

	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {

	screen.Clear()

	if g.initialized {
		screen.DrawImage(g.background, nil)

		g.hero.Draw()

		op := &ebiten.DrawImageOptions{}

		if !g.gameover {
			g.runtime = float64(g.counter) / 60.
		}

		op.GeoM.Translate(-MOVER_WIDTH/2, -MOVER_HEIGHT/2)
		op.GeoM.Translate(g.hero.Pos.X, g.hero.Pos.Y)
		screen.DrawImage(g.hero.Sprite, op)

		op.GeoM.Reset()
		op.GeoM.Translate(0, -16)
		op.GeoM.Rotate(g.hero.Angle)
		op.GeoM.Translate(g.hero.Pos.X, g.hero.Pos.Y)
		screen.DrawImage(assets.ImageBank[assets.Weapon], op)

		//secondary/upgraded weapon sprite; in testing proves sort of distracting
		/*
			if g.hero.Upgrade {
				op.GeoM.Reset()
				op.GeoM.Translate(-16, -16)
				op.GeoM.Scale(0.75, 0.75)
				op.GeoM.Translate(16, 0)
				op.GeoM.Rotate(g.hero.Angle + math.Pi)
				op.GeoM.Translate(g.hero.Pos.X, g.hero.Pos.Y)
				screen.DrawImage(weaponImage, op)
			}
		*/

		//draw shadows--------------------------------------------------------------
		for _, target := range g.targets {
			if target.Action < elements.MoverActionExploding {
				op := &ebiten.DrawImageOptions{}
				op.GeoM.Translate(target.Pos.X-10, target.Pos.Y+10)
				screen.DrawImage(assets.ImageBank[assets.FlyEyeShadow], op)
			}
		}

		if g.boss.Spawned && g.boss.Action < elements.MoverActionExploding {
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Scale(4, 2)
			op.GeoM.Translate(g.boss.Pos.X-96/2, g.boss.Pos.Y+96/2-10)
			screen.DrawImage(assets.ImageBank[assets.FlyEyeShadow], op)
		}

		//draw enemies--------------------------------------------------------------
		for _, target := range g.targets {
			target.Draw()

			op.GeoM.Reset()
			op.GeoM.Translate(-MOVER_WIDTH/2, -MOVER_HEIGHT/2)
			op.GeoM.Rotate(target.Angle)
			op.GeoM.Translate(target.Pos.X, target.Pos.Y)
			screen.DrawImage(target.Sprite, op)
		}

		if g.boss.Spawned {
			g.boss.Draw()
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Translate(-MOVER_WIDTH, -MOVER_HEIGHT)
			op.GeoM.Translate(g.boss.Pos.X, g.boss.Pos.Y)
			screen.DrawImage(g.boss.Sprite, op)

			//text.Draw(screen, fmt.Sprintf("%d", g.boss.Health), fonts.SurviveFont.Arcade, 100, 50, color.White)

			//boss health bar
			x0 := g.boss.Pos.X - 96
			y0 := g.boss.Pos.Y - 60
			vector.DrawFilledRect(screen, float32(x0), float32(y0), 204, 12, color.Black, true)
			vector.DrawFilledRect(screen, float32(x0+2), float32(y0+2), float32(g.boss.Health)*2, 8, color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}, true)
		}

		g.projectileMask.Clear()

		for _, p := range g.projectiles {
			vector.DrawFilledCircle(g.projectileMask, float32(p.Pos.X), float32(p.Pos.Y), 3, color.White, true)
		}

		screen.DrawImage(g.projectileMask, nil)

		/*
			op.GeoM.Reset()
			op.GeoM.Scale(0.25, 0.25)
			screen.DrawImage(g.collisionMask, op)
		*/

		vector.StrokeCircle(screen, float32(g.explosion.Origin.X), float32(g.explosion.Origin.Y), float32(g.explosion.Radius), 3, color.White, true)

		s := fmt.Sprintf("%02.3f", g.runtime)
		if !g.gameover {
			text.Draw(screen, "TIME: "+s, fonts.SurviveFont.Arcade, 640/2-250, 25, color.White)
			text.Draw(screen, fmt.Sprintf("SCORE: %d", g.score*10), fonts.SurviveFont.Arcade, 640/2+100, 25, color.White)
		} else {
			if (g.counter/30)%2 == 0 {
				text.Draw(screen, "TIME: "+s, fonts.SurviveFont.Arcade, 640/2-250, 25, color.White)
				text.Draw(screen, fmt.Sprintf("SCORE: %d", g.score*10), fonts.SurviveFont.Arcade, 640/2+100, 25, color.White)
			}

			text.Draw(screen, "PRESS START TO TRY AGAIN", fonts.SurviveFont.Arcade, 640/2-150, 480/2, color.White)
		}
	}
}

func (g *Game) Layout(width, height int) (int, int) {
	return g.dimensions.Width, g.dimensions.Height
}

func (g *Game) CleanupTargets() {
	// remove dead targets by 1) iterating over all targets
	i := 0
	for _, target := range g.targets {
		//moving valid targets to the front of the slice
		if target.Action < elements.MoverActionDead {
			g.targets[i] = target
			i++
		}
	}

	//then culling the last elements of the slice
	if len(g.targets)-i > 0 {
		//	fmt.Printf("Removing %d elements\n", len(g.targets)-i)
		g.score += len(g.targets) - i
	}

	for j := i; j < len(g.targets); j++ {
		g.targets[j] = nil
	}
	g.targets = g.targets[:i]
}

func (g *Game) StepGame() {

	g.HandleInput()

	if !g.Paused {

		if !g.audioplayer.IsPlaying() {
			g.audioplayer.Play()
		}

		g.hero.Update()
		g.explosion.Update()

		g.UpdateTargets()
		if g.boss.Spawned {
			g.UpdateBoss()
		}

		g.UpdateProjectiles()

		if !g.gameover {
			g.UpdateHeroPosition()
			//append new projectiles
			g.AppendProjectiles()

			//handle pulsewave updates
			g.HandlePulseWaveUpdate()

			if !g.boss.Spawned {
				//add new target with increasing frequency
				g.SpawnEnemies()
				if g.counter > 2000 {
					g.SpawnBoss()
				}
			}

		}

		g.CleanupTargets()
		g.counter++
	} else {
		g.audioplayer.Pause()
	}
}

func (g *Game) SpawnEnemies() {
	f := 40000 / (g.counter + 1)
	if g.counter%f == 0 {
		g.targets = append(g.targets, elements.NewMover())

		x0 := rand.Float64() * 640
		y0 := rand.Float64() * 480
		quadrant := rand.IntN(3)

		switch quadrant {
		case 0:
			g.targets[len(g.targets)-1].SetOrigin(gamedata.Coordinates{X: x0, Y: -MOVER_HEIGHT})
		case 1:
			g.targets[len(g.targets)-1].SetOrigin(gamedata.Coordinates{X: x0, Y: float64(g.dimensions.Height) + MOVER_HEIGHT})
		case 2:
			g.targets[len(g.targets)-1].SetOrigin(gamedata.Coordinates{X: -MOVER_WIDTH, Y: y0})
		case 3:
			g.targets[len(g.targets)-1].SetOrigin(gamedata.Coordinates{X: float64(g.dimensions.Width) + x0, Y: y0})
		default:
			g.targets[len(g.targets)-1].SetOrigin(gamedata.Coordinates{X: x0, Y: y0})
			fmt.Println("WTF " + string(quadrant))
		}

	}
}

func (g *Game) HandlePulseWaveUpdate() {
	if g.explosion.Active {
		if g.explosion.Radius > math.Sqrt(640*640+480*480) {
			g.explosion.ToggleActivate()
			g.explosion.Reset()
			g.ResetTargetTouches()
		}

		//check collisions
		for _, target := range g.targets {
			dx := target.Pos.X - g.hero.Pos.X
			dy := target.Pos.Y - g.hero.Pos.Y
			r := math.Sqrt(dx*dx + dy*dy)

			if r >= g.explosion.Radius-5 && r <= g.explosion.Radius+5 &&
				target.Action <= elements.MoverActionDamaged && !target.Touched {
				target.ToggleColor()
				target.Touched = true
				//target.SetHit()
			}
		}

		//check for boss
		if g.boss.Spawned {
			dx := g.boss.Pos.X - g.hero.Pos.X
			dy := g.boss.Pos.Y - g.hero.Pos.Y
			r := math.Sqrt(dx*dx + dy*dy)

			if r >= g.explosion.Radius-40 && r <= g.explosion.Radius+40 &&
				g.boss.Action <= elements.MoverActionDamaged && !g.boss.Touched {
				g.boss.ToggleColor()
				g.boss.Touched = true
				//target.SetHit()
			}
		}
	}
}

func (g *Game) UpdateProjectiles() {
	for k, p := range g.projectiles {

		p.Update()

		//cleanup projectiles at boundaries
		if p.Pos.X < -640/2 || p.Pos.X > 1.5*640 || p.Pos.Y < -480/2 || p.Pos.Y > 1.5*480 {
			p.Velocity = 0
			delete(g.projectiles, k)
		}

		//compute projectile collisions
		for _, target := range g.targets {
			//first, boundary check
			if p.Pos.X >= target.Pos.X-MOVER_WIDTH/2 && p.Pos.X <= target.Pos.X+MOVER_WIDTH/2 &&
				p.Pos.Y >= target.Pos.Y-MOVER_HEIGHT/2 && p.Pos.Y <= target.Pos.Y+MOVER_HEIGHT/2 &&
				target.Action == elements.MoverActionDamaged {
				//fmt.Println("potential collision")

				//the following computes total collisions in the image using a projectile mask that is a duplicate of what is on screen
				//there's definitely room for optimization here
				g.collisionMask.Clear()
				g.collisionMask.DrawImage(g.projectileMask, nil)

				op := &ebiten.DrawImageOptions{}
				op.GeoM.Reset()
				op.Blend = ebiten.BlendSourceIn
				op.GeoM.Translate(target.Pos.X-MOVER_WIDTH/2, target.Pos.Y-MOVER_HEIGHT/2)
				g.collisionMask.DrawImage(target.Sprite, op)

				if g.HasCollided(g.collisionMask, g.dimensions.Width*g.dimensions.Height*4) {
					//fmt.Println("pixel collision")
					delete(g.projectiles, k)
					//target.ToggleColor()
					target.SetHit()
					//target.SetOrigin(gamedata.Coordinates{X: rand.Float64() * 640, Y: rand.Float64() * 480})
					target.Hit = true

					player := audioContext.NewPlayerFromBytes(assets.TargetHit)
					player.Play()
				}
			}
		}

		//boss check: first, boundary check
		if p.Pos.X >= g.boss.Pos.X-MOVER_WIDTH && p.Pos.X <= g.boss.Pos.X+MOVER_WIDTH &&
			p.Pos.Y >= g.boss.Pos.Y-MOVER_HEIGHT && p.Pos.Y <= g.boss.Pos.Y+MOVER_HEIGHT &&
			g.boss.Action == elements.MoverActionDamaged {
			//fmt.Println("potential collision")

			//the following computes total collisions in the image using a projectile mask that is a duplicate of what is on screen
			//there's definitely room for optimization here
			g.collisionMask.Clear()
			g.collisionMask.DrawImage(g.projectileMask, nil)

			op := &ebiten.DrawImageOptions{}
			op.GeoM.Reset()
			op.Blend = ebiten.BlendSourceIn
			op.GeoM.Translate(g.boss.Pos.X-MOVER_WIDTH/2, g.boss.Pos.Y-MOVER_HEIGHT/2)
			g.collisionMask.DrawImage(g.boss.Sprite, op)

			if g.HasCollided(g.collisionMask, g.dimensions.Width*g.dimensions.Height*4) {
				//fmt.Println("pixel collision")
				delete(g.projectiles, k)
				//target.ToggleColor()
				g.boss.SetHit()
				//target.SetOrigin(gamedata.Coordinates{X: rand.Float64() * 640, Y: rand.Float64() * 480})
				//g.boss.Hit = true

				player := audioContext.NewPlayerFromBytes(assets.TargetHit)
				player.Play()
			}
		}
	}

}

func (g *Game) UpdateTargets() {
	for _, target := range g.targets {

		if target.Action == elements.MoverActionExploding && !target.SplodeInitiated {
			player := audioContext.NewPlayerFromBytes(assets.Splode)
			player.Play()
			target.SplodeInitiated = true
		}

		if !target.Hit && g.hero.Action < elements.HeroActionDying {
			dx := g.hero.Pos.X - target.Pos.X
			dy := g.hero.Pos.Y - target.Pos.Y
			angle := math.Atan2(dy, dx)

			//maxspeed := (float64(g.counter) + 1.) / 1000.
			maxspeed := 2.9
			target.Pos.X += maxspeed * math.Cos(angle)
			target.Pos.Y += maxspeed * math.Sin(angle)
		}

		//compute collision with hero
		if g.hero.Pos.X >= target.Pos.X-MOVER_WIDTH/2 && g.hero.Pos.X <= target.Pos.X+MOVER_WIDTH/2 &&
			g.hero.Pos.Y >= target.Pos.Y-MOVER_HEIGHT/2 && g.hero.Pos.Y <= target.Pos.Y+MOVER_HEIGHT/2 &&
			target.Action < elements.MoverActionDying && g.hero.Action < elements.HeroActionDying {
			g.heroCollisionMask.Clear()
			g.heroCollisionMask.DrawImage(g.hero.Sprite, nil)

			op := &ebiten.DrawImageOptions{}
			op.GeoM.Reset()
			op.Blend = ebiten.BlendSourceIn
			op.GeoM.Translate((g.hero.Pos.X-target.Pos.X)-MOVER_WIDTH/2, (g.hero.Pos.Y-target.Pos.Y)-MOVER_HEIGHT/2)
			g.heroCollisionMask.DrawImage(target.Sprite, op)

			if g.HasCollided(g.heroCollisionMask, MOVER_HEIGHT*MOVER_HEIGHT*4) {
				g.hero.SetHit()
				g.gameover = true

				player := audioContext.NewPlayerFromBytes(assets.HeroDeath)
				player.Play()
			}
		}

		target.Update()
	}
}

func (g *Game) ResetTargetTouches() {
	for _, t := range g.targets {
		t.Touched = false
	}
	g.boss.Touched = false
}

func (g *Game) AppendProjectiles() {
	if g.counter%14 == 0 && ebiten.IsStandardGamepadButtonPressed(0, ebiten.StandardGamepadButtonFrontBottomRight) {

		g.projectiles[g.counter] = elements.NewProjectile(gamedata.Coordinates{X: g.hero.Pos.X, Y: g.hero.Pos.Y}, g.hero.Angle, 5.)

		if g.hero.Upgrade {
			g.projectiles[g.counter+1] = elements.NewProjectile(gamedata.Coordinates{X: g.hero.Pos.X, Y: g.hero.Pos.Y}, g.hero.Angle+math.Pi, 5.)
		}

		player := audioContext.NewPlayerFromBytes(assets.Shot)
		player.Play()
	}
}

func (g *Game) HandleInput() {
	//if len(g.gamepadIDs) > 0 {
	if inpututil.IsStandardGamepadButtonJustPressed(0, ebiten.StandardGamepadButtonRightStick) {
		if !g.explosion.Active && !g.gameover {
			g.explosion.SetOrigin(g.hero.Pos)
			g.explosion.Reset()
			g.explosion.ToggleActivate()
			player := audioContext.NewPlayerFromBytes(assets.Magic)
			player.Play()
		}
	}

	if inpututil.IsStandardGamepadButtonJustPressed(0, ebiten.StandardGamepadButtonCenterRight) {
		if g.gameover {
			g.reset = true
		} else {
			g.Paused = !g.Paused
			var player *audio.Player
			if g.Paused {
				player = audioContext.NewPlayerFromBytes(assets.PauseIn)
			} else {
				player = audioContext.NewPlayerFromBytes(assets.PauseOut)
			}
			player.Play()
		}
	}

	//account for controller sensitivity
	if !g.gameover && !g.Paused {
		xaxis := ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickHorizontal)
		yaxis := ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickVertical)

		if yaxis <= 0.09 && yaxis >= -0.09 {
			yaxis = 0
		}
		if xaxis <= 0.09 && xaxis >= -0.09 {
			xaxis = 0
		}

		inputangle := math.Atan2(yaxis, xaxis)
		g.hero.SetAngle(inputangle)
	}

	if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
		g.events[EventEndgame]()
	}
	//}
}

func (g *Game) UpdateHeroPosition() {
	//handle gamepad input
	inpx := ebiten.GamepadAxisValue(0, 0)
	inpy := ebiten.GamepadAxisValue(0, 1)

	//handle wasd input
	if ebiten.IsKeyPressed(ebiten.KeyD) {
		inpx = 1
	}
	if ebiten.IsKeyPressed(ebiten.KeyA) {
		inpx = -1
	}
	if ebiten.IsKeyPressed(ebiten.KeyS) {
		inpy = 1
	}
	if ebiten.IsKeyPressed(ebiten.KeyW) {
		inpy = -1
	}

	if inpx >= 0.15 || inpx <= -0.15 {
		g.hero.Left = inpx < 0
		g.hero.Pos.X += inpx * 5
	}

	if inpy >= 0.15 || inpy <= -0.15 {
		g.hero.Pos.Y += inpy * 5
	}

}

func (g *Game) ConstructBackground() {
	g.background = ebiten.NewImage(g.dimensions.Width, g.dimensions.Height)
	BLOCK_SIZE := 32

	for i := 0; i < 640/BLOCK_SIZE; i++ {
		for j := 0; j < 480/BLOCK_SIZE; j++ {

			//select random tile in x and y from tileset
			idx_y := rand.IntN(256 / BLOCK_SIZE)
			idx_x := rand.IntN(256 / BLOCK_SIZE)

			x0 := BLOCK_SIZE * idx_x
			y0 := BLOCK_SIZE * idx_y
			x1 := x0 + BLOCK_SIZE
			y1 := y0 + BLOCK_SIZE

			//translate for grid element we're painting
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Translate(float64(i*BLOCK_SIZE), float64(j*BLOCK_SIZE))
			g.background.DrawImage(assets.ImageBank[assets.TileSet].SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), op)
		}
	}

	ax := float64(rand.IntN(640/BLOCK_SIZE) * BLOCK_SIZE)
	ay := float64(rand.IntN(480/BLOCK_SIZE) * BLOCK_SIZE)

	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(ax, ay)
	g.background.DrawImage(assets.ImageBank[assets.Altar], op)
}

func (g *Game) SetDimensions(a gamedata.Area) {
	g.dimensions = a
}

func (g *Game) SetEventHandler(e ScreenManagerEvent, f func()) {
	g.events[e] = f
}

func (g *Game) SpawnBoss() {
	x0 := rand.Float64() * 640
	y0 := rand.Float64() * 480
	quadrant := rand.IntN(3)

	switch quadrant {
	case 0:
		g.boss.Pos = gamedata.Coordinates{X: x0, Y: -(MOVER_HEIGHT * 2)}
	case 1:
		g.boss.Pos = gamedata.Coordinates{X: x0, Y: float64(g.dimensions.Height) + (MOVER_HEIGHT * 2)}
	case 2:
		g.boss.Pos = gamedata.Coordinates{X: -(MOVER_HEIGHT * 2), Y: y0}
	case 3:
		g.boss.Pos = gamedata.Coordinates{X: float64(g.dimensions.Width) + x0, Y: y0}
	default:
		g.boss.Pos = gamedata.Coordinates{X: x0, Y: y0}
		fmt.Println("WTF " + string(quadrant))
	}
	//g.boss.Pos = gamedata.Coordinates{X: 640 / 2, Y: 480 / 2}
	g.boss.Spawned = true
}

func (g *Game) UpdateBoss() {
	g.boss.Update()

	if g.boss.Action == elements.MoverActionExploding && !g.boss.SplodeInitiated {
		g.score += 10
		player := audioContext.NewPlayerFromBytes(assets.Splode)
		player.Play()
		g.boss.SplodeInitiated = true
	}
	/*
		if g.boss.Action >= elements.MoverActionDying {
			g.boss.Spawned = false
		}*/

	if g.boss.Action >= elements.MoverActionDead {
		g.boss.Pos = gamedata.Coordinates{X: -96, Y: -96}
	}

	if g.boss.Action < elements.MoverActionDying {
		dx := g.hero.Pos.X - g.boss.Pos.X
		dy := g.hero.Pos.Y - g.boss.Pos.Y

		g.boss.Right = dx/48 > 0

		g.boss.Pos = gamedata.Coordinates{
			X: g.boss.Pos.X + dx/48,
			Y: g.boss.Pos.Y + dy/48,
		}
	}

	//compute collision with hero
	if g.hero.Pos.X >= g.boss.Pos.X-MOVER_WIDTH && g.hero.Pos.X <= g.boss.Pos.X+MOVER_WIDTH &&
		g.hero.Pos.Y >= g.boss.Pos.Y-MOVER_HEIGHT && g.hero.Pos.Y <= g.boss.Pos.Y+MOVER_HEIGHT &&
		g.boss.Action < elements.MoverActionDying && g.hero.Action < elements.HeroActionDying {
		g.heroCollisionMask.Clear()
		g.heroCollisionMask.DrawImage(g.hero.Sprite, nil)

		op := &ebiten.DrawImageOptions{}
		op.GeoM.Reset()
		op.Blend = ebiten.BlendSourceIn
		op.GeoM.Translate((g.hero.Pos.X-g.boss.Pos.X)-MOVER_WIDTH, (g.hero.Pos.Y-g.boss.Pos.Y)-MOVER_HEIGHT)
		g.heroCollisionMask.DrawImage(g.boss.Sprite, op)

		if g.HasCollided(g.heroCollisionMask, MOVER_HEIGHT*MOVER_HEIGHT*4) {
			g.hero.SetHit()
			g.gameover = true

			player := audioContext.NewPlayerFromBytes(assets.HeroDeath)
			player.Play()
		}

	}
}

func (g *Game) HasCollided(mask *ebiten.Image, size int) bool {
	var result bool = false
	var pixels []byte = make([]byte, size)
	mask.ReadPixels(pixels)
	for i := 0; i < len(pixels); i = i + 4 {
		if pixels[i+3] != 0 {
			result = true
			break
		}
	}
	return result
}

func (g *Game) SetInputs(gamedata.GameInputs) {

}
