/* pdc320.c
 *
 * Copyright 2001 Lutz Mueller
 *
 * 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
 */

/* Originally written by Peter Desnoyers <pjd@fred.cambridge.ma.us>,
 * and adapted for gphoto2 by
 * Nathan Stenzel <nathanstenzel@users.sourceforge.net> and
 * Lutz Mueller <lutz@users.sourceforge.net>
 *
 * Maintained by Nathan Stenzel <nathanstenzel@users.sourceforge.net>
 *
 * Original windows drivers available at:
 *	http://www.polaroid.com/service/software/index.html
 */

#define _BSD_SOURCE

#include "config.h"

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

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

#include "pdc320.h"

#ifdef ENABLE_NLS
#  include <libintl.h>
#  undef _
#  define _(String) dgettext (GETTEXT_PACKAGE, String)
#  ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#  else
#    define N_(String) (String)
#  endif
#else
#  define textdomain(String) (String)
#  define gettext(String) (String)
#  define dgettext(Domain,Message) (Message)
#  define dcgettext(Domain,Message,Type) (Message)
#  define bindtextdomain(Domain,Directory) (Domain)
#  define _(String) (String)
#  define N_(String) (String)
#endif

#define GP_MODULE "pdc320"

/*******************************************************************************/
/* NOTICE: There is a 16x8 block of pixels after the image data.               */
/* All of them are about the same and I believe they are for image correction. */
/*******************************************************************************/

/* Notes on what needs work:
 * 	Make a better header that could correct color problems....
 *		or manage to decode, color correct, and save the image.
 * The quantization tables are incorrect for the PDC320.
 */

/*
 * For the PDC640SE, the init sequence is INIT,ID,ENDINIT,STATE,NUM,SIZE,PIC.
 *
 * During handshake, if the first read byte is not 6 then it is not good. Read
 * the number of bytes mentioned +2 ("FE FF"?)and then INIT and try SIZE
 * again (this is a loop).
 *
 * It is possible that the PDC320 might do a similar handshaking sequence at
 * that same point.
 */

typedef enum {
	PDC320,
	PDC640SE
} PDCModel;

struct _CameraPrivateLibrary {
	PDCModel model;
};

static int
pdc320_escape(
	const unsigned char *inbuf, int inbuflen,
	unsigned char *outbuf
) {
	int i,j;

	j = 0;
	for (i=0;i<inbuflen;i++) {
		switch (inbuf[i]) {
		case 0xe3:
			outbuf[j++] = 0xe5;outbuf[j++] = 3;
			break;
		case 0xe4:
			outbuf[j++] = 0xe5;outbuf[j++] = 2;
			break;
		case 0xe5:
			outbuf[j++] = 0xe5;outbuf[j++] = 1;
			break;
		case 0xe6:
			outbuf[j++] = 0xe5;outbuf[j++] = 0;
			break;
		default:outbuf[j++] = inbuf[i];
			break;
		}
	}
	return j;
}

static int
pdc320_calc_checksum(const unsigned char *buf, int buflen) {
	int checksum, j;

	checksum = 0;
	j = 0;
	for (j=0;j<buflen/2;j++) {
		checksum += buf[j*2];
		checksum += buf[j*2+1]<<8;
	}
	if (buflen & 1)
		checksum += buf[buflen-1];

	while (checksum > 0xffff) {
		checksum =
			(checksum & 0xffff) + 
			((checksum >> 16) & 0xffff);
	}
	return 0xffff-checksum; /* neg checksum actually */
}

static int
pdc320_command(
	GPPort *port,
	const unsigned char *cmd, int cmdlen
) {
	unsigned char csum[2];
	int checksum, off, ret;
	unsigned char *newcmd;

	checksum = pdc320_calc_checksum (cmd, cmdlen);
	csum[0] = checksum & 0xff;
	csum[1] = (checksum >> 8) & 0xff;
	/* 4 times 0xe6, checksum and command, both might be escaped */
	newcmd = malloc (2*(sizeof(csum)+cmdlen)+4);
	if (!newcmd)
		return GP_ERROR_NO_MEMORY;
	memset (newcmd, 0xe6, 4); off = 4;
	off += pdc320_escape (cmd, cmdlen, newcmd + off);
	off += pdc320_escape (csum, 2, newcmd + off);
	ret = gp_port_write (port, (char *)newcmd, off);
	free(newcmd);
	return ret;
}

