/***

    Olive - Non-Linear Video Editor
    Copyright (C) 2019  Olive Team

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#ifndef CACHER_H
#define CACHER_H

#define __STDC_FORMAT_MACROS 1
#include <inttypes.h>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
}

#include <memory>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
#include <QMutex>

#include "rendering/clipqueue.h"

class Clip;

/**
 * @brief The Cacher class
 *
 * For footage clips - usually the majority of clips - decoding can be strenuous on CPU and inconsistent in timing. As
 * a result, we keep a memory cache of upcoming frames that we fill in a background thread so they can be retrieved from
 * a rendering thread later. This class is the background thread filling up a clip's frame cache (also called a "queue"
 * since video files are usually stored with frames in linear chronological order). It involves decoding routines to
 * retrieve raw frames from the file (using libavformat/libavcodec), conversion routines to conform the raw frames to
 * RGBA/S16LE for the rest of the workflow (using libavfilter/libswscale/libswresample), and memory handling routines
 * for keeping the cache within limits defined by the user (see Config::upcoming_queue_type).
 *
 * Generally the Cacher workflow starts by calling Open() which will start the thread, open a file handle, and create a
 * decoding instance. Open() is usually called directly from the parent Clip's Clip::Open() and thus expects the
 * Clip::state_change_lock to be locked. It will unlock it when it's finished opening and is ready to start caching,
 * meaning Clip::state_change_lock can be used to synchronize threads.
 *
 * ---
 *
 * **For video:**
 *
 * After the Cacher has finished opening, request a frame by calling Cache(). Cache() will tell the Cacher
 * information about the current playback state, most importantly the current place in time according to the Sequence's
 * playhead. Cache() determines whether the requested frame is already in the queue, and then signals the Cacher thread
 * to cache ahead if there's room in the queue (and also remove old frames that are no longer necessary). To retrieve
 * the requested frame, call Retrieve().
 *
 * If Cache() found the frame already in the queue, Retrieve() will return immediately with this frame. Otherwise
 * Retrieve() may block while the cacher retrieves it. Therefore it is recommended never to call Retrieve() from
 * the main thread. Retrieve() may also return `nullptr` if there was an issue, e.g. the cacher failed to retrieve
 * the frame.
 *
 * **For audio:**
 *
 * After the Cacher has finished opening, calling Cache() will handle most of the work. It will decode the audio,
 * convert to the correct sample rate and format, reverse or adjust speed if necessary, and send it to the audio
 * buffer ready to be played by the output device. It is important to continually call Cache() as it doesn't get
 * signalled when more samples are available in the audio buffer. Instead, it'll check every time it's called and
 * fill as much of the buffer as it can.
 *
 * If the user seeks, ResetAudio() must be called to signal the Cacher to interrupt the current audio stream and
 * move somewhere else before continuing.
 *
 * ---
 *
 * Finally, when the Cacher/parent Clip are no longer in use, call Close() to free all memory and file handling
 * allocated for the cacher. You can choose whether to wait for Close() and all of its child processes to complete -
 * e.g. if you need to change something with the Clip or attached Footage that changes how it opens and want to be
 * thread-safe - or let the Close thread finish up on its own.
 *
 * Cacher expects to be multithreaded and all of its public functions are thread-safe.
 */
class Cacher : public QThread
{
  Q_OBJECT
public:
  /**
   * @brief Cacher Constructor
   *
   * Create Cacher object. The thread is not started here. To start it, call Open().
   *
   * @param c
   */
  Cacher(Clip* c);

  /**
   * @brief The main QThread loop
   *
   * Once the thread has started, all Cacher functions will be called from here until the Cacher closes at which point
   * it will close and exit gracefully.
   */
  void run();

  /**
   * @brief Open the cacher
   *
   * Starts the thread and all file/decode handlers. Really just sets some default values and starts the thread, which
   * will in turn call OpenWorker() at the start of its functions.
   *
   * Make sure Clip::state_change_lock is LOCKED before calling this function as the opening process will try to unlock
   * it when it's finished (leading to a crash if it's not already locked).
   */
  void Open();

