/*****************************************************************************
 * vlc_vout_display.h: vout_display_t definitions
 *****************************************************************************
 * Copyright (C) 2009 Laurent Aimar
 *
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
 *
 * This program 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.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifndef VLC_VOUT_DISPLAY_H
#define VLC_VOUT_DISPLAY_H 1

#include <vlc_es.h>
#include <vlc_picture.h>
#include <vlc_mouse.h>
#include <vlc_vout.h>
#include <vlc_window.h>
#include <vlc_viewpoint.h>

struct vlc_render_subpicture;

/**
 * \defgroup video_display Video output display
 * Video output display: output buffers and rendering
 *
 * \ingroup video_output
 * @{
 * \file
 * Video output display modules interface
 */

typedef struct vout_display_t vout_display_t;
typedef struct vout_display_owner_t vout_display_owner_t;

/**
 * \defgroup video_align Video alignment
 * @{
 */
#define VLC_VIDEO_ALIGN_CENTER 0
#define VLC_VIDEO_ALIGN_LEFT   1
#define VLC_VIDEO_ALIGN_RIGHT  2
#define VLC_VIDEO_ALIGN_TOP    1
#define VLC_VIDEO_ALIGN_BOTTOM 2

/**
 * Video alignment within the display.
 */
typedef struct vlc_video_align {
    /**
     * Horizontal alignment.
     *
     * This must be one of \ref VLC_VIDEO_ALIGN_CENTER,
     * \ref VLC_VIDEO_ALIGN_LEFT or \ref VLC_VIDEO_ALIGN_RIGHT.
     */
    char horizontal;

    /**
     * Vectical alignment.
     *
     * This must be one of \ref VLC_VIDEO_ALIGN_CENTER,
     * \ref VLC_VIDEO_ALIGN_TOP or \ref VLC_VIDEO_ALIGN_BOTTOM.
     */
    char vertical;
} vlc_video_align_t;
/** @} */

/**
 * Video automatic scale fitting.
 */
enum vlc_video_fitting {
    VLC_VIDEO_FIT_NONE /**< No automatic scaling (use explicit zoom ratio) */,
    VLC_VIDEO_FIT_SMALLER /**< Fit inside / to smallest dimension */,
    VLC_VIDEO_FIT_LARGER /**< Fit outside / to largest dimension */,
    VLC_VIDEO_FIT_WIDTH /**< Fit to width */,
    VLC_VIDEO_FIT_HEIGHT /**< Fit to height */,
};

/**
 * Display placement and zoom configuration.
 */
struct vout_display_placement {
    unsigned width; /**< Requested display pixel width (0 by default). */
    unsigned height; /**< Requested display pixel height (0 by default). */
    vlc_rational_t sar; /**< Requested sample aspect ratio */

    vlc_video_align_t align; /**< Alignment within the window */
    enum vlc_video_fitting fitting; /**< Scaling/fitting mode */
    vlc_rational_t zoom; /**< Zoom ratio (if fitting is disabled) */
    bool full_fill; /**< whether the rendering will take the whole display */
};

/**
 * User configuration for a video output display (\ref vout_display_t)
 *
 * This primarily controls the size of the display area within the video
 * window, as follows:
 * - If \ref vout_display_placement::fitting is not disabled, ie. equals
 *   to \ref VLC_VIDEO_FIT_NONE, in \ref vout_display_cfg::display, the
 *   video size is fitted to the display size.
 * - If \ref vout_display_cfg::window "window" size is valid, the video size
 *   is set to the window size,
 * - Otherwise, the video size is determined from the original video format,
 *   multiplied by the zoom factor.
 */
typedef struct vout_display_cfg {
    struct vlc_window *window; /**< Window */
    struct vout_display_placement display; /**< Display placement properties */
    vlc_icc_profile_t *icc_profile; /**< Currently active ICC profile */
    /** Final source projection requested for display. */
    video_projection_mode_t projection;

    /** Initial viewpoint when projection != PROJECTION_MODE_RECTANGULAR */
    vlc_viewpoint_t viewpoint;
    vlc_stereoscopic_mode_t stereo_mode; /**< Stereoscopic/3D mode used by the display */
} vout_display_cfg_t;

/**
 * Information from a vout_display_t to configure
 * the core behaviour.
 *
 * By default they are all false or NULL.
 *
 */
typedef struct {
    const vlc_fourcc_t *subpicture_chromas; /* List of supported chromas for subpicture rendering. */
} vout_display_info_t;

/**
 * Vout owner structures
 */
struct vout_display_owner_t {
    /* Private place holder for the vout_display_t creator
     */
    void *sys;

