#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <unistd.h>
#include <pthread.h>
#endif

#include <boost/format.hpp>
#include <fstream>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <vector>

#include "SDL2/SDL.h"
#include "SDL2/SDL_syswm.h"

#include "vn_player.h"
#include "vn_renderer.h"
#include "MediaClient.h"
#include "MediaStreamHandler.h"

extern "C" {
#include "libavutil/log.h"
}

void do_exit(int exit_code);

#define _V(x, e, t, ret)                                 \
do {                                                     \
    if(x e) {                                            \
        av_log(NULL, AV_LOG_ERROR,                       \
               "%s:%d %s failed with error: %s\n",       \
               __FILE__, __LINE__, strlen(#t) ? #t : #x, \
               SDL_GetError());                          \
        ret                                              \
    }                                                    \
} while(0);

#define V_E(x, e, t) _V(x, e, t, do_exit(EXIT_FAILURE);)

#define V_W(x, e, t)                                     \
do {                                                     \
    if(x e) {                                            \
        av_log(NULL, AV_LOG_WARNING,                     \
               "%s:%d %s failed with error: %s\n",       \
               __FILE__, __LINE__, strlen(#t) ? #t : #x, \
               SDL_GetError());                          \
    }                                                    \
} while(0);

using std::map;
using std::string;
using std::vector;

static bool verbose;
static unsigned buffer_len = 0; // in ms
static bool disable_decoder = false;
static bool local_streaming = false;
static bool use_hw_decoder = false;
static bool use_rtp_over_tcp = false;
static bool sdl_inited = false;
static bool show_on_screen = false;
static int  renderer_num = -1;

float contrast_scale = 1.0f;
float brightness_scale = 0.0f;
float saturation_scale = 1.0f;
VN_IMAGE_FILL_MODE fill_mode = VN_IMAGE_FILL_ASPECT;

bool zoom_mode = false;
float pan_x = 0.5f;
float pan_y = 0.5f;
float scale = 1.0f;
VN_IMAGE_ZOOM_MODE zoom_filter = VN_IMAGE_ZOOM_BILENEAR;


class Media_Player
{
public:
    Media_Player(unsigned player_num, void* window_handle = NULL);
    ~Media_Player();

    void open(const char *url);
    void play();
    void pause();
    bool is_paused() { return paused_; }
    void resume();
    void step_mode(bool value);

    vn_renderer_context_t *get_renderer() { return r_ctx_; }
    void* window_handle() { return window_handle_; }
    unsigned player_num() { return player_num_; }

private:
   static void new_frame(const vn_player_frame_t*, const vn_player_cache_boundaries_t*, int buf_num, void *client_data);

   static void buffer_changed(const vn_player_cache_boundaries_t*, void *client_data);

   static void state_changed(VN_PLAYER_STREAM_STATE, const vn_player_result_status_t*, void *client_data);

   static void new_stream(const vn_player_stream_info_t*, void *client_data);

   static void stream_removed(const vn_player_stream_info_t*, void *client_data);

   static void msg_log(const char *msg, void *client_data);

private:
    void* window_handle_;
    struct vn_player_config_t     conf_;
    struct vn_player_context_t*   ctx_;
    struct vn_renderer_context_t* r_ctx_;
    unsigned frame_num_;
    unsigned player_num_;
    bool paused_;
};

Media_Player::Media_Player(unsigned player_num, void* window_handle)
    : window_handle_(window_handle), r_ctx_(0)
    , frame_num_(0), player_num_(player_num), paused_(false)
{
    window_handle_ = window_handle;
    if(show_on_screen) {
        r_ctx_ = vn_renderer_create(window_handle);
        assert(r_ctx_);
    }

    memset(&conf_, 0, sizeof(conf_));
    conf_.cache_size = 0;
    conf_.decoder_type = disable_decoder ? DECODER_NONE
                                         : use_hw_decoder ? DECODER_HW
                                                          : DECODER_SW;
    conf_.pixel_format = r_ctx_ ? vn_renderer_get_preferred_pixel_format(r_ctx_)
                                : PIX_NONE; //PIX_YUV420P;
    conf_.rtp_transport = use_rtp_over_tcp? RTP_TRANSPORT_TCP : RTP_TRANSPORT_UDP;
    conf_.client_data = this;
    conf_.buffer_len  = buffer_len;

    struct vn_player_callbacks_t callbacks = {};
    callbacks.buffer_changed = buffer_changed;
    callbacks.new_frame      = new_frame;
    callbacks.new_stream     = new_stream;
    callbacks.state_changed  = state_changed;
    callbacks.stream_removed = stream_removed;
    callbacks.msg_log        = msg_log;

    ctx_ = vn_player_create(&conf_);
    vn_player_set_callbacks(ctx_, &callbacks);
}

Media_Player::~Media_Player()
{
    vn_player_destroy(ctx_);
    if(r_ctx_)
        vn_renderer_destroy(r_ctx_);
}

void Media_Player::open(const char *url)
{
    vn_player_open(ctx_, url);
}

void Media_Player::play()
{
    vn_player_play(ctx_);
    paused_ = false;
}

void Media_Player::pause()
{
    paused_ = true;
    vn_player_pause(ctx_);
}

void Media_Player::resume()
{
    vn_player_resume(ctx_, 1);
    paused_ = false;
}

void Media_Player::step_mode(bool value)
{
    vn_player_set_step_mode(ctx_, value);
}

void Media_Player::new_frame(const vn_player_frame_t *frame, const vn_player_cache_boundaries_t *bounds, int buf_num, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    if (++p->frame_num_ % 100 == 0)
        printf("[%d] Got %d frames (%dx%d)\n", p->player_num_, p->frame_num_, frame->width, frame->height);

    if (verbose) {
        struct timeval tv;
        gettimeofday(&tv, 0);
        printf("%ld.%06d Got frame (%s). Time: %ld.%06d\n", tv.tv_sec, tv.tv_usec, frame->stream_info->type == AUDIO ? "audio" : "video", frame->captured_time->tv_sec, frame->captured_time->tv_usec);
        if (frame->objects_data)
            printf("Metadata: %s\n", frame->objects_data);
    }

    struct vn_renderer_context_t* r_ctx = p->r_ctx_;
    if(!r_ctx)
        return;

    if(verbose) {
        Uint32 t = SDL_GetTicks();
        V_W(vn_renderer_process(r_ctx, frame), <0, vn_renderer_process())
        printf("*** [%d] time elapsed in updating texture: %d\n",
               p->player_num(), SDL_GetTicks() - t);
    } else
        V_W(vn_renderer_process(r_ctx, frame), <0, vn_renderer_process())
}

void Media_Player::buffer_changed(const vn_player_cache_boundaries_t *bounds, void *client_data)
{
//	printf("buffer_changed\n");
}

void Media_Player::state_changed(VN_PLAYER_STREAM_STATE state, const vn_player_result_status_t *result_status, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    struct timeval tv;
    gettimeofday(&tv, 0);
    // if (result_status->error_code)
    printf("[%d] %ld.%06d State_changed: %s (%d). Result: %s(%d)\n", p->player_num_, tv.tv_sec, tv.tv_usec,
           vn_player_state_to_str(state), state, result_status->error_str, result_status->error_code);

    if (state == OPENED)
        p->play();

    /*if (result_status->error_code && result_status->error_code < 10000)
    {
        char err_buf[512];
        strerror_r(result_status->error_code, err_buf, sizeof(err_buf));
        printf("ERROR: %s\n", err_buf);
    }*/
}

void Media_Player::new_stream(const vn_player_stream_info_t *stream_info, void *client_data)
{
    printf("New stream: %s. Duration: %d ms\n", stream_info->type == AUDIO ? "audio" : "video", stream_info->duration);
}

void Media_Player::stream_removed(const vn_player_stream_info_t *stream_info, void *client_data)
{
//	printf("stream_removed\n");
}

void Media_Player::msg_log(const char *msg, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    if (verbose)
        printf("[%d] %s", p->player_num_, msg);
}

void do_exit(int exit_code)
{
    if(sdl_inited)
        SDL_Quit();
    exit(exit_code);
}

int check_wm_info(SDL_SysWMinfo& wm_info)
{
    // get driver specific information about a window
    SDL_Window* window = SDL_CreateWindow("", 0, 0, 0, 0, SDL_WINDOW_HIDDEN);

    SDL_bool bSuccess = SDL_GetWindowWMInfo(window, &wm_info);

    SDL_DestroyWindow(window);

    if(!bSuccess || wm_info.subsystem == SDL_SYSWM_UNKNOWN) {
        fprintf(stderr, "Couldn't get window manager information: %s\n",
                SDL_GetError());
        return -1;
    }

    const char *subsystem;
    switch(wm_info.subsystem)
    {
    case SDL_SYSWM_WINDOWS:   subsystem = "Microsoft Windows(TM)";  break;
    case SDL_SYSWM_X11:       subsystem = "X Window System";        break;
#if SDL_VERSION_ATLEAST(2, 0, 3)
    case SDL_SYSWM_WINRT:     subsystem = "WinRT";                  break;
#endif
    case SDL_SYSWM_DIRECTFB:  subsystem = "DirectFB";               break;
    case SDL_SYSWM_COCOA:     subsystem = "Apple OS X";             break;
    case SDL_SYSWM_UIKIT:     subsystem = "UIKit";                  break;
#if SDL_VERSION_ATLEAST(2, 0, 2)
    case SDL_SYSWM_WAYLAND:   subsystem = "Wayland";                break;
    case SDL_SYSWM_MIR:       subsystem = "Mir";                    break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 4)
    case SDL_SYSWM_ANDROID:   subsystem = "Android";                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
    case SDL_SYSWM_VIVANTE:   subsystem = "Vivante";                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
    case SDL_SYSWM_OS2:       subsystem = "OS2";                    break;
#endif
    default:
        subsystem = "unknown window manager subsystem";
        break;
    }

    printf("This program is running SDL version %d.%d.%d on %s\n",
           (int)wm_info.version.major,
           (int)wm_info.version.minor,
           (int)wm_info.version.patch,
           subsystem);

    return 0;
}

// Parse layout configuration
void parse_layout(string& path2layout, std::vector<string>& urls, std::vector<int>& renderers)
{
    videonext::media::MediaClient* mc = videonext::media::MediaClient::instance();
    videonext::media::ResultStatus result;

    std::ifstream f_layout(path2layout.c_str(), std::ifstream::binary);
    string line;
    char obj_id[37] = {"\0"};
    int renderer;
    int l;
    while(f_layout.is_open() && f_layout.good()) {
        std::getline(f_layout, line);
        if(line[0] == '#') {
            fprintf(stderr, "Skipped line from layout: %s\n", line.c_str());
            continue;
        }
        if(3 == sscanf(line.c_str(), "%s %d %d", obj_id, &l, &renderer)) {
            string url;
            mc->getMediaURL(&result, &url, obj_id, l ? true : false);
            //printf("getMediaURL() result: %d (%s)\n",
            //       result.errorCode, result.errorString.c_str());
            if(result.errorCode != 0 || result.errorString != "No error") {
                fprintf(stderr, "Fail to get media url for objid \"%s\": %d (%s)\n",
                        obj_id, result.errorCode, result.errorString.c_str());
                continue;
            }
            printf("Parsed URL from layout: %s\n", url.c_str());
            urls.push_back(url);
            renderers.push_back(renderer);
        }
    }
    f_layout.close();

    printf("Total URLs size from layout file: %zu\n", urls.size());
}

// create window(s) on screen
void create_layout(Media_Player* players[], unsigned players_num,
                   std::vector<SDL_Window*>& wins, SDL_SysWMinfo& wm_info,
                   vector<int>& renderers, map<Uint32, Media_Player*>& map_)
{
    unsigned W=0, H=0, w=0, h=0, dx=0, dy=0, cols=1, rows=1;

    // Declare display mode structure to be filled in
    SDL_DisplayMode curDisplayMode;
    V_E(SDL_GetCurrentDisplayMode(0, &curDisplayMode), <0,)
    W = curDisplayMode.w;
    H = curDisplayMode.h;

    // calculate layout matrix
    if(players_num > 1 && players_num < 4)
        rows = cols = 2;
    else {
        rows = sqrtf(players_num) + 0.5f;
        cols = players_num / rows;
        if(players_num % rows) cols++;
    }

    w  = W / cols;
    h  = H / rows;
    dx = (W % cols)/cols;
    dy = (H % rows)/rows;

    printf("Layout matrix: %dx%d, display resolution: %dx%d, "
           "cell size: %dx%d, cell gap: %dx%d\n",
           cols, rows, W, H, w, h, dx, dy);

    unsigned p = 0;
    boost::format title_fmt("player[%1%]%2%%3%");
    for(unsigned r=0; r<rows; r++) {
        for(unsigned c=0; c<cols; c++) {
            int r_num = p < renderers.size() ? renderers[p]
                                             : renderer_num;
            string win_title;
            if(r_num >= 0)
                // Hack to use special renderer driver in sdk
                win_title = boost::str(title_fmt % p % "__" % r_num);
            else win_title = boost::str(title_fmt % p % "" % "");

            SDL_Window* sdl_window
                    = SDL_CreateWindow(win_title.c_str(),
                                       c*(w + dx), r*(h + dy),
                                       w, h,
                                       SDL_WINDOW_HIDDEN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE/* | SDL_WINDOW_OPENGL*/);
            SDL_bool bSuccess = SDL_GetWindowWMInfo(sdl_window, &wm_info);

            if(!bSuccess || wm_info.subsystem == SDL_SYSWM_UNKNOWN) {
                fprintf(stderr, "Impossible to display on screen, because couldn't get window[%d] information: %s\n",
                        p, SDL_GetError());
                do_exit(EXIT_FAILURE);
            }

            wins.push_back(sdl_window);

            void* win_id = NULL;
            switch(wm_info.subsystem)
            {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
            case SDL_SYSWM_WINDOWS:
                win_id = (void*&)(wm_info.info.win.window);
                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 3) && defined(SDL_VIDEO_DRIVER_WINRT)
            case SDL_SYSWM_WINRT:
                win_id = (void*&)(wm_info.info.winrt.window);
                break;
#endif
#if defined(SDL_VIDEO_DRIVER_X11)
            case SDL_SYSWM_X11:
                win_id = (void*&)(wm_info.info.x11.window);
                break;
#endif
#if defined(SDL_VIDEO_DRIVER_DIRECTFB)
            case SDL_SYSWM_DIRECTFB:
                win_id = (void*&)(wm_info.info.dfb.window);
                break;
#endif
#if defined(SDL_VIDEO_DRIVER_COCOA)
            case SDL_SYSWM_COCOA:
                win_id = (void*&)(wm_info.info.cocoa.window);
                break;
#endif
#if defined(SDL_VIDEO_DRIVER_UIKIT)
            case SDL_SYSWM_UIKIT:
                win_id = (void*&)(wm_info.info.uikit.window);
                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 2)
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
            case SDL_SYSWM_WAYLAND:
                win_id = (void*&)(wm_info.info.wl.shell_surface); // TODO: check it!
                break;
#endif
#if defined(SDL_VIDEO_DRIVER_MIR)
            case SDL_SYSWM_MIR:
                win_id = (void*&)(wm_info.info.mir.surface);
                break;
#endif
#endif
#if SDL_VERSION_ATLEAST(2, 0, 4) && defined (SDL_VIDEO_DRIVER_ANDROID)
            case SDL_SYSWM_ANDROID:
                win_id = (void*&)(wm_info.info.android.window);
                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5) && defined (SDL_VIDEO_DRIVER_VIVANTE)
            case SDL_SYSWM_VIVANTE:
                win_id = (void*&)(wm_info.info.vivante.window);
                break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
            case SDL_SYSWM_OS2:
                fprintf(stderr, "Unsupported window manager subsystem: OS2\n");
                break;
#endif
            default:
                fprintf(stderr, "Unknown/Unsupported window manager subsystem: %d\n",
                        wm_info.subsystem);
                break;
            }

            players[p] = new Media_Player(p, win_id);

            if(++p >= players_num)
                break;
        }
    }
}

