/*
 * 1394-Based Digital Camera Control Library
 *
 * Written by Damien Douxchamps <ddouxchamps@users.sf.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xvlib.h>
#include <X11/keysym.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <stdint.h>
#include <inttypes.h>

#include <libraw1394/raw1394.h>
#include "dc1394/dc1394.h"


/* uncomment the following to drop frames to prevent delays */
#define MAX_PORTS   4
#define MAX_CAMERAS 8
#define NUM_BUFFERS 8

/* ok the following constant should be by right included thru in Xvlib.h */
#ifndef XV_YV12
#define XV_YV12 0x32315659
#endif

#ifndef XV_YUY2
#define XV_YUY2 0x32595559
#endif

#ifndef XV_UYVY
#define XV_UYVY 0x59565955
#endif


/* declarations for libdc1394 */
uint32_t numCameras = 0;
dc1394camera_t *cameras[MAX_CAMERAS];
dc1394featureset_t features;
dc1394video_frame_t * frames[MAX_CAMERAS];

/* declarations for video1394 */
char *device_name=NULL;

/* declarations for Xwindows */
Display *display=NULL;
Window window=(Window)NULL;
unsigned long width,height;
unsigned long device_width,device_height;
int connection=-1;
XvImage *xv_image=NULL;
XvAdaptorInfo *info;
long format=0;
GC gc;


/* Other declarations */
unsigned long frame_length;
long frame_free;
int frame=0;
int adaptor=-1;

int freeze=0;
int average=0;
int fps;
int res;
char *frame_buffer=NULL;


static struct option long_options[]={
    {"device",1,NULL,0},
    {"fps",1,NULL,0},
    {"res",1,NULL,0},
    {"help",0,NULL,0},
    {NULL,0,0,0}
};

void get_options(int argc,char *argv[])
{
    int option_index=0;
    fps=7;
    res=0;

    while(getopt_long(argc,argv,"",long_options,&option_index)>=0){
        if(optarg){
            switch(option_index){
                /* case values must match long_options */
            case 0:
                device_name=strdup(optarg);
                break;
            case 1:
                fps=atoi(optarg);
                break;
            case 2:
                res=atoi(optarg);
                break;
            }
        }
        if(option_index==3){
            printf( "\n"
                    "        %s - multi-cam monitor for libdc1394 and XVideo\n\n"
                    "Usage:\n"
                    "        %s [--fps=[1,3,7,15,30]] [--res=[0,1,2]] [--device=/dev/video1394/x]\n"
                    "             --fps    - frames per second. default=7,\n"
                    "                        30 not compatible with --res=2\n"
                    "             --res    - resolution. 0 = 320x240 (default),\n"
                    "                        1 = 640x480 YUV4:1:1, 2 = 640x480 RGB8\n"
                    "             --device - specifies video1394 device to use (optional)\n"
                    "                        default = automatic\n"
                    "             --help   - prints this message\n\n"
                    "Keyboard Commands:\n"
                    "        q = quit\n"
                    "        < -or- , = scale -50%%\n"
                    "        > -or- . = scale +50%%\n"
                    "        0 = pause\n"
                    "        1 = set framerate to 1.875 fps\n"
                    "        2 = set framerate tp 3.75 fps\n"
                    "        3 = set framerate to 7.5 fps\n"
                    "        4 = set framerate to 15 fps\n"
                    "        5 = set framerate to 30 fps\n"
                    ,argv[0],argv[0]);
            exit(0);
        }
    }

}

/* image format conversion functions */

static inline
void iyu12yuy2 (unsigned char *src, unsigned char *dest, uint32_t NumPixels) {
    int i=0,j=0;
    register int y0, y1, y2, y3, u, v;
    while (i < NumPixels*3/2) {
        u = src[i++];
        y0 = src[i++];
        y1 = src[i++];
        v = src[i++];
        y2 = src[i++];
        y3 = src[i++];

        dest[j++] = y0;
        dest[j++] = u;
        dest[j++] = y1;
        dest[j++] = v;

        dest[j++] = y2;
        dest[j++] = u;
        dest[j++] = y3;
        dest[j++] = v;
    }
}


static inline
void rgb2yuy2 (unsigned char *RGB, unsigned char *YUV, uint32_t NumPixels) {
    int i, j;
    register int y0, y1, u0, u1, v0, v1 ;
    register int r, g, b;

    for (i = 0, j = 0; i < 3 * NumPixels; i += 6, j += 4) {
        r = RGB[i + 0];
        g = RGB[i + 1];
        b = RGB[i + 2];
        RGB2YUV (r, g, b, y0, u0 , v0);
        r = RGB[i + 3];
        g = RGB[i + 4];
        b = RGB[i + 5];
        RGB2YUV (r, g, b, y1, u1 , v1);
        YUV[j + 0] = y0;
        YUV[j + 1] = (u0+u1)/2;
        YUV[j + 2] = y1;
        YUV[j + 3] = (v0+v1)/2;
    }
}

