@file:Suppress("NESTED_EXTERNAL_DECLARATION")
package org.jetbrains.skia.skottie

import org.jetbrains.skia.impl.Library.Companion.staticLoad
import org.jetbrains.skia.sksg.InvalidationController
import org.jetbrains.skia.*
import org.jetbrains.skia.impl.Managed
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.reachabilityBarrier
import org.jetbrains.skia.ExternalSymbolName
import kotlin.jvm.JvmStatic

class Animation internal constructor(ptr: Long) : Managed(ptr, _FinalizerHolder.PTR) {
    companion object {
        fun makeFromString(data: String): Animation {
            Stats.onNativeCall()
            val ptr = _nMakeFromString(data)
            require(ptr != 0L) { "Failed to create Animation from string=\"$data\"" }
            return Animation(ptr)
        }

        fun makeFromFile(path: String): Animation {
            Stats.onNativeCall()
            val ptr = _nMakeFromFile(path)
            require(ptr != 0L) { "Failed to create Animation from path=\"$path\"" }
            return Animation(ptr)
        }

        fun makeFromData(data: Data): Animation {
            Stats.onNativeCall()
            val ptr = _nMakeFromData(getPtr(data))
            require(ptr != 0L) { "Failed to create Animation from data." }
            return Animation(ptr)
        }

        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetFinalizer")
        external fun _nGetFinalizer(): Long
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nMakeFromString")
        external fun _nMakeFromString(data: String?): Long
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nMakeFromFile")
        external fun _nMakeFromFile(path: String?): Long
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nMakeFromData")
        external fun _nMakeFromData(dataPtr: Long): Long
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nRender")
        external fun _nRender(
            ptr: Long,
            canvasPtr: Long,
            left: Float,
            top: Float,
            right: Float,
            bottom: Float,
            flags: Int
        )

        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nSeek")
        external fun _nSeek(ptr: Long, t: Float, icPtr: Long)
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nSeekFrame")
        external fun _nSeekFrame(ptr: Long, t: Float, icPtr: Long)
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nSeekFrameTime")
        external fun _nSeekFrameTime(ptr: Long, t: Float, icPtr: Long)
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetDuration")
        external fun _nGetDuration(ptr: Long): Float
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetFPS")
        external fun _nGetFPS(ptr: Long): Float
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetInPoint")
        external fun _nGetInPoint(ptr: Long): Float
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetOutPoint")
        external fun _nGetOutPoint(ptr: Long): Float
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetVersion")
        external fun _nGetVersion(ptr: Long): String
        @JvmStatic
        @ExternalSymbolName("org_jetbrains_skia_skottie_Animation__1nGetSize")
        external fun _nGetSize(ptr: Long): Point?

        init {
            staticLoad()
        }
    }

    internal object _FinalizerHolder {
        val PTR = _nGetFinalizer()
    }

    /**
     *
     * Draws the current animation frame
     *
     *
     * It is undefined behavior to call render() on a newly created Animation
     * before specifying an initial frame via one of the seek() variants.
     *
     * @param canvas  destination canvas
     * @return        this
     */
    fun render(canvas: Canvas): Animation {
        return render(canvas, Rect.Companion.makeXYWH(0f, 0f, width, height))
    }

    /**
     *
     * Draws the current animation frame
     *
     *
     * It is undefined behavior to call render() on a newly created Animation
     * before specifying an initial frame via one of the seek() variants.
     *
     * @param canvas  destination canvas
     * @param offset  destination offset
     * @return        this
     */
    fun render(canvas: Canvas, offset: Point): Animation {
        return render(canvas, offset.x, offset.y)
    }

    /**
     *
     * Draws the current animation frame
     *
     *
     * It is undefined behavior to call render() on a newly created Animation
     * before specifying an initial frame via one of the seek() variants.
     *
     * @param canvas  destination canvas
     * @param left    destination offset left
     * @param top     destination offset top
     * @return        this
     */
    fun render(canvas: Canvas, left: Float, top: Float): Animation {
        return render(canvas, Rect.Companion.makeXYWH(left, top, width, height))
    }

