package net.torvald.spriteanimation

import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath
import net.torvald.terrarum.Second
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack

/**
 * This class should not be serialised; save its Animation Description Language instead.
 *
 * Created by minjaesong on 2022-03-23.
 */
abstract class SpriteAnimation(@Transient val parentActor: ActorWithBody) : Disposable {
    protected abstract val currentDelay: Second
    abstract fun update(delta: Float)
    abstract fun render(frameDelta: Float, batch: SpriteBatch, posX: Float, posY: Float, scale: Float = 1f, mode: Int = 0, forcedColourFilter: Color? = null)

    var flipHorizontal = false
    var flipVertical = false
    var colourFilter = Color(-1)

    open fun flip(horizontal: Boolean, vertical: Boolean) {
        flipHorizontal = horizontal
        flipVertical = vertical
    }
}

/* Original code author: Sean Laurvick
 * This code is based on the original author's code written in Lua.
 */

/**
 * This class should not be serialised; save its Animation Description Language instead.
 *
 * Maximum rows: 64
 *
 * Maximum frames: unlimited
 */
class SheetSpriteAnimation(parentActor: ActorWithBody) : SpriteAnimation(parentActor) {

    internal lateinit var textureRegion: TextureRegionPack; private set

    var currentFrame = 0
    var currentRow = 0

    var nFrames: IntArray = intArrayOf(1)
        internal set
    var nRows: Int = 1
        internal set

    override val currentDelay: Second
        get() = delays[currentRow].coerceAtLeast(1f / 16f) // animation delay cannot be too short

    /**
     * Sets delays for each rows. Array size must be the same as the rows of the sheet
     */
    var delays: FloatArray = FloatArray(64) { 0.2f }
        set(value) {
            if (value.filter { it <= 0f }.isNotEmpty()) {
                throw IllegalArgumentException("Delay array contains zero or negative value: $delays")
            }

            field = value
        }

    private var delta = 0f

    var looping = true
    private var animationRunning = true

    private val visible: Boolean
        get() = parentActor.isVisible

    private val offsetX = 0
    private val offsetY = 0

    var cellWidth: Int = 0
    var cellHeight: Int = 0

    fun setSpriteImage(regionPack: TextureRegionPack) {
        textureRegion = regionPack

        cellWidth = regionPack.tileW
        cellHeight = regionPack.tileH
    }

    /**
     * Sets sheet rows and animation frames. Will default to
     * 1, 1 (still image of top left from the sheet) if not called.
     * @param nRows
     * *
     * @param nFrames
     */
    fun setRowsAndFrames(nRows: Int, nFrames: Int) {
        this.nRows = nRows
        this.nFrames = IntArray(nRows) { nFrames }
    }

    fun setFramesOf(row: Int, frameCount: Int) {
        nFrames[row] = frameCount
    }

    fun setFramesCount(framesCount: IntArray) {
        nFrames = framesCount
    }

    override fun update(delta: Float) {
        if (animationRunning) {
            //skip this if animation is stopped
            this.delta += delta

            //println("delta accumulation: $delta, currentDelay: $currentDelay")

            //check if it's time to advance the frame
            while (this.delta >= currentDelay) {
                // advance frame
                if (looping) { // looping, wrap around
                    currentFrame = (currentFrame + 1) % nFrames[currentRow]
                }
                else if (currentFrame < nFrames[currentRow] - 1) { // not looping and haven't reached the end
                    currentFrame += 1
                }

                // discount counter
                this.delta -= currentDelay
            }

            //println("row, frame: $currentRow, $currentFrame")
        }
    }

    /**
     * Render to specific coordinates. Will assume bottom-center point as image position.
     * Will floor to integer.
     * @param g
     * *
     * @param posX bottom-center point
     * *
     * @param posY bottom-center point
     * *
     * @param scale
     */
    override fun render(frameDelta: Float, batch: SpriteBatch, posX: Float, posY: Float, scale: Float, mode: Int, forcedColourFilter: Color?) {
        assert(cellWidth > 0 || cellHeight > 0) {
            "Sprite width or height is set to zero! ($cellWidth, $cellHeight); master: $parentActor"
        }

        if (visible) {
            val region = textureRegion.get(currentFrame, currentRow)
            batch.color = forcedColourFilter ?: colourFilter

            val tx = (parentActor.hitboxTranslateX) * scale
            val txF = (parentActor.hitboxTranslateX + parentActor.baseHitboxW) * scale
            val ty = (parentActor.hitboxTranslateY + (cellHeight - parentActor.baseHitboxH)) * scale
            val tyF = (parentActor.hitboxTranslateY + parentActor.baseHitboxH) * scale

            if (flipHorizontal && flipVertical) {
                batch.draw(region,
                        FastMath.floor(posX).toFloat() + txF,
                        FastMath.floor(posY).toFloat() + tyF,
                        -FastMath.floor(cellWidth * scale).toFloat(),
                        -FastMath.floor(cellHeight * scale).toFloat()
                )
            }
            else if (flipHorizontal && !flipVertical) {
                batch.draw(region,
                        FastMath.floor(posX).toFloat() + txF,
                        FastMath.floor(posY).toFloat() - ty,
                        -FastMath.floor(cellWidth * scale).toFloat(),
                        FastMath.floor(cellHeight * scale).toFloat()
                )
            }
            else if (!flipHorizontal && flipVertical) {
                batch.draw(region,
                        FastMath.floor(posX).toFloat() - tx,
                        FastMath.floor(posY).toFloat() + tyF,
                        FastMath.floor(cellWidth * scale).toFloat(),
                        -FastMath.floor(cellHeight * scale).toFloat()
                )
            }
            else {
                batch.draw(region,
                        FastMath.floor(posX).toFloat() - tx,
                        FastMath.floor(posY).toFloat() - ty,
                        FastMath.floor(cellWidth * scale).toFloat(),
                        FastMath.floor(cellHeight * scale).toFloat()
                )
            }
        }
    }

    fun switchRow(newRow: Int) {
        if (newRow != currentRow) {
            currentRow = newRow
            currentFrame = 1
        }
    }

    fun reset() {
        currentFrame = 1
    }

    fun start() {
        //starts the animation
        animationRunning = true
    }

    fun start(selectFrame: Int) {
        //starts the animation
        animationRunning = true

        //optional: seleft the frame no which to start the animation
        currentFrame = selectFrame
    }

    fun stop() {
        animationRunning = false
    }

    fun stop(selectFrame: Int) {
        animationRunning = false

        currentFrame = selectFrame
    }

    override fun dispose() {
        textureRegion.dispose()
    }
}
