/*
 * 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.
 */

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

#include <sys/time.h>

#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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 16

static  void *win_display;
static  VADisplay va_dpy;
static  VAImageFormat *va_image_formats;
static  int va_num_image_formats = -1;
static  VAConfigID vpp_config_id = VA_INVALID_ID;
static  VASurfaceAttrib *va_surface_attribs;
static  int va_num_surface_attribs = -1;
static  VASurfaceID surface_id[SURFACE_NUM];
static  pthread_mutex_t surface_mutex[SURFACE_NUM];

static  void *drawable_thread0, *drawable_thread1;
static  int surface_width = 352, surface_height = 288;
static  int win_x = 0, win_y = 0;
static  int win_width = 352, win_height = 288;
static  int frame_rate = 0;
static  unsigned long long frame_num_total = ~0;
static  int check_event = 1;
static  int put_pixmap = 0;
static  int test_clip = 0;
static  int display_field = VA_FRAME_PICTURE;
static  pthread_mutex_t gmutex;
static  int box_width = 32;
static  int multi_thread = 0;
static  int verbose = 0;
static  int test_color_conversion = 0;
static  int csc_src_fourcc = 0, csc_dst_fourcc = 0;
static  VAImage csc_dst_fourcc_image;
static  VASurfaceID csc_render_surface;


typedef struct {
    char* fmt_str;
    unsigned int fourcc;
} fourcc_map;
fourcc_map va_fourcc_map[] = {
    {"YUYV", VA_FOURCC_YUY2},
    {"YUY2", VA_FOURCC_YUY2},
    {"NV12", VA_FOURCC_NV12},
    {"YV12", VA_FOURCC_YV12},
    {"BGRA", VA_FOURCC_BGRA},
    {"RGBA", VA_FOURCC_RGBA},
    {"BGRX", VA_FOURCC_BGRX},
    {"RGBX", VA_FOURCC_RGBX},
};
unsigned int map_str_to_vafourcc(char * str)
{
    int i;
    for (i = 0; i < sizeof(va_fourcc_map) / sizeof(fourcc_map); i++) {
        if (!strcmp(va_fourcc_map[i].fmt_str, str)) {
            return va_fourcc_map[i].fourcc;
        }
    }

    return 0;

}
char* map_vafourcc_to_str(unsigned int format)
{
    static char unknown_format[] = "unknown-format";
    int i;
    for (i = 0; i < sizeof(va_fourcc_map) / sizeof(fourcc_map); i++) {
        if (va_fourcc_map[i].fourcc == format) {
            return va_fourcc_map[i].fmt_str;
        }
    }

    return unknown_format;

}

static int
va_value_equals(const VAGenericValue *v1, const VAGenericValue *v2)
{
    if (v1->type != v2->type)
        return 0;

    switch (v1->type) {
    case VAGenericValueTypeInteger:
        return v1->value.i == v2->value.i;
    case VAGenericValueTypeFloat:
        return v1->value.f == v2->value.f;
    case VAGenericValueTypePointer:
        return v1->value.p == v2->value.p;
    case VAGenericValueTypeFunc:
        return v1->value.fn == v2->value.fn;
    }
    return 0;
}

static int
ensure_image_formats(void)
{
    VAStatus va_status;
    VAImageFormat *image_formats;
    int num_image_formats;

    if (va_num_image_formats >= 0)
        return va_num_image_formats;

    num_image_formats = vaMaxNumImageFormats(va_dpy);
    if (num_image_formats == 0)
        return 0;

    image_formats = (VAImageFormat *)malloc(num_image_formats * sizeof(*image_formats));
    if (!image_formats)
        return 0;

    va_status = vaQueryImageFormats(va_dpy, image_formats, &num_image_formats);
    CHECK_VASTATUS(va_status, "vaQuerySurfaceAttributes()");

    va_image_formats = image_formats;
    va_num_image_formats = num_image_formats;
    return num_image_formats;
}

static const VAImageFormat *
lookup_image_format(uint32_t fourcc)
{
    int i;

    if (!ensure_image_formats())
        return NULL;

    for (i = 0; i < va_num_image_formats; i++) {
        const VAImageFormat * const image_format = &va_image_formats[i];
        if (image_format->fourcc == fourcc)
            return image_format;
    }
    return NULL;
}

