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

package net.torvald.spriteanimation

import com.jme3.math.FastMath
import net.torvald.terrarum.gameactors.ActorWithSprite
import org.newdawn.slick.Graphics
import org.newdawn.slick.Image
import org.newdawn.slick.SlickException
import org.newdawn.slick.SpriteSheet

class SpriteAnimation(val parentActor: ActorWithSprite, val cellWidth: Int, val cellHeight: Int) {

    private var spriteImage: SpriteSheet? = null
    var currentFrame = 0
    var currentRow = 0
    var nFrames: Int = 1
        private set
    var nRows: Int = 1
        private set
    var delay = 200
    private var delta = 0
    val looping = true
    private var animationRunning = true
    private var flipHorizontal = false
    private var flipVertical = false
    private val visible: Boolean
        get() = parentActor.isVisible

    private val offsetX = 0
    private val offsetY = 0

    private var prevScale = 1f
    private var currentImage: Image? = null

    /**
     * Sets spritesheet.
     * MUST be called AFTER setDimension.
     * @param imagePath path to the sprite sheet image.
     * *
     * @throws SlickException
     */
    fun setSpriteImage(imagePath: String) {
        spriteImage = SpriteSheet(imagePath, cellWidth, cellHeight)
    }

    fun setSpriteImage(image: Image) {
        spriteImage = SpriteSheet(image, cellWidth, cellHeight)
    }

    /**
     * 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 = nFrames
    }

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

            //check if it's time to advance the frame
            if (this.delta >= this.delay) {
                //if set to not loop, keep the frame at the last frame
                if (this.currentFrame == this.nFrames && !this.looping) {
                    this.currentFrame = this.nFrames - 1
                }

                //advance one frame, then reset delta counter
                this.currentFrame = this.currentFrame % this.nFrames
                this.delta = 0
            }
        }
    }

    /**
     * Render to specific coordinates. Will assume bottom-center point as image position.
     * Will round to integer.
     * @param g
     * *
     * @param posX bottom-center point
     * *
     * @param posY bottom-center point
     * *
     * @param scale
     */
    @JvmOverloads fun render(g: Graphics, posX: Float, posY: Float, scale: Float = 1f) {
        if (cellWidth == 0 || cellHeight == 0) {
            throw Error("Sprite width or height is set to zero! ($cellWidth, $cellHeight); master: $parentActor")
        }

        // Null checking
        if (currentImage == null) {
            currentImage = getScaledSprite(scale)
        }

        if (visible) {
            // re-scale image if scale has been changed
            if (prevScale != scale) {
                currentImage = getScaledSprite(scale)
                prevScale = scale
            }

            val flippedImage = currentImage!!.getFlippedCopy(flipHorizontal, flipVertical)
            flippedImage.draw(
                    Math.round(posX).toFloat(),
                    FastMath.floor(posY).toFloat(),
                    FastMath.floor(cellWidth * scale).toFloat(),
                    FastMath.floor(cellHeight * scale).toFloat()
            )
        }
    }

    fun switchRow(newRow: Int) {
        currentRow = newRow % nRows

        //if beyond the frame index then reset
        if (currentFrame > nFrames) {
            reset()
        }
    }

    fun setSpriteDelay(newDelay: Int) {
        if (newDelay > 0) {
            delay = newDelay
        }
        else {
            throw IllegalArgumentException("Delay equal or less than zero")
        }
    }

    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
    }

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

    fun flippedHorizontal(): Boolean {
        return flipHorizontal
    }

    fun flippedVertical(): Boolean {
        return flipVertical
    }

    private fun getScaledSprite(scale: Float): Image {
        val selectedImage = spriteImage!!.getSprite(currentFrame, currentRow)
        selectedImage.filter = Image.FILTER_NEAREST
        return selectedImage.getScaledCopy(scale)
    }
}
