/*
 Little deamon to take a picture every few seconds

 syntax
 vidcapd0 [-t time] [-q quality] [-j] [-m thresh] filename
        and more; see usage()
 */
#include <features.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
//#include <linux/types.h>
#include <asm/types.h>
#include <linux/ip.h>
#include <errno.h>
#include <sys/ioctl.h>
#include "vidcapd0.h"
#include "videodev.h"


/* these globals contain the app default values */
int repeat_rate     = 0;
int capture_width   = 352;
int capture_height  = 240;
char* filename      = "/tmp/vidcap.jpg";
FILE* fout          = stdout;
int jpeg_Q          = 0;
int debug           = 0;
static int vga_initted = 0;
int motion_threshold = 5;

int nBits;
unsigned char* pBits;

int check_motion(int h_vidcap);

void usage(void) {
    printf("VIDCAP2: capture an images and write it to a JPEG file\n"
           "syntax: vidcap2 [-h] [-r rate] [-w width] [-q Qfactor] [-d] \n"
           "                [-b brighness] [-c contrast] filename\n"
           "   -h help                 -d debug (ni)\n"
           "   -r rate in secs         -w width in pixels\n"
           "   -b brightness[10-240] (ni)\n"
           "   -c contrast [30-90] (ni)\n"
           "   -m motion threshold     -p soundfile (ni)\n"
           " (ni) not implemented\n");
}
int main(int argc, char** argv)
{
    int i;
    int r; /* return values */

    if (argc==1) {
        usage();
        exit(1);
    }
    
    while ((i = getopt(argc, argv, "dhr:w:q:m:")) != EOF) {
        switch (i) {
        case 'd':
            debug++;
            break;
        case 'r':
            repeat_rate = atoi(optarg);
            if (repeat_rate<0 || repeat_rate>10000) {
                printf("The -r parameter must be between 1 and 10000 seconds; \n"
                       "use 0 to force a single image\n");
                exit(1);
            }
            break;
        case 'w':
            capture_width = atoi(optarg);
            if (capture_width<10 || capture_width>748) {
                printf("The -w parameter must be between 10 and 748\n");
                exit(1);
            }
            capture_height = capture_width * _HEIGHT / _WIDTH;
            break;
        case 'q':
            jpeg_Q = atoi(optarg);
            break;
        case 'h':
        default:
            usage();
            exit(1);
        }
        
    }

    /* I need a temp file in order to pass the image to cjpeg
     so, I take the given filename, and add a tilde to make my temp file */
    if (argv[optind] && argv[optind][0]) {
        filename = argv[optind];
        char buf[200];
        strcpy(buf, filename);
        strcat(buf, "~");
        fout = fopen(buf,"w");
        if (fout == 0) {
            fprintf(stderr, "problem opening the file %s; aborted\n",buf);
            exit(2);
        }
    }

    /* open the vidcap; this could also have O_NONBLOCK, if you wish */
    int h_vidcap = open("/dev/video0",O_RDONLY);
    if (h_vidcap<0) {
        printf("error: cannot open the video capture device\n");
        exit(1);
    }
    
    /* set up the capture session */
    struct video_format vf;
    vf.width = capture_width;
    vf.height = vf.width * 7 / 10;  /* this save the user from having to specify height */
    vf.depth = 24;
    vf.pixelformat = PIX_FMT_RGB24;
    vf.flags = 0;
    r = ioctl(h_vidcap, VIDIOC_S_FMT, &vf);
//    printf("ioctl returned %d, width %d height %d\n",r,vf.width,vf.height);

    /* create a buffer to hold the bitmap */
    bitmap bm(vf.width, vf.height, 3);
    /* just a quick read, which will tell us if we are in X
       the data is thrown away */
    r = read(h_vidcap, bm.GetBits(), bm.Size());
    if (r<0 && errno!=EAGAIN) {
        printf("error: this program must be executed from within a graphics environment\n"
               "such as Xwindows or svgalib\n");
        exit(1);
    }
    
    do {
        /* loop reading until we get a full image */
        do {
            r = read(h_vidcap, bm.GetBits(), bm.Size());
        } while(r<0 && errno==EAGAIN);

        if (r<0) {
            printf("error reading capture file\n");
            continue;
        }
        
        /* transfer from capture format (BGR) to ppm format (RGB) */
        jpeg* jpg_current = bmp2jpg(&bm,jpeg_Q);

        /* save the .ppm file, and call cjpeg to make our jpeg file */
        write_jpeg(jpg_current, fout);

        delete jpg_current;
        
        /* just a cutsie demo; delete this eventually */
        check_motion(h_vidcap);
        
        sleep (repeat_rate);
    } while (repeat_rate);
}

