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

#include "SDL2_Renderer_Impl.h"

namespace videonext { namespace media {

static const struct sdl_texture_format_entry {
    enum AVPixelFormat format; int texture_fmt;
} sdl_texture_format_map[] = {
    { AV_PIX_FMT_RGB8,           SDL_PIXELFORMAT_RGB332 },
    { AV_PIX_FMT_RGB444,         SDL_PIXELFORMAT_RGB444 },
    { AV_PIX_FMT_RGB555,         SDL_PIXELFORMAT_RGB555 },
    { AV_PIX_FMT_BGR555,         SDL_PIXELFORMAT_BGR555 },
    { AV_PIX_FMT_RGB565,         SDL_PIXELFORMAT_RGB565 },
    { AV_PIX_FMT_BGR565,         SDL_PIXELFORMAT_BGR565 },
    { AV_PIX_FMT_RGB24,          SDL_PIXELFORMAT_RGB24 },
    { AV_PIX_FMT_BGR24,          SDL_PIXELFORMAT_BGR24 },
    { AV_PIX_FMT_0RGB32,         SDL_PIXELFORMAT_RGB888 },
    { AV_PIX_FMT_0BGR32,         SDL_PIXELFORMAT_BGR888 },
    { AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888 },
    { AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888 },
    { AV_PIX_FMT_RGB32,          SDL_PIXELFORMAT_ARGB8888 },
    { AV_PIX_FMT_RGB32_1,        SDL_PIXELFORMAT_RGBA8888 },
    { AV_PIX_FMT_BGR32,          SDL_PIXELFORMAT_ABGR8888 },
    { AV_PIX_FMT_BGR32_1,        SDL_PIXELFORMAT_BGRA8888 },
    { AV_PIX_FMT_YUV420P,        SDL_PIXELFORMAT_IYUV },
    { AV_PIX_FMT_YUVJ420P,       SDL_PIXELFORMAT_IYUV }, // ?
    { AV_PIX_FMT_YUYV422,        SDL_PIXELFORMAT_YUY2 },
    { AV_PIX_FMT_UYVY422,        SDL_PIXELFORMAT_UYVY },
    { AV_PIX_FMT_NONE,           0 },
};

struct destr_context_t
{
    SDL_Window *sdl_window;
    SDL_Renderer *renderer;
    SDL_Texture  *texture;
};

SDL2_Renderer_Impl::SDL2_Renderer_Impl()
    : sdl_window_(0), renderer_(0), texture_(0)
    , texture_width_(0), texture_height_(0)
    , sdl_texture_fmt_(0), texture_uploaded_(SDL_FALSE), frame_(0)
{
    lock_ = SDL_CreateMutex();
    cond_ = SDL_CreateCond();
}

// guarantees deffered destruction in the main video thread
int destroy_func(void* param)
{
    destr_context_t* d_ctx = reinterpret_cast<destr_context_t*>(param);
    if(!d_ctx)
        return 0;

    if (d_ctx->texture)
        SDL_DestroyTexture(d_ctx->texture);
    if (d_ctx->renderer)
        SDL_DestroyRenderer(d_ctx->renderer);
    if (d_ctx->sdl_window)
        SDL_DestroyWindow(d_ctx->sdl_window);

    free(d_ctx);
    return 0;
}

SDL2_Renderer_Impl::~SDL2_Renderer_Impl()
{
    SDL_Event e;
    SDL_memset(&e, 0, sizeof(e)); /* or SDL_zero(e) */

    destr_context_t* d_ctx = (destr_context_t*)calloc(1, sizeof(destr_context_t));
    if (d_ctx) {
        d_ctx->texture = texture_;
        d_ctx->renderer = renderer_;
        d_ctx->sdl_window = sdl_window_;
    }

    // deffered destruction in the main video thread
    e.type = SDL_USEREVENT;
    e.user.windowID = SDL_GetWindowID(sdl_window_);
    e.user.timestamp = SDL_GetTicks();
    e.user.data1 = (void*)&destroy_func;
    e.user.data2 = d_ctx;
    SDL_PushEvent(&e);
}

// make uploading texture in the main video thread without extra copying the frame
int upload_texture_func(void* param)
{
    return reinterpret_cast<SDL2_Renderer_Impl*>(param)->upload_texture();
}

/*virtual*/ int SDL2_Renderer_Impl::init(void* window_handle)
{
    // hint for WGL only, currently
    char win_str[24];
    sprintf(win_str, "%p", window_handle);
    SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT, win_str);

    sdl_window_ = SDL_CreateWindowFrom(window_handle);
    V_R(sdl_window_, == 0, SDL_CreateWindowFrom())

    if(SDL_GetWindowFlags(sdl_window_) & SDL_WINDOW_HIDDEN)
        SDL_ShowWindow(sdl_window_);