// one-off timer function
Uint32 on_timer_func(Uint32, void*)
{
    SDL_Event e;
    SDL_memset(&e, 0, sizeof(e)); /* or SDL_zero(e) */

    e.type = SDL_USEREVENT;
    SDL_PushEvent(&e);

    return 0;
}

static int players_destr(void *param)
{
    Media_Player** players = reinterpret_cast<Media_Player**>(param);
    unsigned p = 0;
    do
        delete players[p];
    while(players[++p] != NULL);

    return p;
}

int repaint(vn_renderer_context_t* r_ctx, unsigned p)
{
    struct vn_image_calibration_t ic = {brightness_scale, contrast_scale, saturation_scale};
    struct vn_image_pan_zoom_t pz = {pan_x, pan_y, scale, zoom_filter};
    if (pan_x < 0)
        pan_x = 0;
    if (pan_y < 0)
        pan_y = 0;
    if (pan_x > 1)
        pan_x = 1;
    if (pan_y > 1)
        pan_y = 1;

    int r;
    if(verbose) {
        Uint32 t = SDL_GetTicks();
        r = vn_renderer_paint(r_ctx, fill_mode, &ic, zoom_mode ? &pz : 0);
        V_W(r, <0, vn_renderer_paint())
        printf("*** [%d] time elapsed in rendering texture: %d\n",
               p, SDL_GetTicks() - t);
    } else {
        r = vn_renderer_paint(r_ctx, fill_mode, &ic, zoom_mode ? &pz : 0);
        V_W(r, <0, vn_renderer_paint())
    }

    return r;
}

