/**
  ******************************************************************************
  * File Name          : DirectFrameBufferVideoController.hpp
  ******************************************************************************
  * This file is generated by TouchGFX Generator 4.24.0. Please, do not edit!
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
#ifndef TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP
#define TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP

#include <touchgfx/widgets/VideoWidget.hpp>
#include <MJPEGDecoder.hpp>
#include <string.h>

/**
 * Strategy:
 * Decode directly into the framebuffer in draw.
 * Tick will decide if we are going to a new frame.
 */
template <uint32_t no_streams, touchgfx::Bitmap::BitmapFormat output_format>
class DirectFrameBufferVideoController : public touchgfx::VideoController
{
public:
    DirectFrameBufferVideoController()
        : VideoController(), allowSkipFrames(true)
    {
        assert((no_streams > 0) && "Video: Number of streams zero!");

        // Clear arrays
        memset(mjpegDecoders, 0, sizeof(mjpegDecoders));
    }

    virtual Handle registerVideoWidget(touchgfx::VideoWidget& widget)
    {
        // Find stream handle for Widget
        Handle handle = getFreeHandle();

        streams[handle].isActive = true;

        //Set Widget buffer format and address
        widget.setVideoBufferFormat(output_format, 0, 0);
        widget.setVideoBuffer((uint8_t*)0);

        return handle;
    }

    virtual void unregisterVideoWidget(const Handle handle)
    {
        streams[handle].isActive = false;
    }

    virtual void setFrameRate(const Handle handle, uint32_t ui_frames, uint32_t video_frames)
    {
        assert(handle < no_streams);
        Stream& stream = streams[handle];

        // Reset counters
        stream.frameCount = 0;
        stream.tickCount = 0;

        // Save requested frame rate ratio
        stream.frame_rate_ticks = ui_frames;
        stream.frame_rate_video = video_frames;
    }

    virtual void setVideoData(const Handle handle, const uint8_t* movie, const uint32_t length)
    {
        assert(handle < no_streams);

        // Reset decoder to first frame
        mjpegDecoders[handle]->setVideoData(movie, length);

        // Lower flag to show the first frame
        Stream& stream = streams[handle];
        stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber();
        stream.doDecodeNextFrame = false;

        // Stop playing
        setCommand(handle, PAUSE, 0);
    }

    virtual void setVideoData(const Handle handle, touchgfx::VideoDataReader& reader)
    {
        assert(handle < no_streams);

        // Reset decoder to first frame
        mjpegDecoders[handle]->setVideoData(reader);

        // Lower flag to show the first frame
        Stream& stream = streams[handle];
        stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber();
        stream.doDecodeNextFrame = false;

        // Stop playing
        setCommand(handle, PAUSE, 0);
    }

    virtual void setCommand(const Handle handle, Command cmd, uint32_t param)
    {
        assert(handle < no_streams);
        Stream& stream = streams[handle];

        switch (cmd)
        {
        case PLAY:
            // Cannot Play without movie
            if (mjpegDecoders[handle]->hasVideo())
            {
                stream.isPlaying = true;
                stream.isShowingOneFrame = false;
                // Reset counters
                stream.frameCount = 0;
                stream.tickCount = 0;
                // If non-repeating video stopped at the end, kick to next frame
                if (!stream.repeat)
                {
                    MJPEGDecoder* const decoder = mjpegDecoders[handle];
                    if (decoder->getCurrentFrameNumber() == decoder->getNumberOfFrames())
                    {
                        decoder->gotoNextFrame();
                    }
                }
            }
            break;
        case PAUSE:
            stream.isPlaying = false;
            stream.isShowingOneFrame = false;
            break;
        case SEEK:
            stream.seek_to_frame = param;
            // Reset counters
            stream.frameCount = 0;
            stream.tickCount = 0;
            break;
        case SHOW:
            stream.seek_to_frame = param;
            stream.isShowingOneFrame = true;
            stream.doDecodeNextFrame = true;
            // Reset counters
            stream.frameCount = 0;
            stream.tickCount = 0;
            break;
        case STOP:
            stream.isPlaying = false;
            stream.isShowingOneFrame = false;
            stream.seek_to_frame = 1;
            // Reset counters
            stream.frameCount = 0;
            stream.tickCount = 0;
            break;
        case SET_REPEAT:
            stream.repeat = (param > 0);
            break;
        }
    }

