/*****************************************************************************
 * player.h: Player internal interface
 *****************************************************************************
 * Copyright © 2018-2019 VLC authors and VideoLAN
 *
 * 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_PLAYER_INTERNAL_H
#define VLC_PLAYER_INTERNAL_H

#include <vlc_player.h>
#include <vlc_list.h>
#include <vlc_vector.h>
#include <vlc_atomic.h>
#include <vlc_media_library.h>
#include <vlc_vout_osd.h>

#include "input/input_internal.h"

struct vlc_player_track_priv
{
    struct vlc_player_track t;
    vout_thread_t *vout; /* weak reference */
    vlc_tick_t delay;
    /* only valid if selected and if category is VIDEO_ES or SPU_ES */
    enum vlc_vout_order vout_order;
    /* Used to save or not the track selection */
    bool selected_by_user;
};

typedef struct VLC_VECTOR(struct vlc_player_program *)
    vlc_player_program_vector;

typedef struct VLC_VECTOR(struct vlc_player_track_priv *)
    vlc_player_track_vector;

struct vlc_player_title_list
{
    vlc_atomic_rc_t rc;
    size_t count;
    struct vlc_player_title array[];
};

struct vlc_player_input
{
    input_thread_t *thread;
    vlc_player_t *player;
    bool started;

    /* Monitor the OPENING_S -> PLAYING_S transition. */
    bool playing;

    enum vlc_player_state state;
    enum vlc_player_error error;
    enum vlc_player_media_stopping_reason stopping_reason;
    float rate;
    int capabilities;
    vlc_tick_t length;
    bool live;

    double position;
    vlc_tick_t time;
    vlc_tick_t normal_time;

    vlc_tick_t pause_date;

    bool recording;

    float signal_quality;
    float signal_strength;
    float cache;

    struct input_stats_t stats;

    vlc_tick_t cat_delays[DATA_ES];

    vlc_player_program_vector program_vector;
    vlc_player_track_vector video_track_vector;
    vlc_player_track_vector audio_track_vector;
    vlc_player_track_vector spu_track_vector;
    const struct vlc_player_track_priv *teletext_source;

    struct vlc_player_title_list *titles;

    size_t title_selected;
    size_t chapter_selected;

    struct vlc_list node;

    bool teletext_enabled;
    bool teletext_transparent;
    unsigned teletext_page;

    struct
    {
        vlc_tick_t time;
        double pos;
        bool set;
    } abloop_state[2];

    struct
    {
        vlc_ml_playback_states_all states;
        enum
        {
            VLC_RESTOREPOINT_TITLE,
            VLC_RESTOREPOINT_POSITION,
            VLC_RESTOREPOINT_NONE,
        } restore;
        float pos;
        bool restore_states;
        bool delay_restore;
        /* Keep a trace of tracks as they appear since they won't be available
         * for probing when the input gets stopped
         */
        bool has_video_tracks;
        bool has_audio_tracks;
    } ml;

    unsigned repeat;
};

struct vlc_player_listener_id
{
    const struct vlc_player_cbs *cbs;
    void *cbs_data;
    struct vlc_list node;
};

struct vlc_player_metadata_listener_id
{
    const union vlc_player_metadata_cbs *cbs;
    void *cbs_data;

    enum vlc_player_metadata_option option;
    union
    {
        vlc_audio_meter_plugin *audio_meter;
    };
    struct vlc_list node;
};

struct vlc_player_vout_listener_id
{
    const struct vlc_player_vout_cbs *cbs;
    void *cbs_data;
    struct vlc_list node;
};

struct vlc_player_aout_listener_id
{
    const struct vlc_player_aout_cbs *cbs;
    void *cbs_data;
    struct vlc_list node;
};

enum vlc_player_timer_source_type
{
    VLC_PLAYER_TIMER_TYPE_BEST,
    VLC_PLAYER_TIMER_TYPE_SMPTE,
    VLC_PLAYER_TIMER_TYPE_COUNT
};