    int num_drivers = SDL_GetNumRenderDrivers();
    V_R(num_drivers, <0, SDL_GetNumRenderDrivers())

    printf("Drivers count: %d\n", num_drivers);
    if(num_drivers == 0) {
        av_log(NULL, AV_LOG_WARNING, "There is no driver available\n");
        return -1;
    }

    for (int i=0; i<num_drivers; i++)
    {
        SDL_RendererInfo dr_info;
        _V(SDL_GetRenderDriverInfo(i, &dr_info), <0, , continue;)
        printf("Driver[%d] name: %s\n", i, dr_info.name);

        if(dr_info.flags & SDL_RENDERER_SOFTWARE)
            printf(" the renderer is a software fallback\n");
        if(dr_info.flags & SDL_RENDERER_ACCELERATED)
            printf(" the renderer uses hardware acceleration\n");
        if(dr_info.flags & SDL_RENDERER_PRESENTVSYNC)
            printf(" present is synchronized with the refresh rate\n");
        if(dr_info.flags & SDL_RENDERER_TARGETTEXTURE)
            printf(" the renderer supports rendering to texture\n");

        char tex_fmts[512] = {"\0"};
        char* ptr = tex_fmts;
        for(unsigned j=0; j<dr_info.num_texture_formats; j++)
            ptr += sprintf(ptr, "'%s' ", SDL_GetPixelFormatName(dr_info.texture_formats[j]));
        printf(" num_tex_fmts=%d (%s), max_width=%d, max_height=%d\n",
               dr_info.num_texture_formats, tex_fmts,
               dr_info.max_texture_width, dr_info.max_texture_height);
    }

    // parse window title
    const char* title = SDL_GetWindowTitle(sdl_window_);
    char* ptr = strstr((char*)title, "__");
    printf("Window title: '%s', ptr: '%s'\n", title, ptr);
    int renderer_num = -1;
    if(ptr && 1 == sscanf(ptr, "__%d", &renderer_num))
        printf("Parsed renderer number from window title '%s': %d\n",
               title, renderer_num);

    // TODO: add a fallback logic from the best available renderer driver down to software renderer
    renderer_ = SDL_CreateRenderer(sdl_window_, renderer_num, renderer_num == -1
                                   ? SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
                                   : 0);

    V_R(renderer_, == 0, SDL_CreateRenderer())
    printf("SDL_CreateRenderer(): OK\n");
    SDL_RendererInfo render_info;
    _V(SDL_GetRendererInfo(renderer_, &render_info), <0, , return 0;)

    char tex_fmts[1024] = {"\0"};
    char* p = tex_fmts;
    for(unsigned j=0; j<render_info.num_texture_formats; j++) {
        p += sprintf(p, "%s'%s'",
                     j ? ", " : "",
                     SDL_GetPixelFormatName(render_info.texture_formats[j]));

        if(render_info.texture_formats[j] == SDL_PIXELFORMAT_IYUV) {
            sdl_texture_fmt_ = SDL_PIXELFORMAT_IYUV;
            //break;
        }
    }

    printf("Used renderer driver name: %s, max_width=%d, max_height=%d, supported formats: %s\n",
           render_info.name, render_info.max_texture_width, render_info.max_texture_height, tex_fmts);

    if(sdl_texture_fmt_ != SDL_PIXELFORMAT_IYUV) {
        sdl_texture_fmt_ = SDL_PIXELFORMAT_ARGB8888;
        printf("Only SDL_PIXELFORMAT_ARGB8888 supported\n");
    } else
        printf("SDL_PIXELFORMAT_IYUV supported\n");

    // insert (winID ==> Media_Player*) record into our map_
    SDL_Event e;
    SDL_memset(&e, 0, sizeof(e)); /* or SDL_zero(e) */
    e.type = SDL_USEREVENT;
    e.user.timestamp = SDL_GetTicks();
    e.user.windowID = SDL_GetWindowID(sdl_window_);
    e.user.data1 = window_handle;
    SDL_PushEvent(&e);