/* helper functions */

void set_frame_length(unsigned long size, int numCameras)
{
    frame_length=size;
    dc1394_log_debug("Setting frame size to %ld kb",size/1024);
    frame_free=0;
    frame_buffer = malloc( size * numCameras);
}

void display_frames()
{
    uint32_t i;

    if(!freeze && adaptor>=0){
        for (i = 0; i < numCameras; i++) {
            if (!frames[i])
                continue;
            switch (res) {
            case DC1394_VIDEO_MODE_640x480_YUV411:
                iyu12yuy2( frames[i]->image,
                           (unsigned char *)(frame_buffer + (i * frame_length)),
                           (device_width*device_height) );
                break;

            case DC1394_VIDEO_MODE_320x240_YUV422:
            case DC1394_VIDEO_MODE_640x480_YUV422:
                memcpy( frame_buffer + (i * frame_length),
                        frames[i]->image, device_width*device_height*2);
                break;

            case DC1394_VIDEO_MODE_640x480_RGB8:
                rgb2yuy2( frames[i]->image,
                          (unsigned char *) (frame_buffer + (i * frame_length)),
                          (device_width*device_height) );
                break;
            }
        }

        xv_image=XvCreateImage(display,info[adaptor].base_id,format,frame_buffer,
                               device_width,device_height * numCameras);
        XvPutImage(display,info[adaptor].base_id,window,gc,xv_image,
                   0,0,device_width,device_height * numCameras,
                   0,0,width,height);

        xv_image=NULL;
    }
}

void QueryXv()
{
    uint32_t num_adaptors;
    int num_formats;
    XvImageFormatValues *formats=NULL;
    int i,j;
    char xv_name[5];

    XvQueryAdaptors(display,DefaultRootWindow(display),&num_adaptors,&info);

    for(i=0;i<num_adaptors;i++) {
        formats=XvListImageFormats(display,info[i].base_id,&num_formats);
        for(j=0;j<num_formats;j++) {
            xv_name[4]=0;
            memcpy(xv_name,&formats[j].id,4);
            if(formats[j].id==format) {
                dc1394_log_error("using Xv format 0x%x %s %s",formats[j].id,xv_name,(formats[j].format==XvPacked)?"packed":"planar");
                if(adaptor<0)adaptor=i;
            }
        }
    }
    XFree(formats);
    if(adaptor<0)
        dc1394_log_error("No suitable Xv adaptor found");

}


void cleanup(void) {
    int i;
    for (i=0; i < numCameras; i++) {
        dc1394_video_set_transmission(cameras[i], DC1394_OFF);
        dc1394_capture_stop(cameras[i]);
    }
    if ((void *)window != NULL)
        XUnmapWindow(display,window);
    if (display != NULL)
        XFlush(display);
    if (frame_buffer != NULL)
        free( frame_buffer );
}


/* trap ctrl-c */
void signal_handler( int sig) {
    signal( SIGINT, SIG_IGN);
    cleanup();
    exit(0);
}