struct vlc_player_timer_id
{
    vlc_tick_t period;
    vlc_tick_t last_update_date;

    union
    {
        const struct vlc_player_timer_cbs *cbs;
        const struct vlc_player_timer_smpte_cbs *smpte_cbs;
    };
    void *data;

    struct vlc_list node;
};

struct vlc_player_timer_source
{
    struct vlc_list listeners; /* list of struct vlc_player_timer_id */
    vlc_es_id_t *es; /* weak reference */
    struct vlc_player_timer_point point;
    bool seeking;

    /* Set once a pause has been reported to this source's listener.
     * The input might signal paused clock and then output
     * would signal it. This flag filters this case here. */
    bool pause_reported;

    union
    {
        struct {
            unsigned long last_framenum;
            unsigned frame_rate;
            unsigned frame_rate_base;
            unsigned frame_resolution;
            unsigned df_fps;
            int df;
            int frames_per_10mins;
        } smpte;
    };
};

enum vlc_player_timer_event
{
    VLC_PLAYER_TIMER_EVENT_PLAYING,
    VLC_PLAYER_TIMER_EVENT_PAUSED,
    VLC_PLAYER_TIMER_EVENT_DISCONTINUITY,
    VLC_PLAYER_TIMER_EVENT_STOPPING,
};

struct vlc_player_timer
{
    vlc_mutex_t lock;

    bool input_live;
    vlc_tick_t input_length;
    vlc_tick_t input_normal_time;
    vlc_tick_t last_ts;
    vlc_tick_t start_offset;
    double input_position;

    vlc_tick_t seek_ts;
    double seek_position;
    enum
    {
        UPDATE_STATE_RESUMED,
        UPDATE_STATE_PAUSED,
        UPDATE_STATE_RESUMING,
    } update_state;
    vlc_tick_t pause_date;
    bool stopping;

    struct vlc_player_timer_source sources[VLC_PLAYER_TIMER_TYPE_COUNT];
#define best_source sources[VLC_PLAYER_TIMER_TYPE_BEST]
#define smpte_source sources[VLC_PLAYER_TIMER_TYPE_SMPTE]
};

struct vlc_player_t
{
    struct vlc_object_t obj;
    vlc_mutex_t lock;
    vlc_mutex_t metadata_listeners_lock;
    vlc_mutex_t aout_listeners_lock;
    vlc_mutex_t vout_listeners_lock;
    vlc_cond_t start_delay_cond;

    bool start_paused;
    bool play_and_pause;

    bool pause_on_cork;
    bool corked;

    struct vlc_list listeners;
    struct vlc_list metadata_listeners;
    struct vlc_list aout_listeners;
    struct vlc_list vout_listeners;

    input_resource_t *resource;
    vlc_renderer_item_t *renderer;

    input_item_t *media;
    struct vlc_player_input *input;

    bool releasing_media;
    input_item_t *next_media;

    char *video_string_ids;
    char *audio_string_ids;
    char *sub_string_ids;

    enum vlc_player_state global_state;
    bool started;

    /**
     * Playing a tiny stream (either empty, or with unreported errors) in a loop
     * would cause high CPU usage. To mitigate the problem, temporize if
     * several EOS are received too quickly.
     */
#define VLC_PLAYER_EOS_BURST_THRESHOLD VLC_TICK_FROM_MS(250)
    vlc_tick_t last_eos;
    unsigned eos_burst_count;
    unsigned repeat;

    bool deleting;
    struct
    {
        vlc_thread_t thread;
        vlc_cond_t wait;
        vlc_cond_t notify;
        struct vlc_list stop_inputs;
        struct vlc_list stopping_inputs;
        struct vlc_list joinable_inputs;
    } mainloop;

    struct vlc_player_timer timer;
};