static int
pdc320_simple_command (GPPort *port, const unsigned char cmd) {
	return pdc320_command(port, &cmd, 1);
}

static int
pdc320_simple_reply (GPPort *port, const unsigned char expcode,
	unsigned int replysize, unsigned char *reply
) {
	unsigned char csum[2];
	int calccsum;

	CR (gp_port_read (port, (char *)reply, 1));
	if (reply[0] != expcode) {
		GP_DEBUG("*** reply got 0x%02x, expected 0x%02x\n",
			reply[0], expcode
		);
		return GP_ERROR;
	}
	CR (gp_port_read (port, (char *)reply+1, replysize-1));
	CR (gp_port_read (port, (char *)csum, 2));
	calccsum = pdc320_calc_checksum (reply, replysize);
	if (calccsum != ((csum[1] << 8) | csum[0])) {
		GP_DEBUG("csum %x vs %x\n",calccsum,((csum[0]<<8)|csum[1]));
		return GP_ERROR;
	}
	return GP_OK;
}

static int
pdc320_simple_command_reply (GPPort *port,
	const unsigned char cmd, const unsigned char expcode,
	unsigned int replysize, unsigned char *reply
) {
	CR (pdc320_simple_command (port, cmd));
	CR (pdc320_simple_reply (port, expcode, replysize, reply));
	return GP_OK;
}

static int
pdc320_init (GPPort *port)
{
	unsigned char buf[32];
	unsigned char e6[4];
	int i;

	GP_DEBUG ("*** PDC320_INIT ***");
	
	/* The initial command is prefixed by 4 raw E6. */
	memset(e6,0xe6,sizeof(e6));
	CR (gp_port_write (port, (char *)e6, sizeof (e6) ));

	GP_DEBUG ("*** PDC320_INIT ***");
	CR (pdc320_simple_command_reply (port, PDC320_INIT, 5, 1, buf));
	GP_DEBUG ("*** PDC320_ID ***");
	CR (pdc320_simple_command_reply (port, PDC320_ID, 0, 12, buf));
	GP_DEBUG ("*** PDC320_STATE ***");
	CR (pdc320_simple_command_reply (port, PDC320_STATE, 2, 22, buf));

	for (i=0;i<9;i++) {
		GP_DEBUG ("%d: %d (0x%x)",i,((buf[2+i*2]<<8)|buf[2+2*i+1]), ((buf[2+i*2]<<8)|buf[2+2*i+1]));
	}
	GP_DEBUG ("*** PDC320_ENDINIT ***");
	return pdc320_simple_command_reply (port, PDC320_ENDINIT, 9, 1, buf);
}

static int
pdc320_num (GPPort *port)
{
	unsigned char buf[2];

	GP_DEBUG ("*** PDC320_NUM ***");
	CR (pdc320_simple_command_reply (port, PDC320_NUM, 3, 2, buf));
	GP_DEBUG ("The camera contains %i files.", buf[1]);
	return buf[1];
}

static int
pdc320_delete (GPPort *port)
{
	unsigned char buf[1];

	GP_DEBUG ("*** PDC320_DELETE ***");
	return pdc320_simple_command_reply(port, PDC320_DEL, 8, 1, buf);
}

static int
pdc320_size (Camera *camera, int n)
{
	int size;
	unsigned char buf[5];
	unsigned char cmd[2];

	GP_DEBUG ("*** PDC320_SIZE ***");
	cmd[0] = PDC320_SIZE;
	cmd[1] = n;
	CR (pdc320_command (camera->port, cmd, sizeof (cmd)));
	CR (pdc320_simple_reply (camera->port, 6, 5, buf));
	size = (buf[1] << 24) + (buf[2] << 16) + (buf[3] << 8) + buf[4];
	GP_DEBUG ("Image %i has size %i.", n, size);
	return (size);
}

/* Unclear. */
static int
pdc320_0c (Camera *camera, int n)
{
	int size, i;
	unsigned char buf[3], *xbuf;
	unsigned char cmd[2];
	
	cmd[0] = 0x0c;
	cmd[1] = n;		/* n is from 1 on up */

	/* Write the command */
	GP_DEBUG ("*** PDC320_0c ***");
	CR (pdc320_command (camera->port, cmd, sizeof (cmd)));
	CR (gp_port_read (camera->port, (char *)buf, 3));
	if (buf[0] != 7)
		return GP_ERROR;
	size = (buf[1] << 8) | buf[2];
	xbuf = malloc (size);
	CR (gp_port_read (camera->port, (char *)xbuf, size));
	for (i=0; i<size; i++) {
		GP_DEBUG ("buf[%d]=0x%02x", i, xbuf[i]);
	}
	CR (gp_port_read (camera->port, (char *)buf, 2));
	/* checksum is calculated from both, but i am not clear how. */
	free (xbuf);
	return GP_OK;
}

static int
pdc320_pic (Camera *camera, int n, unsigned char **data, int *size)
{
	unsigned char cmd[2];
	unsigned char buf[2048];
	int remaining, f1, f2, i, len;
	int chunksize=2000;

	/* Get the size of the picture and allocate the memory */
	GP_DEBUG ("Checking size of image %i...", n);
	CR (*size = pdc320_size (camera, n));
	*data = malloc (sizeof (char) * (*size));
	if (!*data)
		return (GP_ERROR_NO_MEMORY);

	cmd[0] = PDC320_PIC;
	cmd[1] = n;

	CR_FREE (pdc320_command (camera->port, cmd, sizeof (cmd)), *data);
	switch (camera->pl->model) {
	case PDC640SE:
		chunksize = 528;
		break;
	case PDC320:
		chunksize = 2000;
		break;
	}

	len = *size;
	for (i = 0; i < *size; i += chunksize) {

		/* How many bytes do we read in this round? */
		remaining = *size - i;
		len = (remaining > chunksize) ? chunksize : remaining;

		/* Read the frame number */
		usleep (10000);
		CR_FREE (gp_port_read (camera->port, (char *)buf, 5), *data);
		f1 = (buf[1] << 8) + buf[2];
		f2 = (buf[3] << 8) + buf[4];
		GP_DEBUG ("Reading frame %d "
				 "(%d)...", f1, f2);

		/* Read the actual data */
		usleep(1000);
		CR_FREE (gp_port_read (camera->port, (char *)*data + i, len), *data);
		
		/* Read the checksum */
		CR_FREE (gp_port_read (camera->port, (char *)buf, 2), *data);
	}

	return (GP_OK);
}

int
camera_id (CameraText *id)
{
	strcpy (id->text, "Polaroid DC320");

	return (GP_OK);
}

int
camera_abilities (CameraAbilitiesList *list) 
{
	int i;
	CameraAbilities a;

	for (i = 0; models[i].model; i++) {
		memset(&a, 0, sizeof(a));
		strcpy (a.model, models[i].model);
		a.status		= GP_DRIVER_STATUS_EXPERIMENTAL;
		a.port			= GP_PORT_SERIAL;
		a.speed[0]		= 115200;
		a.speed[1]		= 0;
		a.operations		= GP_OPERATION_NONE;
		a.file_operations	= GP_FILE_OPERATION_NONE;
		a.folder_operations	= GP_FOLDER_OPERATION_DELETE_ALL;
		CR (gp_abilities_list_append (list, a));
	}
	return (GP_OK);
}

static int
get_file_func (CameraFilesystem *fs, const char *folder, const char *filename,
	       CameraFileType type, CameraFile *file, void *user_data,
	       GPContext *context)
{
	Camera *camera = user_data;
	jpeg *myjpeg;
	chunk *tempchunk;
	int r, n, size, width, height;
	unsigned char *data;

	if ((type != GP_FILE_TYPE_RAW) && (type != GP_FILE_TYPE_NORMAL))
		return (GP_ERROR_NOT_SUPPORTED);

	/*
	 * Get the number of the picture from the filesystem and increment
	 * since we need a range starting with 1.
	 */
	GP_DEBUG ("Getting number from fs...");
	CR (n = gp_filesystem_number (camera->fs, folder, filename, context));
	n++;

	/* Get the file */
	GP_DEBUG ("Getting file %i...", n);
	CR (pdc320_pic (camera, n, &data, &size));
	r = pdc320_0c (camera, n);
	if (r < GP_OK) {
		free (data);
		return r;
	}

	/* Post-processing */
	switch (type) {
	case GP_FILE_TYPE_RAW:
		CR (gp_file_set_data_and_size (file, (char *)data, size));
		CR (gp_file_set_mime_type (file, GP_MIME_RAW));
		break;
	case GP_FILE_TYPE_NORMAL:
	default:
		GP_DEBUG ("Using Nathan Stenzel's experimental jpeg.c\n");
		GP_DEBUG ("About to make jpeg header\n");
		width = data[4]*256 + data[5];
		height = data[2]*256 + data[3];
		GP_DEBUG ("Width=%i\tHeight=%i\n", width, height);
		myjpeg = gpi_jpeg_header(width,height/2, 0x11,0x11,0x21, 1,0,0, &chrominance, &luminance,
		    0,0,0, gpi_jpeg_chunk_new_filled(HUFF_00),
		    gpi_jpeg_chunk_new_filled(HUFF_10), NULL, NULL);
		GP_DEBUG ("Turning the picture data into a chunk data type\n");
		tempchunk = gpi_jpeg_chunk_new(size);
		tempchunk->data = data;
		GP_DEBUG ("Adding the picture data to the jpeg structure\n");
		gpi_jpeg_add_marker(myjpeg, tempchunk, 6, size-1);
		GP_DEBUG ("Writing the jpeg file\n");
		gpi_jpeg_write(file, filename, myjpeg);
		GP_DEBUG ("Cleaning up the mess\n");
		gpi_jpeg_destroy(myjpeg);
		free (tempchunk);
		break;
    	}
	return (GP_OK);
}

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

	CR (pdc320_delete (camera->port));
	return (GP_OK);
}