    return 0;
}

/*virtual*/ VN_PLAYER_PIXEL_FORMAT SDL2_Renderer_Impl::get_preferred_pixel_format() const
{
    return sdl_texture_fmt_ == SDL_PIXELFORMAT_IYUV
            ? PIX_YUV420P
            : PIX_RGB32; // should be supported by any renderer driver
}

/*virtual*/ int SDL2_Renderer_Impl::process(const vn_player_frame_t *frame)
{
    SDL_Event e;
    SDL_memset(&e, 0, sizeof(e)); /* or SDL_zero(e) */
    e.type = SDL_USEREVENT;
    e.user.windowID = SDL_GetWindowID(sdl_window_);

    SDL_LockMutex(lock_);

    frame_ = frame;
    e.user.timestamp = SDL_GetTicks();
    e.user.data1 = (void*)&upload_texture_func;
    e.user.data2 = this;
    SDL_PushEvent(&e);

    //if(!texture_uploaded_)
        // waiting until texture upload in the main video thread
        SDL_CondWait(cond_, lock_);
    texture_uploaded_ = SDL_FALSE;
    SDL_UnlockMutex(lock_);

    return 0;
}

int SDL2_Renderer_Impl::upload_texture()
{
    SDL_LockMutex(lock_);

    const vn_player_frame_t *frame = frame_;
    //SDL_RenderClear(renderer_);

    if(texture_width_ != frame->width || texture_height_ != frame->height)
    {
        if (texture_)
            SDL_DestroyTexture(texture_);

        texture_ = SDL_CreateTexture(renderer_, sdl_texture_fmt_, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height);
        texture_width_  = frame->width;
        texture_height_ = frame->height;

        printf("linesizes: [0]=%d, [1]=%d, [2]=%d, [3]=%d\n",
               frame->data_size[0],
               frame->data_size[1],
               frame->data_size[2],
               frame->data_size[3]
        );
    }

    // FIXME: seems like this code is superfluous
    /*int r = SDL_RenderSetViewport(renderer_, NULL);
    if (r)
        fprintf(stderr, "SDL_RenderSetViewport(): %s\n", SDL_GetError());*/

    int r = -1;
    switch(sdl_texture_fmt_) {
    case SDL_PIXELFORMAT_IYUV:
    case SDL_PIXELFORMAT_YUY2:
    case SDL_PIXELFORMAT_UYVY:
        if (frame->data_size[0] > 0 && frame->data_size[1] > 0 && frame->data_size[2] > 0) {
            r = SDL_UpdateYUVTexture(texture_, NULL,
                                     frame->data[0], frame->data_size[0],
                                     frame->data[1], frame->data_size[1],
                                     frame->data[2], frame->data_size[2]);
        }/* else if (frame->data_size[0] < 0 && frame->data_size[1] < 0 && frame->data_size[2] < 0) {
            r = SDL_UpdateYUVTexture(texture_, NULL, frame->data[0] + frame->data_size[0] * (frame->height - 1), -frame->data_size[0],
                                     frame->data[1] + frame->data_size[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->data_size[1],
                                     frame->data[2] + frame->data_size[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->data_size[2]);
        }*/
        _V(r, <0, SDL_UpdateYUVTexture(),
           SDL_CondSignal(cond_);
           SDL_UnlockMutex(lock_);
           return -1;
        )
        break;
    case SDL_PIXELFORMAT_RGB332:
    case SDL_PIXELFORMAT_RGB444:
    case SDL_PIXELFORMAT_RGB555:
    case SDL_PIXELFORMAT_BGR555:
    case SDL_PIXELFORMAT_RGB565:
    case SDL_PIXELFORMAT_BGR565:
    case SDL_PIXELFORMAT_RGB24:
    case SDL_PIXELFORMAT_BGR24:
    case SDL_PIXELFORMAT_RGB888:
    case SDL_PIXELFORMAT_RGBX8888:
    case SDL_PIXELFORMAT_BGR888:
    case SDL_PIXELFORMAT_BGRX8888:
    case SDL_PIXELFORMAT_ARGB8888:
    case SDL_PIXELFORMAT_RGBA8888:
    case SDL_PIXELFORMAT_ABGR8888:
    case SDL_PIXELFORMAT_BGRA8888:
        r = SDL_UpdateTexture(texture_, NULL, frame->data[0], frame->data_size[0]);
        _V(r, <0, SDL_UpdateTexture(),
           SDL_CondSignal(cond_);
           SDL_UnlockMutex(lock_);
           return -1;
        )
        break;
    default:
        fprintf(stderr, "Unsupported pixel format\n");
        SDL_CondSignal(cond_);
        SDL_UnlockMutex(lock_);
        abort();
        break;
    }

    texture_uploaded_ = SDL_TRUE;
    SDL_CondSignal(cond_);
    SDL_UnlockMutex(lock_);

    SDL_Event e;
    SDL_memset(&e, 0, sizeof(e)); /* or SDL_zero(e) */

    e.type = SDL_WINDOWEVENT;
    e.window.event = SDL_WINDOWEVENT_EXPOSED;
    e.window.timestamp = SDL_GetTicks();
    e.window.windowID = SDL_GetWindowID(sdl_window_);
    SDL_PushEvent(&e);

    return 0;
}

/*virtual*/ int SDL2_Renderer_Impl::draw(Image_Fill_Mode mode, Image_Calibration *ic, Image_Pan_Zoom *pz)
{
    if(!texture_)
        return 0;

    SDL_RenderClear(renderer_);
    V_R(SDL_RenderCopy(renderer_, texture_, NULL, NULL), <0, SDL_RenderCopy())
    SDL_RenderPresent(renderer_);

    return 0;
}

}}