#ifndef NDEBUG
/*
 * Assert that the player mutex is locked.
 *
 * This is exposed in this internal header because the playlist and its
 * associated player share the lock to avoid lock-order inversion issues.
 */
static inline void
vlc_player_assert_locked(vlc_player_t *player)
{
    assert(player);
    vlc_mutex_assert(&player->lock);
}
#else
#define vlc_player_assert_locked(x) ((void) (0))
#endif

static inline struct vlc_player_input *
vlc_player_get_input_locked(vlc_player_t *player)
{
    vlc_player_assert_locked(player);
    return player->input;
}

#define vlc_player_SendEvent(player, event, ...) do { \
    vlc_player_listener_id *listener; \
    vlc_list_foreach(listener, &player->listeners, node) \
    { \
        if (listener->cbs->event) \
            listener->cbs->event(player, ##__VA_ARGS__, listener->cbs_data); \
    } \
} while(0)

#define vlc_player_SendEventCount(player, event, count, ...) do { \
    vlc_player_listener_id *listener; \
    count = 0; \
    vlc_list_foreach(listener, &player->listeners, node) \
    { \
        if (listener->cbs->event) { \
            listener->cbs->event(player, ##__VA_ARGS__, listener->cbs_data); \
            count++; \
        } \
    } \
} while(0)

static inline const char *
es_format_category_to_string(enum es_format_category_e cat)
{
    switch (cat)
    {
        case VIDEO_ES: return "Video";
        case AUDIO_ES: return "Audio";
        case SPU_ES: return "Subtitle";
        default: return NULL;
    }
}

/*
 * player.c
 */

vlc_object_t *
vlc_player_GetObject(vlc_player_t *player);

int
vlc_player_OpenNextMedia(vlc_player_t *player);

void
vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
                                       struct vlc_player_input *input);

void
vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
                                       struct vlc_player_input *input);

void
vlc_player_SignalAtoBLoop(vlc_player_t *player);

/*
 * player_track.c
 */

struct vlc_player_program *
vlc_player_program_New(int id, const char *name);

int
vlc_player_program_Update(struct vlc_player_program *prgm, int id,
                          const char *name);

struct vlc_player_program *
vlc_player_program_vector_FindById(vlc_player_program_vector *vec, int id,
                                   size_t *idx);

struct vlc_player_track_priv *
vlc_player_track_priv_New(vlc_es_id_t *id, const char *name, const es_format_t *fmt);

void
vlc_player_track_priv_Delete(struct vlc_player_track_priv *trackpriv);

int
vlc_player_track_priv_Update(struct vlc_player_track_priv *trackpriv,
                             const char *name, const es_format_t *fmt);

struct vlc_player_track_priv *
vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
                                 size_t *idx);

int
vlc_player_GetFirstSelectedTrackId(const vlc_player_track_vector* tracks);

/*
 * player_title.c
 */

struct vlc_player_title_list *
vlc_player_title_list_Create(input_title_t *const *array, size_t count,
                             int title_offset, int chapter_offset);

/*
 * player_input.c
 */

static inline vlc_player_track_vector *
vlc_player_input_GetTrackVector(struct vlc_player_input *input,
                                enum es_format_category_e cat)
{
    switch (cat)
    {
        case VIDEO_ES:
            return &input->video_track_vector;
        case AUDIO_ES:
            return &input->audio_track_vector;
        case SPU_ES:
            return &input->spu_track_vector;
        default:
            return NULL;
    }
}

struct vlc_player_track_priv *
vlc_player_input_FindTrackById(struct vlc_player_input *input, vlc_es_id_t *id,
                               size_t *idx);

struct vlc_player_input *
vlc_player_input_New(vlc_player_t *player, input_item_t *item);

void
vlc_player_input_Delete(struct vlc_player_input *input);

void
vlc_player_input_SelectTracksByStringIds(struct vlc_player_input *input,
                                         enum es_format_category_e cat,
                                         const char *str_ids);