static int
camera_about (Camera *camera, CameraText *about, GPContext *context)
{
	strcpy (about->text, _("Download program for several Polaroid cameras. "
		"Originally written by Peter Desnoyers "
		"<pjd@fred.cambridge.ma.us>, and adapted for gphoto2 by "
		"Nathan Stenzel <nathanstenzel@users.sourceforge.net> and "
		"Lutz Mueller <lutz@users.sf.net>.\n"
		"Polaroid 640SE testing was done by Michael Golden "
		"<naugrim@juno.com>."));
	return (GP_OK);
}

static int
file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
		void *data, GPContext *context)
{
	int n;
	Camera *camera = data;

	/* Fill the list */
	CR (n = pdc320_num (camera->port));
	gp_list_populate (list, "PDC320%04i.jpg", n);
	return (GP_OK);
}

static int
camera_summary (Camera *camera, CameraText *summary, GPContext *context)
{
	char buf[12];

	GP_DEBUG ("*** PDC320_ID ***");
	CR (pdc320_simple_command_reply (camera->port, PDC320_ID, 0, 12, (unsigned char *)buf));
	sprintf (summary->text, _("Model: %x, %x, %x, %x"), buf[8],buf[9],buf[10],buf[11]);
	return (GP_OK);
}

static int
camera_exit (Camera *camera, GPContext *context)
{
	if (!camera)
		return (GP_ERROR_BAD_PARAMETERS);

	if (camera->pl) {
		free (camera->pl);
		camera->pl = NULL;
	}

	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)
{
	GPPortSettings settings;
	CameraAbilities abilities;
	int result;

        /* First, set up all the function pointers */
	camera->functions->exit		= camera_exit;
        camera->functions->about        = camera_about;
	camera->functions->summary      = camera_summary;

	/* Now, tell the filesystem where to get lists and info */
	gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera);

	camera->pl = malloc (sizeof (CameraPrivateLibrary));
	if (!camera->pl)
		return (GP_ERROR_NO_MEMORY);

	/* What model are we talking to? */
	gp_camera_get_abilities (camera, &abilities);
	if (	!strcmp (abilities.model, "Polaroid:Fun! 320") ||
		!strcmp (abilities.model, "Polaroid Fun! 320")
	)
		camera->pl->model = PDC320;
	else if (!strcmp (abilities.model, "Polaroid:640SE") ||
		 !strcmp (abilities.model, "Polaroid 640SE")
	)
		camera->pl->model = PDC640SE;
	else {
		free (camera->pl);
		camera->pl = NULL;
		return (GP_ERROR_MODEL_NOT_FOUND);
	}

	/* Open the port and check if the camera is there */
	gp_port_get_settings (camera->port, &settings);
	if (!settings.serial.speed)
		settings.serial.speed = 115200;
	gp_port_set_settings (camera->port, settings);
	gp_port_set_timeout (camera->port, 30000);

	/* Check if the camera is really there */
	result = pdc320_init (camera->port);
	if (result < 0) {
		free (camera->pl);
		camera->pl = NULL;
		return (result);
	}
	return (GP_OK);
}
