/**
 * @file   video_display.c
 * @author Colin Perkins    <csp@isi.edu>
 * @author Martin Benes     <martinbenesh@gmail.com>
 * @author Lukas Hejtmanek  <xhejtman@ics.muni.cz>
 * @author Petr Holub       <hopet@ics.muni.cz>
 * @author Milos Liska      <xliska@fi.muni.cz>
 * @author Jiri Matela      <matela@ics.muni.cz>
 * @author Dalibor Matura   <255899@mail.muni.cz>
 * @author Martin Pulec     <pulec@cesnet.cz>
 * @author Ian Wesley-Smith <iwsmith@cct.lsu.edu>
 *
 * @ingroup display
 */
/*
 * Copyright (c) 2001-2003 University of Southern California
 * Copyright (c) 2005-2021 CESNET z.s.p.o.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 
 *      This product includes software developed by the University of Southern
 *      California Information Sciences Institute. This product also includes
 *      software developed by CESNET z.s.p.o.
 * 
 * 4. Neither the name of the University, Institute, CESNET nor the names of
 *    its contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/**
 * @todo
 * On-fly postprocess reconfiguration is not ok in general case. The pitch or
 * supported codecs might have changed due to the reconfiguration, which is,
 * however, not reflected by decoder which queried the data before.
 */

#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#include "debug.h"
#include "lib_common.h"
#include "module.h"
#include "perf.h"
#include "utils/thread.h"
#include "video.h"
#include "video_display.h"
#include "vo_postprocess.h"

#define DISPLAY_MAGIC 0x01ba7ef1

/// @brief This struct represents initialized video display state.
struct display {
        struct module mod;
        uint32_t magic;    ///< For debugging. Conatins @ref DISPLAY_MAGIC
        const struct video_display_info *funcs;
        void *state;       ///< state of the created video capture driver
        pthread_t thread_id; ///< thread ID of the display thread (@see display_run)

        struct vo_postprocess_state *postprocess;
        int pp_output_frames_count, display_pitch;
        struct video_desc saved_desc;
        enum video_mode saved_mode;
};

/**This variable represents a pseudostate and may be returned when initialization
 * of module was successful but no state was created (eg. when driver had displayed help). */
int display_init_noerr;

void list_video_display_devices(bool full)
{
        printf("Available display devices:\n");
        list_modules(LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION, full);
        if (!full) {
                printf("(use \"fullhelp\" to show hidden displays)\n");
        }
}

/*
 * Display initialisation and playout routines...
 */

/**
 * @brief Initializes video display.
 * @param[in] requested_display  video display module name, not NULL
 * @param[in] fmt    command-line entered format string, not NULL
 * @param[in] flags  bit sum of @ref display_flags
 * @param[out] state output display state. Defined only if initialization was successful.
 * @retval    0  if sucessful
 * @retval   -1  if failed
 * @retval    1  if successfully shown help (no state returned)
 */
int initialize_video_display(struct module *parent, const char *requested_display,
                const char *fmt, unsigned int flags, const char *postprocess, struct display **out)
{
        assert (requested_display != NULL && fmt != NULL && out != NULL);

        if (postprocess && (strcmp(postprocess, "help") == 0 || strcmp(postprocess, "fullhelp") == 0)) {
                show_vo_postprocess_help(strcmp(postprocess, "fullhelp") == 0);
                return 1;
        }

        const struct video_display_info *vdi = (const struct video_display_info *)
                        load_library(requested_display, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION);

        if (vdi) {
                struct display *d = calloc(1, sizeof(struct display));
                d->magic = DISPLAY_MAGIC;
                d->funcs = vdi;

                module_init_default(&d->mod);
                d->mod.cls = MODULE_CLASS_DISPLAY;
                module_register(&d->mod, parent);

                d->state  = d->funcs->init(&d->mod, fmt, flags);

                if (d->state == NULL) {
                        debug_msg("Unable to start display %s\n",
                                        requested_display);
                        module_done(&d->mod);
                        free(d);
                        return -1;
                } else if (d->state == &display_init_noerr) {
                        module_done(&d->mod);
                        free(d);
                        return 1;
                }

                if (postprocess) {
                        d->postprocess = vo_postprocess_init(postprocess);
                        if (!d->postprocess) {
                                display_done(d);
                                return 1;
                        }
                }

                *out = d;
                return 0;
        }

        log_msg(LOG_LEVEL_ERROR, "WARNING: Selected '%s' display card "
                        "was not found.\n", requested_display);
        return -1;
}

