/*
 * Copyright (c) 2013 Nicolas George
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef AVFILTER_FRAMESYNC_H
#define AVFILTER_FRAMESYNC_H

#include "bufferqueue.h"

enum EOFAction {
    EOF_ACTION_REPEAT,
    EOF_ACTION_ENDALL,
    EOF_ACTION_PASS
};

/*
 * TODO
 * Export convenient options.
 */

/**
 * This API is intended as a helper for filters that have several video
 * input and need to combine them somehow. If the inputs have different or
 * variable frame rate, getting the input frames to match requires a rather
 * complex logic and a few user-tunable options.
 *
 * In this API, when a set of synchronized input frames is ready to be
 * procesed is called a frame event. Frame event can be generated in
 * response to input frames on any or all inputs and the handling of
 * situations where some stream extend beyond the beginning or the end of
 * others can be configured.
 *
 * The basic working of this API is the following: set the on_event
 * callback, then call ff_framesync_activate() from the filter's activate
 * callback.
 */

/**
 * Stream extrapolation mode
 *
 * Describe how the frames of a stream are extrapolated before the first one
 * and after EOF to keep sync with possibly longer other streams.
 */
enum FFFrameSyncExtMode {

    /**
     * Completely stop all streams with this one.
     */
    EXT_STOP,

    /**
     * Ignore this stream and continue processing the other ones.
     */
    EXT_NULL,

    /**
     * Extend the frame to infinity.
     */
    EXT_INFINITY,
};

/**
 * Input stream structure
 */
typedef struct FFFrameSyncIn {

    /**
     * Extrapolation mode for timestamps before the first frame
     */
    enum FFFrameSyncExtMode before;

    /**
     * Extrapolation mode for timestamps after the last frame
     */
    enum FFFrameSyncExtMode after;

    /**
     * Time base for the incoming frames
     */
    AVRational time_base;

    /**
     * Current frame, may be NULL before the first one or after EOF
     */
    AVFrame *frame;

    /**
     * Next frame, for internal use
     */
    AVFrame *frame_next;

    /**
     * PTS of the current frame
     */
    int64_t pts;

    /**
     * PTS of the next frame, for internal use
     */
    int64_t pts_next;

    /**
     * Boolean flagging the next frame, for internal use
     */
    uint8_t have_next;

    /**
     * State: before first, in stream or after EOF, for internal use
     */
    uint8_t state;

    /**
     * Synchronization level: frames on input at the highest sync level will
     * generate output frame events.
     *
     * For example, if inputs #0 and #1 have sync level 2 and input #2 has
     * sync level 1, then a frame on either input #0 or #1 will generate a
     * frame event, but not a frame on input #2 until both inputs #0 and #1
     * have reached EOF.
     *
     * If sync is 0, no frame event will be generated.
     */
    unsigned sync;

} FFFrameSyncIn;

/**
 * Frame sync structure.
 */
typedef struct FFFrameSync {
    const AVClass *class;

    /**
     * Parent filter context.
     */
    AVFilterContext *parent;

    /**
     * Number of input streams
     */
    unsigned nb_in;

    /**
     * Time base for the output events
     */
    AVRational time_base;

    /**
     * Timestamp of the current event
     */
    int64_t pts;

    /**
     * Callback called when a frame event is ready
     */
    int (*on_event)(struct FFFrameSync *fs);

    /**
     * Opaque pointer, not used by the API
     */
    void *opaque;

    /**
     * Index of the input that requires a request
     */
    unsigned in_request;

    /**
     * Synchronization level: only inputs with the same sync level are sync
     * sources.
     */
    unsigned sync_level;

    /**
     * Flag indicating that a frame event is ready
     */
    uint8_t frame_ready;

    /**
     * Flag indicating that output has reached EOF.
     */
    uint8_t eof;

    /**
     * Pointer to array of inputs.
     */
    FFFrameSyncIn *in;

    int opt_repeatlast;
    int opt_shortest;
    int opt_eof_action;

} FFFrameSync;

