/*
 * Copyright (c) 2008-2009 Intel Corporation. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


/* gcc -o putsurface putsurface.c -lva -lva-x11 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/time.h>

#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <va/va.h>
#include <va/va_x11.h>

#include <assert.h>

#include <pthread.h>

/*currently, if XCheckWindowEvent was called  in more than one thread, it would cause
 * XIO:  fatal IO error 11 (Resource temporarily unavailable) on X server ":0.0"
 *       after 87 requests (83 known processed) with 0 events remaining.
 *
 *       X Error of failed request:  BadGC (invalid GC parameter)
 *       Major opcode of failed request:  60 (X_FreeGC)
 *       Resource id in failed request:  0x600034
 *       Serial number of failed request:  398
 *       Current serial number in output stream:  399
 * The root cause is unknown. */

#define CHECK_VASTATUS(va_status,func)                                  \
if (va_status != VA_STATUS_SUCCESS) {                                   \
    fprintf(stderr,"%s:%s (%d) failed,exit\n", __func__, func, __LINE__); \
    exit(1);                                                            \
}

#include "loadsurface.h"


#define SURFACE_NUM 5
static  VASurfaceID surface_id[SURFACE_NUM];
static  int surface_width=352, surface_height=288;
static  int win_width=352, win_height=288;
static  Window win_thread0, win_thread1;
static  Pixmap pixmap_thread0, pixmap_thread1;
static  GC context_thread0, context_thread1;
static  Display *x11_display;
static  VADisplay *va_dpy;
static  int multi_thread=0;
static  int put_pixmap = 0;
static  int test_clip = 0;
static  int display_field = VA_FRAME_PICTURE;
static  int check_event = 1;
static  int verbose=0;
static  pthread_mutex_t surface_mutex[SURFACE_NUM];

static pthread_mutex_t gmutex;

static  int box_width=32;

static Pixmap create_pixmap(int width, int height)
{
    int screen = DefaultScreen(x11_display);
    Window root;
    Pixmap pixmap;
    XWindowAttributes attr;
    
    root = RootWindow(x11_display, screen);

    XGetWindowAttributes (x11_display, root, &attr);
    
    printf("Create a pixmap from ROOT window %dx%d\n\n", attr.width, attr.height);
    pixmap = XCreatePixmap(x11_display, root, attr.width, attr.height,
                           DefaultDepth(x11_display, DefaultScreen(x11_display)));

    return pixmap;
}

static int create_window(int width, int height)
{
    int screen = DefaultScreen(x11_display);
    Window root, win;

    root = RootWindow(x11_display, screen);

    printf("Create window0 for thread0\n");
    win_thread0 = win = XCreateSimpleWindow(x11_display, root, 0, 0, width, height,
                                            0, 0, WhitePixel(x11_display, 0));
    if (win) {
        XSizeHints sizehints;
        sizehints.width  = width;
        sizehints.height = height;
        sizehints.flags = USSize;
        XSetNormalHints(x11_display, win, &sizehints);
        XSetStandardProperties(x11_display, win, "Thread 0", "Thread 0",
                               None, (char **)NULL, 0, &sizehints);

        XMapWindow(x11_display, win);
    }
    context_thread0 = XCreateGC(x11_display, win, 0, 0);
    XSelectInput(x11_display, win, KeyPressMask | StructureNotifyMask);
    XSync(x11_display, False);

    if (put_pixmap)
        pixmap_thread0 = create_pixmap(width, height);
    
    if (multi_thread == 0)
        return 0;

    printf("Create window1 for thread1\n");
    
    win_thread1 = win = XCreateSimpleWindow(x11_display, root, width, 0, width, height,
                                            0, 0, WhitePixel(x11_display, 0));
    if (win) {
        XSizeHints sizehints;
        sizehints.width  = width;
        sizehints.height = height;
        sizehints.flags = USSize;
        XSetNormalHints(x11_display, win, &sizehints);
        XSetStandardProperties(x11_display, win, "Thread 1", "Thread 1",
                               None, (char **)NULL, 0, &sizehints);

        XMapWindow(x11_display, win);
    }
    if (put_pixmap)
        pixmap_thread1 = create_pixmap(width, height);

    context_thread1 = XCreateGC(x11_display, win, 0, 0);
    XSelectInput(x11_display, win, KeyPressMask | StructureNotifyMask);
    XSync(x11_display, False);
    
    return 0;
}

