/***************************************************************************
    \file GUI_xvRender.cpp
    \author mean fixounet@free.fr, This file is strongly derivated from xine/mplayer/mpeg2dec

    copyright            : (C) 2002/2016 by mean
    email                : fixounet@free.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define VERBOSE_XV

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xvlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "ADM_default.h"
#include "GUI_render.h"

#include "GUI_accelRender.h"
#include "GUI_xvRender.h"

// Instantiator
//
VideoRenderBase *spawnXvRender()
{
    return new XvRender();
}

/**
    \fn XvRender
 *  \brief Ctor
*/
XvRender::XvRender(void)
{
}
/**
 * \fn XvRender dtor
 */
XvRender::~XvRender()
{
}
/**
 *  \fn rescaleDisplay
 */
void XvRender::rescaleDisplay(void)
{
    double dw = displayWidth;
    double dh = displayHeight;
    dw *= info.scalingFactor;
    dh *= info.scalingFactor;
    displayWidth = (uint32_t)(dw + 0.5);
    displayHeight = (uint32_t)(dh + 0.5);
}
/**
    \fn init
*/
bool XvRender::init(GUI_WindowInfo *window, uint32_t w, uint32_t h, float zoom)
{
    ADM_info("[Xvideo]Xv start\n");
    info = *window;
    baseInit(w, h, zoom);
    rescaleDisplay();
    return lowLevelXvInit(window, w, h);
}
/**
    \fn stop
*/
bool XvRender::stop(void)
{
    ADM_assert(xv_port);
    ADM_assert(xv_display);

    ADM_info("[Xvideo] Releasing Xv Port\n");
    XLockDisplay(xv_display);
    if (XvUngrabPort(xv_display, xv_port, 0) != Success)
    {
        ADM_warning("[Xvideo] Trouble releasing port...\n");
    }
    XUnlockDisplay(xv_display);
    xvimage = NULL;
    xv_display = NULL;
    xv_port = 0;
    return true;
}
/**
    \fn displayImage
*/
bool XvRender::displayImage(ADMImage *src)
{
    if (xvimage)
    {
        XLockDisplay(xv_display);
        int plane = xvimage->pitches[0] * imageHeight;
        int plane2 = xvimage->pitches[1] * (imageHeight >> 1);
        BitBlit((uint8_t *)xvimage->data, xvimage->pitches[0], src->GetReadPtr(PLANAR_Y), src->GetPitch(PLANAR_Y),
                imageWidth, imageHeight);
        BitBlit((uint8_t *)xvimage->data + plane, xvimage->pitches[1], src->GetReadPtr(PLANAR_V),
                src->GetPitch(PLANAR_V), imageWidth / 2, imageHeight / 2);
        BitBlit((uint8_t *)xvimage->data + plane + plane2, xvimage->pitches[2], src->GetReadPtr(PLANAR_U),
                src->GetPitch(PLANAR_U), imageWidth / 2, imageHeight / 2);
        XUnlockDisplay(xv_display);
        xvDraw(imageWidth, imageHeight, displayWidth, displayHeight);
    }
    return 1;
}

/**
    \fn changeZoom
*/
bool XvRender::changeZoom(float newZoom)
{
    ADM_info("changing zoom, xv render.\n");
    calcDisplayFromZoom(newZoom);
    currentZoom = newZoom;
    rescaleDisplay();
    return true;
}
/**
    \fn refresh
*/
bool XvRender::refresh(void)
{
    // since we dont know how to redraw without help, ask above
    ADM_info("XV:refresh\n");
    xvDraw(imageWidth, imageHeight, displayWidth, displayHeight);
    return true;
}