    /* Event coming from the module
     *
     * This function is set prior to the module instantiation and must not
     * be overwritten nor used directly (use the vout_display_SendEvent*
     * wrapper.
     *
     * You can send it at any time i.e. from any vout_display_t functions or
     * from another thread.
     * Be careful, it does not ensure correct serialization if it is used
     * from multiple threads.
     */
    void (*viewpoint_moved)(void *sys, const vlc_viewpoint_t *vp);
};

/**
 * "vout display" open callback
 *
 * @param vd vout display context
 * @param fmtp It can be changed by the module to request a different format.
 * @param context The video context to configure the display for.
 * @return VLC_SUCCESS or a VLC error code
 */
typedef int (*vout_display_open_cb)(vout_display_t *vd,
                                    video_format_t *fmtp,
                                    vlc_video_context *context);

#define set_callback_display(activate, priority) \
    { \
        vout_display_open_cb open__ = activate; \
        (void) open__; \
        set_callback(activate) \
    } \
    set_capability( "vout display", priority )

struct vout_display_place_t;

struct vlc_display_operations
{
    /**
     * Destroys the display.
     */
    void       (*close)(vout_display_t *);

    /**
     * Prepares a picture and an optional subpicture for display (optional).
     *
     * This callback is called once a picture buffer content is ready,
     * as far in advance as possible to the intended display time,
     * but only after the previous picture was displayed.
     *
     * The callback should perform any preprocessing operation that will not
     * actually cause the picture to be shown, such as blending the subpicture
     * or upload the picture to video memory. If supported, this can also
     * queue the picture to be shown asynchronously at the given date.
     *
     *
     * If \ref vlc_display_operations.prepare and
     * \ref vlc_display_operations.display are not \c NULL, there is an
     * implicit guarantee that display will be invoked with the exact same
     * picture afterwards:
     * prepare 1st picture, display 1st picture, prepare 2nd picture, display
     * 2nd picture, and so on.
     *
     * \note The picture buffers may have multiple references.
     * Therefore the pixel content of the picture or of the subpicture
     * must not be changed.
     *
     * \param pic picture
     * \param subpic subpicture to render over the picture
     * \param date time when the picture is intended to be shown
     */
    void       (*prepare)(vout_display_t *, picture_t *pic,
                          const struct vlc_render_subpicture *subpic, vlc_tick_t date);

    /**
     * Displays a picture.
     *
     * This callback is invoked at the time when the picture should be shown.
     * The picture must be displayed as soon as possible.
     *
     * If NULL, prepare must be valid. In that case, the plugin can handle
     * asynchronous display at the time given by the prepare call.
     *
     * \note The picture buffers may have multiple references.
     * Therefore the pixel content of the picture or of the subpicture
     * must not be changed.
     */
    void       (*display)(vout_display_t *, picture_t *pic);

    /**
     * Let the display module know the display size has changed.
     *
     * \return VLC_SUCCESS if the size is accepted.
     * \return an error if the size is not accepted and
     * \ref vlc_display_operations::reset_pictures "reset_pictures" needs to be called.
     *
     * When the callback is NULL, it is considered as returning VLC_SUCCESS.
     */
    int (*set_display_size)(vout_display_t *, unsigned width, unsigned height);

    /**
     * Reset the picture format handled by the module.
     * This occurs after an error in \ref vlc_display_operations::set_display_size,
     * \ref vlc_display_operations::set_source_aspect,
     * \ref vlc_display_operations::set_source_crop or
     * \ref vlc_display_operations::video_place_changed
     * calls returns an error.
     *
     * \param ftmp video format that the module expects as input
     */
    int       (*reset_pictures)(vout_display_t *, video_format_t *fmtp);

    /**
     * Notifies a change of VR/360° viewpoint.
     *
     * May be NULL.
     *
     * \param vp viewpoint to use on the next render
     */
    int        (*set_viewpoint)(vout_display_t *, const vlc_viewpoint_t *vp);

    /**
     * Notifies a change in output ICC profile.
     *
     * May be NULL. Memory owned by the caller.
     *
     * \param prof new ICC profile associated with display, or NULL for none
     */
    void       (*set_icc_profile)(vout_display_t *, const vlc_icc_profile_t *prof);

    /**
     * Notifies a change in the input format.
     *
     * The format size is not expected to change.
     *
     * \param fmt the requested input format
     * \param ctx the video context
     * \return VLC_SUCCESS on success, another value on error
     */
    int (*update_format)(vout_display_t *, const video_format_t *fmt,
                         vlc_video_context *ctx);

