/* 
 * usbcam based CPia2 Image Capture Program for NetBSD, version 0.2
 * cpia2shot - Simple image capture program.
 * Copyright (C) 2004 Takafumi Mizuno <taka-qce@ls-a.jp>
 *
 * Portions of this program were modeled after or adapted from the
 * Cpia2 based usb camera driver;
 *  Copyright 2001, STMicrolectronics, Inc.
 * see cpia2_core.c and README and below;
 * 
 * CPiA2 Linux Device Driver Project
 *  http://cpia2.sourceforge.net/
 *
 * This program is free software; you can redistribute it and/or modify
 * 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.
 *
 * This program 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 General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <sys/ioctl.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <err.h>

#if defined(__DragonFly__)
#include <bus/usb/usb.h>
#else
#include <dev/usb/usb.h>
#endif
#ifndef  USB_SET_PACKETCHKSUM
#define USB_SET_PACKETCHKSUM	_IOW ('U', 115, int)
#endif

#include "linux_usbif.h"
#include "cpia2.h"

#include "cpia2shot.h"
#define IFNUM 0
#define DUMMYFLAG 0

struct usb_device usbdev;
struct camera_data *cpia2dev;

char dev[FILENAME_MAX];

#define ISOBUF_SIZE	1023
unsigned char isobuf[ISOBUF_SIZE];	/* one raw isoc packets data */

#define	STORE_BUFSIZE	(68*1024)		/* same size cam->frame_size */
unsigned char storebuf[STORE_BUFSIZE];

#define LOCAL_X	352
#define LOCAL_Y 288
#define LOCAL_D 3

#define PLANE_SIZE	(LOCAL_X*LOCAL_Y)
/* MAX_FRAME_SIZE_PER_DESC ... margin */
#define IMAGE_SIZE	(PLANE_SIZE*LOCAL_D)

static int chksum = 2;

static int image_grab(struct camera_data *cpia2dev);

int main(int argc, char *argv[])
{
    int i = 0;
    int ret = 1;

    /* 
     * check option
     */
    if (argc == 1) {
	/* search camera */
	for ( i = 0; i < 15; ++i ) {
#ifdef __FreeBSD__
	    sprintf(dev, "/dev/ugen%d", i);
#else
	    sprintf(dev, "/dev/ugen%d.00", i);
#endif
	    if ( (usbdev.fd = open(dev, O_RDWR)) < 0 ) {
		continue;
	    }
	    if ( (cpia2dev = (struct camera_data *)cpia2_usb_probe(&usbdev, IFNUM)) == NULL ) {
		close(usbdev.fd);
		usbdev.fd = -1;
		continue;
	    } else {
		break;
	    }
	}
    } else {
	if ( argv[1][0] != '/' ) {
	    fprintf(stderr, "usage: %s [device-name]\n", argv[0]);
	    fprintf(stderr, "    [device-name] must be started slash \'/\' character.\n");
	    exit(1);
	}
	strncpy(dev, argv[1], FILENAME_MAX-1);
	if ( (usbdev.fd = open(dev, O_RDWR)) < 0 ) {
	    err(1, "%s", dev);
	} else {
	    if ( (cpia2dev = (struct camera_data *)cpia2_usb_probe(&usbdev, IFNUM)) == NULL ) {
		close(usbdev.fd);
		usbdev.fd = -1;
	    }
	}
    }

    if ( usbdev.fd < 0 ) {
	fprintf(stderr, "Not found target usb camera, or Permission denied\n");
	exit(1);
    }

    if ( cpia2_usb_open(cpia2dev) != 0 ) {
	ret = 1;
	goto OPENERR;
    }

    /* enable checksum to isoc data */
    chksum = 1;
    if ( (ret = ioctl(usbdev.fd, USB_SET_PACKETCHKSUM, (void *)&chksum)) != 0 ) {
	    fprintf(stderr, "You should apply following kernel patch to use this package.\n");
	    fprintf(stderr, "\thttp://www2.starcat.ne.jp/~takam/bsd/cpia2-netbsd-sys-1.6.2.diff\n");
	    exit(1);
    }

    /* set size and start stream */
    cpia2dev->params.roi.width = LOCAL_X;
    cpia2dev->params.roi.height = LOCAL_Y;
    cpia2dev->params.camera_state.stream_mode = DEFAULT_ALT;	/* from cpia2_v4l */
    if ( cpia2_set_format(cpia2dev) < 0 ) {
	ret = 2;
	goto MODEINIERR;
    }

    /* open endpoint  */
#ifdef __FreeBSD__
    sprintf(usbdev.epdevname, "%s.1", dev);
#else
    sprintf(usbdev.epdevname, "%s.01", strtok(dev,"."));
#endif
    if ((usbdev.efd = open(usbdev.epdevname, O_RDONLY)) < 0) {
	perror(usbdev.epdevname);
	ret = 2;
	goto EPOPENERR;
    } 

    memset((void *)isobuf, 0, ISOBUF_SIZE);
    memset((void *)storebuf, 0, STORE_BUFSIZE);

    fprintf(stderr," waiting for saturation image data ...");
#define EXPOSURE_TIME 300
    for ( i = 0; i < EXPOSURE_TIME; i++ ) {
	    ret = image_grab(cpia2dev);
	    if ( ret < -1 ) break;
	    if ( ret > 50*1024 ) break; /* 50k = about CIF jpeg size */

	    /* wait */
	    if ((i % 50) == 0) fprintf(stderr,"*");
	    fflush(stderr);
    }

    if ( ret > 0 ) {
	    fwrite(storebuf, 1, ret, stdout);
	    ret = 0;
    }

    close(usbdev.efd);

EPOPENERR:
MODEINIERR:
    cpia2_usb_close(cpia2dev);
OPENERR:
    cpia2_usb_disconnect(&usbdev, (void *)cpia2dev);

    /* disable checksum to isoc data */
    chksum = 0;
    (void)ioctl(usbdev.fd, USB_SET_PACKETCHKSUM, (void *)&chksum);

    close(usbdev.fd);
    fprintf(stderr, "\nDone.\n");

    return ret;
}

