import com.soywiz.klock.milliseconds
import com.soywiz.korev.Key
import com.soywiz.korev.dispatch
import com.soywiz.korge.tween.get
import com.soywiz.korge.tween.tween
import com.soywiz.korge.view.*
import com.soywiz.korim.color.Colors
import com.soywiz.korio.async.launch
import com.soywiz.korma.geom.*
import com.soywiz.korma.random.get
import kotlin.random.Random

const val NUMBER_OF_ASTEROIDS = 15
const val BULLET_SIZE = 14


/**
 * A single game state with all views and updaters (gets destroyed on game restart)
 */
class Game(val stage: Stage) {

    /** A shortcut to check if a given key is pressed */
    private fun Key.isPressed(): Boolean = stage.views.input.keys[this]

    private var gameOver = false

    private val ship = Ship()

    init {
        spawnRandomAsteroids(ship.image)
    }

    private val stageUpdater = stage.addUpdater { time ->

        if (!gameOver) {
            val scale = time / 16.0.milliseconds

            if (ship.bulletReload > 0) ship.bulletReload -= scale

            if (Key.LEFT.isPressed()) ship.image.rotation -= 3.degrees * scale
            if (Key.RIGHT.isPressed()) ship.image.rotation += 3.degrees * scale
            if (Key.UP.isPressed()) ship.image.advance(2.0 * scale)
            if (Key.DOWN.isPressed()) ship.image.advance(-1.5 * scale)

            if ((Key.LEFT_CONTROL.isPressed() || Key.SPACE.isPressed()) && ship.bulletReload <= 0) {
                fireBullet()
            }
        } else {
            if (Key.R.isPressed()) {
                dispatch(GameRestartEvent())
            }
        }
    }


    private fun fireBullet() {

        ship.bulletReload = 20.0

        with(stage) {
            val bullet = image(assets.bulletBitmap)
                .center()
                .position(ship.image.x, ship.image.y)
                .rotation(ship.image.rotation)
                .advance(assets.shipSize * 0.75)

            bullet.onCollision {
                if (it is Asteroid) {
                    bullet.removeFromParent()
                    it.divide()
                }
            }

            bullet.addUpdater {
                val scale = it / 16.milliseconds
                bullet.advance(+3.0 * scale)
                // If the bullet flies off the screen, discard it
                if (bullet.x < -BULLET_SIZE || bullet.y < -BULLET_SIZE || bullet.x > WIDTH + BULLET_SIZE || bullet.y > HEIGHT + BULLET_SIZE) {
                    bullet.removeFromParent()
                }
            }
        }
    }

    private fun spawnRandomAsteroids(ship: Image) {
        val random = Random
        repeat(NUMBER_OF_ASTEROIDS) {
            val asteroid = spawnAsteroid(0.0, 0.0)
            do {
                asteroid.x = random[0.0, WIDTH.toDouble()]
                asteroid.y = random[0.0, HEIGHT.toDouble()]
                asteroid.angle = random[0.0, 360.0].degrees
            } while (asteroid.collidesWith(ship) || ship.distanceTo(asteroid) < 100.0)
        }
    }

    private fun spawnAsteroid(x: Double, y: Double): Asteroid {
        return Asteroid(stage.assets).addTo(stage).xy(x, y)
    }

    fun setGameOver() {
        gameOver = true

        ship.image.removeFromParent()

        with(stage) {
            // Display the Game Over overlay (with a momentary flash)
            val overlay = solidRect(WIDTH, HEIGHT, Colors.YELLOW)
            stage.launch {
                overlay.tween(overlay::color[Colors.RED.withA(64)], time = 300.milliseconds)
            }

            val gameOverText = text("GAME OVER", 64.0)
                .centerOnStage()

            text("Press R to restart")
                .centerXOnStage()
                .alignTopToBottomOf(gameOverText, 10.0)
        }
    }

    fun detach() {
        stageUpdater.cancel(GameRestart)
    }

    // The ship controlled by the player
    inner class Ship {
        val image = with(stage) {
            image(assets.shipBitmap)
                .center()
                .position(320, 240)
        }

        // A cooldown period to prevent bullet spamming
        var bulletReload = 0.0

        init {
            image.onCollision {
                if (it is Asteroid) {
                    setGameOver()
                }
            }
        }
    }
}

fun View.distanceTo(other: View) = Point.distance(x, y, other.x, other.y)

fun View.advance(amount: Double, rot: Angle = (-90).degrees) = this.apply {
    x += (this.rotation + rot).cosine * amount
    y += (this.rotation + rot).sine * amount
}

// A dummy throwable to cancel updatables
object GameRestart : Throwable()