/**
 * @brief This function performs cleanup after done.
 * @param d display do be destroyed (must not be NULL)
 */
void display_done(struct display *d)
{
        assert(d->magic == DISPLAY_MAGIC);
        d->funcs->done(d->state);
        module_done(&d->mod);
        vo_postprocess_done(d->postprocess);
        free(d);
}

ADD_TO_PARAM("override-mainloop-req", "* override-mainloop-req\n"
                "  Override display requirement for mainloop.\n");

/**
 * Returns true if display has a run routine that needs to be run in a main thread
 * (@see display_run).
 */
bool display_needs_mainloop(struct display *d)
{
        assert(d->magic == DISPLAY_MAGIC);
        if (get_commandline_param("override-mainloop-req") != NULL) {
                return true;
        }
        if (d->funcs->needs_mainloop == DISPLAY_NEEDS_MAINLOOP) {
                return true;
        }
        if (d->funcs->needs_mainloop == DISPLAY_DOESNT_NEED_MAINLOOP) {
                return false;
        }
        return d->funcs->needs_mainloop(d->state);
}

#define CHECK(cmd) do { \
        int ret = cmd; \
        if (ret != 0) { \
                errno = ret; \
                perror(#cmd); \
                abort(); \
        } \
} while(0)

static void *display_run_helper(void *args)
{
        set_thread_name("display");
        struct display *d = args;
        assert(d->magic == DISPLAY_MAGIC);
        d->funcs->run(d->state);
        return NULL;
}

/**
 * @brief Display mainloop function.
 *
 * This call is entered in main thread and the display is either run in
 * a separate thread or directly here.
 *
 * The later variant is mainly for GUI displays (GL/SDL), which usually need
 * to be run from main thread of the * program (OS X).
 *
 * The function must quit after receiving a poisoned pill (frame == NULL) to
 * a display_put_frame() call.

 * @param d display to be run
 */
void display_run(struct display *d)
{
        assert(d->magic == DISPLAY_MAGIC);
        if (display_needs_mainloop(d)) {
                d->funcs->run(d->state);
        } else {
                CHECK(pthread_create(&d->thread_id, NULL, display_run_helper, d));
        }
}

/**
 * Joins the display task if run in a separate thread (@see display_run and @see display_needs_mainloop).
 */
void display_join(struct display *d)
{
        assert(d->magic == DISPLAY_MAGIC);
        if (!display_needs_mainloop(d)) {
                CHECK(pthread_join(d->thread_id, NULL));
        }
}

static struct response *process_message(struct display *d, struct msg_universal *msg)
{
        if (strncasecmp(msg->text, "postprocess ", strlen("postprocess ")) == 0) {
                log_msg(LOG_LEVEL_WARNING, "On fly changing postprocessing is currently "
                                "only an experimental feature! Use with caution!\n");
                const char *text = msg->text + strlen("postprocess ");

                struct vo_postprocess_state *postprocess_old = d->postprocess;

                if (strcmp(text, "flush") != 0) {
                        d->postprocess = vo_postprocess_init(text);
                        if (!d->postprocess) {
                                d->postprocess = postprocess_old;
                                log_msg(LOG_LEVEL_ERROR, "Unable to create postprocess '%s'.\n", text);
                                return new_response(RESPONSE_BAD_REQUEST, NULL);
                        }
                } else {
                        d->postprocess = NULL;
                }

                vo_postprocess_done(postprocess_old);

                display_reconfigure(d, d->saved_desc, d->saved_mode);

                return new_response(RESPONSE_OK, NULL);
        } else {
                log_msg(LOG_LEVEL_ERROR, "Unknown command '%s'.\n", msg->text);
                return new_response(RESPONSE_BAD_REQUEST, NULL);
        }
}

/**
 * @brief Returns video framebuffer which will be written to.
 *
 * Currently there is a restriction on number of concurrently acquired frames - only one frame
 * can be hold at the moment. Every obtained frame from this call has to be returned back
 * with display_put_frame()
 *
 * @return               video frame
 */