  /**
   * @brief Request a frame to be cached
   *
   * For video, this function is part 1 of the Cache()/Retrieve() workflow. It signals the thread to start caching and
   * provides a few other details about the playback state. For optimization it'll also check the frame queue if it
   * already contains the requested frame and use it if so, potentially speeding up Retrieve() later on. Otherwise
   * it'll interrupt any currently caching operation and signal it to start again.
   * While Retrieve() will block until the correct frame is retrieved, this function will return fairly quickly (either
   * immediately if the frame was found in the queue, or once the cacher has restarted caching if not). This means
   * Cache() can be called from another thread and then that other thread can do other work while the cacher is
   * retrieving the frame, finally calling Retrieve() once the frame is absolutely necessary.
   *
   * For audio, this function will do all the work of signalling the thread to start caching and sending samples to
   * the output audio buffer. It's used in tandem with ResetAudio() when the Timeline header is changed abruptly.
   *
   * @param playhead
   *
   * The current Timeline played position in frames
   *
   * @param scrubbing
   *
   * **TRUE** if the user is currently scrubbing. **FALSE** if not.
   *
   * @param nests
   *
   * A hierarchy of nested sequences, if the playback traversed any to get to this clip.
   *
   * @param playback_speed
   *
   * The current playback speed (controlled by Shuttle Left/Stop/Right)
   */
  void Cache(long playhead, bool scrubbing, QVector<Clip*>& nests, int playback_speed);

  /**
   * @brief Retrieve frame requested by Cache()
   *
   * Part 2 of the Cache()/Retrieve() workflow, only used for video. Whichever frame was requested by Cache(), this
   * function will try to retrieve it. In most cases, this function will be pretty quick as the frame will be available
   * immediately from Cache()'s optimization or the cacher thread will be close to retrieving the correct frame anyway.
   * However it does block for however long it takes to retrieve the correct frame (if the cacher is running) so it's
   * not recommended to call this from any main/GUI thread.
   *
   * @return
   *
   * The frame requested by Cache(), or `nullptr` if there was an error (e.g. the cacher wasn't running and no frame was
   * available).
   */
  AVFrame* Retrieve();

  /**
   * @brief Close the cacher and free any allocated memory
   *
   * When the Cacher thread is no longer needed, Close() should be called in order to free system resources. This will
   * signal the thread to exit gracefully, but will not delete the thread object since the cacher may need to be
   * re-opened later by Open().
   *
   * @param wait_for_finish
   *
   * **TRUE** if this function should block the calling thread until the Clip has finished closing. Often necessary if
   * the Clip is being closed specifically to make changes to it.
   */
  void Close(bool wait_for_finish);

  /**
   * @brief Interrupt and reset audio state
   *
   * Used in tandem with Cache(), only for audio clips. Cache() will decode and send audio continually as it's
   * repeatedly called. If the audio stream needs to be interrupted and moved somewhere else for any reason
   * (e.g. the user seeked somewhere else), then it's necessary to call ResetAudio() to signal the cacher to
   * seek to the next place indicated by Cache().
   */
  void ResetAudio();

  /**
   * @brief Retrieve current media width
   *
   * In some situations, the actual media we're using may be a different resolution to how we're treating it (e.g.
   * lower resolution proxies). While most functions will happily treat the media as its original resolution, some
   * processes will need the absolute resolution from the file which can be acquired here.
   *
   * Only call after the thread has been opened by Open().
   *
   * @return
   *
   * The true width of the current video file.
   */
  int media_width();

  /**
   * @brief Retrieve current media height
   *
   * See media_width().
   *
   * Only call after the thread has been opened by Open().
   *
   * @return
   *
   * The true height of the current video file.
   */
  int media_height();

  /**
   * @brief Retrieve media time base
   *
   * For some timing operations, it's necessary to use the source media's timebase. Similar to media_width() and
   * media_height(), we need the accurate timebase from the file as a proxy's timebase may or may not be the same
   * as the source file.
   *
   * Only call after the thread has been opened by Open().
   *
   * @return
   *
   * The timebase of the file.
   */
  AVRational media_time_base();

  /**
   * @brief Get cacher queue object
   *
   * @return
   *
   * A pointer to the cacher's internal frame queue
   */
  ClipQueue* queue();

private:
  /**
   * @brief Reference to the parent clip. Set in the constructor and never changed during this object's lifetime.
   */
  Clip* clip;

  /**
   * @brief Frame queue
   *
   * Valid fames are cached into this, which also does memory handling when necessary.
   */
  ClipQueue queue_;

  /**
   * @brief Main wait condition
   *
   * Used with Clip::cache_lock as the main block while the the Cacher thread isn't running. Wake this condition
   * to start caching.
   */
  QWaitCondition wait_cond_;

  /**
   * @brief Main thread wait condition
   *
   * Used with main_thread_lock_ to block Cache() while waiting for a response from the cacher thread.
   */
  QWaitCondition main_thread_wait_;