    /**
     * Set the source projection used by the display.
     *
     * May be NULL.
     *
     * \param display the display to change projection for
     * \param projection the new projection mode considered for the source
     * \return VLC_SUCCESS on succes, another value if changing projection failed
     */
    int (*change_source_projection)(vout_display_t *display,
                                    video_projection_mode_t projection);

    /**
     * Notifies the new Stereoscopic 3D mode in \ref vout_display_cfg_t changed.
     *
     * May be NULL.
     *
     * \retval VLC_SUCCESS if the display handled the change
     * \retval VLC_EGENERIC if the display handled the change was not handled
     */
    int        (*set_stereo)(vout_display_t *, vlc_stereoscopic_mode_t);

    /**
     * Let the display module know the placement of the video in the display changed.
     *
     * May be NULL.
     *
     * This is always called from the same thread as prepare/display.
     *
     * \return VLC_SUCCESS if the placement change is accepted.
     * \return an error if the placement is not accepted and
     * \ref vlc_display_operations::reset_pictures "reset_pictures" needs to be called.
     *
     * When the callback is NULL, it is considered as returning VLC_SUCCESS.
     */
    int (*video_place_changed)(vout_display_t *, const struct vout_display_place_t *);

    /**
     * Notifies a change of the sample aspect ratio.
     *
     * May be NULL.
     *
     * \return VLC_SUCCESS on success, another value on error
     * \return VLC_EGENERIC if a \ref vlc_display_operations::reset_pictures
     *         request is necessary
     *
     * When the callback is NULL, it is considered as returning VLC_SUCCESS.
     */
    int (*set_source_aspect)(vout_display_t *, const video_format_t *);

    /**
     * Notifies a change of the video crop values.
     *
     * The cropping requested is stored by source \ref video_format_t `i_x`/`y_offset`
     * and `i_visible_width`/`height`
     *
     * May be NULL.
     *
     * \return VLC_SUCCESS on success, another value on error
     * \return VLC_EGENERIC if a \ref vlc_display_operations::reset_pictures
     *         request is necessary
     *
     * When the callback is NULL, it is considered as returning VLC_SUCCESS.
     */
    int (*set_source_crop)(vout_display_t *, const video_format_t *);
};

/**
 * Video placement.
 *
 * This structure stores the result of a vout_display_PlacePicture() call.
 */
typedef struct vout_display_place_t {
    int x; /*< Relative pixel offset from the display left edge */
    int y; /*< Relative pixel offset from the display top edge */
    unsigned width; /*< Picture pixel width */
    unsigned height; /*< Picture pixel height */
} vout_display_place_t;

struct vout_display_t {
    struct vlc_object_t obj;

    /**
     * User configuration.
     *
     * This cannot be modified directly. It reflects the current values.
     */
    const vout_display_cfg_t *cfg;

    /**
     * Source video format.
     *
     * This is the format of the video that is being displayed (after decoding
     * and filtering). It cannot be modified.
     *
     * \note
     * Cropping is not requested while in the open function.
     */
    const video_format_t *source;

    /**
     * Placement of the source picture in the display. (cannot be NULL)
     *
     * This cannot be modified directly and cannot be NULL.
     * It reflects the current values.
     */
    const vout_display_place_t *place;

    /**
     * Picture format.
     *
     * This is the format of the pictures that are supplied to the
     * \ref vlc_display_operations::prepare "prepare" and
     * \ref vlc_display_operations::display "display" callbacks.
     * Ideally, it should be identical or as close as possible as \ref source.
     *
     * This can only be changed from the display module activation callback,
     * or within a \ref vlc_display_operations::reset_pictures "reset_pictures"
     * request.
     *
     * By default, it is equal to \ref source except for the aspect ratio
     * which is undefined(0) and is ignored.
     */
    const video_format_t *fmt;

    /* Information
     *
     * You can only set them in the open function.
     */
    vout_display_info_t info;

    /* Reserved for the vout_display_t owner.
     *
     * It must not be overwritten nor used directly by a module.
     */
    vout_display_owner_t owner;

    /**
     * Private data for the display module.
     *
     * A module is free to use it as it wishes.
     */
    void *sys;

    /**
     * Callbacks the display module must set on Open.
     */
    const struct vlc_display_operations *ops;
};

/**
 * Creates video output display.
 */
VLC_API
vout_display_t *vout_display_New(vlc_object_t *,
    const video_format_t *, vlc_video_context *,
    const vout_display_cfg_t *, const char *module,
    const vout_display_owner_t *);

/**
 * Destroys a video output display.
 */