struct video_frame *display_get_frame(struct display *d)
{
        struct message *msg;
        while((msg = check_message(&d->mod))) {
                struct response *r = process_message(d, (struct msg_universal *) msg);
                free_message(msg, r);
        }

        perf_record(UVP_GETFRAME, d);
        assert(d->magic == DISPLAY_MAGIC);
        if (d->postprocess) {
                return vo_postprocess_getf(d->postprocess);
        } else {
                return d->funcs->getf(d->state);
        }
}

/**
 * @brief Puts filled video frame.
 * After calling this function, video frame cannot be used.
 *
 * @param d        display to be putted frame to
 * @param frame    frame that has been obtained from display_get_frame() and has not yet been put.
 *                 Should not be NULL unless we want to quit display mainloop.
 * @param flags specifies blocking behavior (@ref display_put_frame_flags)
 * @retval      0  if displayed succesfully
 * @retval      1  if not displayed
 */
int display_put_frame(struct display *d, struct video_frame *frame, int flags)
{
        perf_record(UVP_PUTFRAME, frame);
        assert(d->magic == DISPLAY_MAGIC);

        if (!frame) {
                return d->funcs->putf(d->state, frame, flags);
        }

        if (d->postprocess) {
                int display_ret = 0;
		for (int i = 0; i < d->pp_output_frames_count; ++i) {
			struct video_frame *display_frame = d->funcs->getf(d->state);
			int ret = vo_postprocess(d->postprocess,
					frame,
					display_frame,
					d->display_pitch);
                        frame = NULL;
			if (!ret) {
				d->funcs->putf(d->state, display_frame, PUTF_DISCARD);
				return 1;
			}

			display_ret = d->funcs->putf(d->state, display_frame, flags);
		}
                return display_ret;
        } else {
                return d->funcs->putf(d->state, frame, flags);
        }
}

/**
 * @brief Reconfigure display to new video format.
 *
 * video_desc::color_spec, video_desc::interlacing
 * and video_desc::tile_count are set according
 * to properties obtained from display_ctl_property().
 *
 * @param d    display to be reconfigured
 * @param desc new video description to be reconfigured to
 * @retval TRUE  if reconfiguration succeeded
 * @retval FALSE if reconfiguration failed
 */
int display_reconfigure(struct display *d, struct video_desc desc, enum video_mode video_mode)
{
        assert(d->magic == DISPLAY_MAGIC);

        d->saved_desc = desc;
        d->saved_mode = video_mode;

        if (d->postprocess) {
                bool pp_does_change_tiling_mode = false;
                size_t len = sizeof(pp_does_change_tiling_mode);
                if (vo_postprocess_get_property(d->postprocess, VO_PP_DOES_CHANGE_TILING_MODE,
                                        &pp_does_change_tiling_mode, &len)) {
                        if(len == 0) {
                                // just for sake of completness since it shouldn't be a case
                                log_msg(LOG_LEVEL_WARNING, "Warning: unable to get pp tiling mode!\n");
                        }
                }
		struct video_desc pp_desc = desc;

                if (!pp_does_change_tiling_mode) {
                        pp_desc.width *= get_video_mode_tiles_x(video_mode);
                        pp_desc.height *= get_video_mode_tiles_y(video_mode);
                        pp_desc.tile_count = 1;
                }
                if (!vo_postprocess_reconfigure(d->postprocess, pp_desc)) {
                        log_msg(LOG_LEVEL_ERROR, "[video dec.] Unable to reconfigure video "
                                        "postprocess.\n");
                        return false;
                }
		struct video_desc display_desc;
                int render_mode; // WTF ?
		vo_postprocess_get_out_desc(d->postprocess, &display_desc, &render_mode, &d->pp_output_frames_count);
		int rc = d->funcs->reconfigure_video(d->state, display_desc);
                len = sizeof d->display_pitch;
                d->display_pitch = PITCH_DEFAULT;
                d->funcs->ctl_property(d->state, DISPLAY_PROPERTY_BUF_PITCH,
					&d->display_pitch, &len);
                if (d->display_pitch == PITCH_DEFAULT) {
			d->display_pitch = vc_get_linesize(display_desc.width, display_desc.color_spec);
		}

                return rc;
        } else {
		return d->funcs->reconfigure_video(d->state, desc);
	}
}