  /**
   * @brief Main thread mutex
   *
   * Used with main_thread_wait_ to block Cache() while waiting for a response from the cacher thread.
   */
  QMutex main_thread_lock_;

  /**
   * @brief Retrieve() wait condition
   *
   * Used with retrieve_lock_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet.
   */
  QWaitCondition retrieve_wait_;

  /**
   * @brief Retrieve() mutex
   *
   * Used with retrieve_wait_ to block Retrieve() if the cacher hasn't retrieved the correct frame yet.
   */
  QMutex retrieve_lock_;

  /**
   * @brief Set and used by CacheAudioWorker if the decoder receives an EOF.
   *
   * Deprecated. CacheAudioWorker() is functional but probably should be rewritten.
   */
  bool reached_end;

  /**
   * @brief Current Sequence playhead set by Cache()
   */
  long playhead_;

  /**
   * @brief Current Sequence scrubbing state set by Cache()
   */
  bool scrubbing_;

  /**
   * @brief Current Sequence playback speed set by Cache()
   */
  int playback_speed_;

  /**
   * @brief Current nested Sequence hierarchy set by Cache()
   */
  QVector<Clip*> nests_;

  /**
   * @brief Signal cache to continue operation after one cycle rather than wait for another signal
   *
   * Each cycle of the cacher thread (see run()) will set this to false in the beginning. Each call of Cache() will set
   * this to **TRUE**. If this variable is **TRUE**, the cacher won't wait for another signal before starting the next
   * cache cycle, and will instead just start it.
   *
   * Used if Cache() is called and interrupts the cacher while it's already running so that the cacher will restart
   * itself automatically rather than wait for the next cache signal.
   */
  bool queued_;

  /**
   * @brief Interrupt the current cache cycle
   *
   * A cache cycle will cache several frames at a time. Since decoding can be strenuous and time consuming, the
   * cycle can be interrupted if it needs to abruptly start caching somewhere else. Best used in tandem with
   * queued_ to automatically start the next cache cycle.
   */
  bool interrupt_;

  // ffmpeg media handling
  /**
   * @brief FFmpeg format/file context - used for media decoding
   */
  AVFormatContext* formatCtx;

  /**
   * @brief FFmpeg decoder context - used for media decoding
   */
  AVCodecContext* codecCtx;

  /**
   * @brief FFmpeg stream - used for media decoding
   */
  AVStream* stream;

  /**
   * @brief FFmpeg packet - used for media decoding
   */
  AVPacket* pkt;

  /**
   * @brief FFmpeg frame - used for media decoding
   *
   * This is usually used as a raw decoded frame before the RGBA conversion/AVFilter stack. Converted/filtered frames go
   * into Cacher::queue.
   */
  AVFrame* frame_;

  /**
   * @brief Retrieved frame reference for Retrieve()
   *
   * If a frame was found by either Cache() or CacheVideoWorker(), it's set here. If no frame is ready yet, this is set
   * to `nullptr`.
   */
  AVFrame* retrieved_frame = nullptr;

  // converters/filters
  /**
   * @brief FFmpeg filter stack
   *
   * Used for conversion from the media's pixel format to RGBA for OpenGL. Also any other FFmpeg filters are implemented
   * here if necessary (e.g. yadif for deinterlacing). GLSL effects are preferred when available since FFmpeg filters
   * aren't always fast enough for realtime playback.
   */
  AVFilterGraph* filter_graph;

  /**
   * @brief FFmpeg buffer source
   *
   * Raw decoded frames are added to this for conversion/filtering
   */
  AVFilterContext* buffersrc_ctx;

  /**
   * @brief FFmpeg buffer sink
   *
   * Converted/filtered frames are retrieved from here and sent to Cacher::queue.
   */
  AVFilterContext* buffersink_ctx;

  /**
   * @brief FFmpeg codec reference
   */
  AVCodec* codec;

  /**
   * @brief Options set by the cacher for FFmpeg's decoders (settings like multithreading or other optimizations)
   */
  AVDictionary* opts;

  // audio playback variables
  /**
   * @brief Internal audio reset variable
   *
   * Set by AudioReset() and read by CacheAudioWorker() when the audio state needs to be interrupted and reset.
   */
  bool audio_reset_;

  /**
   * @brief Internal reverse target variable
   *
   * Used by CacheAudioWorker() to stitch audio frames together when reversing. Stores the current frame's timestamp
   * so it knows how much to decode up to when it backtracks and decodes the next samples.
   */
  int64_t reverse_target_;

  /**
   * @brief Internal frame sample index variable
   *
   * Used by CacheAudioWorker() to mark which part of the audio frame to read from
   */
  int frame_sample_index_;

