/* library.c
 *
 * Copyright (C) 2000,2001,2002 donn morrison - dmorriso@gulf.uvic.ca
 *
 * 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
 */

/****************************************************
 * kodak dc3200 digital camera driver library       *
 * for gphoto2                                      *
 *                                                  *
 * author: donn morrison - dmorriso@gulf.uvic.ca    *
 * date: dec 2000 - jan 2002                        *
 * license: gpl                                     *
 * version: 1.6                                     *
 *                                                  *
 ****************************************************/

#define _DARWIN_C_SOURCE
#include "config.h"
#include "library.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#include "libgphoto2/i18n.h"

#include "dc3200.h"


/*
 * FIXME: Use properly sized integer types. The dc3200 code used lots
 *        of u_char, u_long, u_int, which may be wrong on non-32bit systems.
 */

/* #define DEBUG */

/*
 * dc3200_set_speed
 *
 * handshakes a baud rate with the camera
 *
 */
int dc3200_set_speed(Camera *camera, int baudrate)
{
	int baudcode = C9600;
	unsigned char resp[INI_PACKET_LEN], msg[INI_PACKET_LEN];
	int resp_len = INI_PACKET_LEN, msg_len = INI_PACKET_LEN;

	msg[0] = 0xAF;
	msg[1] = 0x00;

	switch(baudrate) {
	case 9600:
		baudcode = C9600;
		break;
	case 19200:
		baudcode = C19200;
		break;
	case 38400:
		baudcode = C38400;
		break;
	case 57600:
		baudcode = C57600;
		break;
	case 115200:
		baudcode = C115200;
		break;
	default:
		printf("unsupported baudrate: %d.\n", baudrate);
		return GP_ERROR;
	}

	msg[2] = baudcode;

	/* send our preferred baud rate */
	if(dc3200_send_command(camera, msg, msg_len, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;
	/* the camera responds with our preferred baud rate or its
	 * maximum baud rate, whichever is lowest
	 */

	/* compile a confirmation packet */
	msg[0] = 0x9F;
	msg[1] = 0x00;
	if(resp_len == 2) {
		msg_len = 2;
	} else {
		/* calculate reply baud handshake */
		msg[2] = (resp[2] + 1) / 2;
		msg_len = INI_PACKET_LEN;
	}

	if(dc3200_send_command(camera, msg, msg_len, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;

	if(resp_len == 2) {
		/* set the handshaked baudrate
		 * supports only 9600
		 */
		return C9600;
	} else if(resp[1] == 0x01) {
		/* set the handshaked baudrate */
		return (resp[2]*2) - 1;
	} else {
		/* unknown baud */
		return GP_ERROR;
	}

	return GP_ERROR;
}

/*
 * dc3200_setup
 *
 * sends two commands essential for survival
 *
 */
int dc3200_setup(Camera *camera)
{
	/*
	 * some strange commands that must be
	 * completed after initialization and
	 * before any camera access
	 *
	 * version info? ...
	 *
	 */

	unsigned char cmd1[5] = {0x01, 0, 0x01, 0x00, 0x0F};
	unsigned char cmd2[8] = {0x01, 0, 0x80, 0x00, 0x01, 0x81, 0x00, 0x03};

	unsigned char ack[ACK_PACKET_LEN], resp[DEF_PACKET_LEN];
	int ack_len = ACK_PACKET_LEN, resp_len = DEF_PACKET_LEN;

	cmd1[1] = dc3200_calc_seqnum(camera);
	cmd2[1] = dc3200_calc_seqnum(camera);

	if(dc3200_send_command(camera, cmd1, sizeof(cmd1), ack, &ack_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_check_ack(camera, ack, ack_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_recv_response(camera, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_send_ack(camera, resp[1]) == GP_ERROR)
		return GP_ERROR;

	if(dc3200_send_command(camera, cmd2, sizeof(cmd2), ack, &ack_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_check_ack(camera, ack, ack_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_recv_response(camera, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;
	if(dc3200_send_ack(camera, resp[1]) == GP_ERROR)
		return GP_ERROR;

	/* set the cmd sequence number */
	camera->pl->cmd_seqnum = 0;
	camera->pl->rec_seqnum = 0;

	return GP_OK;
}


/*
 * dc3200_get_data
 *
 * retrieves data from the camera as either file
 * data or file/folder list data
 *
 */

	/* command to retrieve directory listing
	unsigned char list_pkt[DEF_PACKET_LEN] = {0x01, dc3200_calc_seqnum(camera), 0x80, 0x00, 0x20, 0x03, 26 + strlen(filename),
							0xc1, 0x50, 0x00, 0x00, 0x00, 0x00, 19 + strlen(filename),
							0x04, 0x01, 0x00, 0x01, 0x00, 0x01, 0x60, 0x01, 0x00, 0x01, 0x00, 0x05,
							0x00, 0x00, 0x00, 2 + strlen(filename), 0x01};
	*/
	/* command to get the picture
	unsigned char get_pkt[DEF_PACKET_LEN] = {0x01, calc_seqnum(), 0x80, 0x00, 0x20, 0x03, 26 + strlen(filename),
				0xC1, 0x50, 0x00, 0x00, 0x00, 0x00, 19 + strlen(filename),
				0x04, 0x01, 0x00, 0x01, 0x01, 0x04, 0x60, 0x10, 0x00, 0x01, 0x00, 0x05,
				0x00, 0x00, 0x00, 2 + strlen(filename), 0x01};
	*/

int dc3200_get_data(Camera *camera, unsigned char **data, unsigned long *data_len, int command, const char *folder, const char *filename)
{
	unsigned char ack[ACK_PACKET_LEN], resp[DEF_PACKET_LEN];
	int ack_len = ACK_PACKET_LEN, resp_len = DEF_PACKET_LEN;
	unsigned long num_left = 0;
	int data_start_pos = 0;
	unsigned char *ptr_data = NULL;
	unsigned int pid = 0;

	/* command bytes */
	unsigned char cb1=0x00,cb2=0x00,cb3=0x00;

	/* packet parts */
	unsigned char pkt_hdr[7];
	unsigned char pkt_typ[7];
	unsigned char pkt_cmd[12];
	unsigned char pkt_len[5];

	unsigned char *packet;
	int packet_len;
	char *file = NULL;
	char *ptr = NULL;

	if(folder) {
		if(filename) {
			/* concatenate the folder + filename */
			file = aprintf("%s%s%s", folder, folder[strlen(folder)-1] != '\\' ? "\\" : "", filename);
		} else {
			file = strdup(folder);
		}
		if(!file)
			return GP_ERROR;
	} else {
		return GP_ERROR;
	}

	/* change all /'s to \'s */
	while((ptr = strchr(file, '/'))) {
		ptr[0] = '\\';
	}

	/* there should not be a trailing '\', except for root folder */
	if(strlen(file) > 1 && file[strlen(file)-1] == '\\')
		file[strlen(file)-1] = 0;

	switch (command) {
	case CMD_GET_PREVIEW:
		cb1 = 0x02; cb2 = 0x70; cb3 = 0x11;
		break;
	case CMD_GET_FILE:
		cb1 = 0x01; cb2 = 0x60; cb3 = 0x10;
		break;
	case CMD_LIST_FILES:
		cb1 = 0x01; cb2 = 0x60; cb3 = 0x01;
		break;
	}

	camera->pl->cmd_seqnum++;

	pkt_hdr[0] = 0x01;
	pkt_hdr[1] = dc3200_calc_seqnum(camera);
	pkt_hdr[2] = 0x80; pkt_hdr[3] = 0x00; pkt_hdr[4] = 0x20; pkt_hdr[5] = 0x03;
	pkt_hdr[6] = 26 + strlen(file);

	pkt_typ[0] = 0xC1;
	pkt_typ[1] = 0x50;
	pkt_typ[2] = 0x00; pkt_typ[3] = 0x00; pkt_typ[4] = 0x00; pkt_typ[5] = 0x00;
	pkt_typ[6] = 19 + strlen(file);

	pkt_cmd[0]  = 0x04; pkt_cmd[1]  =  cb1; /* cmd */
	pkt_cmd[2]  = 0x00; pkt_cmd[3]  = 0x01;	/* ??? */
	pkt_cmd[4]  = (camera->pl->cmd_seqnum >> 8) & 0xff; pkt_cmd[5]  = camera->pl->cmd_seqnum & 0xff; /* SEQ */
	pkt_cmd[6]  =  cb2; pkt_cmd[7]  =  cb3;	/* cmd */
	pkt_cmd[8]  = 0x00; pkt_cmd[9]  = 0x01;	/* ??? */
	pkt_cmd[10] = 0x00; pkt_cmd[11] = 0x05;	/* ??? */

	pkt_len[0] = 0x00; pkt_len[1] = 0x00; pkt_len[2] = 0x00;
	pkt_len[3] = strlen(file) + 2;
	pkt_len[4] = 0x01;

	packet_len = sizeof(pkt_hdr) + sizeof(pkt_typ) + sizeof(pkt_cmd) + sizeof(pkt_len) + strlen(file) + 2;
	packet = malloc(packet_len);
	if(!packet) {
		free(file);
		return GP_ERROR;
	}

	/* clear packet */
	memset(packet, 0, packet_len);

	/* copy pkt parts into packet */
	memcpy(packet, pkt_hdr, sizeof(pkt_hdr));
	memcpy(packet + sizeof(pkt_hdr), pkt_typ, sizeof(pkt_typ));
	memcpy(packet + sizeof(pkt_hdr) + sizeof(pkt_typ), pkt_cmd, sizeof(pkt_cmd));
	memcpy(packet + sizeof(pkt_hdr) + sizeof(pkt_typ) + sizeof(pkt_cmd), pkt_len, sizeof(pkt_len));
	memcpy(packet + sizeof(pkt_hdr) + sizeof(pkt_typ) + sizeof(pkt_cmd) + sizeof(pkt_len), file, strlen(file));

	free(file);

	/* send the command to list files */
	if(dc3200_send_command(camera, packet, packet_len, ack, &ack_len) == GP_ERROR){
		free(packet);
		return GP_ERROR;
	}

	free(packet);

	do {
		resp_len = DEF_PACKET_LEN;
		if(dc3200_recv_response(camera, resp, &resp_len) == GP_ERROR) {
			return GP_ERROR;
		}

		if(dc3200_send_ack(camera, resp[1]) == GP_ERROR) {
			return GP_ERROR;
		}

		if(resp[1] == camera->pl->rec_seqnum)
			continue;

		camera->pl->rec_seqnum = resp[1];

		/*
		 * we decide what action to take based on
		 * the number of packets we are expecting
		 *
		 * resp[7] holds this info
		 *
		 */
		switch(resp[7]) {
		case 0xC1:
			/*
			 * SINGLE PACKET
			 */

			/* get the total list length from the data header */
			*data_len = bytes_to_l(resp[26], resp[27], resp[28], resp[29]);
			num_left = 0;
			data_start_pos = 31;
			*data_len -= 1;

			/* allocate a buffer to store all this data */
			*data = malloc(*data_len);
			if(*data == NULL) {
				return GP_ERROR;
			}

			ptr_data = *data;

			/* copy this portion of the data */
			memcpy(ptr_data, resp + data_start_pos, resp_len - data_start_pos);
			/* incr the buff ptr */
			ptr_data += resp_len - data_start_pos;
			break;

		case 0x41:
			/*
			 * MULTIPACKET, START
			 */

			/* get the total list length from the data header */
			*data_len = bytes_to_l(resp[34], resp[35], resp[36], resp[37]);
			num_left = bytes_to_l(resp[12], resp[13], resp[14], resp[15]);
			data_start_pos = 39;
			*data_len -= 1;

			/* allocate a buffer to store all this data */
			*data = malloc(*data_len);
			if(*data == NULL) {
				return GP_ERROR;
			}

			ptr_data = *data;

			/* copy this portion of the data */
			memcpy(ptr_data, resp + data_start_pos, resp_len - data_start_pos);
			/* incr the data ptr */
			ptr_data += resp_len - data_start_pos;

			/* update gphoto2 frontend */
			if(command == CMD_GET_FILE || command == CMD_GET_PREVIEW)
			{
				pid = gp_context_progress_start(camera->pl->context,(int)*data_len,"%s", filename);
				gp_context_progress_update(camera->pl->context, pid, ptr_data - *data);
				if(gp_context_cancel(camera->pl->context) == GP_CONTEXT_FEEDBACK_CANCEL) {
					free(*data);
					dc3200_cancel_get_data(camera);
					return GP_ERROR_CANCEL;
				}
			}
			break;
		case 0x01:
			/*
			 * MULTIPACKET, MIDDLE
			 */
			/* calculate the number of packets remaining */
			num_left = bytes_to_l(resp[12], resp[13], resp[14], resp[15]);
			data_start_pos = 16;

			if(*data == NULL) {
				return GP_ERROR;
			}

			/* copy this portion of the data */
			memcpy(ptr_data, resp + data_start_pos, resp_len - data_start_pos);
			/* incr the buff ptr */
			ptr_data += resp_len - data_start_pos;

			/* update gphoto2 frontend */
			if(command == CMD_GET_FILE || command == CMD_GET_PREVIEW)
			{
				gp_context_progress_update(camera->pl->context, pid, ptr_data - *data);
				if(gp_context_cancel(camera->pl->context) == GP_CONTEXT_FEEDBACK_CANCEL) {
					free(*data);
					dc3200_cancel_get_data(camera);
					return GP_ERROR_CANCEL;
				}
			}
			break;
		case 0x81:
			/*
			 * MULTIPACKET, END
			 */
			/* calculate the number of packets remaining */
			num_left = bytes_to_l(resp[12], resp[13], resp[14], resp[15]);
			data_start_pos = 16;

			if(*data == NULL) {
				return GP_ERROR;
			}

			/* copy this portion of the data */
			memcpy(ptr_data, resp + data_start_pos, resp_len - data_start_pos);
			/* incr the buff ptr */
			ptr_data += resp_len - data_start_pos;

			/* update gphoto2 frontend */
			if(command == CMD_GET_FILE || command == CMD_GET_PREVIEW)
			{
				gp_context_progress_update(camera->pl->context, pid, ptr_data - *data);
				if(gp_context_cancel(camera->pl->context) == GP_CONTEXT_FEEDBACK_CANCEL) {
					free(*data);
					dc3200_cancel_get_data(camera);
					return GP_ERROR_CANCEL;
				}
			}
			break;
		default:
			return GP_ERROR;
		}
	} while(num_left > 1);

	if(pid != 0 && (command == CMD_GET_FILE || command == CMD_GET_PREVIEW))
		gp_context_progress_stop(camera->pl->context, pid);

	return GP_OK;
}

/*
 * dc3200_cancel_get_data
 *
 * stops a get data command
 *
 */
int dc3200_cancel_get_data(Camera *camera)
{
	unsigned char pkt[20] = {0x01, 0, 0x80, 0x00, 0x20, 0x03, 0x0d, 0xc1, 0x50, 0xc0,
		0x00, 0x00, 0x00, 0x06, 0x04, 0x01, 0x00, 0x01, 0, 0};
	unsigned char ack[ACK_PACKET_LEN], resp[DEF_PACKET_LEN];
	int ack_len = ACK_PACKET_LEN, resp_len = DEF_PACKET_LEN;

	pkt[1] = dc3200_calc_seqnum(camera);
	pkt[18] = (camera->pl->cmd_seqnum >> 8) & 0xff;
	pkt[19] = camera->pl->cmd_seqnum & 0xff;

	/* wait a bit ... */
	sleep(1);

	/* clear the buffer */
	dc3200_clear_read_buffer(camera);

	if(dc3200_send_command(camera, (unsigned char *)pkt, 20, ack, &ack_len) == GP_ERROR)
		return GP_ERROR;

	if(dc3200_recv_response(camera, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;
	else
		dc3200_send_ack(camera, resp[1]);

	resp_len = DEF_PACKET_LEN;
	if(dc3200_recv_response(camera, resp, &resp_len) == GP_ERROR)
		return GP_ERROR;
	else
		dc3200_send_ack(camera, resp[1]);

	return GP_OK;
}

/*
 * dc3200_send_command
 *
 * if cmd == NULL || cmd_len == 0, send_command waits for a response
 * otherwise, it sends a command and waits for an acknowledgement
 *
 */
int dc3200_send_command(Camera *camera, unsigned char *cmd, int cmd_len, unsigned char *ack, int *ack_len)
{
	int sends, reads;
	int buff_len = *ack_len;
	int received = 0;
	unsigned char *buff = NULL;

	buff = malloc(sizeof(unsigned char) * *ack_len);
	if(buff == NULL) {
		return GP_ERROR;
	}

	sends = SEND_RETRIES;

	while(sends > 0) {
		reads = READ_RETRIES;
		/* check that we are actually sending a command
		 * and not just waiting for data
		 */
		if(cmd != NULL && cmd_len > 0) {
			/* clear the read buffer */
			dc3200_clear_read_buffer(camera);

			/* send the command */
			dc3200_send_packet(camera, cmd, cmd_len);
		}

		/* wait for response */
		while(reads > 0) {
			if(dc3200_recv_packet(camera, buff, &buff_len) != GP_ERROR) {
				received = 1;
				break;
			}
			reads--;
		}

		if(received) {
			if(buff_len > *ack_len) {
				/* buffer size too small */
				free(buff);
				return GP_ERROR;
			}

			/* sukksess */
			*ack_len = buff_len;
			memcpy(ack, buff, buff_len);
			free(buff);
			return GP_OK;
		} else {
			sends--;
		}
	}

	/* send failed */
	free(buff);
	return GP_ERROR;
}

/*
 * dc3200_recv_response
 *
 * receives a response from a previous command
 *
 */
int dc3200_recv_response(Camera *camera, unsigned char *resp, int *resp_len)
{
	return dc3200_send_command(camera, NULL, 0, resp, resp_len);
}

/*
 * dc3200_send_ack
 *
 * sends an ack packet
 *
 */
int dc3200_send_ack(Camera *camera, int seqnum)
{
	unsigned char ack[ACK_PACKET_LEN];
	ack[0] = 0x01;
	ack[1] = seqnum + 16;

	return dc3200_send_packet(camera, ack, sizeof(ack));
}

/*
 * dc3200_check_ack
 *
 * checks an ack packet (well ok not really)
 *
 */
int dc3200_check_ack(Camera *camera, unsigned char *ack, int ack_len)
{
	return GP_OK;
}

/*
 * dc3200_send_packet
 *
 * compiles a packet from data and sends it
 *
 */
int dc3200_send_packet(Camera *camera, unsigned char *data, int data_len)
{
	int res;

	int buff_len = data_len;
	unsigned char *buff = NULL;

	buff = malloc(sizeof(unsigned char) * buff_len);
	if(!buff)
		return GP_ERROR;

	memcpy(buff, data, buff_len);

	/* compile this into a packet */
	res = dc3200_compile_packet(camera, &buff, &buff_len);
	if(res == GP_ERROR)
		return res;

#ifdef DEBUG
	dump_buffer(buff, buff_len, "s", 16);
#endif
	res = gp_port_write(camera->port, (char *)buff, data_len + 3);
	free(buff);
	return res;
}

/*
 * dc3200_recv_packet
 *
 * waits for a packet to arrive, checks it, and returns it
 *
 */
int dc3200_recv_packet(Camera *camera, unsigned char *data, int *data_len)
{
	int complete = 0;
	int num_read = 0, res = 0, fails = 0;

	/* allocate storage for size, checksum, and EOP */
	unsigned char *buff = NULL;

	buff = malloc(sizeof(unsigned char) * (*data_len + 3));
	if(buff == NULL)
		return GP_ERROR;

	memset(buff, 0, *data_len + 3);

	/*
	 * - read data until we get an 0xFF
	 * - process packet
	 *
	 */

	res = gp_port_read(camera->port, (char *)&buff[num_read], 1);

	while(res >= 0 && fails < READ_RETRIES) {
		if(res == 0) {
			/* read nothing */
			fails++;
		} else {
			/* reset # of fails */
			fails = 0;
			num_read++;
			if(buff[num_read-1] == 0xFF) {
				complete = 1;
				break;
			}
			if(num_read == *data_len + 3) {
				/* we've reached the buffer limit */
				complete = 0;
				break;
			}
		}
		res = gp_port_read(camera->port, (char *)&buff[num_read], 1);
	}

	if(!complete) {
		/* packet incomplete */
		free(buff);
		return GP_ERROR;
	}

#ifdef DEBUG
	dump_buffer(buff, num_read, "r", 16);
#endif

	if(dc3200_process_packet(camera, buff, &num_read) == GP_ERROR) {
		free(buff);
		return GP_ERROR;
	}

	/* save the last successful packet received time */
	time(&(camera->pl->last));

	memcpy(data, buff, *data_len);

	/* return with new packet size */
	*data_len = num_read;

	free(buff);
	return GP_OK;
}

/*
 * dc3200_compile_packet
 *
 * changes 0xFF -> 0xFE 0x01
 *         0xFE -> 0xFE 0x00
 * adds packet length, checksum, and EOP (0xFF)
 *
 */
int dc3200_compile_packet(Camera *camera, unsigned char **data, int *data_len)
{
	int count, i, j;
	unsigned char *new_data = NULL;
	unsigned char *tmp_ptr  = NULL;

	/* realloc + 2 for len and checksum */
	*data_len += 2;

	tmp_ptr = (unsigned char*)realloc(*data, *data_len);
	if(tmp_ptr == NULL)
		return GP_ERROR;

	*data = tmp_ptr;

	/* add length, checksum */
	(*data)[*data_len - 2] = *data_len - 2;
	(*data)[*data_len - 1] = dc3200_calc_checksum(camera, *data, *data_len - 1);

	/* this is a hack to fix the 0xFF/0xFE problem.
	 * we adjust a "don't care" byte so the checksum
	 * is not 0xFF or 0xFE
	 */
	if((*data)[*data_len - 1] >= 0xFE) {
		if(*data_len > 19) {
			(*data)[19] += 2;
			(*data)[*data_len - 1] = dc3200_calc_checksum(camera, *data, *data_len - 1);
			printf("adjusting checksum to %02x\n", (*data)[*data_len - 1]);
		}
	}

	/* count number of 0xfe's and 0xff's */
	count = 0;
	for(i=0;i<*data_len; i++) {
		if((*data)[i] == 0xFE || (*data)[i] == 0xFF)
			count++;
	}

	new_data = (unsigned char*)malloc(*data_len + count + 3); /* 3 for length, checksum, EOP */
	if(!new_data)
		return GP_ERROR;

	j = 0;
	for(i=0;i<*data_len; i++) {
		if((*data)[i] == 0xFE || (*data)[i] == 0xFF) {
			printf("(*data)[i]        == %02x\n", (*data)[i]);
			printf("(*data)[i] - 0xFE == %02x\n", (*data)[i] - 0xFE);
			new_data[j++] = 0xFE;
			new_data[j++] = (*data)[i] - 0xFE;
		} else {
			new_data[j++] = (*data)[i];
		}
	}

	/* adjust the new packet count */
	*data_len += count + 1;

	new_data[*data_len - 1] = 0xFF;

	/* free the old packet data */
	free(*data);

	/* set the ptr */
	*data = new_data;

	return GP_OK;
}

/*
 * dc3200_process_packet
 *
 * changes 0xFE 0x01 -> 0xFF
 *         0xFE 0x00 -> 0xFE
 * checks & removes packet length, checksum, EOP (0xFF)
 *
 */
int dc3200_process_packet(Camera *camera, unsigned char *data, int *data_len)
{
	int count = 0, i;
	int length, checksum;
	unsigned char *buff;

	if(data == NULL || *data_len < 1) {
		return GP_ERROR;
	}

	/* allocate a new buffer */
	buff = (unsigned char*)malloc(sizeof(unsigned char) * *data_len);
	if(!buff)
		return GP_ERROR;

	/* change 0xFE 0x00 | 0x01 -> 0xFF | 0xFE */
	for(i=0; i<*data_len; i++) {
		if(data[i] == 0xFE) {
			if(i<*data_len-1) { /* so we don't run out of array bounds */
				if(data[i+1] == 0x00) {
					buff[count] = 0xFE;
					/* advance i so we don't pick up the trailing byte */
					i++;
					/* incr count */
					count++;
				} else if(data[i+1] == 0x01) {
					buff[count] = 0xFF;
					i++;
					count++;
				}
			} else {
				/* we've found an 0xFE but there is no trailing byte */
				free(buff);
				return GP_ERROR;
			}
		} else {
			buff[count] = data[i];
			count++;
		}
	}

	/* copy over the old buffer */
	memcpy(data, buff, count);

	length = data[count - 3];
	checksum = data[count - 2];

	/* make sure its a valid packet */
	if(length != count - 3 || checksum != dc3200_calc_checksum(camera, data, count - 2)) {
		printf("%02x=%02x %02x=%02x\n", length, count - 3, checksum, dc3200_calc_checksum(camera, data, count - 2));
		free(buff);
		return GP_ERROR;
	}

	/* adjust packet size, strip length, checksum, EOP */
	*data_len = count - 3;

	free(buff);
	return GP_OK;
}

/*
 * dc3200_calc_checksum
 *
 * calculates the checksum for a byte array
 *
 */
int dc3200_calc_checksum(Camera *camera, unsigned char * buffer, int len)
{
	int sum = 0, i;

	for(i=0; i<len; i++) {
		sum += buffer[i];
	}

	sum = -sum + 0xFF;
	if(sum < 0) sum = sum & 0xFF;

	return sum;
}

/*
 * dc3200_calc_seqnum
 *
 * calculates the seqnum for a packet
 *
 */
int dc3200_calc_seqnum(Camera *camera)
{
	if(camera->pl->pkt_seqnum >= 0x1F || camera->pl->pkt_seqnum < 0x10) {
		camera->pl->pkt_seqnum = 0x10;
		return camera->pl->pkt_seqnum;
	} else {
		camera->pl->pkt_seqnum++;
		return camera->pl->pkt_seqnum;
	}
}

/*
 * dc3200_keep_alive
 *
 * sends and receives a ping packet to keep the
 * camera awake
 *
 */
int dc3200_keep_alive(Camera *camera)
{
	unsigned char ka[ACK_PACKET_LEN]; /* keepalive */
	unsigned char ak[ACK_PACKET_LEN]; /* keepalive ack */
	int ak_len = ACK_PACKET_LEN;

	ka[0] = 0xCF;
	ka[1] = 0x01;

	/* send keep alive packet */
	if(dc3200_send_command(camera, ka, sizeof(ka), ak, &ak_len) == GP_ERROR) {
		return GP_ERROR;
	}

	/* check the ack (should be same packet) */
	if(memcmp(ak,ka,ak_len) == 0) {
		return GP_OK;
	}

	return GP_ERROR;
}

/*
 * dc3200_clear_read_buffer
 *
 * clears data in the read buffer by reading it out
 *
 * there is definitely a better way to accomplish this...
 *
 */
int dc3200_clear_read_buffer(Camera *camera)
{
	unsigned char byte;
	int count = 0;

	gp_port_set_timeout(camera->port, 0);

	while(gp_port_read(camera->port, (char *)&byte, 1) > 0)
		count++;

	if(count > 0)
		printf("cleared %d bytes from read buffer\n", count);

	gp_port_set_timeout(camera->port, TIMEOUT);

	return GP_OK;
}

/*
 * dump_buffer
 *
 * displays a byte array nicely formatted in hex format
 *
 */
int dump_buffer(unsigned char * buffer, int len, char * title, int bytesperline)
{
	char spacer[80];
	int i;

	memset(spacer, 0, sizeof(spacer));
	memset(spacer, ' ', strlen(title)+2);

	printf("%s: ", title);

	for(i=0; i<len; i++) {
		if(i%bytesperline==0 && i > 0) {
			printf("\n%s", spacer);
		}
		printf("%02x ", buffer[i]);
	}

	printf("\n");

	printf("%s: ", title);

	for(i=0; i<len; i++) {
		if(i%bytesperline==0 && i > 0) {
			printf("\n%s", spacer);
		}
		if(buffer[i] >= 0x20 && buffer[i] < 0x7F) {
			printf("%c", buffer[i]);
		} else {
			printf(".");
		}
	}

	printf("\n");

	return GP_OK;
}

unsigned long bytes_to_l(int a, int b, int c, int d)
{
	unsigned long res = a;
	res = res << 24 | b << 16 | c << 8 | d;
	return res;
}