    virtual bool updateFrame(const Handle handle, touchgfx::VideoWidget& widget)
    {
        assert(handle < no_streams);
        Stream& stream = streams[handle];

        bool hasMoreFrames = true;

        if (stream.isPlaying || stream.isShowingOneFrame)
        {
            // Increase tickCount
            stream.tickCount += HAL::getInstance()->getLCDRefreshCount();

            // Lower flag
            stream.isShowingOneFrame = false;

            if (stream.doDecodeNextFrame)
            {
                MJPEGDecoder* const decoder = mjpegDecoders[handle];
                // Invalidate to get widget redrawn
                widget.invalidate();
                // Seek or increment video frame
                if (stream.seek_to_frame > 0)
                {
                    decoder->gotoFrame(stream.seek_to_frame);
                    hasMoreFrames = (stream.seek_to_frame < decoder->getNumberOfFrames());
                    stream.seek_to_frame = 0;
                }
                else
                {
                    if (stream.skip_frames > 0)
                    {
                        decoder->gotoFrame(decoder->getCurrentFrameNumber() + stream.skip_frames);
                        stream.frameCount += stream.skip_frames;
                        stream.skip_frames = 0;
                    }
                    if (stream.repeat)
                    {
                        hasMoreFrames = decoder->gotoNextFrame();
                    }
                    else
                    {
                        if (decoder->getCurrentFrameNumber() < decoder->getNumberOfFrames())
                        {
                            hasMoreFrames = decoder->gotoNextFrame();
                        }
                        else
                        {
                            stream.isPlaying = false;
                            hasMoreFrames = false;
                        }
                    }
                }

                stream.frameNumber = decoder->getCurrentFrameNumber();
                stream.frameCount++;
            }

            // Save decode status for next frame
            stream.doDecodeNextFrame = decodeForNextTick(stream);
        }

        return hasMoreFrames;
    }

    virtual void draw(const Handle handle, const touchgfx::Rect& invalidatedArea, const touchgfx::VideoWidget& widget)
    {
        assert(handle < no_streams);

        if (output_format != Bitmap::RGB565 && output_format != Bitmap::RGB888 && output_format != Bitmap::ARGB8888)
        {
            return;
        }

        if (mjpegDecoders[handle]->hasVideo())
        {
            uint8_t* wbuf = (uint8_t*)touchgfx::HAL::getInstance()->lockFrameBufferForRenderingMethod(touchgfx::HAL::HARDWARE);
            const touchgfx::Rect& absolute = widget.getAbsoluteRect();

            // Get frame buffer pointer to upper left of widget in framebuffer coordinates
            switch (output_format)
            {
            case Bitmap::RGB565:
                wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 2;
                break;
            case Bitmap::RGB888:
                wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 3;
                break;
            case Bitmap::ARGB8888:
                wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 4;
                break;
            default:
                break;
            }

            // Decode relevant part of the frame to the framebuffer
            mjpegDecoders[handle]->decodeFrame(invalidatedArea, wbuf, touchgfx::HAL::FRAME_BUFFER_WIDTH);
            // Release frame buffer
            touchgfx::HAL::getInstance()->unlockFrameBuffer();
        }
    }

    void addDecoder(MJPEGDecoder& decoder, uint32_t index)
    {
        assert(index < no_streams);
        mjpegDecoders[index] = &decoder;
    }

    virtual uint32_t getCurrentFrameNumber(const Handle handle)
    {
        assert(handle < no_streams);
        Stream& stream = streams[handle];

        return stream.frameNumber;
    }

    virtual void getVideoInformation(const Handle handle, touchgfx::VideoInformation* data)
    {
        assert(handle < no_streams);
        mjpegDecoders[handle]->getVideoInfo(data);
    }

    virtual bool getIsPlaying(const Handle handle)
    {
        assert(handle < no_streams);
        Stream& stream = streams[handle];
        return stream.isPlaying;
    }

    virtual void setVideoFrameRateCompensation(const bool allow)
    {
        allowSkipFrames = allow;
    }

private:
    class Stream
    {
    public:
        Stream()
            : frameCount(0), frameNumber(0), tickCount(0),
              frame_rate_video(0), frame_rate_ticks(0),
              seek_to_frame(0),
              isActive(false), isPlaying(false), isShowingOneFrame(false), repeat(true),
              doDecodeNextFrame(false)
        {
        }
        uint32_t frameCount;       // Video frames decoded since play
        uint32_t frameNumber;      // Video frame showed number
        uint32_t tickCount;        // UI frames since play
        uint32_t frame_rate_video; // Ratio of frames wanted counter
        uint32_t frame_rate_ticks; // Ratio of frames wanted divider
        uint32_t seek_to_frame;    // Requested next frame number
        uint32_t skip_frames;      // Number of frames to skip to keep frame rate
        bool isActive;
        bool isPlaying;
        bool isShowingOneFrame;
        bool repeat;
        bool doDecodeNextFrame; // High if we should go to next frame in next tick
    };

    MJPEGDecoder* mjpegDecoders[no_streams];
    Stream streams[no_streams];
    bool allowSkipFrames;

    /**
     * Return true, if new video frame should be decoded for the next tick (keep video decode framerate low)
     */
    bool decodeForNextTick(Stream& stream)
    {
        // Running in UI thread

        // Compare tickCount/frameNumber to frame_rate_ticks/frame_rate_video
        if ((stream.tickCount * stream.frame_rate_video) > (stream.frame_rate_ticks * stream.frameCount))
        {
            if (allowSkipFrames)
            {
                stream.skip_frames = (stream.tickCount * stream.frame_rate_video - stream.frame_rate_ticks * stream.frameCount) / stream.frame_rate_ticks;
                if (stream.skip_frames > 0)
                {
                    stream.skip_frames--;
                }
            }
            return true;
        }
        return false;
    }

    Handle getFreeHandle()
    {
        for (uint32_t i = 0; i < no_streams; i++)
        {
            if (streams[i].isActive == false)
            {
                return static_cast<VideoController::Handle>(i);
            }
        }

        assert(0 && "Unable to find free video stream handle!");
        return static_cast<VideoController::Handle>(0);
    }
};

#endif // TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP
