/* blink2.c
 *
 * Copyright 2003 Marcus Meissner <marcus@jet.franken.de>
 *
 * 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 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#include "config.h"

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

#ifdef HAVE_LIBJPEG
# include <jpeglib.h>
#endif

#include <gphoto2/gphoto2-library.h>
#include <gphoto2/gphoto2-port-log.h>
#include <gphoto2/gphoto2-result.h>

#include "libgphoto2/i18n.h"


#define BLINK2_GET_NUMPICS		0x08
#define BLINK2_GET_MEMORY		0x0a
#define BLINK2_GET_DIR			0x0d
#define BLINK2_INIT_CAPTURE		0x0e
#define BLINK2_DELETE_ALL		0x12
#define BLINK2_CHECK_CAPTURE_FINISH	0x16
#define BLINK2_SET_EXPOSURE_COUNT	0x17
#define BLINK2_GET_FIRMWARE_ID		0x18

static int
blink2_getnumpics (GPPort *port, GPContext *context, unsigned int *numpics) {
	unsigned char buf[2];
	int ret;

	ret = gp_port_usb_msg_read(port, BLINK2_GET_NUMPICS, 0x03, 0, (char*)buf, 2);
	if (ret < GP_OK)
		return ret;
	gp_log(GP_LOG_DEBUG, "blink2", "ret is %d", ret);
	if (ret < 2) return GP_ERROR_IO_READ;
	*numpics = (buf[0]<<8) | buf[1];
	return GP_OK;
}

#ifdef HAVE_LIBJPEG
/* for the jpeg decompressor source manager. */
static void _jpeg_init_source(j_decompress_ptr cinfo) { }

static boolean _jpeg_fill_input_buffer(j_decompress_ptr cinfo) {
	fprintf(stderr, "(), should not get here.\n");
	return FALSE;
}

static void _jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
	fprintf(stderr, "(%ld), should not get here.\n", num_bytes);
}

static boolean _jpeg_resync_to_restart(j_decompress_ptr cinfo, int desired) {
	fprintf(stderr, "(desired=%d), should not get here.\n", desired);
	return FALSE;
}
static void _jpeg_term_source(j_decompress_ptr cinfo) { }
#endif

static int
file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
		void *data, GPContext *context)
{
	Camera		*camera = data;
	unsigned int	i;
	int		ret;
	int		bytes;
	unsigned int	numpics;
	unsigned char	*xbuf, buf[8];

	ret = blink2_getnumpics( camera->port, context, &numpics );
	if (ret < GP_OK) return ret;

	bytes = ((8*(1+numpics))+0x3f) & ~0x3f;
	xbuf = malloc(bytes);
	ret = gp_port_usb_msg_read( camera->port,BLINK2_GET_DIR,0x03,0,(char*)buf,1 );
	if (ret < GP_OK)  {
		free(xbuf);
		return ret;
	}
	if (ret < 1) return GP_ERROR_IO_READ;
	ret = gp_port_read( camera->port, (char*)xbuf, bytes);
	if (ret < GP_OK) {
		free(xbuf);
		return ret;
	}
	if (ret < bytes) return GP_ERROR_IO_READ;
	for ( i=0; i < numpics; i++) {
		char name[20];
		if (xbuf[8*(i+1)])
			sprintf( name, "image%04d.avi", i);
		else
			sprintf( name, "image%04d.pnm", i);
		gp_list_append( list, name, NULL);
	}
	free(xbuf);
	return GP_OK;
}

