package partisan.weforge.xyz.pulse

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.*
import android.util.AttributeSet
import android.view.Choreographer
import android.view.MotionEvent
import android.view.View
import kotlin.math.*
import kotlin.random.Random

class SecretView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs), Choreographer.FrameCallback {

    private val bulletPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE
        strokeWidth = 6f
    }
    private val starPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE
        alpha = 40
        style = Paint.Style.FILL
    }
    private val playerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 4f
    }
    private val enemyPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 4f
    }
    private val rocketPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.RED
        style = Paint.Style.STROKE
        strokeWidth = 3f
    }
    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE
        textAlign = Paint.Align.CENTER
        textSize = 64f
        typeface = Typeface.DEFAULT_BOLD
    }
    private val retryRect = RectF()
    private val retryPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.DKGRAY
    }
    private val retryTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE
        textAlign = Paint.Align.CENTER
        textSize = 48f
    }

    private var playerX = 0f
    private var viewWidth = 0f
    private var viewHeight = 0f

    private var isTouching = false
    private var bulletCooldownMs = 0L
    private var gameOver = false
    private var score = 0

    private val bullets = mutableListOf<Bullet>()
    private val enemyBullets = mutableListOf<Bullet>()
    private val enemies = mutableListOf<Enemy>()
    private val rockets = mutableListOf<Rocket>()
    private val stars = mutableListOf<Star>()
    private val explosions = mutableListOf<Explosion>()
    private val rocketTrails = mutableListOf<Pair<Float, Float>>()

    private data class Bullet(var x: Float, var y: Float, val dy: Float = -15f)
    private data class Star(var x: Float, var y: Float, val radius: Float, val speed: Float)
    private data class Rocket(var x: Float, var y: Float, var angle: Float, val trail: MutableList<Pair<Float, Float>> = mutableListOf())
    private data class Explosion(var x: Float, var y: Float, var timer: Int = 12)

    private var lastLogicTime = 0L
    private val logicStepMs = 16L

    private var waveTimer = 0L
    private var currentWave = 0
    private var enemiesLeftInWave = 0
    private var currentWaveType = ""

    init {
        for (i in 0 until 50) {
            stars.add(Star(Random.nextFloat() * 1080f, Random.nextFloat() * 1920f, Random.nextFloat() * 2f + 1f, Random.nextFloat() * 2f + 0.5f))
        }

        val colorAttrs = intArrayOf(com.google.android.material.R.attr.colorPrimaryVariant)
        context.obtainStyledAttributes(colorAttrs).use {
            val primary = it.getColor(0, Color.CYAN)
            playerPaint.color = primary
            enemyPaint.color = primary
        }

        Choreographer.getInstance().postFrameCallback(this)
        lastLogicTime = System.nanoTime()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w.toFloat()
        viewHeight = h.toFloat()
        // Kinda hacky way to center things, but I'd rather do this here than in update(), since it can't be in init() as the screen size isn't initialized at that point.
        playerX = viewWidth / 2f
        retryRect.set(viewWidth / 2f - 120f, viewHeight / 2f + 60f, viewWidth / 2f + 120f, viewHeight / 2f + 130f)
    }

    override fun doFrame(frameTimeNanos: Long) {
        if (!gameOver) {
            val now = System.nanoTime()
            while ((now - lastLogicTime) / 1_000_000 >= logicStepMs) {
                update(logicStepMs)
                lastLogicTime += logicStepMs * 1_000_000
            }
            invalidate()
            Choreographer.getInstance().postFrameCallback(this)
        }
    }

    private fun update(deltaMs: Long) {
        stars.forEach {
            it.y += it.speed * deltaMs / 16f
            if (it.y > viewHeight) {
                it.y = 0f
                it.x = Random.nextFloat() * viewWidth
            }
        }

        rockets.forEach { rocket ->
            rocket.trail.add(0, rocket.x to rocket.y)
            if (rocket.trail.size > 20) {
                rocket.trail.removeLast()
            }
        }

        explosions.forEach { it.timer-- }
        explosions.removeIf { it.timer <= 0 }


        bullets.forEach { it.y += it.dy * deltaMs / 16f }
        bullets.removeIf { it.y < 0 }

        bulletCooldownMs -= deltaMs
        if (isTouching && bulletCooldownMs <= 0) {
            bullets.add(Bullet(playerX, viewHeight - 130f))
            bulletCooldownMs = 150L
        }

        enemies.forEach {
            it.update(deltaMs)
            it.x = max(20f, min(it.x, viewWidth - 20f))
            if (it.y > viewHeight + 100f) it.y = -40f // respawn at top
        }

        rockets.forEach {
            val targetAngle = atan2(viewHeight - 100f - it.y, playerX - it.x)
            it.angle += ((targetAngle - it.angle + PI).mod(2 * PI) - PI).toFloat() * 0.1f
            it.x += cos(it.angle) * 6f
            it.y += sin(it.angle) * 6f
        }
        rockets.removeIf { it.x < 0 || it.x > viewWidth || it.y < 0 || it.y > viewHeight }

        enemyBullets.forEach { it.y += it.dy * deltaMs / 16f }
        enemyBullets.removeIf { it.y > viewHeight }
        
        checkCollisions()
        spawnEnemies(deltaMs)
    }

    private fun checkCollisions() {
        val enemyIter = enemies.iterator()
        while (enemyIter.hasNext()) {
            val enemy = enemyIter.next()
            if (hypot(playerX - enemy.x, viewHeight - 100f - enemy.y) < 40f) {
                explosions.add(Explosion(playerX, viewHeight - 100f))
                gameOver = true
                return
            }
            for (b in bullets) {
                if (hypot(b.x - enemy.x, b.y - enemy.y) < 30f) {
                    explosions.add(Explosion(enemy.x, enemy.y))
                    enemyIter.remove()
                    score += 10
                    break
                }
            }
        }

        for (b in enemyBullets) {
            if (hypot(b.x - playerX, b.y - (viewHeight - 100f)) < 20f) {
                explosions.add(Explosion(playerX, viewHeight - 100f))
                gameOver = true
                return
            }
        }

        val rocketIter = rockets.iterator()
        while (rocketIter.hasNext()) {
            val rocket = rocketIter.next()
            if (hypot(playerX - rocket.x, viewHeight - 100f - rocket.y) < 30f) {
                explosions.add(Explosion(playerX, viewHeight - 100f))
                gameOver = true
                return
            }
            for (b in bullets) {
                if (hypot(b.x - rocket.x, b.y - rocket.y) < 20f) {
                    explosions.add(Explosion(rocket.x, rocket.y))
                    rocketIter.remove()
                    score += 15
                    break
                }
            }
        }
    }

    private fun spawnEnemies(deltaMs: Long) {
        waveTimer -= deltaMs

        // Passive asteroids
        if (Random.nextFloat() < 0.002f) {
            enemies.add(EnemyAsteroid(Random.nextFloat() * viewWidth, -40f, viewWidth))
        }

        // Setup new wave
        if (enemiesLeftInWave <= 0 && waveTimer <= 0) {
            currentWave++
            currentWaveType = when (currentWave % 3) {
                0 -> "easy"
                1 -> "medium"
                else -> "hard"
            }

            enemiesLeftInWave = when (currentWaveType) {
                "easy" -> 3 + currentWave
                "medium" -> 2 + currentWave / 2
                "hard" -> 1 + currentWave / 4
                else -> 3
            }

            waveTimer = 3000L
        }

        // Spawn enemies in group
        if (enemiesLeftInWave > 0 && enemies.count { it !is EnemyAsteroid } < 3) {
            val baseX = Random.nextFloat() * (viewWidth - 100f) + 50f
            val baseY = -40f
            val spacing = 35f
            val sharedOffset = Random.nextFloat() * 1000f
            val sharedFireTime = Random.nextLong(2000L, 4000L)

            val formationSize = when (currentWaveType) {
                "easy" -> 5
                "medium" -> 3
                "hard" -> 1
                else -> 3
            }

            for (i in 0 until min(formationSize, enemiesLeftInWave)) {
                val offsetX = (i - (formationSize - 1) / 2f) * spacing
                val x = baseX + offsetX

                // The group sync isn't working as enemies gradually get out of sync, but whatever, it's fine.
                val enemy = when (currentWaveType) {
                    "easy" -> EnemyEasy(x, baseY)
                    "medium" -> EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { enemyBullets.add(it) }
                    "hard" -> EnemyHard(x, baseY, { rockets.add(it) }, sharedOffset, sharedFireTime)
                    else -> EnemyEasy(x, baseY)
                }

                enemies.add(enemy)
                enemiesLeftInWave--
            }
        }
    }

    private fun resetGame() {
        bullets.clear()
        enemyBullets.clear()
        enemies.clear()
        rockets.clear()
        explosions.clear()
        rocketTrails.clear()
        score = 0
        currentWave = 0
        enemiesLeftInWave = 0
        gameOver = false
        playerX = viewWidth / 2f
        lastLogicTime = System.nanoTime()
        Choreographer.getInstance().postFrameCallback(this)
    }

    override fun onDraw(canvas: Canvas) {
        viewWidth = width.toFloat()
        viewHeight = height.toFloat()

        canvas.drawColor(Color.parseColor("#121212"))
        stars.forEach { canvas.drawCircle(it.x, it.y, it.radius, starPaint) }

        enemyBullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y + 20f, rocketPaint) }

        rockets.forEach { rocket ->
            rocket.trail.forEachIndexed { index, (x, y) ->
                val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
                rocketPaint.alpha = alpha
                canvas.drawCircle(x, y, 2f, rocketPaint)
            }
        }
        rocketPaint.alpha = 255

        explosions.forEach {
            val radius = 40f * (1f - it.timer / 12f.toFloat())
            val alpha = (255 * (it.timer / 12f.toFloat())).toInt()
            val paint = Paint().apply {
                color = Color.YELLOW
                this.alpha = alpha
                style = Paint.Style.STROKE
                strokeWidth = 3f
            }
            canvas.drawCircle(it.x, it.y, radius, paint)
        }

        bullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y - 20f, bulletPaint) }
        enemies.forEach { it.draw(canvas, enemyPaint) }
        rockets.forEach { canvas.drawCircle(it.x, it.y, 10f, rocketPaint) }

        if (!gameOver) {
            val baseY = viewHeight - 100f
            val safeX = max(30f, min(playerX, viewWidth - 30f))
            playerX = safeX
            val path = Path().apply {
                moveTo(safeX, baseY - 30f)
                lineTo(safeX - 30f, baseY + 30f)
                lineTo(safeX + 30f, baseY + 30f)
                close()
            }
            canvas.drawPath(path, playerPaint)
        } else {
            canvas.drawText("Game Over", viewWidth / 2f, viewHeight / 2f - 60f, textPaint)
            canvas.drawText("Score: $score", viewWidth / 2f, viewHeight / 2f + 10f, textPaint)
            canvas.drawText("Tap to restart", retryRect.centerX(), retryRect.centerY() + 16f, retryTextPaint)
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (gameOver && event.action == MotionEvent.ACTION_DOWN) {
            resetGame()
            return true
        }

        when (event.action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
                playerX = max(30f, min(event.x, width - 30f))
                isTouching = true
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> isTouching = false
        }
        return true
    }

    private interface Enemy {
        var x: Float
        var y: Float
        fun update(deltaMs: Long)
        fun draw(canvas: Canvas, paint: Paint)
    }

    private class EnemyAsteroid(
        override var x: Float,
        override var y: Float,
        private val screenWidth: Float
    ) : Enemy {
        private var vx = Random.nextFloat() * 2f - 1f
        private val vy = Random.nextFloat() * 2f + 2f
        private var angle = Random.nextFloat() * 360f
        private val rotationSpeed = Random.nextFloat() * 2f * if (Random.nextBoolean()) 1 else -1

        override fun update(deltaMs: Long) {
            x += vx * deltaMs / 16f
            y += vy * deltaMs / 16f
            angle = (angle + rotationSpeed * deltaMs / 16f) % 360f

            if (x < 20f || x > screenWidth - 20f) {
                vx = -vx
                x = max(20f, min(x, screenWidth - 20f))
            }
        }

        override fun draw(canvas: Canvas, paint: Paint) {
            canvas.save()
            canvas.rotate(angle, x, y)
            val path = Path().apply {
                moveTo(x - 20f, y)
                lineTo(x - 10f, y - 15f)
                lineTo(x + 10f, y - 15f)
                lineTo(x + 20f, y)
                lineTo(x + 10f, y + 15f)
                lineTo(x - 10f, y + 15f)
                close()
            }
            canvas.drawPath(path, paint)
            canvas.restore()
        }
    }

    private class EnemyEasy(override var x: Float, override var y: Float) : Enemy {
        private val offset = Random.nextFloat() * 1000f
        override fun update(deltaMs: Long) {
            y += 2f * deltaMs / 16f
            x += sin((y + offset) / 50f) * 2f
        }
        override fun draw(canvas: Canvas, paint: Paint) {
            val path = Path().apply {
                moveTo(x, y - 20f)
                lineTo(x + 20f, y)
                lineTo(x, y + 20f)
                lineTo(x - 20f, y)
                close()
            }
            canvas.drawPath(path, paint)
        }
    }

        private class EnemyMedium(
        override var x: Float,
        override var y: Float,
        private val offset: Float,
        private var fireTimer: Long,
        val fireEnemyBullet: (Bullet) -> Unit
    ) : Enemy {
        override fun update(deltaMs: Long) {
            y += 2.5f * deltaMs / 16f
            x += sin((y + offset) / 40f) * 3f

            fireTimer -= deltaMs
            if (fireTimer <= 0) {
                fireEnemyBullet(Bullet(x, y + 30f, dy = 10f))
                fireTimer = Random.nextLong(3000L, 6000L)
            }
        }

        override fun draw(canvas: Canvas, paint: Paint) {
            val path = Path().apply {
                moveTo(x, y - 25f)
                lineTo(x + 15f, y)
                lineTo(x, y + 25f)
                lineTo(x - 15f, y)
                close()
            }
            canvas.drawPath(path, paint)
        }
    }

    private class EnemyHard(
        override var x: Float,
        override var y: Float,
        val fireRocket: (Rocket) -> Unit,
        private val offset: Float = Random.nextFloat() * 1000f,
        private var fireTimer: Long = Random.nextLong(2000L, 5000L)
    ) : Enemy {
        private var cooldown = 0L
        private var firing = false

        override fun update(deltaMs: Long) {
            if (firing) {
                cooldown += deltaMs
                if (cooldown > 500 && cooldown < 1300) {
                    fireRocket(Rocket(x, y - 30f, -PI.toFloat() / 2))
                    cooldown = 1300
                } else if (cooldown > 2000) {
                    cooldown = 0L
                    fireTimer = Random.nextLong(15000L, 25000L)
                    firing = false
                }
                return
            }

            y += 3f * deltaMs / 16f
            x += sin((y + offset) / 25f) * 4f
            fireTimer -= deltaMs
            if (fireTimer <= 0) {
                firing = true
                cooldown = 0L
            }
        }

        override fun draw(canvas: Canvas, paint: Paint) {
            val path = Path().apply {
                moveTo(x, y - 25f)
                lineTo(x + 10f, y - 10f)
                lineTo(x + 20f, y + 10f)
                lineTo(x, y + 25f)
                lineTo(x - 20f, y + 10f)
                lineTo(x - 10f, y - 10f)
                close()
            }
            canvas.drawPath(path, paint)
        }
    }
}