    /**
     *
     * Draws the current animation frame
     *
     *
     * It is undefined behavior to call render() on a newly created Animation
     * before specifying an initial frame via one of the seek() variants.
     *
     * @param canvas       destination canvas
     * @param dst          destination rect
     * @param renderFlags  render flags
     * @return             this
     */
    fun render(canvas: Canvas, dst: Rect, vararg renderFlags: RenderFlag): Animation {
        return try {
            Stats.onNativeCall()
            var flags = 0
            for (flag in renderFlags) flags = flags or flag._flag
            _nRender(_ptr, getPtr(canvas), dst.left, dst.top, dst.right, dst.bottom, flags)
            this
        } finally {
            reachabilityBarrier(canvas)
        }
    }

    /**
     *
     * Updates the animation state for |t|.
     *
     * @param t   normalized [0..1] frame selector (0 → first frame, 1 → final frame)
     * @return    this
     */
    fun seek(t: Float): Animation {
        return seek(t, null)
    }

    /**
     *
     * Updates the animation state for |t|.
     *
     * @param t   normalized [0..1] frame selector (0 → first frame, 1 → final frame)
     * @param ic  invalidation controller (dirty region tracking)
     * @return    this
     */
    fun seek(t: Float, ic: InvalidationController?): Animation {
        return try {
            Stats.onNativeCall()
            _nSeek(_ptr, t, getPtr(ic))
            this
        } finally {
            reachabilityBarrier(ic)
        }
    }

    /**
     *
     * Update the animation state to match |t|, specified as a frame index i.e.
     * relative to [] * [].
     *
     *
     * Fractional values are allowed and meaningful - e.g.
     * 0.0 → first frame 1.0 → second frame 0.5 → halfway between first and second frame
     *
     * @param t   frame index
     * @return    this
     */
    fun seekFrame(t: Float): Animation {
        return seekFrame(t, null)
    }

    /**
     *
     * Update the animation state to match |t|, specified as a frame index i.e.
     * relative to [] * [].
     *
     *
     * Fractional values are allowed and meaningful - e.g.
     * 0.0 → first frame 1.0 → second frame 0.5 → halfway between first and second frame
     *
     * @param t   frame index
     * @param ic  invalidation controller (dirty region tracking)
     * @return    this
     */
    fun seekFrame(t: Float, ic: InvalidationController?): Animation {
        return try {
            Stats.onNativeCall()
            _nSeekFrame(_ptr, t, getPtr(ic))
            this
        } finally {
            reachabilityBarrier(ic)
        }
    }

    /**
     *
     * Update the animation state to match t, specifed in frame time i.e.
     * relative to [].
     *
     * @param t   frame time
     * @return    this
     */
    fun seekFrameTime(t: Float): Animation {
        return seekFrameTime(t, null)
    }

    /**
     *
     * Update the animation state to match t, specifed in frame time i.e.
     * relative to [].
     *
     * @param t   frame time
     * @param ic  invalidation controller (dirty region tracking)
     * @return    this
     */
    fun seekFrameTime(t: Float, ic: InvalidationController?): Animation {
        return try {
            Stats.onNativeCall()
            _nSeekFrameTime(_ptr, t, getPtr(ic))
            this
        } finally {
            reachabilityBarrier(ic)
        }
    }

    /**
     * @return  the animation duration in seconds
     */
    val duration: Float
        get() = try {
            Stats.onNativeCall()
            _nGetDuration(_ptr)
        } finally {
            reachabilityBarrier(this)
        }

    /**
     * @return  the animation frame rate (frames / second)
     */
    val fPS: Float
        get() = try {
            Stats.onNativeCall()
            _nGetFPS(_ptr)
        } finally {
            reachabilityBarrier(this)
        }

    /**
     * @return  Animation in point, in frame index units
     */
    val inPoint: Float
        get() = try {
            Stats.onNativeCall()
            _nGetInPoint(_ptr)
        } finally {
            reachabilityBarrier(this)
        }

    /**
     * @return  Animation out point, in frame index units
     */
    val outPoint: Float
        get() = try {
            Stats.onNativeCall()
            _nGetOutPoint(_ptr)
        } finally {
            reachabilityBarrier(this)
        }
    val version: String
        get() = try {
            Stats.onNativeCall()
            _nGetVersion(_ptr)
        } finally {
            reachabilityBarrier(this)
        }

    private var _size: Point? = null
    val size: Point
        get() {
            if (_size == null) {
                _size = _nGetSize(_ptr)
            }
            return _size!!
        }
    val width: Float
        get() = size.x
    val height: Float
        get() = size.y
}