void handle_events(Media_Player* players[], unsigned players_num,
                   std::map<Uint32, Media_Player*>& map_)
{
    // Handle SDL events
    SDL_Event e;
    std::map<Uint32, Media_Player*>::iterator iter;
    for(;;)
    {
        V_E(SDL_WaitEvent(&e), ==0, )
        if(verbose)
                fprintf(stderr, "got event: %d (%d, %d)\n", e.type,
                        e.type == SDL_WINDOWEVENT ? e.window.windowID : 0,
                        e.type == SDL_WINDOWEVENT ? e.window.event : 0);

        /*for(iter=map_.begin(); iter!=map_.end(); iter++)
            fprintf(stderr, "%d==>%p, ", iter->first, iter->second);
        fprintf(stderr, "\n");*/

        if(e.type == SDL_USEREVENT) {
            if(e.user.data1 == 0 && e.user.data2 == 0 && e.user.code == 0) {
                printf("*** TIMER EVENT ***\n");
                return;
            }

            if(e.user.data1 && e.user.data2) {
                // let's call specified function with parameter
                iter = map_.find(e.user.windowID);
                if(iter != map_.end())
                    reinterpret_cast<int (*)(void*)>(e.user.data1)(e.user.data2);
            } else if(e.user.data1) {
                // let's find player by specified window_handle and add it to our map
                for(unsigned p=0; p<players_num; p++)
                    if(players[p]->window_handle() == e.user.data1) {
                        map_.insert(std::make_pair(e.user.windowID, players[p]));
                        break;
                    }
            }
        } else if (e.type == SDL_WINDOWEVENT && (iter=map_.find(e.window.windowID)) != map_.end())
        {
            if(verbose)
                fprintf(stderr, "got event: %d, win_id: %d\n",
                        e.type, e.window.windowID);

            switch (e.window.event) {  // http://wiki.libsdl.org/moin.fcg/SDL_WindowEvent
            case SDL_WINDOWEVENT_SHOWN:
                if(verbose) printf("SDL_WINDOWEVENT_SHOWN\n");
                break;
            case SDL_WINDOWEVENT_MAXIMIZED:
                if(verbose) printf("SDL_WINDOWEVENT_MAXIMIZED\n");
                break;
            case SDL_WINDOWEVENT_RESTORED:
                if(verbose) printf("SDL_WINDOWEVENT_RESTORED\n");
                break;
            case SDL_WINDOWEVENT_FOCUS_GAINED:
                if(verbose) printf("SDL_WINDOWEVENT_FOCUS_GAINED\n");
                break;
            case SDL_WINDOWEVENT_HIDDEN:
                if(verbose) printf("SDL_WINDOWEVENT_HIDDEN\n");
                break;
            case SDL_WINDOWEVENT_MINIMIZED:
                if(verbose) printf("SDL_WINDOWEVENT_MINIMIZED\n");
                break;
            case SDL_WINDOWEVENT_FOCUS_LOST:
                if(verbose) printf("SDL_WINDOWEVENT_FOCUS_LOST\n");
                break;
            case SDL_WINDOWEVENT_RESIZED:
                if(verbose) printf("SDL_WINDOWEVENT_RESIZED\n");
                break;
            case SDL_WINDOWEVENT_EXPOSED:
            {
                if(verbose) printf("SDL_WINDOWEVENT_EXPOSED\n");
                Media_Player& player = *iter->second;
                repaint(player.get_renderer(), player.player_num());
                break;
            }
            }
        }
    }
}