/* save the RGB data in the temp file, then call cjpeg for a conversion */
void write_jpeg(jpeg* jp, FILE* fout) {
    fseek(fout, 0, SEEK_SET);
    fwrite(jp->Bits(),1,jp->Len(),fout);
    fflush(fout);
    char buf[200];
    sprintf(buf,"cjpeg %s~ > %s",filename,filename);
    system(buf);
}

/* change the native RGB format (which is actually blue-green-red)
   to the Unix .ppm RGB format (which is actually red-green-blue) */
jpeg* bmp2jpg(bitmap* pbm, int jpeg_Q_factor) {
    /* a meg */
    unsigned char* pBig = new unsigned char[pbm->Width() * pbm->Height() * 3 * 4];
    unsigned char* pWalk = pBig;
    unsigned char* pSrc = (unsigned char*) pbm->GetBits();
    if (pBig==0) {
        printf("error: ran out of memory\n");
        exit(2);
    }
    sprintf(pWalk, "P6\n%d %d\n255\n",pbm->Width(),pbm->Height());
    pWalk = pWalk + strlen(pWalk);
    int i,j;
//    printf("moving %d pixels\n",pbm->Width()*pbm->Height());
//    printf("bytes source %p\n",pSrc);
    for (i=0; i<pbm->Height(); i++) {
        for (j=0; j<pbm->Width(); j++) {
            unsigned long p1, p2;
            //            YUV2RGB(*pSrc++, &p1, &p2);
            // we have to reverse the rgb for Unix ppm
            *pWalk++ = pSrc[2];
            *pWalk++ = pSrc[1];
            *pWalk++ = pSrc[0];
            pSrc += 3;
        }
    }
    pWalk = stpcpy(pWalk, "\n");
    jpeg* jp = new jpeg(pBig, pWalk-pBig);
//    printf("writing %d %d bytes\n",pWalk-pBig,jp->Len());
    return jp;
}

int check_motion(int h_vidcap) {
    struct video_format vf;
    static bitmap* old_bm = 0;
    /* take note of current capture format */
    ioctl(h_vidcap, VIDIOC_G_FMT, &vf);
    /* temporarily change it to YUYV */
    vf.pixelformat = PIX_FMT_YUYV;
    ioctl(h_vidcap, VIDIOC_S_FMT, &vf);
    int r;
    static int nQuiets = 5;

    /* sample the video */
    bitmap* bm = new bitmap(vf.width, vf.height, 2);
    do {
        r = read(h_vidcap, bm->GetBits(), bm->Size());
    } while (r<0 && errno==EAGAIN);

    /* compare the new video to the old video */
    if (old_bm) {

        int i,j,acc;
        unsigned char* pBits = bm->GetBits();
        unsigned char* pOldBits = old_bm->GetBits();
        acc = 0;
        /* only compare the Y bytes, which are the even-number bytes */
        for (i=0; i<vf.height; i++) {
            for (j=0; j<vf.width; j++, pBits+=2, pOldBits+=2) {
                if (abs(*pBits - *pOldBits)>motion_threshold) acc++;
            }
        }
        delete old_bm;

        // make a bar chart
        for (i=0; i < acc * 120 / bm->Size(); i++)
            printf("*");
        printf("\n");

        /* coarse threshold */
        if (acc > bm->Size()/12) {
            if (nQuiets>4)
                system("cat /etc/welcome.au >/dev/audio");
            nQuiets = 0;
        }
        /* fine threshold */
        else if (acc > bm->Size()/30) {
            if ((rand() & 15) == 15)
                system("bplay ./seeyou.wav &>/dev/null");
            nQuiets = 0;
        }
        else {
            nQuiets++;
            if (nQuiets==3)
                system("bplay ./goaway.wav &>/dev/null");
        }
//        printf("thresh %d, acc %d, nbits %d\n",motion_threshold, acc, bm->Size()/2);
    }
    old_bm = bm;
    /* and restore the capture format */
    vf.pixelformat = PIX_FMT_RGB24;
    ioctl(h_vidcap, VIDIOC_S_FMT, &vf);
}