/**
    \fn xvDraw
*/
bool XvRender::xvDraw(uint32_t w, uint32_t h, uint32_t destW, uint32_t destH)
{
    if (!xvimage)
        return false;
    XLockDisplay(xv_display);
    XvShmPutImage(xv_display, xv_port, xv_win, xv_gc, xvimage, 0, 0, w, h, 0, 0, destW, destH, False);
    XUnlockDisplay(xv_display);
    XSync(xv_display, False);
    return true;
}
/**
    \fn lowLevelXvInit
*/
#define WDN xv_display
bool XvRender::lowLevelXvInit(GUI_WindowInfo *window, uint32_t w, uint32_t h)
{

    unsigned int port, adaptors;
    static XvAdaptorInfo *ai;
    static XvAdaptorInfo *curai;
    static XShmSegmentInfo Shminfo;

    xv_display = (Display *)window->display;
    xv_win = window->systemWindowId;
    xv_port = 0;
    {
        unsigned int ver, rel, req, ev, err;
        if (Success != XvQueryExtension(WDN, &ver, &rel, &req, &ev, &err))
        {
            ADM_info("[Xvideo] Query Extension failed\n");
            return false;
        }
    }
    /* check for Xvideo support */
    if (Success != XvQueryAdaptors(WDN, DefaultRootWindow(WDN), &adaptors, &ai))
    {
        ADM_info("[Xvideo] Query Adaptor failed\n");
        return false;
    }
    curai = ai;

    // Dump infos
    port = 0;
    for (int i = 0; (!port) && (i < adaptors); i++)
    {
        XvFormat *formats;
        displayAdaptorInfo(i, curai);
        formats = curai->formats;
        for (int k = 0; (k < curai->num_ports) && !port; k++)
        {
            if (lookupYV12(WDN, k + curai->base_id, &xv_format))
            {
                port = k + curai->base_id;
                if (Success == XvGrabPort(WDN, port, 0))
                {
                    ADM_info("[Xvideo] Xv YV12 found at port :%d, format : %" PRIi32"\n", port, xv_format);
                    break;
                }
                port = 0;
            }
        }
        if (port)
        {
            break;
        }
        curai++;
    }
    //
    if (!port)
    {
        ADM_info("[Xvideo] no port found\n");
        return false;
    }
    {
        xv_port = port;

        XSetWindowAttributes xswa;
        XWindowAttributes attribs;
        static Atom xv_atom;
        unsigned long xswamask;
        int erCode;

        /* check if colorkeying is needed */
        xv_atom = getAtom("XV_AUTOPAINT_COLORKEY", xv_display, xv_port);
        if (xv_atom != None)
        {
            XvSetPortAttribute(xv_display, xv_port, xv_atom, 1);
        }
        else
        {
            ADM_warning("No autopaint \n");
        }

        /* if we have to deal with colorkeying ... */

        xvimage = XvShmCreateImage(WDN, xv_port, xv_format, 0, w, h, &Shminfo);

        if (!xvimage)
        {
            ADM_warning("XvShmCreateImage failed\n");
            return false;
        }
        Shminfo.shmid = shmget(IPC_PRIVATE, xvimage->data_size, IPC_CREAT | 0777);
        if (Shminfo.shmid <= 0)
        {
            ADM_warning("shmget failed\n");
            return false;
        }
        Shminfo.shmaddr = (char *)shmat(Shminfo.shmid, 0, 0);
        Shminfo.readOnly = False;
        if (Shminfo.shmaddr == (char *)-1)
        {
            ADM_warning("Shmat failed\n");
            return false;
        }
        xvimage->data = Shminfo.shmaddr;
        XShmAttach(WDN, &Shminfo);
        XSync(WDN, False);
        erCode = shmctl(Shminfo.shmid, IPC_RMID, 0);
        if (erCode)
        {
            ADM_warning("Shmctl failed :%d\n", erCode);
            return false;
        }
        memset(xvimage->data, 0, xvimage->data_size);
        memset(xvimage->data + xvimage->offsets[1], 128, xvimage->pitches[1] * (xvimage->height / 2));
        memset(xvimage->data + xvimage->offsets[2], 128, xvimage->pitches[2] * (xvimage->height / 2));
        xv_xgc.graphics_exposures = False;
        xv_gc = XCreateGC(xv_display, xv_win, 0L, &xv_xgc);
    }
    ADM_info("[Xvideo] Xv init succeedeed\n");
    return true;
}

/**
    \fn lookupYV12
 *  \brief search for YV12 compatible port, returns it in fmt
*/
bool XvRender::lookupYV12(Display *dis, uint32_t port, uint32_t *fmt)
{
    XvImageFormatValues *formatValues;
    int imgfmt;
    int k;
    bool found = false;

    formatValues = XvListImageFormats(dis, port, &imgfmt);
    // when "formatValues" is NULL, imgfmt should be zero, too
    //    if (formatValues)
    // this will run endless or segfault if the colorspace searched for isn't found
    for (k = 0; !found && (k < imgfmt); k++)
    {
        ADM_info("[Xvideo]%d/%d: %" PRIx32 " %d --> %s\n", k, imgfmt, port, formatValues[k].id, formatValues[k].guid);
        if (!strcmp(formatValues[k].guid, "YV12"))
        {
            found = true;
            *fmt = formatValues[k].id;
        }
    }
    if (formatValues) // checking if it's no NULL-pointer won't hurt
        XFree(formatValues);
    return found;
}
/**
    \fn getAtom
*/
Atom XvRender::getAtom(const char *string, Display *xv_display, unsigned int xv_port)
{
    XvAttribute *attributes;
    int attrib_count, i;
    Atom xv_atom = None;

    attributes = XvQueryPortAttributes(xv_display, xv_port, &attrib_count);
    if (!attributes)
        return None;

    for (i = 0; i < attrib_count; ++i)
    {
        if (!strcmp(attributes[i].name, string))
        {
            xv_atom = XInternAtom(xv_display, string, False);
            XFree(attributes);
            return xv_atom;
        }
    }
    XFree(attributes);
    return None;
}
/**
 *
 * @param curai
 */
void XvRender::displayAdaptorInfo(int num, XvAdaptorInfo *curai)
{
#ifdef VERBOSE_XV
    ADM_info("[Xvideo]_______________________________\n");
    ADM_info("[Xvideo] Adaptor           : %d\n", num);
    ADM_info("[Xvideo] Base ID           : %ld\n", curai->base_id);
    ADM_info("[Xvideo] Nb Port           : %lu\n", curai->num_ports);
#define CHECK(x) (curai->type & x) ? (" | " #x) : ""
    ADM_info("[Xvideo] Type              : %d%s%s%s%s%s\n", curai->type, CHECK(XvInputMask), CHECK(XvOutputMask),
             CHECK(XvVideoMask), CHECK(XvStillMask), CHECK(XvImageMask));

    ADM_info("[Xvideo] Name              : %s\n", curai->name);
    ADM_info("[Xvideo] Num Adap          : %lu\n", curai->num_adaptors);
    ADM_info("[Xvideo] Num fmt           : %lu\n", curai->num_formats);
#endif
}
//