static int
get_file_func (CameraFilesystem *fs, const char *folder, const char *filename,
	       CameraFileType type, CameraFile *file, void *data,
	       GPContext *context)
{
	Camera *camera = data;
	int image_no, result;
	unsigned int i;
	int ret, bytes;
	unsigned int numpics;
	unsigned char	*xbuf, buf[8];

	struct xaddr {
		unsigned int type, start,len;
	} *addrs;

	ret = blink2_getnumpics( camera->port, context, &numpics );
	if (ret < GP_OK) return ret;
	gp_log(GP_LOG_DEBUG, "blink2","numpics is %d", numpics);
	bytes = ((8*(1+numpics))+0x3f) & ~0x3f;
	xbuf = malloc(bytes);
	if (!xbuf)
		return GP_ERROR_NO_MEMORY;
	addrs = (struct xaddr*)malloc(sizeof(struct xaddr)*numpics);
	if (!addrs) {
		free(xbuf);
		return GP_ERROR_NO_MEMORY;
	}
	ret = gp_port_usb_msg_read( camera->port,BLINK2_GET_DIR,0x03,0,(char*)buf,1 );
	if (ret < GP_OK) {
		free(addrs);
		free(xbuf);
		return ret;
	}
	if (ret < 1) return GP_ERROR_IO_READ;
	ret = gp_port_read (camera->port, (char*)xbuf, bytes);
	if (ret < GP_OK) {
		free(addrs);
		free(xbuf);
		return ret;
	}
	if (ret < bytes) return GP_ERROR_IO_READ;
	for ( i=0; i < numpics; i++) {
		int end, start;

		start = (xbuf[8*i+ 5] << 16) |
			(xbuf[8*i+ 6] <<  8) |
			(xbuf[8*i+ 7]);
		end = 	(xbuf[8*i+13] << 16) |
			(xbuf[8*i+14] <<  8) |
			(xbuf[8*i+15]);

		if (end < start)
			return GP_ERROR_IO_READ;

		addrs[i].start = start;
		addrs[i].len = (end-start)/4;
		addrs[i].type = xbuf[8*(i+1)];
		gp_log(GP_LOG_DEBUG, "blink2","%d - %d", start, (end-start)/4);
	}
	free(xbuf);

	image_no = gp_filesystem_number(fs, folder, filename, context);
	if(image_no < 0) {
		free(addrs);
		return image_no;
	}
	if (image_no < 0 || (unsigned int)image_no >= numpics) {
		free(addrs);
		gp_log(GP_LOG_DEBUG, "blink2","image %d requested, but only %d pics on camera?", image_no, numpics);
		return GP_ERROR;
	}
	switch (type) {
	case GP_FILE_TYPE_NORMAL:
#ifdef HAVE_LIBJPEG
{
		char *convline,*convline2,*rawline;
		unsigned char	*jpegdata;
		unsigned int curread, start, len, pitch;
		struct jpeg_decompress_struct	dinfo;
		struct jpeg_source_mgr		xjsm;
		struct jpeg_error_mgr		jerr;

		memset( buf, 0, sizeof(buf));
		if (addrs[image_no].type)
			gp_file_set_mime_type (file, GP_MIME_AVI);
		else
			gp_file_set_mime_type (file, GP_MIME_JPEG);
		start = addrs[image_no].start;
		len = addrs[image_no].len;

		if (len >= UINT_MAX / 8) {
			result = GP_ERROR_NO_MEMORY;
			break;
		}
		jpegdata = (unsigned char*)malloc (len*8);
		if (!jpegdata) {
			result = GP_ERROR_NO_MEMORY;
			break;
		}
		buf[0] = (start >> 24) & 0xff;
		buf[1] = (start >> 16) & 0xff;
		buf[2] = (start >>  8) & 0xff;
		buf[3] =  start        & 0xff;
		buf[4] = (len >> 24) & 0xff;
		buf[5] = (len >> 16) & 0xff;
		buf[6] = (len >>  8) & 0xff;
		buf[7] =  len        & 0xff;
		result = gp_port_usb_msg_write (camera->port,BLINK2_GET_MEMORY,0x03,0,(char*)buf,8);
		if (result < GP_OK) {
			free (jpegdata);
			break;
		}
		len	*= 8;
		curread  = 0;
		do {
			int res;
			res = gp_port_read (camera->port, (char*)(jpegdata+curread), len-curread );
			if (res < GP_OK) {
				result = GP_OK;
				break;
			}
			if (!res) break;
			curread += res;
		} while (curread<=len);

		memset( &dinfo, 0, sizeof(dinfo));
		xjsm.next_input_byte	= jpegdata;
		xjsm.bytes_in_buffer	= len;
		xjsm.init_source	= _jpeg_init_source;
		xjsm.fill_input_buffer	= _jpeg_fill_input_buffer;
		xjsm.skip_input_data	= _jpeg_skip_input_data;
		xjsm.resync_to_restart	= _jpeg_resync_to_restart;
		xjsm.term_source	= _jpeg_term_source;
		dinfo.err		= jpeg_std_error(&jerr);
		jpeg_create_decompress(&dinfo);
		dinfo.src		= &xjsm;
		ret = jpeg_read_header(&dinfo,TRUE);
		if (ret != JPEG_HEADER_OK) {
			jpeg_destroy_decompress(&dinfo);
			free (jpegdata);
			break;
		}
		dinfo.out_color_space = JCS_RGB;
		jpeg_start_decompress(&dinfo);

		pitch = (dinfo.output_width*dinfo.output_components+3)&~3;

		rawline = malloc(pitch);
		convline = malloc(dinfo.output_width*2*3);
		convline2 = malloc(dinfo.output_width*2*2*3);
		{
			char foo[30];
			sprintf(foo,"P6\n%d %d 255\n",dinfo.output_width, dinfo.output_height*2);
			gp_file_append (file, foo, strlen(foo));
		}
		for (i = 0; i < dinfo.output_height ; i++ ) {
			unsigned int j;
			JSAMPROW row[1];
			JSAMPARRAY arr = row;

			row[0] = (JSAMPROW)rawline;
			jpeg_read_scanlines(&dinfo,arr,1);

			memcpy(convline+((dinfo.output_width/16-1)*16+8)*3, rawline+((dinfo.output_width/16-1)*16+8)*3, 8*3);
			memcpy(convline+pitch/2, rawline, 8*3);
			for (j=0;j<(dinfo.output_width/16-1);j++) {
				if ((j&1) == 0)
					memcpy(convline+((j/2)*16)*3,rawline+(j*16+8)*3,16*3);
				else
					memcpy(convline+pitch/2+((j/2)*16+8)*3,rawline+(j*16+8)*3,16*3);
			}
			for (j=0;j<dinfo.output_width*2;j++) {
				memcpy(convline2+ j*2*3   , convline+j*3,3);
				memcpy(convline2+(j*2+1)*3, convline+j*3,3);
			}
			gp_file_append (file, convline2, pitch*2);
		}
		free(convline2);
		free(convline);
		free(rawline);
		free(jpegdata);
		gp_file_set_mime_type (file, GP_MIME_PPM);
		jpeg_destroy_decompress(&dinfo);
		break;
	}
#else
	/* fall through to raw mode if no libjpeg */
#endif
	case GP_FILE_TYPE_RAW: {
		char buf2[4096];
		unsigned int start, len;
		unsigned int curread;
		memset( buf, 0, sizeof(buf));

		if (addrs[image_no].type)
			gp_file_set_mime_type (file, GP_MIME_AVI);
		else
			gp_file_set_mime_type (file, GP_MIME_JPEG);

		start = addrs[image_no].start;
		len = addrs[image_no].len;
		buf[0] = (start >> 24) & 0xff;
		buf[1] = (start >> 16) & 0xff;
		buf[2] = (start >>  8) & 0xff;
		buf[3] =  start        & 0xff;
		buf[4] = (len >> 24) & 0xff;
		buf[5] = (len >> 16) & 0xff;
		buf[6] = (len >>  8) & 0xff;
		buf[7] =  len	& 0xff;
		result = gp_port_usb_msg_write(camera->port,BLINK2_GET_MEMORY,0x03,0,(char*)buf,8);
		if (result < GP_OK)
			break;
		len = len*8;
		do {
			int ret;

			curread = 4096;
			if (curread > len) curread = len;
			ret = gp_port_read( camera->port, buf2, curread);
			if (ret < GP_OK) {
				result = GP_OK;
				break;
			}
			len -= ret;
			result = gp_file_append( file, buf2, ret);
			if (result < GP_OK)
				break;
		} while (len>0);
		break;
	}
	default:
		result = GP_ERROR_NOT_SUPPORTED;
		break;
	}
	free(addrs);
	if (result < 0)
		return result;
	return (GP_OK);
}