int main(int argc,char *argv[])
{
    XEvent xev;
    XGCValues xgcv;
    long background=0x010203;
    int i, j;
    dc1394_t * d;
    dc1394camera_list_t * list;

    get_options(argc,argv);
    /* process options */
    switch(fps) {
    case 1: fps =        DC1394_FRAMERATE_1_875; break;
    case 3: fps =        DC1394_FRAMERATE_3_75; break;
    case 15: fps = DC1394_FRAMERATE_15; break;
    case 30: fps = DC1394_FRAMERATE_30; break;
    case 60: fps = DC1394_FRAMERATE_60; break;
    default: fps = DC1394_FRAMERATE_7_5; break;
    }
    switch(res) {
    case 1:
        res = DC1394_VIDEO_MODE_640x480_YUV411;
        device_width=640;
        device_height=480;
        format=XV_YUY2;
        break;
    case 2:
        res = DC1394_VIDEO_MODE_640x480_RGB8;
        device_width=640;
        device_height=480;
        format=XV_YUY2;
        break;
    default:
        res = DC1394_VIDEO_MODE_320x240_YUV422;
        device_width=320;
        device_height=240;
        format=XV_UYVY;
        break;
    }

    dc1394error_t err;

    d = dc1394_new ();
    err=dc1394_camera_enumerate (d, &list);
    DC1394_ERR_RTN(err,"Failed to enumerate cameras");

    if (list->num == 0) {
        dc1394_log_error("No cameras found");
        return 1;
    }

    j = 0;
    for (i = 0; i < list->num; i++) {
        if (j >= MAX_CAMERAS)
            break;
        cameras[j] = dc1394_camera_new (d, list->ids[i].guid);
        if (!cameras[j]) {
            dc1394_log_warning("Failed to initialize camera with guid %llx", list->ids[i].guid);
            continue;
        }
        j++;
    }
    numCameras = j;
    dc1394_camera_free_list (list);

    if (numCameras == 0) {
        dc1394_log_error("No cameras found");
        exit (1);
    }

    /* setup cameras for capture */
    for (i = 0; i < numCameras; i++) {

        err=dc1394_video_set_iso_speed(cameras[i], DC1394_ISO_SPEED_400);
        DC1394_ERR_CLN_RTN(err,cleanup(),"Could not set ISO speed");

        err=dc1394_video_set_mode(cameras[i], res);
        DC1394_ERR_CLN_RTN(err,cleanup(),"Could not set video mode");

        err=dc1394_video_set_framerate(cameras[i], fps);
        DC1394_ERR_CLN_RTN(err,cleanup(),"Could not set framerate");

        err=dc1394_capture_setup(cameras[i],NUM_BUFFERS, DC1394_CAPTURE_FLAGS_DEFAULT);
        DC1394_ERR_CLN_RTN(err,cleanup(),"Could not setup camera-\nmake sure that the video mode and framerate are\nsupported by your camera");

        err=dc1394_video_set_transmission(cameras[i], DC1394_ON);
        DC1394_ERR_CLN_RTN(err,cleanup(),"Could not start camera iso transmission");

    }

    fflush(stdout);
    if (numCameras < 1) {
        perror("no cameras found :(\n");
        cleanup();
        exit(-1);
    }

    switch(format){
    case XV_YV12:
        set_frame_length(device_width*device_height*3/2, numCameras);
        break;
    case XV_YUY2:
    case XV_UYVY:
        set_frame_length(device_width*device_height*2, numCameras);
        break;
    default:
        dc1394_log_error("Unknown format set (internal error)");
        exit(255);
    }

    /* make the window */
    display=XOpenDisplay(getenv("DISPLAY"));
    if(display==NULL) {
        dc1394_log_error("Could not open display \"%s\"",getenv("DISPLAY"));
        cleanup();
        exit(-1);
    }

    QueryXv();

    if ( adaptor < 0 ) {
        cleanup();
        exit(-1);
    }

    width=device_width;
    height=device_height * numCameras;

    window=XCreateSimpleWindow(display,DefaultRootWindow(display),0,0,width,height,0,
                               WhitePixel(display,DefaultScreen(display)),
                               background);

    XSelectInput(display,window,StructureNotifyMask|KeyPressMask);
    XMapWindow(display,window);
    connection=ConnectionNumber(display);

    gc=XCreateGC(display,window,0,&xgcv);

    /* main event loop */
    while(1){

        for (i = 0; i < numCameras; i++) {
            if (dc1394_capture_dequeue(cameras[i], DC1394_CAPTURE_POLICY_WAIT, &frames[i])!=DC1394_SUCCESS)
                dc1394_log_error("Failed to capture from camera %d", i);
        }

        display_frames();
        XFlush(display);

        while(XPending(display)>0){
            XNextEvent(display,&xev);
            switch(xev.type){
            case ConfigureNotify:
                width=xev.xconfigure.width;
                height=xev.xconfigure.height;
                display_frames();
                break;
            case KeyPress:
                switch(XKeycodeToKeysym(display,xev.xkey.keycode,0)){
                case XK_q:
                case XK_Q:
                    cleanup();
                    exit(0);
                    break;
                case XK_comma:
                case XK_less:
                    width=width/2;
                    height=height/2;
                    XResizeWindow(display,window,width,height);
                    display_frames();
                    break;
                case XK_period:
                case XK_greater:
                    width=2*width;
                    height=2*height;
                    XResizeWindow(display,window,width,height);
                    display_frames();
                    break;
                case XK_0:
                    freeze = !freeze;
                    break;
                case XK_1:
                    fps =        DC1394_FRAMERATE_1_875;
                    for (i = 0; i < numCameras; i++)
                        dc1394_video_set_framerate(cameras[i], fps);
                    break;
                case XK_2:
                    fps =        DC1394_FRAMERATE_3_75;
                    for (i = 0; i < numCameras; i++)
                        dc1394_video_set_framerate(cameras[i], fps);
                    break;
                case XK_4:
                    fps = DC1394_FRAMERATE_15;
                    for (i = 0; i < numCameras; i++)
                        dc1394_video_set_framerate(cameras[i], fps);
                    break;
                case XK_5:
                    fps = DC1394_FRAMERATE_30;
                    for (i = 0; i < numCameras; i++)
                        dc1394_video_set_framerate(cameras[i], fps);
                    break;
                case XK_3:
                    fps = DC1394_FRAMERATE_7_5;
                    for (i = 0; i < numCameras; i++)
                        dc1394_video_set_framerate(cameras[i], fps);
                    break;
                }
                break;
            }
        } /* XPending */

        for (i = 0; i < numCameras; i++) {
            if (frames[i])
                dc1394_capture_enqueue (cameras[i], frames[i]);
        }

    } /* while not interrupted */

    exit(0);
}