/**
 * Get the class for the framesync object.
 */
const AVClass *framesync_get_class(void);

/**
 * Pre-initialize a frame sync structure.
 *
 * It sets the class pointer and inits the options to their default values.
 * The entire structure is expected to be already set to 0.
 * This step is optional, but necessary to use the options.
 */
void ff_framesync_preinit(FFFrameSync *fs);

/**
 * Initialize a frame sync structure.
 *
 * The entire structure is expected to be already set to 0 or preinited.
 *
 * @param  fs      frame sync structure to initialize
 * @param  parent  parent AVFilterContext object
 * @param  nb_in   number of inputs
 * @return  >= 0 for success or a negative error code
 */
int ff_framesync_init(FFFrameSync *fs, AVFilterContext *parent, unsigned nb_in);

/**
 * Configure a frame sync structure.
 *
 * Must be called after all options are set but before all use.
 *
 * @return  >= 0 for success or a negative error code
 */
int ff_framesync_configure(FFFrameSync *fs);

/**
 * Free all memory currently allocated.
 */
void ff_framesync_uninit(FFFrameSync *fs);

/**
 * Get the current frame in an input.
 *
 * @param fs      frame sync structure
 * @param in      index of the input
 * @param rframe  used to return the current frame (or NULL)
 * @param get     if not zero, the calling code needs to get ownership of
 *                the returned frame; the current frame will either be
 *                duplicated or removed from the framesync structure
 */
int ff_framesync_get_frame(FFFrameSync *fs, unsigned in, AVFrame **rframe,
                            unsigned get);

/**
 * Examine the frames in the filter's input and try to produce output.
 *
 * This function can be the complete implementation of the activate
 * method of a filter using framesync.
 */
int ff_framesync_activate(FFFrameSync *fs);

/**
 * Initialize a frame sync structure for dualinput.
 *
 * Compared to generic framesync, dualinput assumes the first input is the
 * main one and the filtering is performed on it. The first input will be
 * the only one with sync set and generic timeline support will just pass it
 * unchanged when disabled.
 *
 * Equivalent to ff_framesync_init(fs, parent, 2) then setting the time
 * base, sync and ext modes on the inputs.
 */
int ff_framesync_init_dualinput(FFFrameSync *fs, AVFilterContext *parent);

/**
 * @param f0  used to return the main frame
 * @param f1  used to return the second frame, or NULL if disabled
 * @return  >=0 for success or AVERROR code
 */
int ff_framesync_dualinput_get(FFFrameSync *fs, AVFrame **f0, AVFrame **f1);

/**
 * Same as ff_framesync_dualinput_get(), but make sure that f0 is writable.
 */
int ff_framesync_dualinput_get_writable(FFFrameSync *fs, AVFrame **f0, AVFrame **f1);

#define FRAMESYNC_DEFINE_CLASS(name, context, field) \
static int name##_framesync_preinit(AVFilterContext *ctx) { \
    context *s = ctx->priv; \
    ff_framesync_preinit(&s->field); \
    return 0; \
} \
static const AVClass *name##_child_class_next(const AVClass *prev) { \
    return prev ? NULL : framesync_get_class(); \
} \
static void *name##_child_next(void *obj, void *prev) { \
    context *s = obj; \
    s->fs.class = framesync_get_class(); /* FIXME */ \
    return prev ? NULL : &s->field; \
} \
static const AVClass name##_class = { \
    .class_name       = #name, \
    .item_name        = av_default_item_name, \
    .option           = name##_options, \
    .version          = LIBAVUTIL_VERSION_INT, \
    .category         = AV_CLASS_CATEGORY_FILTER, \
    .child_class_next = name##_child_class_next, \
    .child_next       = name##_child_next, \
}

#endif /* AVFILTER_FRAMESYNC_H */