char *
vlc_player_input_GetSelectedTrackStringIds(struct vlc_player_input *input,
                                           enum es_format_category_e cat) VLC_MALLOC;

vlc_tick_t
vlc_player_input_GetTime(struct vlc_player_input *input, bool seeking,
                         vlc_tick_t system_now);

double
vlc_player_input_GetPos(struct vlc_player_input *input, bool seeking,
                        vlc_tick_t system_now);

int
vlc_player_input_Start(struct vlc_player_input *input);

void
vlc_player_input_SeekByPos(struct vlc_player_input *input, double position,
                           enum vlc_player_seek_speed speed,
                           enum vlc_player_whence whence);

void
vlc_player_input_SeekByTime(struct vlc_player_input *input, vlc_tick_t time,
                            enum vlc_player_seek_speed speed,
                            enum vlc_player_whence whence);

void
vlc_player_input_UpdateViewpoint(struct vlc_player_input *input,
                                 const vlc_viewpoint_t *viewpoint,
                                 enum vlc_player_whence whence);

void
vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state,
                             vlc_tick_t state_date);

bool
vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, bool forced);

/*
 * player_timer.c
*/

void
vlc_player_InitTimer(vlc_player_t *player);

void
vlc_player_DestroyTimer(vlc_player_t *player);

void
vlc_player_ResetTimer(vlc_player_t *player);

void
vlc_player_UpdateTimerEvent(vlc_player_t *player, vlc_es_id_t *es_source,
                            enum vlc_player_timer_event event,
                            vlc_tick_t system_date);

void
vlc_player_UpdateTimerSeekState(vlc_player_t *player, vlc_tick_t time,
                                double position);

void
vlc_player_UpdateTimer(vlc_player_t *player, vlc_es_id_t *es_source,
                       bool es_source_is_master,
                       const struct vlc_player_timer_point *point,
                       vlc_tick_t normal_time,
                       unsigned frame_rate, unsigned frame_rate_base,
                       vlc_tick_t start_offset);

void
vlc_player_RemoveTimerSource(vlc_player_t *player, vlc_es_id_t *es_source);

int
vlc_player_GetTimerPoint(vlc_player_t *player, bool *seeking,
                         vlc_tick_t system_now,
                         vlc_tick_t *out_ts, double *out_pos);

/*
 * player_vout.c
 */

void
vlc_player_vout_AddCallbacks(vlc_player_t *player, vout_thread_t *vout);

void
vlc_player_vout_DelCallbacks(vlc_player_t *player, vout_thread_t *vout);

/*
 * player_aout.c
 */

audio_output_t *
vlc_player_aout_Init(vlc_player_t *player);

void
vlc_player_aout_Deinit(vlc_player_t *player);

/*
 * player_osd.c
 */

void
vlc_player_osd_Message(vlc_player_t *player, const char *fmt, ...);

void
vlc_player_osd_Icon(vlc_player_t *player, vlc_osd_widget_type type);

void
vlc_player_osd_Position(vlc_player_t *player,
                        struct vlc_player_input *input, vlc_tick_t time,
                        double position);
void
vlc_player_osd_Volume(vlc_player_t *player, bool mute_action);

int
vlc_player_vout_OSDCallback(vlc_object_t *this, const char *var,
                            vlc_value_t oldval, vlc_value_t newval, void *data);

void
vlc_player_osd_Track(vlc_player_t *player, vlc_es_id_t *id, bool select);

void
vlc_player_osd_Tracks(vlc_player_t *player, vlc_es_id_t * const*selected, vlc_es_id_t *unselect);

void
vlc_player_osd_Program(vlc_player_t *player, const char *name);

/*
 * player/medialib.c
 */

void
vlc_player_input_RestoreMlStates(struct vlc_player_input* input, bool force_pos);

void
vlc_player_UpdateMLStates(vlc_player_t *player, struct vlc_player_input* input);

#endif