static VASurfaceID get_next_free_surface(int *index)
{
    VASurfaceStatus surface_status;
    int i;

    assert(index);

    for (i=0; i<SURFACE_NUM; i++) {
        surface_status = 0;
        vaQuerySurfaceStatus(va_dpy, surface_id[i], &surface_status);
        if (surface_status == VASurfaceReady)
        {
            if (0 == pthread_mutex_trylock(&surface_mutex[i]))
            {
                *index = i;
                break;
            }
        }
    }

    if (i==SURFACE_NUM)
        return VA_INVALID_SURFACE;
    else
        return surface_id[i];
}

/*
 * Helper function for profiling purposes
 */
static unsigned long get_tick_count(void)
{
    struct timeval tv;
    if (gettimeofday(&tv, NULL))
        return 0;
    return tv.tv_usec/1000+tv.tv_sec*1000;
}

static int putsurface_thread(void *data)
{
    int width=win_width, height=win_height;
    Drawable draw;
    Window win = (Window)data;
    Pixmap pixmap = 0;
    GC context = NULL;
    int quit = 0;
    VAStatus vaStatus;
    int row_shift = 0;
    int index = 0;
    Bool is_event; 
    XEvent event;
    unsigned int frame_num=0, start_time, putsurface_time;
    VARectangle cliprects[2]; /* client supplied clip list */
    
    if (win == win_thread0) {
        printf("Enter into thread0\n\n");
        pixmap = pixmap_thread0;
        context = context_thread0;
    }
    
    if (win == win_thread1) {
        printf("Enter into thread1\n\n");
        pixmap = pixmap_thread1;
        context = context_thread1;
    }
    
    if (put_pixmap) {
        printf("vaPutSurface into a Pixmap, then copy into the Window\n\n");
        draw = pixmap;
    } else {
        printf("vaPutSurface into a Window directly\n\n");
        draw = win;
    }

    putsurface_time = 0;
    while (!quit) {
        VASurfaceID surface_id = VA_INVALID_SURFACE;
        
        while (surface_id == VA_INVALID_SURFACE)
            surface_id = get_next_free_surface(&index);

        if (verbose) printf("Thread %x Display surface 0x%p,\n", (unsigned int)win, (void *)surface_id);

        upload_surface(va_dpy, surface_id, box_width, row_shift, display_field);

        start_time = get_tick_count();
        vaStatus = vaPutSurface(va_dpy, surface_id, draw,
                                0,0,surface_width,surface_height,
                                0,0,width,height,
                                (test_clip==0)?NULL:&cliprects[0],
                                (test_clip==0)?0:2,
                                display_field);
        CHECK_VASTATUS(vaStatus,"vaPutSurface");
        putsurface_time += (get_tick_count() - start_time);
        
        if ((frame_num % 0xff) == 0) {
            fprintf(stderr, "%.2f FPS             \r", 256000.0 / (float)putsurface_time);
            putsurface_time = 0;

            if (test_clip) {
                srand((unsigned)time(NULL));
                
                cliprects[0].x = (rand() % width);
                cliprects[0].y = (rand() % height);
                cliprects[0].width = (rand() % (width - cliprects[0].x));
                cliprects[0].height = (rand() % (height - cliprects[0].y));

                cliprects[1].x = (rand() % width);
                cliprects[1].y = (rand() % height);
                cliprects[1].width = (rand() % (width - cliprects[1].x));
                cliprects[1].height = (rand() % (height - cliprects[1].y));
                printf("\nTest clip (%d,%d, %d x %d) and (%d,%d, %d x %d) \n",
                       cliprects[0].x, cliprects[0].y, cliprects[0].width, cliprects[0].height,
                       cliprects[1].x, cliprects[1].y, cliprects[1].width, cliprects[1].height);
            }
        }
        
        if (put_pixmap)
            XCopyArea(x11_display, pixmap, win,  context, 0, 0, width, height, 0, 0);
        
        pthread_mutex_unlock(&surface_mutex[index]);

        if (check_event) {
            pthread_mutex_lock(&gmutex);
            is_event = XCheckWindowEvent(x11_display, win, StructureNotifyMask|KeyPressMask,&event);
            pthread_mutex_unlock(&gmutex);
            if (is_event) {
                /* bail on any focused key press */
                if(event.type == KeyPress) {  
                    quit = 1;
                    break;
                }

                /* rescale the video to fit the window */
                if(event.type == ConfigureNotify) { 
                    width = event.xconfigure.width;
                    height = event.xconfigure.height;
                    printf("Scale window to %dx%d\n", width, height);
                }	
            }
        }
        
        row_shift++;
        if (row_shift==(2*box_width)) row_shift= 0;

        frame_num++;
    }

    pthread_exit(NULL);
}