  /**
   * @brief Internal audio buffer write variable
   *
   * Used by CacheAudioWorker() to mark which part of the audio buffer to write to
   */
  qint64 audio_buffer_write;

  /**
   * @brief Internal variable that holds the playhead the last time the audio state was reset
   */
  long audio_target_frame;

  /**
   * @brief Main while loop condition to determine whether thread should continue looping
   *
   * Open() sets this to **TRUE**, Close() sets this to **FALSE**. If it's false, the main loop in run() will exit and
   * the thread will exit cleanly. It's not recommended to set this variable directly, use Open() and Close() instead.
   */
  bool caching_;

  /**
   * @brief Internal variable for whether the current Cacher state is valid or not
   *
   * If there was an error opening the Cacher for any reason, this will be false.
   */
  bool is_valid_state_;

  /**
   * @brief Internal function for opening the file handles and decoder
   *
   * After the thread has started, it'll call this function to start all resources necessary for caching. Any
   * FFmpeg decoding variables and filters are set up here.
   *
   * This is
   * fundamentally different from Open(), this is only meant to be called within the cacher thread and never from
   * outside and doesn't start the thread like Open() does.
   */
  void OpenWorker();

  /**
   * @brief Internal function for starting a cache cycle
   *
   * This used to have more function, but now just differentiates between CacheVideoWorker() for video clips and
   * CacheAudioWorker() for audio clips.
   */
  void CacheWorker();

  /**
   * @brief Internal function for closing cacher
   *
   * Called if the main thread loop in run() exits by setting caching_ to **FALSE**. Free's up handles and memory
   * allocated by OpenWorker().
   */
  void CloseWorker();

  /**
   * @brief Internal function for resetting audio state
   *
   * This used to be a common function, but is now simply a legacy function for CacheAudioWorker(). Resets and
   * flushes decoders and seeks to the correct timestamp.
   */
  void Reset();

  /**
   * @brief Internal function for setting retrieved_frame and waking up any threads waiting for it
   *
   * @param f
   *
   * The frame to set as the retrieved frame.
   */
  void SetRetrievedFrame(AVFrame* f);

  /**
   * @brief Internal function to wake an external calling thread
   *
   * In some situations, Cache() may wait for the cacher to respond before returning. This is to assist in thread
   * synchronization, making sure the cacher has started working and has locked any resources it needs before any
   * other threads can access them (e.g. with a function like Retrieve() ). This must be called at the start of
   * any CacheVideoWorker() or CacheAudioWorker() control paths to ensure the render thread doesn't get stuck.
   */
  void WakeMainThread();

  /**
   * @brief Retrieve frame from decoder
   *
   * Retrieves the next decoded frame from the decoder. Depending on the source media, this frame may or may not be
   * suitable for usage later in the pipeline as it may or may not be the correct pixel/sample format. For a suitable
   * frame for the pipeline, use RetrieveFrameAndProcess() instead (which in turn uses this function anyway).
   *
   * @param f
   *
   * Frame buffer to decode frame into
   *
   * @return
   *
   * FFmpeg error code (>= 0 on success, a negative error code on failure)
   */
  int RetrieveFrameFromDecoder(AVFrame* f);

  /**
   * @brief Retrieve frame from decoder and run it through filter stack
   *
   * Retrieves the next decoded frame and runs it through the AVFilter stack to create an RGBA frame compatible with
   * the rest of the pipeline and OpenGL. Use this function if you need a ready-made frame.
   *
   * @param f
   *
   * A pointer to an AVFrame object. It does not need to be allocated, as this function allocates an AVFrame itself.
   * You'll also need to free it later with av_frame_free() (though ClipQueue will do this automatically if the frame is
   * added to it).
   *
   * @return
   *
   * FFmpeg error code (>= 0 on success, a negative error code on failure)
   */
  int RetrieveFrameAndProcess(AVFrame **f);

  /**
   * @brief Internal video caching function
   *
   * Performs one video cache cycle. Seeks the media and cleans old frames from the queue if necessary. Decodes frames
   * and adds them to the queue (after calculating whether they're necessary).
   */
  void CacheVideoWorker();

  /**
   * @brief Internal audio caching function
   *
   * Perform one audio cache cycle. Retrieves audio from decoder, reverses and changes speed if necessary, and sends
   * audio to the audio buffer which will later be sent to the audio output device.
   */
  void CacheAudioWorker();

  /**
   * @brief Internal function using the Cacher's known information to determine whether this media is playing in reverse
   */
  bool IsReversed();
};

#endif // CACHER_H