static int
delete_all_func (CameraFilesystem *fs, const char *folder, void *data, GPContext *context)
{
	Camera *camera = data;
	int ret;
	char buf[1];

	ret = gp_port_usb_msg_read( camera->port, BLINK2_DELETE_ALL, 0x03, 0, buf, 1);
	if (ret < GP_OK)
		return ret;
	if (ret < 1) return GP_ERROR_IO_READ;
	return GP_OK;
}

static int
camera_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path, GPContext *context)
{
	int ret;
	unsigned int oldnumpics, numpics;
	char buf[1];

	ret = blink2_getnumpics (camera->port, context, &oldnumpics);
	if (ret < GP_OK)
		return ret;

	ret = gp_port_usb_msg_read( camera->port, BLINK2_INIT_CAPTURE, 0x03, 0, buf, 1);
	if (ret < GP_OK)
		return ret;
	if (ret < 1) return GP_ERROR_IO_READ;
	do {
		ret = gp_port_usb_msg_read( camera->port, BLINK2_CHECK_CAPTURE_FINISH, 0x03, 0, buf, 1);
		if (ret < GP_OK)
			return ret;
		if (ret < 1) return GP_ERROR_IO_READ;
		sleep(1);
	} while (buf[0] == 0x00);

	ret = blink2_getnumpics (camera->port, context, &numpics);
	if (ret < GP_OK)
		return ret;
	if (numpics == oldnumpics)
		return (GP_ERROR);
	strcpy (path->folder, "/");
	sprintf (path->name, "image%04d.pnm", numpics-1);
	return (GP_OK);
}


int
camera_abilities (CameraAbilitiesList *list)
{
	CameraAbilities a;

	memset(&a, 0, sizeof(a));
	strcpy(a.model,	"SiPix:Blink 2");
	a.status = GP_DRIVER_STATUS_EXPERIMENTAL;
	a.port     		= GP_PORT_USB;
	a.speed[0] 		= 0;
	a.usb_vendor		= 0x0c77;
	a.usb_product		= 0x1011;
	a.operations		=  GP_OPERATION_CAPTURE_IMAGE;
	a.file_operations	=  GP_FILE_OPERATION_NONE;
	a.folder_operations	=  GP_FOLDER_OPERATION_DELETE_ALL;
	gp_abilities_list_append(list, a);

	a.usb_product		= 0x1010;
	strcpy(a.model, "SiPix:Snap");
	gp_abilities_list_append(list, a);

	a.usb_product		= 0x1015;
	strcpy(a.model, "SiPix:CAMeleon");
	gp_abilities_list_append(list, a);

	return (GP_OK);
}

int
camera_id (CameraText *id)
{
	strcpy(id->text, "SiPix Blink2");
	return (GP_OK);
}

static CameraFilesystemFuncs fsfuncs = {
	.file_list_func = file_list_func,
	.get_file_func = get_file_func,
	.delete_all_func = delete_all_func
};

int
camera_init (Camera *camera, GPContext *context)
{
	char buf[6];
	int ret;
	GPPortSettings settings;

	camera->functions->capture              = camera_capture;

	gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera);
	gp_port_get_settings( camera->port, &settings);
	settings.usb.config = 1;
	settings.usb.interface = 0;
	settings.usb.altsetting = 0;
	ret = gp_port_set_settings (camera->port, settings);
	if (ret < GP_OK) return ret;

	ret = gp_port_usb_msg_read( camera->port, BLINK2_GET_FIRMWARE_ID, 0x03, 0, buf, 6);
	if (ret < GP_OK)
		return ret;
	if (ret < 6) return GP_ERROR_IO_READ;
	ret = gp_port_usb_msg_read( camera->port, 0x04, 0x03, 0, buf, 1);
	if (ret < GP_OK)
		return ret;
	if (ret < 1) return GP_ERROR_IO_READ;
	return GP_OK;
}