VLC_API void vout_display_Delete(vout_display_t *);

/**
 * Prepares a picture for display.
 *
 * This renders a picture for subsequent display, with vout_display_Display().
 *
 * \note A reference to the input picture is consumed by the function, which
 * returns a reference to an output picture for display. The input and output
 * picture may or may not be equal depending on the underlying display setup.
 *
 * \bug Currently, only one picture can be prepared at a time. It must be
 * displayed with vout_display_Display() before any picture is prepared or
 * before the display is destroyed with vout_display_Delete().
 *
 \ bug Rendering subpictures is not supported with this function yet.
 * \c subpic must be @c NULL .
 *
 * \param vd display to prepare the picture for
 * \param picture picure to be prepared
 * \param subpic reserved, must be NULL
 * \param date intended time to show the picture
 * \return The prepared picture is returned, NULL on error.
 */
VLC_API picture_t *vout_display_Prepare(vout_display_t *vd, picture_t *picture,
                                        const struct vlc_render_subpicture *subpic, vlc_tick_t date);

/**
 * Displays a picture.
 */
static inline void vout_display_Display(vout_display_t *vd, picture_t *picture)
{
    if (vd->ops->display != NULL)
        vd->ops->display(vd, picture);
}

VLC_API
void vout_display_SetSize(vout_display_t *vd, unsigned width, unsigned height);

static inline void vout_display_SendEventMousePressed(vout_display_t *vd, int button)
{
    vlc_window_ReportMousePressed(vd->cfg->window, button);
}
static inline void vout_display_SendEventMouseReleased(vout_display_t *vd, int button)
{
    vlc_window_ReportMouseReleased(vd->cfg->window, button);
}
static inline void vout_display_SendEventViewpointMoved(vout_display_t *vd,
                                                        const vlc_viewpoint_t *vp)
{
    if (vd->owner.viewpoint_moved)
        vd->owner.viewpoint_moved(vd->owner.sys, vp);
}

/**
 * Helper function that applies the necessary transforms to the mouse position
 * and then calls vout_display_SendEventMouseMoved.
 *
 * \param vd vout_display_t.
 * \param m_x Mouse x position (relative to place, origin is top left).
 * \param m_y Mouse y position (relative to place, origin is top left).
 */
static inline void vout_display_SendMouseMovedDisplayCoordinates(vout_display_t *vd, int m_x, int m_y)
{
    vlc_window_ReportMouseMoved(vd->cfg->window, m_x, m_y);
}

static inline bool vout_display_cfg_IsWindowed(const vout_display_cfg_t *cfg)
{
    return cfg->window->type != VLC_WINDOW_TYPE_DUMMY;
}

static inline int vout_display_ChangeProjection(vout_display_t *vd, video_projection_mode_t projection)
{
    if (vd->ops->change_source_projection == NULL)
        return VLC_ENOTSUP;
    return vd->ops->change_source_projection(vd, projection);
}

/**
 * Computes the default display size given the source and
 * the display configuration.
 *
 * This assumes that the picture is already cropped.
 */
VLC_API
void vout_display_GetDefaultDisplaySize(unsigned *width, unsigned *height,
                                        const video_format_t *source,
                                        const struct vout_display_placement *);

/**
 * Compares two \ref vout_display_place_t.
 */
static inline bool vout_display_PlaceEquals(const vout_display_place_t *p1,
                                            const vout_display_place_t *p2)
{
    return p1->x == p2->x && p1->width == p2->width &&
            p1->y == p2->y && p1->height == p2->height;
}

/**
 * Computes the intended picture placement inside the display.
 *
 * This function computes where to show a picture inside the display with
 * respect to the provided parameters, and returns the result
 * in a \ref vout_display_place_t structure.
 *
 * This assumes that cropping is done by an external mean.
 *
 * \param place Storage space for the picture placement [OUT]
 * \param source Video source format
 * \param cfg Display configuration
 */
VLC_API
void vout_display_PlacePicture(vout_display_place_t *restrict place,
                               const video_format_t *restrict source,
                               const struct vout_display_placement *cfg);

/**
 * Translates coordinates.
 *
 * This translates coordinates from window pixel coordinate space to
 * original video sample coordinate space.
 *
 * \param x pointer to abscissa to be translated
 * \param y pointer to ordinate to be translated
 * \param fmt video format
 * \param dp display configuration
 */
void vout_display_TranslateCoordinates(int *x, int *y,
                                       const video_format_t *fmt,
                                       const struct vout_display_placement *dp);

/** @} */
#endif /* VLC_VOUT_DISPLAY_H */