void print_usage(char* argv[], int exit_code)
{
   fprintf(stderr, "Usage: %s options\nOptions:\n", argv[0]);
   fprintf(stderr,
           "-u, --url              direct URL (if specified next 6 parameters are ignored except when using layout from file)\n"
           "-c, --overcast-host    Overcast host to connect to\n"
           "-r, --realm_login      Login\n"
           "-w, --realm_passwd     Password\n"
           "-a, --auth-token       Authorization token (could be used instead of login/passwd for realm)\n"
           "-o, --objid            Camera OBJID (shadowed by 'objid' parameter from the layout file if exist)\n"
           "-l, --local-streaming  Enable local streaming (stream from cloud by default).\n"
           "                       Shadowed by 'isLocal' parameter from the layout file if exist.\n"
           "-n, --players-num      Number of concurrent players (default 1)\n"
           "-b, --buffer-len       Playout buffer length (in ms)\n"
           "-t, --playback-time    Time of seconds for each iteration (defalut 10secs)\n"
           "-i, --iterations-num   Number of iterations (default infinity)\n"
           "-s, --show-on-screen   Show on screen\n"
           "-x, --use-hw-decoder   Use hardware decoder\n"
           "-p, --use-rtp-over-tcp Force RTP-over-TCP\n"
           "-m, --renderer-to-use  Index number of the renderer to use (use the best renderer by default,\n"
           "                       shadowed by 'renderer_num' parameter from the layout file if exist)\n"
           "-d, --disable-decoding Disable decoding at all. -x and -s does no matter\n"
           "-g, --layout-config    Path to layout configuration file in the format(<objid> <isLocal, 1/0> <renderer_num, -1..n>)\n"
           "-v, --verbose          Be verbose\n"
           "-h, --help             Show this help\n\n"
           "Examples:\n"
           "   ./sdl_play -u \"rtsp://10.0.0.1:8554/xmedia?dev=101&authorizationid=0\"\n"
           "   ./sdl_play -c test1.vsaas.videonext.net -a c4e061cec37aee8c58888f440a7e10995e268ad28b187a681663e23e6490ea09 -o ad466710-a4a1-11e8-98e9-42010a8e0005 -s -n 4 -t 30\n"
    );

   do_exit(exit_code);
}