static int
ensure_surface_attribs(void)
{
    VAStatus va_status;
    VASurfaceAttrib *surface_attribs;
    unsigned int num_image_formats, num_surface_attribs;

    if (va_num_surface_attribs >= 0)
        return va_num_surface_attribs;

    num_image_formats = vaMaxNumImageFormats(va_dpy);
    if (num_image_formats == 0)
        return 0;

    va_status = vaCreateConfig(va_dpy, VAProfileNone, VAEntrypointVideoProc,
                               NULL, 0, &vpp_config_id);
    CHECK_VASTATUS(va_status, "vaCreateConfig()");

    /* Guess the number of surface attributes, thus including any
       pixel-format supported by the VA driver */
    num_surface_attribs = VASurfaceAttribCount + num_image_formats;
    surface_attribs = (VASurfaceAttrib *)malloc(num_surface_attribs * sizeof(*surface_attribs));
    if (!surface_attribs)
        return 0;

    va_status = vaQuerySurfaceAttributes(va_dpy, vpp_config_id,
                                         surface_attribs, &num_surface_attribs);
    if (va_status == VA_STATUS_SUCCESS)
        va_surface_attribs =  surface_attribs;
    else if (va_status == VA_STATUS_ERROR_MAX_NUM_EXCEEDED) {
        va_surface_attribs = (VASurfaceAttrib *)realloc(surface_attribs,
                             num_surface_attribs * sizeof(*va_surface_attribs));
        if (!va_surface_attribs) {
            free(surface_attribs);
            return 0;
        }
        va_status = vaQuerySurfaceAttributes(va_dpy, vpp_config_id,
                                             va_surface_attribs, &num_surface_attribs);
    }
    CHECK_VASTATUS(va_status, "vaQuerySurfaceAttributes()");
    va_num_surface_attribs = num_surface_attribs;
    return num_surface_attribs;
}

static const VASurfaceAttrib *
lookup_surface_attrib(VASurfaceAttribType type, const VAGenericValue *value)
{
    int i;

    if (!ensure_surface_attribs())
        return NULL;

    for (i = 0; i < va_num_surface_attribs; i++) {
        const VASurfaceAttrib * const surface_attrib = &va_surface_attribs[i];
        if (surface_attrib->type != type)
            continue;
        if (!(surface_attrib->flags & VA_SURFACE_ATTRIB_SETTABLE))
            continue;
        if (va_value_equals(&surface_attrib->value, value))
            return surface_attrib;
    }
    return NULL;
}

int csc_preparation()
{
    VAStatus va_status;

    // 1. make sure dst fourcc is supported for vaImage
    if (!lookup_image_format(csc_dst_fourcc)) {
        test_color_conversion = 0;
        printf("VA driver doesn't support %s image, skip additional color conversion\n",  map_vafourcc_to_str(csc_dst_fourcc));
        return test_color_conversion;
    }

    // 2. make sure src_fourcc is supported for vaSurface
    VASurfaceAttrib surface_attribs[1], * const s_attrib = &surface_attribs[0];
    s_attrib->type = VASurfaceAttribPixelFormat;
    s_attrib->flags = VA_SURFACE_ATTRIB_SETTABLE;
    s_attrib->value.type = VAGenericValueTypeInteger;
    s_attrib->value.value.i = csc_src_fourcc;

    if (!lookup_surface_attrib(VASurfaceAttribPixelFormat, &s_attrib->value)) {
        printf("VA driver doesn't support %s surface, skip additional color conversion\n",  map_vafourcc_to_str(csc_src_fourcc));
        test_color_conversion = 0;
        goto cleanup;
    }

    // 3 create all objs required by csc
    // 3.1 vaSurface with src fourcc
    va_status = vaCreateSurfaces(
                    va_dpy,
                    VA_RT_FORMAT_YUV420, surface_width, surface_height,
                    &surface_id[0], SURFACE_NUM,
                    surface_attribs, 1
                );
    CHECK_VASTATUS(va_status, "vaCreateSurfaces");

    // 3.2 vaImage with dst fourcc
    VAImageFormat image_format;
    image_format.fourcc = csc_dst_fourcc;
    image_format.byte_order = VA_LSB_FIRST;
    image_format.bits_per_pixel = 16;

    va_status = vaCreateImage(va_dpy, &image_format,
                              surface_width, surface_height,
                              &csc_dst_fourcc_image);
    CHECK_VASTATUS(va_status, "vaCreateImage");


    // 3.3 create a temp VASurface for final rendering(vaPutSurface)
    s_attrib->value.value.i = VA_FOURCC_NV12;
    va_status = vaCreateSurfaces(va_dpy, VA_RT_FORMAT_YUV420,
                                 surface_width, surface_height,
                                 &csc_render_surface, 1,
                                 surface_attribs, 1);
    CHECK_VASTATUS(va_status, "vaCreateSurfaces");


cleanup:
    return test_color_conversion;
}

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

    assert(index);

    if (multi_thread == 0) {
        i = *index;
        i++;
        if (i == SURFACE_NUM)
            i = 0;
        *index = i;

        return surface_id[i];
    }

    for (i = 0; i < SURFACE_NUM; i++) {
        surface_status = (VASurfaceStatus)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];
}