/*
 * imaeg_grab
 * Original: cpia2_usb_complete(@cpia2_usb.c)
 */
static int image_grab(struct camera_data *cam)
{
    struct usb_device *udev;
    struct framebuf frame;
    u_int32_t errcnt;

    ssize_t grablen;
    ssize_t readlen; /* return value of read(2) */
    unsigned char *cdata, *workbuf;
    int app2_offset;

    udev = cam->dev;

    errcnt = 0;
    grablen = 0;
    cdata = isobuf;
    workbuf = storebuf;
    /* recieve isoc transfer data  */
    for (;;) {
	    readlen = read(udev->efd, (void *)cdata, ISOBUF_SIZE-2);
	    if ( readlen < 0 ) {
		    perror("read");
		    return -2;
	    }
	    if ( readlen == 0 ) {
		    if ((errcnt++) > 60000u) return -2; /* XXX */
	    } else {
		    errcnt = 0;
	    }
	    if ( grablen + readlen > STORE_BUFSIZE ) {
		    DBG("isoc read data overflow = %d\n", readlen);
		    return -1;
	    }
	    if ( grablen == 0 ) {
		    /* search header */
		    if ((cdata[0] == 0xD8) && (cdata[1] == 0xFF)) {
			    workbuf[0] = 0xFF;
			    workbuf[1] = 0xD8;
			    memcpy((workbuf+2), (cdata+1), readlen-1);
			    grablen = readlen + 1;
		    } else if ((cdata[0] == 0xFF) && (cdata[1] == 0xD8)
			       && (cdata[2] == 0xFF)) {
			    memcpy(workbuf, cdata, readlen);
			    grablen = readlen;
		    } else {
			    /* igonore data */
			    continue;
		    }
	    } else if ( readlen > 0 ) {
		    memcpy((workbuf+grablen), cdata, readlen);
		    grablen += readlen;
	    }

	    /* search footer */
	    if (( grablen >= 3 ) && (workbuf[grablen-3] == 0xFF) &&
		(workbuf[grablen-2] == 0xD9) &&	(workbuf[grablen-1] == 0xFF)) {
		    workbuf[grablen-1] = 0;
		    grablen--;
		    break;
	    } else if (( grablen > 2 ) && (workbuf[grablen-2] == 0xFF) &&
					      (workbuf[grablen-1] == 0xD9)) {
		    break;
	    }
    }

    /* DBG("isoc transfer data size = %d\n", grablen); */

    /* setup framebuf member */
    cam->frame_size = STORE_BUFSIZE;
    frame.length = (int)grablen;
    frame.data = storebuf;
    cam->workbuff = &frame;

    /* process_frame */
    if ( ( grablen > 3 ) &&
	 (app2_offset = check_for_app2_segment(cam->workbuff->data)) > 0 ) {
	    if(parse_app2_segment(cam, app2_offset)) {
		    return (int)cam->workbuff->length;
	    }
    }

    DBG("fail to comvert jpeg data\n");

    return -1;

}