static void restrict_returned_codecs(codec_t *display_codecs,
                size_t *display_codecs_count, codec_t *pp_codecs,
                int pp_codecs_count)
{
        int i;

        for (i = 0; i < (int) *display_codecs_count; ++i) {
                int j;

                int found = FALSE;

                for (j = 0; j < pp_codecs_count; ++j) {
                        if(display_codecs[i] == pp_codecs[j]) {
                                found = TRUE;
                        }
                }

                if(!found) {
                        memmove(&display_codecs[i], (const void *) &display_codecs[i + 1],
                                        sizeof(codec_t) * (*display_codecs_count - i - 1));
                        --*display_codecs_count;
                        --i;
                }
        }
}

/**
 * @brief Gets property from video display.
 * @param[in]     d         video display state
 * @param[in]     property  one of @ref display_property
 * @param[in]     val       pointer to output buffer where should be the property stored
 * @param[in]     len       provided buffer length
 * @param[out]    len       actual size written
 * @retval      TRUE      if succeeded and result is contained in val and len
 * @retval      FALSE     if the query didn't succeeded (either not supported or error)
 */
int display_ctl_property(struct display *d, int property, void *val, size_t *len)
{
        assert(d->magic == DISPLAY_MAGIC);
        if (d->postprocess) {
                switch (property) {
                case DISPLAY_PROPERTY_BUF_PITCH:
                        *(int *) val = PITCH_DEFAULT;
                        *len = sizeof(int);
                        return TRUE;
		case DISPLAY_PROPERTY_CODECS:
			{
                                codec_t display_codecs[VIDEO_CODEC_COUNT];
                                codec_t pp_codecs[VIDEO_CODEC_COUNT];
                                size_t display_codecs_count, pp_codecs_count;
                                size_t nlen;
                                bool ret;
                                nlen = sizeof display_codecs;
                                ret = d->funcs->ctl_property(d->state, DISPLAY_PROPERTY_CODECS, display_codecs, &nlen);
                                if (!ret) {
                                        log_msg(LOG_LEVEL_ERROR, "[Display] Unable to get display supported codecs.\n");
                                        return FALSE;
                                }
                                display_codecs_count = nlen / sizeof(codec_t);
                                nlen = sizeof pp_codecs;
                                ret = vo_postprocess_get_property(d->postprocess, VO_PP_PROPERTY_CODECS, pp_codecs, &nlen);
                                if (ret) {
					if (nlen == 0) { // problem detected
						log_msg(LOG_LEVEL_ERROR, "[Decoder] Unable to get supported codecs.\n");
						return FALSE;

					}
                                        pp_codecs_count = nlen / sizeof(codec_t);
                                        restrict_returned_codecs(display_codecs, &display_codecs_count,
                                                        pp_codecs, pp_codecs_count);
                                }
                                nlen = display_codecs_count * sizeof(codec_t);
                                if (nlen <= *len) {
                                        *len = nlen;
                                        memcpy(val, display_codecs, nlen);
                                        return TRUE;
                                } else {
                                        return FALSE;
                                }
                        }
			break;
                default:
                        return d->funcs->ctl_property(d->state, property, val, len);
                }
        } else {
                return d->funcs->ctl_property(d->state, property, val, len);
        }
}

/**
 * @brief Puts audio data.
 * @param d     video display
 * @param frame audio frame to be played
 */
void display_put_audio_frame(struct display *d, struct audio_frame *frame)
{
        assert(d->magic == DISPLAY_MAGIC);
        d->funcs->put_audio_frame(d->state, frame);
}

/**
 * This function instructs video driver to reconfigure itself
 *
 * @param               d               video display structure
 * @param               quant_samples   number of bits per sample
 * @param               channels        count of channels
 * @param               sample_rate     samples per second
 * @retval              TRUE            if reconfiguration succeeded
 * @retval              FALSE           if reconfiguration failed
 */
int display_reconfigure_audio(struct display *d, int quant_samples, int channels, int sample_rate)
{
        assert(d->magic == DISPLAY_MAGIC);
        return d->funcs->reconfigure_audio(d->state, quant_samples, channels, sample_rate);
}