static int upload_source_YUV_once_for_all()
{
    int box_width_loc = 8;
    int row_shift_loc = 0;
    int i;

    for (i = 0; i < SURFACE_NUM; i++) {
        printf("\rLoading data into surface %d.....", i);
        upload_surface(va_dpy, surface_id[i], box_width_loc, row_shift_loc, 0);

        row_shift_loc++;
        if (row_shift_loc == (2 * box_width_loc)) row_shift_loc = 0;
    }
    printf("\n");

    return 0;
}

/*
 * 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 void update_clipbox(VARectangle *cliprects, int width, int height)
{
    if (test_clip == 0)
        return;

    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);
}

static void* putsurface_thread(void *data)
{
    int width = win_width, height = win_height;
    void *drawable = data;
    int quit = 0;
    VAStatus vaStatus;
    int row_shift = 0;
    int index = 0;
    unsigned int frame_num = 0, start_time, putsurface_time;
    VARectangle cliprects[2]; /* client supplied clip list */
    int continue_display = 0;

    if (drawable == drawable_thread0)
        printf("Enter into thread0\n\n");
    if (drawable == drawable_thread1)
        printf("Enter into thread1\n\n");

    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: %p Display surface 0x%x,\n", drawable, surface_id);

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

        if (check_event)
            pthread_mutex_lock(&gmutex);

        start_time = get_tick_count();
        if ((continue_display == 0) && getenv("FRAME_STOP")) {
            char c;
            printf("Press any key to display frame %d...(c/C to continue)\n", frame_num);
            c = getchar();
            if (c == 'c' || c == 'C')
                continue_display = 1;
        }
        if (test_color_conversion) {
            static int _put_surface_count = 0;
            if (_put_surface_count++ % 50 == 0) {
                printf("do additional colorcoversion from %s to %s\n", map_vafourcc_to_str(csc_src_fourcc), map_vafourcc_to_str(csc_dst_fourcc));
            }
            // get image from surface, csc_src_fourcc to csc_dst_fourcc conversion happens
            vaStatus = vaGetImage(va_dpy, surface_id, 0, 0,
                                  surface_width, surface_height, csc_dst_fourcc_image.image_id);
            CHECK_VASTATUS(vaStatus, "vaGetImage");

            // render csc_dst_fourcc image to temp surface
            vaStatus = vaPutImage(va_dpy, csc_render_surface, csc_dst_fourcc_image.image_id,
                                  0, 0, surface_width, surface_height,
                                  0, 0, surface_width, surface_height);
            CHECK_VASTATUS(vaStatus, "vaPutImage");

            // render the temp surface, it should be same with original surface without color conversion test
            vaStatus = vaPutSurface(va_dpy, csc_render_surface, CAST_DRAWABLE(drawable),
                                    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");

        } else {
            vaStatus = vaPutSurface(va_dpy, surface_id, CAST_DRAWABLE(drawable),
                                    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 (check_event)
            pthread_mutex_unlock(&gmutex);

        pthread_mutex_unlock(&surface_mutex[index]); /* locked in get_next_free_surface */

        if ((frame_num % 0xff) == 0) {
            fprintf(stderr, "%.2f FPS             \r", 256000.0 / (float)putsurface_time);
            putsurface_time = 0;
            update_clipbox(cliprects, width, height);
        }

        if (check_event)
            check_window_event(win_display, drawable, &width, &height, &quit);

        if (multi_thread) { /* reload surface content */
            row_shift++;
            if (row_shift == (2 * box_width)) row_shift = 0;
        }

        if (frame_rate != 0) /* rough framerate control */
            usleep(1000 / frame_rate * 1000);

        frame_num++;
        if (frame_num >= frame_num_total)
            quit = 1;
    }

    if (drawable == drawable_thread1)
        pthread_exit(NULL);

    return 0;
}
int main(int argc, char **argv)
{
    int major_ver, minor_ver;
    VAStatus va_status;
    pthread_t thread1;
    int ret;
    int c;
    int i;
    char str_src_fmt[5], str_dst_fmt[5];

    static struct option long_options[] = {
        {"fmt1",  required_argument,       NULL, '1'},
        {"fmt2",  required_argument,       NULL, '2'},
        {0, 0, 0, 0}
    };

    while ((c = getopt_long(argc, argv, "w:h:g:r:d:f:tcep?n:1:2:v", long_options, NULL)) != EOF) {
        switch (c) {
        case '?':
            printf("putsurface <options>\n");
            printf("           -g <widthxheight+x_location+y_location> window geometry\n");
            printf("           -w/-h resolution of surface\n");
            printf("           -r <framerate>\n");
            printf("           -d the dimension of black/write square box, default is 32\n");
            printf("           -t multi-threads\n");
            printf("           -c test clipbox\n");
            printf("           -f <1/2> top field, or bottom field\n");
            printf("           -1 source format (fourcc) for color conversion test\n");
            printf("           -2 dest   format (fourcc) for color conversion test\n");
            printf("           --fmt1 same to -1\n");
            printf("           --fmt2 same to -2\n");
            printf("           -v verbose output\n");
            exit(0);
            break;
        case 'g':
            ret = sscanf(optarg, "%dx%d+%d+%d", &win_width, &win_height, &win_x, &win_y);
            if (ret != 4) {
                printf("invalid window geometry, must be widthxheight+x_location+y_location\n");
                exit(0);
            } else
                printf("Create window at (%d, %d), width = %d, height = %d\n",
                       win_x, win_y, win_width, win_height);
            break;
        case 'r':
            frame_rate = atoi(optarg);
            break;
        case 'w':
            surface_width = atoi(optarg);
            break;
        case 'h':
            surface_height = atoi(optarg);
            break;
        case 'n':
            frame_num_total = 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 '1':
            sscanf(optarg, "%5s", str_src_fmt);
            csc_src_fourcc = map_str_to_vafourcc(str_src_fmt);

            if (!csc_src_fourcc) {
                printf("invalid fmt1: %s\n", str_src_fmt);
                exit(0);
            }
            break;
        case '2':
            sscanf(optarg, "%5s", str_dst_fmt);
            csc_dst_fourcc = map_str_to_vafourcc(str_dst_fmt);

            if (!csc_dst_fourcc) {
                printf("invalid fmt1: %s\n", str_dst_fmt);
                exit(0);
            }
            break;
        case 'v':
            verbose = 1;
            printf("Enable verbose output\n");
            break;
        }
    }

    if (csc_src_fourcc && csc_dst_fourcc) {
        test_color_conversion = 1;
    }

    win_display = (void *)open_display();
    if (win_display == NULL) {
        fprintf(stderr, "Can't open the connection of display!\n");
        exit(-1);
    }
    create_window(win_display, win_x, win_y, win_width, win_height);

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

    if (test_color_conversion) {
        ret = csc_preparation();
    }
    if (!test_color_conversion || !ret) {
        va_status = vaCreateSurfaces(
                        va_dpy,
                        VA_RT_FORMAT_YUV420, surface_width, surface_height,
                        &surface_id[0], SURFACE_NUM,
                        NULL, 0
                    );
    }
    CHECK_VASTATUS(va_status, "vaCreateSurfaces");
    if (multi_thread == 0) /* upload the content for all surfaces */
        upload_source_YUV_once_for_all();

    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, putsurface_thread, (void*)drawable_thread1);

    putsurface_thread((void *)drawable_thread0);

    if (multi_thread == 1)
        pthread_join(thread1, (void **)&ret);
    printf("thread1 is free\n");

    if (test_color_conversion) {
        // destroy temp surface/image
        va_status = vaDestroySurfaces(va_dpy, &csc_render_surface, 1);
        CHECK_VASTATUS(va_status, "vaDestroySurfaces");

        va_status = vaDestroyImage(va_dpy, csc_dst_fourcc_image.image_id);
        CHECK_VASTATUS(va_status, "vaDestroyImage");
    }

    if (vpp_config_id != VA_INVALID_ID) {
        vaDestroyConfig(va_dpy, vpp_config_id);
        vpp_config_id = VA_INVALID_ID;
    }

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

    free(va_image_formats);
    free(va_surface_attribs);
    close_display(win_display);

    return 0;
}