int main(int argc, char* argv[])
{
    int next_option;
    const char* const short_options = "hvu:c:r:w:a:o:lsm:n:t:b:i:xpdg:";
    const struct option long_options[] = {
    {"help",             0, NULL, 'h'},
    {"verbose",          0, NULL, 'v'},
    {"url",              1, NULL, 'u'},
    {"overcast-host",    1, NULL, 'c'},
    {"realm_login",      1, NULL, 'r'},
    {"realm_passwd",     1, NULL, 'w'},
    {"auth-token",       1, NULL, 'a'},
    {"objid",            1, NULL, 'o'},
    {"local-streaming",  0, NULL, 'l'},
    {"show-on-screen",   0, NULL, 's'},
    {"renderer-to-use",  1, NULL, 'm'},
    {"players-num",      1, NULL, 'n'},
    {"playback-time",    1, NULL, 't'},
    {"buffer-len",       1, NULL, 'b'},
    {"iterations-num",   1, NULL, 'i'},
    {"use-hw-decoder",   0, NULL, 'x'},
    {"use-rtp-over-tcp", 0, NULL, 'p'},
    {"disable-decoding", 0, NULL, 'd'},
    {"layout-config",    1, NULL, 'g'},
    {NULL,               0, NULL, 0}};

    string host, username, passwd, token, objid, def_url, path2layout = "layout";
    unsigned players_num = 1;
    unsigned playback_time = 10;
    unsigned iterations_num = ~0;

    do {
        next_option = getopt_long(argc, argv, short_options, long_options, NULL);

        switch (next_option)
        {
        case 'h': /* -h or --help */
            print_usage(argv, EXIT_SUCCESS);

        case 'p':
            use_rtp_over_tcp = true;
            break;

        case 'u':
            if (optarg)
                def_url = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'c':
            if (optarg)
                host = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'r':
            if (optarg)
                username = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'w':
            if (optarg)
                passwd = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'a':
            if (optarg)
                token = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'o':
            if (optarg)
                objid = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'l':
            local_streaming = true;
            break;

        case 's':
            show_on_screen = true;
            break;

        case 'm':
            if (optarg)
                renderer_num = atoi(optarg);
            else print_usage(argv, EXIT_FAILURE);
            show_on_screen = true;
            break;

        case 'n':
            if (optarg)
                players_num = atol(optarg);
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'i':
            if (optarg)
                iterations_num = atol(optarg);
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 't':
            if (optarg)
                playback_time = atol(optarg);
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'v':
            verbose = true;
            break;

        case 'x':
            use_hw_decoder = true;
            break;

        case 'b':
            if (optarg)
                buffer_len = atol(optarg);
            else print_usage(argv, EXIT_FAILURE);
            break;

        case 'd':
            disable_decoder = true;
            break;

        case 'g':
            if (optarg)
                path2layout = optarg;
            else print_usage(argv, EXIT_FAILURE);
            break;

        case '?': /* The user specified an invalid option*/
            print_usage(argv, EXIT_FAILURE);

        case -1: /* Done with options */
            break;
        }
    } while (next_option != -1);

    if((host.empty() && def_url.empty())
        || (token.empty() && (username.empty() || passwd.empty())))
        print_usage(argv, EXIT_FAILURE);

    if(disable_decoder)
        show_on_screen = false;

    videonext::media::MediaClient* mc = videonext::media::MediaClient::instance();
    videonext::media::ResultStatus result;

    if(!host.empty()) {
        mc->login(&result, host, 443, username, passwd, true, 60);
        if(result.errorCode != 0 && token.empty()) {
            fprintf(stderr, "login() result: %d (%s)\n",
                    result.errorCode, result.errorString.c_str());
            exit(EXIT_FAILURE);
        }
    }
    mc->setAuth(token, token);

    SDL_SysWMinfo wm_info; // driver specific information
    SDL_VERSION(&wm_info.version); // initialize info structure with SDL version info
    if(show_on_screen)
    {
        // Initialize SDL2
        sdl_inited = !SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER /*| SDL_INIT_AUDIO*/);
        V_E(sdl_inited, ==false, SDL_Init()) //"Unable to initialize SDL: %s

        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
        SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

        if(check_wm_info(wm_info) < 0) {
            fprintf(stderr, "Impossible to go further\n");
            do_exit(EXIT_FAILURE);
        }
    }


    for(unsigned i = 1; i <= iterations_num; i++)
    {
        printf("********** Iteration: %d **********\n", i);
        Media_Player *players[256];
        string url = def_url;
        std::vector<SDL_Window*> tmp_windows;
        std::vector<string> urls;
        std::vector<int> renderers;
        std::map<Uint32, Media_Player*> map_;

        // Parsing layout
        parse_layout(path2layout, urls, renderers);
        if(objid.empty() && url.empty() && urls.empty()) {
            fprintf(stderr, "url/objid or path to the correct layout file must be specified\n");
            print_usage(argv, EXIT_FAILURE);
        }

        if(players_num <= urls.size())
            players_num = urls.size();
        else {
            if(objid.empty() && url.empty()) {
                fprintf(stderr, "Since the number of players is bigger than the size of the layout, "
                        "the default objid or url should also be specified for the rest of players\n");
                print_usage(argv, EXIT_FAILURE);
            }

            if(url.empty()) {
                mc->getMediaURL(&result, &url, objid, local_streaming);
                if(result.errorCode != 0 || result.errorString != "No error") {
                    fprintf(stderr, "Fail to get media url for objid \"%s\": %d (%s)\n",
                            objid.c_str(), result.errorCode, result.errorString.c_str());
                    do_exit(EXIT_FAILURE);
                }
            }
        }

        if(show_on_screen) {
            // create players inside this function
            create_layout(players, players_num, tmp_windows, wm_info, renderers, map_);
            for(unsigned p = 0; p < players_num; p++)
                players[p]->open(p < urls.size() ? urls[p].c_str()
                                                 : url.c_str());
        } else {
            for(unsigned p = 0; p < players_num; p++)
            {
                players[p] = new Media_Player(p);
                players[p]->open(p < urls.size() ? urls[p].c_str()
                                                 : url.c_str());
            }
        }

        SDL_TimerID timer_id = SDL_AddTimer(playback_time*1000, on_timer_func, NULL);
        V_E(timer_id, ==0, SDL_AddTimer())

        handle_events(players, players_num, map_);

        V_W(SDL_RemoveTimer(timer_id), == SDL_FALSE, )

        // destroy players in another thread to avoid deadlock
        players[players_num] = NULL;
        SDL_Thread* thread = SDL_CreateThread(players_destr, "PlayersDestructor", players);
        V_E(thread, == NULL, SDL_CreateThread())

        // let's handle only 'upload_texture' user events to avoid deadlock
        SDL_Event e;
        while(SDL_WaitEventTimeout(&e, 3000))
            if(e.type == SDL_USEREVENT && e.user.data1 && e.user.data2)
                reinterpret_cast<int (*)(void*)>(e.user.data1)(e.user.data2);

        int thread_ret;
        SDL_WaitThread(thread, &thread_ret);

        size_t j=0;
        for(; j<tmp_windows.size(); j++)
            SDL_DestroyWindow(tmp_windows[j]);

        printf("Number of players destroyed: %d, number of windows destroyed: %zu\n",
               thread_ret, j);
    }

    return 0;
}