int main(int argc,char **argv)
{
    int major_ver, minor_ver;
    VAStatus va_status;
    pthread_t thread1;
    int ret;
    char c;
    int i;

    while ((c =getopt(argc,argv,"w:h:d:f:tcep?nv") ) != EOF) {
        switch (c) {
            case '?':
                printf("putsurface <options>\n");
                printf("           -p output to pixmap\n");
                printf("           -d the dimension of black/write square box, default is 32\n");
                printf("           -t multi-threads\n");
                printf("           -e don't check X11 event\n");
                printf("           -c test clipbox\n");
                printf("           -f <1/2> top field, or bottom field\n");
                printf("           -v verbose output\n");
                exit(0);
                break;
            case 'w':
                win_width = atoi(optarg);
                break;
            case 'h':
                win_height = atoi(optarg);
                break;
            case 'd':
                box_width = atoi(optarg);
                break;
            case 't':
                multi_thread = 1;
                printf("Two threads to do vaPutSurface\n");
                break;
            case 'e':
                check_event = 0;
                break;
            case 'p':
                put_pixmap = 1;
                break;
            case 'c':
                test_clip = 1;
                break;
            case 'f':
                if (atoi(optarg) == 1) {
                    printf("Display TOP field\n");
                    display_field = VA_TOP_FIELD;
                } else if (atoi(optarg) == 2) {
                    printf("Display BOTTOM field\n");
                    display_field = VA_BOTTOM_FIELD;
                } else
                    printf("The validate input for -f is: 1(top field)/2(bottom field)\n");
                break;
            case 'v':
                verbose = 1;
                printf("Enable verbose output\n");
                break;
        }
    }
    
    x11_display = XOpenDisplay(":0.0");
    if (x11_display == NULL) {
        fprintf(stderr, "Can't connect X server!\n");
        exit(-1);
    }

    create_window(win_width, win_height);

    va_dpy = vaGetDisplay(x11_display);
    va_status = vaInitialize(va_dpy, &major_ver, &minor_ver);
    CHECK_VASTATUS(va_status, "vaInitialize");

    surface_width = win_width;
    surface_height = win_height;
    va_status = vaCreateSurfaces(va_dpy,surface_width, surface_height,
                                VA_RT_FORMAT_YUV420, SURFACE_NUM, &surface_id[0]);
    CHECK_VASTATUS(va_status, "vaCreateSurfaces");

    if (check_event)
        pthread_mutex_init(&gmutex, NULL);
    
    for(i = 0; i< SURFACE_NUM; i++)
        pthread_mutex_init(&surface_mutex[i], NULL);
    
    if (multi_thread == 1) 
        ret = pthread_create(&thread1, NULL, (void *)putsurface_thread, (void*)win_thread1);

    putsurface_thread((void *)win_thread0);

    if (multi_thread == 1) 
        pthread_join(thread1, (void **)&ret);

    vaDestroySurfaces(va_dpy,&surface_id[0],SURFACE_NUM);    
    vaTerminate(va_dpy);
    
    return 0;
}
