//--------------------------------------------------------------------------------
// FrameAnimator.hpp
//--------------------------------------------------------------------------------
// An animator that places all the frames in the VRAM and changes the tile
// IDs to do the animation
//--------------------------------------------------------------------------------
#pragma once

#include <tonc.h>
#include <cstdint>
#include <utility>
#include "graphics.hpp"
#include "graphics/AnimationPose.hpp"
#include "graphics/SpriteSize.hpp"
#include "data/AnimationFrame.hpp"
#include "util/AllocatorPointer.hpp"
#include "util/integer-sequence-utils.hpp"

// Contains the base of the frame animator, used to be able to share code
class FrameAnimatorBase final
{
    // The pointer to the tile indices
    const u16* frameTileIndices;
    // The pointer to the frame data
    const AnimationFrame* animationFrames;
    // The number of GBA frames to wait between each animation frame
    u16 frameTime;
    // The GBA count, and important properties of the animation
    u16 frameCount, curFrame, repeatFrame, endFrame;

public:
    // Constructor and destructor
    FrameAnimatorBase(const u16* frameTileIndices, const AnimationFrame* animationFrames, u16 frameTime);
    ~FrameAnimatorBase() {}

    // Sets an animation pose
    void setAnimationPose(const AnimationPose& pose);

    // Update the animation
    void update();

    // Gets the animation tile ID for that day
    u16 getTileId() const
    { 
        auto curTile = animationFrames[curFrame].tileIdAttributes & 0x3FFF;
        return frameTileIndices[curTile];
    }
};

// Provides a place where we can put all the graphics
template <const auto& Png, SpriteSize Size>
class FrameStore final : public Allocator<FrameStore<Png, Size>>
{
    // Useful typedefs
    using TPng = std::remove_cv_t<std::remove_reference_t<decltype(Png)>>;
    constexpr static const auto N = TPng::NumAnimationFrames;
    using Indices = bit_sequence_t<N>;
    using Sizes = exp2_seq_t<Indices>;
    using PrefixSizes = prefix_sum_t<Sizes>;
    static constexpr auto TotalSize = seq_sum_v<Sizes>;

    // Dirty technique to allow partial specialization of functions
    template <typename> struct dummy {};

    // The pointer where the animation frames are stored
    const TILE* animationTiles;

    // The places to store the tile indices
    u16 tileIndices[TotalSize];

    // The clear function
    template <std::size_t... Ofs>
    void clearUtil(dummy<std::index_sequence<Ofs...>>)
    {
        ((void)graphics::freeObjTiles(tileIndices[Ofs]), ...);
    }
    void clear() { clearUtil(dummy<PrefixSizes>{}); }

    // The alloc function is a bit more difficult, but doable nevertheless
    template <std::size_t Order, std::size_t N>
    void allocSingle()
    {
        // Set the allocation function
        constexpr auto SprSize = SizeUtils::logBlocks(Size);
        constexpr auto LogBlocks = SprSize + Order;
        tileIndices[N] = graphics::allocateObjTiles(LogBlocks);
        for (uint i = 1; i < (1 << Order); i++)
            tileIndices[N+i] = tileIndices[N] + (i << SprSize);

        // Transfer the data
        auto frame = animationTiles + (N << SprSize);
        auto dest = &tile_mem_obj[0][tileIndices[N]];
        memcpy32(dest, frame, (sizeof(TILE)/sizeof(u32)) << LogBlocks);
    }

    template <std::size_t... Orders, std::size_t... Ns>
    void allocUtil(dummy<std::index_sequence<Orders...>>, dummy<std::index_sequence<Ns...>>)
    {
        ((void)allocSingle<(std::size_t)Orders, (std::size_t)Ns>(), ...);
    }
    void alloc() { allocUtil(dummy<Indices>{}, dummy<PrefixSizes>{}); }

public:
    FrameStore() : animationTiles((const TILE*)Png.png.tiles), tileIndices() {};
    ~FrameStore() {}

    const u16* getFrameStore() const { return tileIndices; }

    template <typename U>
    friend class Allocator;
};

// The pointer for the frame store
template <const auto& Png, SpriteSize Size>
class FrameStorePointer final : public AllocatorPointer<FrameStore<Png, Size>>
{
public:
    INHERIT_ALLOCATOR_CTORS(FrameStorePointer, FrameStore<Png, Size>)

    const u16* getFrameStore() const
    {
        ASSERT(this->allocator);
        return this->allocator->getFrameStore();
    }
};

// The frame animator, to be used with a special definition generated by the tools
template <const auto& Png, SpriteSize Size>
class FrameAnimator
{
    FrameStorePointer<Png, Size> pointer;
    FrameAnimatorBase base;

public:
    using Pointer = FrameStorePointer<Png, Size>;

    FrameAnimator(FrameStore<Png, Size>& store, u16 frameTime)
        : pointer(store, false), base(store.getFrameStore(), Png.animationFrames, frameTime) {}

    // Sets an animation pose
    void setAnimationPose(const AnimationPose& pose) { base.setAnimationPose(pose); }

    // Update the animation
    void update() { base.update(); }

    // Gets the animation tile ID for that day
    u16 getTileId() const { return base.getTileId(); }

    bool isVisible() const { return (bool)pointer; }
    void setVisible(bool visible) { pointer.setActive(visible); }
};
