/* library.c
 *
 * Copyright (C) 2002 Michel Koltan <koltan@gmx.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>
#include <gphoto2/gphoto2.h>

#include "dc210.h"
#include "library.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

static int dc210_cmd_error = 0;
static const char ppmheader[] = "P6\n96 72\n255\n";

#ifdef DEBUG
static int firststatus = 1;
static char oldstatus[DC210_STATUS_SIZE];
#endif

/***************************************************************************

 Global in- and output procedures

****************************************************************************/

static void cfa2ppm 
(CameraFile * file)
{
	/* this is a very quick and dirty hack to convert cfa into ppm */
	unsigned char buf[THUMBHEIGHT][THUMBWIDTH];  
	unsigned char rgb[THUMBHEIGHT][THUMBWIDTH][3];
	unsigned char val;
	unsigned int addval;
	const char * data;
	unsigned long datasize;
	int i, x, y;

	/* get data */

	DC210_DEBUG("Converting CFA to PPM\n");

	gp_file_get_data_and_size(file, &data, &datasize);

	/* split 8 bit values into to 4 bit values and blow up again to 8 bit*/

	i = 0;
	for(y = 0; y < THUMBHEIGHT; y++ )
		for(x = 0; x < THUMBWIDTH; x+=2 ){
			val = (unsigned char) data[i] >> 4;
			buf[y][x] = (val << 4) | val;
			val = (unsigned char) data[i++] & 0x0F;
			buf[y][x+1] = (val << 4) | val;
		};

	/* fill in the exact values */
	for( y = 0; y < THUMBHEIGHT; y += 2 )
		for( x = 0; x < THUMBWIDTH; x += 2 )
		{
			rgb[y][x][GREEN] = buf[y][x];
			rgb[y][x+1][GREEN] = buf[y][x];
			rgb[y+1][x][GREEN] = buf[y+1][x+1];
			rgb[y+1][x+1][GREEN] = buf[y+1][x+1];
			rgb[y][x][RED] = buf[y][x+1];
			rgb[y][x+1][RED] = buf[y][x+1];
			rgb[y+1][x][RED] = buf[y][x+1];
			rgb[y+1][x+1][RED] = buf[y][x+1];
			rgb[y][x][BLUE] = buf[y+1][x]; 
			rgb[y][x+1][BLUE] = buf[y+1][x];
			rgb[y+1][x][BLUE] = buf[y+1][x];
			rgb[y+1][x+1][BLUE] = buf[y+1][x];
		};

	/* fill in the interpolated values, don't care about borders */
	for ( y = 1; y < THUMBHEIGHT - 2; y+= 2)
		for ( x = 0; x < THUMBWIDTH - 2; x +=2){
			/* 2 missing green values */
			addval = rgb[y][x][GREEN] + rgb[y][x+2][GREEN] 
				+ rgb[y-1][x+1][GREEN] + rgb[y+1][x+1][GREEN];
			rgb[y][x+1][GREEN] = addval / 4;

			addval = rgb[y+1][x-1][GREEN] + rgb[y+1][x+1][GREEN]
				+ rgb[y][x][GREEN] + rgb[y+2][x][GREEN];
			rgb[y+1][x][GREEN] = addval / 4;

			/* 3 missing red values */
			addval = rgb[y-1][x][RED] + rgb[y+1][x][RED];
			rgb[y][x][RED] = addval / 2;

			addval = rgb[y-1][x][RED] + rgb[y-1][x+2][RED] 
				+ rgb[y+1][x][RED] + rgb[y+1][x+2][RED];
			rgb[y][x+1][RED] = addval / 4;

			addval = rgb[y+1][x][RED] + rgb[y+1][x+2][RED];
			rgb[y+1][x+1][RED] = addval / 2;

			/* 3 missing blue values */
			addval = rgb[y][x-1][BLUE] + rgb[y][x+1][BLUE];
			rgb[y][x][BLUE] = addval / 2;

			addval = rgb[y][x-1][BLUE] + rgb[y][x+1][BLUE] 
				+ rgb[y+2][x-1][BLUE] + rgb[y+2][x+1][BLUE];
			rgb[y+1][x][BLUE] = addval / 4;

			addval = rgb[y][x+1][BLUE] + rgb[y+2][x+1][BLUE];
			rgb[y+1][x+1][BLUE] = addval / 2;
		};

	gp_file_clean(file);
	gp_file_append(file, ppmheader, sizeof(ppmheader) - 1);
	gp_file_append(file, (char *) rgb, THUMBWIDTH * THUMBHEIGHT * 3);
	gp_file_set_mime_type(file, GP_MIME_PPM);

};

/* Utility procedures for command execution */

static void dc210_cmd_init
(char * cmd, unsigned char command_byte)
{
	/* utility procedure to initalize a command string */

	memset (cmd, 0, 8);

	cmd[0] = command_byte;
	cmd[7] = 0x1a;

};

static void dc210_cmd_packet_init
(char * cmd_packet, const char * filename)
{
	/* prepare command packet */
	memset (cmd_packet, 0x00, DC210_CMD_DATA_SIZE);
	memset (cmd_packet + 48, 0xFF, 8);
	strcpy(cmd_packet, "\\PCCARD\\DCIMAGES\\");
	strcpy(cmd_packet + 17, filename);

	DC210_DEBUG("Complete filename is %s\n", cmd_packet);

};

static int dc210_write_single_char 
(Camera *camera, unsigned char response) 
{
	/* utility procedure to write a single character */

	int i;

	for (i=0; i< RETRIES; i++){
		if (gp_port_write(camera->port, (char *)&response, 1) >= 0)
			return (GP_OK);
	};

	return GP_ERROR;

};

static int dc210_read_single_char 
(Camera *camera, unsigned char * response)
{

	/* utility procedure to read a single character */

	int i;
	signed char error;

	for (i = 0; i < RETRIES; i++){

		error = gp_port_read(camera->port, (char *)response, 1);

		if (error < 0){
			if (error == GP_ERROR_TIMEOUT)
				continue;
			else{
				DC210_DEBUG("Real bad error reading single character. Errornumber: %d\n", error);
				return GP_ERROR;
			};
		};
		
		return GP_OK;

	};
	
	return GP_ERROR_TIMEOUT;

};

/* Command execution */

static int dc210_execute_command 
(Camera *camera, char *cmd)
{

	/* This procedure sends a command to the camera and
	   waits for acknowledgement. Calling procedures have
	   to check the returncode; if the command succeeded
	   there are three possible follow ups:
	   - Read a DC210_COMMAND_COMPLETE in case of
	     simple commands
           - Send an additional command package in case of
	     sophisticated commands
	   - Read data from the camera
	   But all this depends on the command and should not
	   be mixed in this procedure. */

	int i,k ;
	unsigned char response;
	signed char error;
  
	dc210_cmd_error = DC210_CMD_OKAY;

	for (i = 0; i < RETRIES; i++){

		/* write command */
		if (gp_port_write(camera->port, cmd, 8) < 0){
			DC210_DEBUG("Could not write to port.\n");
			dc210_cmd_error = DC210_WRITE_ERROR;
			continue; /* try again */
		};

		for (k = 0; k < RETRIES; k++){
			error = gp_port_read(camera->port, (char *)&response, 1);
			if (error != 1){
				if (error == GP_ERROR_TIMEOUT){
					dc210_cmd_error = DC210_TIMEOUT_ERROR;
					continue; /* wait a little bit longer */
				}
				else{
					DC210_DEBUG("Real bad error reading answer. Errornumber: %d\n", error);
					dc210_cmd_error = DC210_READ_ERROR;
					return error;
				};
			};
			
			switch (response){
			case DC210_COMMAND_ACK:
				DC210_DEBUG("Command 0x%02X acknowledged.\n", (unsigned char) cmd[0]);
				return GP_OK; break; /* that's fine, just what we expected */
			case DC210_COMMAND_NAK:
				DC210_DEBUG("Sorry, but the camera seems not to understand the command 0x%02X\n", (unsigned char) cmd[0]);
				dc210_cmd_error = DC210_NAK_ERROR;
				break; /* anyway, send the command again */
			default:
				DC210_DEBUG("Strange. Unexpected response for command 0x%02X: 0x%02X\n", (unsigned char) cmd[0], response);
				dc210_cmd_error = DC210_GARBAGE_ERROR;
				return GP_ERROR;
				break;
			};
			break;
		};
		
	};

	/* command does definitely not work */

	DC210_DEBUG("Command definitely didn't work.\n");
	
	return GP_ERROR;

};

/* appropriate actions to a command */

static int dc210_write_command_packet
(Camera * camera, char * data)
{

	unsigned char checksum;
	int i, error;
	unsigned char answer;

	/* calculating checksum */
	checksum = 0;
	for (i = 0; i < DC210_CMD_DATA_SIZE; i ++){
		checksum ^= data[i];
	};

	for (i = 0; i < RETRIES; i++){

		/* write the startbyte */
		dc210_write_single_char(camera, DC210_CMD_PACKET_FOLLOWING);

		/* write packet */
		gp_port_write(camera->port, data, DC210_CMD_DATA_SIZE);

		/* write checksum */
		dc210_write_single_char(camera, checksum);

		/* read answer */

		error = gp_port_read(camera->port, (char *)&answer, 1);

		if (error < 0) return GP_ERROR;

		if (answer == DC210_CORRECT_PACKET) return GP_OK;
		
		if (answer != DC210_ILLEGAL_PACKET) {
			DC210_DEBUG("Strange answer to command packet: 0x%02X.\n", answer);
			return GP_ERROR;
		};

	};

	DC210_DEBUG("Could not send command packet.\n");

	return GP_ERROR;

};

static int dc210_wait_for_response 
(Camera *camera, int expect_busy, GPContext *context)
{
	/* Waits for a command to finish.
	   Expects either a timeout, a DC210_BUSY, 
	   a DC210_COMMAND_COMPLETE or a
	   DC210_PACKET_FOLLOWING; all other answers
	   are considered errors */

	unsigned char response;
	int error = 0;
	int counter = 0;
	int progress_id = 0;

	if (context)
	  progress_id = gp_context_progress_start (context, expect_busy, _("Waiting..."));

	while (1){

	  error = dc210_read_single_char(camera, &response);
	  if (error < 0){
	          if (context)
			  gp_context_progress_stop (context, progress_id);
		  return error;
	  };

	  switch (response){
	  case DC210_BUSY:
		  /* wait a little bit longer */
		  if (context && counter <= expect_busy)
			  gp_context_progress_update (context, progress_id, counter++);
		  break;
	  case DC210_COMMAND_COMPLETE:
	  case DC210_PACKET_FOLLOWING:
		  if (context)
			  gp_context_progress_stop (context, progress_id);
		  return response;
		  break;
	  default:
		  if (context)
			  gp_context_progress_stop (context, progress_id);
		  DC210_DEBUG("Command terminated with errorcode 0x%02X.\n", response);
		  return GP_ERROR;
	  };
	};

};

static int dc210_read_single_block 
(Camera *camera, unsigned char * b, int blocksize) 
{

  int i, k, error;
  char cs_read, cs_computed;

  for (i = 0; i < RETRIES; i++){

    if (dc210_wait_for_response(camera, 0, NULL) != DC210_PACKET_FOLLOWING)
      return GP_ERROR;

    error = 1;
    for (k = 0; k < RETRIES; k++){
      if (gp_port_read(camera->port, (char *)b, blocksize) < 0){
	continue;
      };
      error = 0;
      break;
    };
  
    if (error) return GP_ERROR;

    if (dc210_read_single_char(camera, (unsigned char *)&cs_read) < 0)
      return GP_ERROR;

    cs_computed = 0;
    for (k = 0 ; k < blocksize; k++)
      cs_computed ^= b[k];
    if (cs_computed == cs_read){
      dc210_write_single_char(camera, DC210_CORRECT_PACKET);
      return GP_OK;
    };

    dc210_write_single_char(camera, DC210_ILLEGAL_PACKET);

  };

  return GP_ERROR;

};

static int dc210_read_to_file
(Camera *camera, CameraFile * f, int blocksize, long int expectsize, GPContext *context) 
{

  int packets, k, l, fatal_error, packet_following;
  unsigned int progress_id = 0;
  unsigned char cs_read, cs_computed;
  unsigned char * b;

  int blocks = expectsize / blocksize;
  int remaining = expectsize % blocksize;

  if (NULL == (b = malloc(blocksize))) return GP_ERROR;

  if (remaining) blocks++;

  if (context)
	  progress_id = gp_context_progress_start (context, blocks, _("Getting data..."));

  fatal_error = 0;
  packets = 0;

  packet_following = dc210_wait_for_response(camera, 0, NULL);
  while (DC210_PACKET_FOLLOWING == packet_following){
	  fatal_error = 1;
	  for (k = 0; k < RETRIES; k++){
		  /* read packet */
	          if (gp_port_read(camera->port, (char *)b, blocksize) < 0){
		          dc210_write_single_char(camera, DC210_ILLEGAL_PACKET);
			  packet_following = dc210_wait_for_response(camera, 0, NULL);
			  continue;
		  };
		  /* read checksum */
		  if (dc210_read_single_char(camera, &cs_read) == GP_ERROR){
			  free(b); 
			  return GP_ERROR;
		  };
		  /* test checksum */
		  cs_computed = 0;
		  for (l = 0 ; l < blocksize; l++)
			  cs_computed ^= b[l];
		  if (cs_computed != cs_read){
			  dc210_write_single_char(camera, DC210_ILLEGAL_PACKET);
			  packet_following = dc210_wait_for_response(camera, 0, NULL);
			  continue;
		  };
		  /* append to file */
		  if (packets == blocks - 1 && remaining)
			  gp_file_append(f, (char *)b, remaining);
		  else
			  gp_file_append(f, (char *)b, blocksize);
		  /* request next packet */
		  dc210_write_single_char(camera, DC210_CORRECT_PACKET);
		  packet_following = dc210_wait_for_response(camera, 0, NULL);
		  fatal_error = 0;
		  if (context)
			  gp_context_progress_update (context, progress_id, packets);
		  packets++;
		  break;
	  };
	  if (fatal_error) break;
  };

  if (packet_following < 0)
	  fatal_error = 1;

  if (context)
	  gp_context_progress_stop (context, progress_id);

  free(b);

  if (fatal_error) return GP_ERROR;

  return GP_OK;

};

/***************************************************************************

 Working procedures

****************************************************************************/

/***** Set procedures *****/

static int dc210_set_option (Camera * camera, char command, unsigned int value, int valuesize){

	char cmd[8];
	dc210_cmd_init(cmd, command);

	switch(valuesize){
	case 0:
		break;
	case 1:
		cmd[2] = value & 0xFF;
		break;
	case 2:
		cmd[3] = value & 0xFF;
		cmd[2] = (value >> 8) & 0xFF;
		break;
	case 4:
		cmd[5] = value & 0xFF;
		cmd[4] = (value >> 8) & 0xFF;
		cmd[3] = (value >> 16) & 0xFF;
		cmd[2] = (value >> 24) & 0xFF;
		break;
	default:
		return GP_ERROR;
	};
	
	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;

	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

	return GP_OK;

};

int dc210_set_compression (Camera * camera, dc210_compression_type compression){

	return dc210_set_option(camera, DC210_SET_QUALITY, compression, 1);

};

int dc210_set_flash (Camera * camera, dc210_flash_type flash, char preflash){

	if (flash != DC210_FLASH_NONE && preflash){
		return dc210_set_option(camera, DC210_SET_FLASH, flash + 3, 1);
	}
	else
		return dc210_set_option(camera, DC210_SET_FLASH, flash, 1);

};

int dc210_set_zoom (Camera * camera, dc210_zoom_type zoom){

	return dc210_set_option(camera, DC210_SET_ZOOM, zoom, 1);
	
};

int dc210_set_file_type (Camera * camera, dc210_file_type_type file_type){

	return dc210_set_option(camera, DC210_SET_FILE_TYPE, file_type, 1);

};

int dc210_set_resolution (Camera * camera, dc210_resolution_type res){

	return dc210_set_option(camera, DC210_SET_RESOLUTION, res, 1);

};

int dc210_set_delay (Camera * camera){

	/* This function works, but I do no have the slightest idea,
	   what the effect is / should be.
	   There is *no* delay, when you execute this function before
	   capturing. */

	return dc210_set_option(camera, DC210_SET_DELAY, 10, 4);

};

int dc210_set_exp_compensation (Camera * camera, signed int compensation){

	unsigned char param = abs(compensation);

	if (compensation < 0) param |= 0x80;

	return dc210_set_option(camera, DC210_SET_EXPOSURE, param, 1);

};

int dc210_system_time_callback (Camera * camera, CameraWidget * widget, GPContext * context){

	return dc210_set_option(camera, DC210_SET_TIME, (time(NULL) - CAMERA_SET_EPOC) << 1, 4);

};

static int dc210_format_card (Camera * camera, char * album_name, GPContext * context){

  char data[DC210_CMD_DATA_SIZE];
  char cmd[8];
  char * subst;
  unsigned char answer[16];
  unsigned char checksum_read, checksum;
  int i;

  memset (data, 0, DC210_CMD_DATA_SIZE);

  /* Make the album name acceptable - we need at least 8 characters
     which are not blank and there are no blanks allowed before the
     last character.
  */
  if (album_name != NULL && strlen(album_name) > 0){
	  strncpy(data, album_name, 11);
	  /* substitute blanks in the first 8 characters*/
	  while (NULL != (subst = strchr(data, ' '))) *subst = '_';
	  if (strlen(data) < 8) strncat(data, "________", 8 - strlen(data));
  };
  
  DC210_DEBUG("Album name is '%s'\n", data);

  dc210_cmd_init(cmd, DC210_CARD_FORMAT);

  dc210_execute_command(camera, cmd);
  dc210_write_command_packet(camera, data);
  if (dc210_wait_for_response(camera, 3, context) != DC210_PACKET_FOLLOWING) return GP_ERROR;

  gp_port_read(camera->port, (char *)answer, 16);
  gp_port_read(camera->port, (char *)&checksum_read, 1);
  checksum = 0;

  for (i = 0; i < 16; i++) checksum ^= answer[i];
  if (checksum_read != checksum) return GP_ERROR;

  DC210_DEBUG("Flash card formatted.\n");

  if (dc210_write_single_char(camera, DC210_CORRECT_PACKET) == GP_ERROR) return GP_ERROR;
  if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

  gp_filesystem_reset(camera->fs);

  return GP_OK;

};

static int dc210_get_card_status (Camera * camera, dc210_card_status * card_status){

  char cmd[8];
  unsigned char answer[16];
  unsigned char checksum_read, checksum;
  int i;

  card_status->open = 0;
  card_status->program = 0;
  card_status->space = 0;

  dc210_cmd_init(cmd, DC210_CARD_STATUS);
  dc210_execute_command(camera, cmd);
  if (dc210_wait_for_response(camera, 0, NULL) != DC210_PACKET_FOLLOWING) return GP_ERROR;

  gp_port_read(camera->port, (char *)answer, 16);
  gp_port_read(camera->port, (char *)&checksum_read, 1);

  checksum = 0;
  for (i = 0; i < 16; i++) checksum ^= answer[i];

  if (checksum_read != checksum)
	  DC210_DEBUG("Error reading card status.\n");
  else
	  DC210_DEBUG("Card status correctly read.\n");

  if (answer[0] & 0x08) card_status->open = 1;
  DC210_DEBUG("Card status open is %d\n", card_status->open);
  card_status->program = answer[1] * 0x100 + answer[2];
  /* set the space in kb */
  card_status->space = (answer[3] * 0x1000000 + answer[4] * 0x10000 + answer[5] * 0x100 + answer[6]) / 1024;

  if (dc210_write_single_char(camera, DC210_CORRECT_PACKET) == GP_ERROR) return GP_ERROR;
  if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

  return GP_OK;

};

int dc210_format_callback(Camera * camera, CameraWidget * widget, GPContext * context){

	CameraWidget * window;
	char * album_name;

	gp_widget_get_root(widget, &window);

	gp_widget_get_child_by_label (window, _("Album name"), &widget);
	gp_widget_get_value (widget, &album_name);

	return dc210_format_card(camera, album_name, context);

};

static int dc210_check_battery (Camera *camera){

  char cmd[8];
  dc210_cmd_init(cmd, DC210_CHECK_BATTERY);
	
  if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
  if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

  return GP_OK;

};

int dc210_init_port (Camera *camera){

	/* sending a break command resets the
	   speed to 9600 */
	int camera_speeds[] = {115200, 19200, 38400, 57600};
	int i, desired_speed;
	GPPortSettings settings;

	gp_port_get_settings (camera->port, &settings);
	gp_port_set_timeout (camera->port, TIMEOUT);
	settings.serial.bits     = 8;
	settings.serial.parity   = 0;
	settings.serial.stopbits = 1;

	desired_speed = settings.serial.speed;
	if (! desired_speed) desired_speed = 115200;

	DC210_DEBUG("Desired port speed is %d.\n", desired_speed);

	/* only set the speed if nothing was specified on the command line */
	if (! settings.serial.speed) settings.serial.speed = 9600;
	gp_port_set_settings (camera->port, settings);

	/* ok, run a dummy command; if the camera was off,
	   this will activate it and set the speed to 9600; 
	   but the command will return an error;
	   It makes no sense to shorten the timout time, 
	   because the camera may need up to 10 seconds (information
	   of the manual) to startup */
	if (dc210_check_battery(camera) == GP_OK) return GP_OK;

	/* Fine, the last command didn't work, but we are now
	   sure, the camera is working, if it is connected
	   and has enough power.
	   This is the time to send a break to reset the speed
	   to 9600 */
	
	gp_camera_set_port_speed(camera, 9600);
	gp_port_send_break(camera->port, 300);
	usleep(300 * 1000);

	/* Excellent. Now our dummy command should work */

	if (dc210_check_battery(camera) == GP_OK){
	  return dc210_set_speed(camera, desired_speed);
	};

	/* My, my, this is not nice. We change the timeout
	   and try other speeds as last resort */

	gp_port_set_timeout (camera->port, 100);

	for (i = 0; i < 4; i++){
		settings.serial.speed = camera_speeds[i];
		gp_port_set_settings (camera->port, settings);

		if (dc210_check_battery(camera) == GP_OK){
			gp_port_set_timeout (camera->port, TIMEOUT);
			return dc210_set_speed(camera, desired_speed);
		};

		DC210_DEBUG("What a pity. Speed %d does not work.\n", camera_speeds[i]);
	};

	/* reset the timeout */
	gp_port_set_timeout (camera->port, TIMEOUT);

	return GP_ERROR;

};

int dc210_set_speed (Camera *camera, int speed) {

	unsigned char cmd[8];
	GPPortSettings settings;

	dc210_cmd_init((char*)cmd, DC210_SET_SPEED);
	
	switch (speed) {
	case 9600:
		cmd[2] = 0x96; cmd[3] = 0x00; break;
	case 19200:
		cmd[2] = 0x19; cmd[3] = 0x20; break;
	case 38400:
		cmd[2] = 0x38; cmd[3] = 0x40; break;
	case 57600:
		cmd[2] = 0x57; cmd[3] = 0x60; break;
	case 115200:
		cmd[2] = 0x11; cmd[3] = 0x52; break;
	default:
		return (GP_ERROR);
	};

	if (dc210_execute_command(camera, (char*)cmd) == GP_ERROR) return GP_ERROR;

	gp_port_get_settings (camera->port, &settings);
	settings.serial.speed = speed;
	gp_port_set_settings (camera->port, settings);

	DC210_DEBUG("Port speed set to %d.\n", speed);

	return GP_OK;
  
};

/****** Picture actions *****/

int dc210_take_picture 
(Camera * camera, GPContext * context)
{
	char cmd[8];
	dc210_cmd_init(cmd, DC210_TAKE_PICTURE);

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	switch (dc210_wait_for_response(camera, 5, context)){
	  /* we allow a timeout error here and check the result via status */
	  case DC210_COMMAND_COMPLETE:
	  case GP_ERROR_TIMEOUT: return GP_OK; break;
  	  default: return GP_ERROR; break;
	};

	return GP_ERROR;

};

int dc210_delete_picture 
(Camera * camera, unsigned int picno)
{

	char cmd[8];
	unsigned int pic_offset = picno - 1;

	dc210_cmd_init(cmd, DC210_DELETE_PICTURE);
	cmd[3] = pic_offset & 0xFF;
	cmd[2] = (pic_offset >> 8) & 0xFF;

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

	return GP_OK;

};

int dc210_delete_last_picture 
(Camera * camera )
{

  dc210_status status;

  if (dc210_get_status(camera, &status) == GP_ERROR)
	  return GP_ERROR;

  if (0 == status.numPicturesInCamera)
	  return GP_ERROR;

  return dc210_delete_picture(camera, status.numPicturesInCamera);

};

int dc210_delete_picture_by_name 
(Camera * camera, const char * filename )
{

        char cmd[8];
	char cmd_packet[DC210_CMD_DATA_SIZE];

	dc210_cmd_init(cmd, DC210_CARD_FILE_DEL);
	dc210_cmd_packet_init(cmd_packet, filename);

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_write_command_packet(camera, cmd_packet) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

	return GP_OK;

};

int dc210_download_last_picture 
(Camera * camera, CameraFile *file, dc210_picture_type type, GPContext *context)
{

	dc210_status status;
	dc210_picture_info picinfo;

	if (dc210_get_status(camera, &status) == GP_ERROR) return GP_ERROR;
	if (status.numPicturesInCamera == 0) return GP_ERROR;

	if (dc210_get_picture_info(camera, &picinfo, status.numPicturesInCamera) == GP_ERROR)
		return GP_ERROR;

	if (dc210_get_picture_info(camera, &picinfo, status.numPicturesInCamera) == GP_ERROR)
		return GP_ERROR;

	return dc210_download_picture_by_name(camera, file, picinfo.image_name, type, context);

};

int dc210_open_card
(Camera * camera)
{

	char cmd[8];
	dc210_card_status card_status;

	/* check if card is already opened */
	dc210_get_card_status(camera, &card_status);
	if (card_status.open) return GP_OK;

	dc210_cmd_init(cmd, DC210_OPEN_CARD);

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

	return GP_OK;
};

#if 0 /* This function is not used by anybody */
static int dc210_close_card
(Camera * camera)
{

	char cmd[8];
	dc210_cmd_init(cmd, DC210_CLOSE_CARD);

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

	return GP_OK;
};
#endif

int dc210_download_picture_by_name
(Camera * camera, CameraFile *file, const char *filename, dc210_picture_type type, GPContext *context)
{
  
        char cmd[8];
	char cmd_packet[DC210_CMD_DATA_SIZE];
	dc210_picture_info picinfo;

	/* prepare command */
	if (type == DC210_FULL_PICTURE){
		/* we need the file size for the progress bar */
		if (dc210_get_picture_info_by_name(camera, &picinfo, filename) == GP_ERROR)
			return GP_ERROR;
		DC210_DEBUG("Picture size is %d\n", picinfo.picture_size);
		dc210_cmd_init(cmd, DC210_CARD_READ_FILE);
	}
	else
		dc210_cmd_init(cmd, DC210_CARD_READ_THUMB);

	if (type == DC210_RGB_THUMB) cmd[4] = 1;

	dc210_cmd_packet_init(cmd_packet, filename);

	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_write_command_packet(camera, cmd_packet) == GP_ERROR) return GP_ERROR;

	switch (type){
	case DC210_FULL_PICTURE:
		if (picinfo.file_type == DC210_FILE_TYPE_JPEG)
			gp_file_set_mime_type(file, GP_MIME_JPEG);
		if (dc210_read_to_file(camera, file, 
				       DC210_CARD_BLOCK_SIZE, 
				       picinfo.picture_size, context) == GP_ERROR)
			return GP_ERROR;
		break;
	case DC210_CFA_THUMB:
		/* this thumb type is much faster to download, because it has just
		   a 6th of the size of rgb data */
		if (dc210_read_to_file(camera, file, 
				       DC210_DOWNLOAD_BLOCKSIZE, 48 * 72, NULL) == GP_ERROR)
			return GP_ERROR;
		/* convert cfa data to rgb: */
		cfa2ppm(file);
		break;
	  break;
	case DC210_RGB_THUMB:
		/* if you really need nice thumbs, you can use this, but I don't think it's
		   worth the 6 times longer download time */
		gp_file_set_mime_type(file, GP_MIME_PPM);
		gp_file_append(file, ppmheader, sizeof(ppmheader) - 1);
		if (dc210_read_to_file(camera, file, 
				       DC210_DOWNLOAD_BLOCKSIZE, 96 * 72 * 3, NULL) == GP_ERROR)
			return GP_ERROR;
		break;
	};
	
	return GP_OK;
};

int dc210_capture (Camera *camera, CameraFilePath *path, GPContext *context) 
{

	dc210_status status;
	dc210_picture_info picinfo;
	int pictures_in_camera;

	/* dc210_take_picture no longer returns an error on timeout; check
	   the working of the command via status */

	if (dc210_get_status(camera, &status) == GP_ERROR) return GP_ERROR;
	pictures_in_camera = status.numPicturesInCamera;

	if (dc210_take_picture(camera, context) == GP_ERROR) return GP_ERROR;
	
	if (dc210_get_status(camera, &status) == GP_ERROR) return GP_ERROR;
	if (pictures_in_camera == status.numPicturesInCamera) return GP_ERROR; 

	if (dc210_get_picture_info(camera, &picinfo, status.numPicturesInCamera) == GP_ERROR) return GP_ERROR;

	strcpy(path->folder, "/");
	strcpy(path->name, picinfo.image_name);

	return GP_OK;

};

/***** Retrieve information *****/

int dc210_get_picture_number (Camera *camera, const char * filename){

	/* This function is terrible slow if you have a lot of pictures
	   in the camera; therefor it should not be used */

	dc210_status status;
	dc210_picture_info picinfo;
	int i;

	if (dc210_get_status(camera, &status) == GP_ERROR) return -1;

	for (i = 1; i <= status.numPicturesInCamera; i++){
		
		if (dc210_get_picture_info(camera, &picinfo, i) == GP_ERROR) return -1;
		
		if (strcmp(picinfo.image_name, filename) == 0){
			return i;
		};
		
	}; 

	return -1;

};

int dc210_get_filenames (Camera *camera, CameraList *list, GPContext *context) 
{
	char cmd[8];
	CameraFile *file;
	char filename[13];
	const char * data;
	unsigned long datasize;
	int pictures, i;

	gp_file_new(&file);

	dc210_cmd_init(cmd, DC210_GET_ALBUM_FILENAMES);

	dc210_execute_command(camera, cmd);
  
	dc210_read_to_file(camera, file, DC210_DIRLIST_SIZE, 0, NULL);

	gp_file_get_data_and_size(file, &data, &datasize);

	pictures = (unsigned char) data[0] * 0x100 + (unsigned char) data[1];
	DC210_DEBUG("There are %d pictures in the camera\n", pictures);

	filename[8] = '.';
	filename[12] = '\0';
	for (i = 0; i < pictures; i++){
		strncpy(filename, data + 2 + i*20, 8);
		strncpy(filename + 9, data + 10 + i*20, 3);
		DC210_DEBUG("Adding filename %s to list\n", filename);
		gp_list_append(list, filename, NULL);
	};

	gp_file_free(file);

	return GP_OK;

};

static void dc210_picinfo_from_block (dc210_picture_info * picinfo, unsigned char * data){

	picinfo->camera_type = data[1];
	picinfo->file_type = data[2];
	picinfo->resolution = data[3];
	picinfo->compression = data[4];
	picinfo->picture_number = data[6] * 0x100 + data[7];
	picinfo->picture_size = data[8] * 0x1000000 + data[9] * 0x10000 + data[10] * 0x100 + data[11];
	picinfo->preview_size = 96 * 72 * 3 + sizeof(ppmheader);
	picinfo->picture_time = CAMERA_GET_EPOC + ((data[12] * 0x1000000 + 
						    data[13] * 0x10000 + 
						    data[14] * 0x100 + 
						    data[15]) >> 1);
	picinfo->flash_used = data[16];
	picinfo->flash = data[17];
	picinfo->preflash = (data[17] > 2);
	if (picinfo->preflash) picinfo->flash -= 3;
	picinfo->zoom = data[21];
	picinfo->f_number = data[26];
	picinfo->battery = data[27];
	picinfo->exposure_time = data[28] * 0x1000000 + data[29] * 0x10000 + data[30] * 0x100 + data[31];
	strncpy(picinfo->image_name, (char *)&data[32], 12);
	picinfo->image_name[12] = 0;
	
}

int dc210_get_picture_info (Camera *camera, dc210_picture_info *picinfo, unsigned int picno) {

	char cmd[8];
	unsigned char data[DC210_PICINFO_SIZE];
	unsigned int pic_offset = picno - 1;

	dc210_cmd_init(cmd, DC210_GET_PICINFO);
	cmd[3] = pic_offset & 0xFF;
	cmd[2] = (pic_offset >> 8) & 0xFF;
	    
	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_read_single_block(camera, data, DC210_PICINFO_SIZE) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;
	
	dc210_picinfo_from_block(picinfo, data);
		
	return GP_OK;

};

int dc210_get_picture_info_by_name (Camera *camera, dc210_picture_info *picinfo, const char * filename) {

	char cmd[8];
	char cmd_packet[DC210_CMD_DATA_SIZE];
	unsigned char data[DC210_CARD_BLOCK_SIZE];
	
	dc210_cmd_init(cmd, DC210_CARD_GET_PICINFO);
	dc210_cmd_packet_init(cmd_packet, filename);
	    
	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_write_command_packet(camera, cmd_packet) == GP_ERROR) return GP_ERROR;
	if (dc210_read_single_block(camera, data, DC210_CARD_BLOCK_SIZE) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;
	
	dc210_picinfo_from_block(picinfo, data);
		
	return GP_OK;

};

int dc210_get_status (Camera *camera, dc210_status *status) {

#ifdef DEBUG
	int i;
#endif

	char data[DC210_STATUS_SIZE];
	char cmd[8];

	/* you have to check the battery status explicitely 
	   before reading the status table*/
	dc210_check_battery(camera);

	/* check the card */
	dc210_get_card_status(camera, &(status->card_status));

	dc210_cmd_init(cmd, DC210_GET_STATUS);
	
	if (dc210_execute_command(camera, cmd) == GP_ERROR) return GP_ERROR;
	if (dc210_read_single_block(camera, (unsigned char *)data, DC210_STATUS_SIZE) == GP_ERROR) return GP_ERROR;
	if (dc210_wait_for_response(camera, 0, NULL) != DC210_COMMAND_COMPLETE) return GP_ERROR;

#ifdef DEBUG
	if (firststatus){
	  firststatus = 0;
	}
	else{
		for (i = 0; i < DC210_STATUS_SIZE; i++){
			if (oldstatus[i] != data[i] && (i < 12 || i > 15)){
				DC210_DEBUG("Statusdata differs at offset %03d (old: 0x%02X, new: 0x%02X)\n", 
					    i, (unsigned char) oldstatus[i], (unsigned char) data[i]);
			};
		};
	};
	memcpy(oldstatus, data, DC210_STATUS_SIZE);
#endif

	status->firmwareMajor         = data[2];
	status->firmwareMinor         = data[3];
	status->battery               = data[8];
	status->acstatus              = data[9];
	
	/* seconds since unix epoc */
	status->time = CAMERA_GET_EPOC + ((unsigned char) data[12] * 0x1000000 + 
					  (unsigned char) data[13] * 0x10000 + 
					  (unsigned char) data[14] * 0x100 + 
					  (unsigned char) data[15]) / 2;
	
	status->zoom                  = data[16];
	status->compression_type      = data[19];
	status->flash                 = data[20];
	status->exp_compensation      = data[21] & 0x7F;
	if (data[21] & 0x80) status->exp_compensation *= -1;
	status->preflash = (data[20] > 2);
	if (status->preflash) status->flash -= 3;
	status->resolution            = data[22];
	status->file_type             = data[23];
	status->totalPicturesTaken    = (unsigned char) data[25] * 0x100 + (unsigned char) data[26];
	status->totalFlashesFired     = (unsigned char) data[27] * 0x100 + (unsigned char) data[28];
	status->numPicturesInCamera   = (unsigned char) data[56] * 0x100 + (unsigned char) data[57];
	status->remainingLow          = (unsigned char) data[68] * 0x100 + (unsigned char) data[69];
	status->remainingMedium       = (unsigned char) data[70] * 0x100 + (unsigned char) data[71];
	status->remainingHigh         = (unsigned char) data[72] * 0x100 + (unsigned char) data[73];
	strncpy(status->album_name, &data[77], 11);
	status->album_name[11] = 0;

	return GP_OK;

}

/* Debugging stuff, not really needed in the driver */

#ifdef DEBUG

static int dc210_read_dummy_packet(Camera * camera){

	int i;
	signed char error;
	unsigned char answer, lastanswer, checksum;

	checksum = 0;
	lastanswer = 0;
	for ( i = 0; i < 2048; i++){
	
		error = gp_port_read(camera->port, &answer, 1);

		if (error < 0){
			DC210_DEBUG("Packet read exhausted. Packet size: %d\n", i-1);
			if (checksum == lastanswer)
			  DC210_DEBUG("Checksum is okay. Sending okay.\n");
			else
			  DC210_DEBUG("Checksum is not okay. Sending correct packet anyway.\n");
			answer = DC210_CORRECT_PACKET;
			gp_port_write(camera->port, &answer, 1);
			return GP_OK;
		};
		DC210_DEBUG(" Byte %03d: 0x%02X", i, answer);
		if (answer > 32)
		  DC210_DEBUG(" (%c)", answer);
		DC210_DEBUG("\n");
		checksum ^= lastanswer;
		lastanswer = answer;
	};

	DC210_DEBUG("Packet too long. Sending cancel.\n");
	answer = DC210_CANCEL;
	gp_port_write(camera->port, &answer, 1);

	return GP_OK;

};

static int dc210_test_command
(Camera * camera, unsigned char cmdbyte, unsigned char *databytes){

	/* this is a debug command to test the behaviour
	   of certain camera commands */
	unsigned char cmd[8];
	unsigned char answer;
	signed char error;
	int packetcount = 0;
	char dummy_cmd_packet[DC210_CMD_DATA_SIZE];

	memset (dummy_cmd_packet, 0, DC210_CMD_DATA_SIZE);

	dc210_cmd_init(cmd, cmdbyte);

	memcpy(cmd + 2, databytes, 4); 

	/* okay, write the command and wait for acknowledge */

	gp_port_write(camera->port, cmd, 8);

	error = gp_port_read(camera->port, &answer, 1);

	if (error < 0){
		DC210_DEBUG("Command 0x%02X produced port read error %d",
		       cmdbyte, error);
		return GP_OK;
	};

	switch (answer){
	case DC210_COMMAND_ACK:
		DC210_DEBUG("Okay, command 0x%02X acknowledged.\n", cmdbyte);
		break;
	case DC210_COMMAND_NAK:
		DC210_DEBUG("Sorry, command 0x%02X not acknowledged.\n", cmdbyte);
		return GP_OK;
	default:
		DC210_DEBUG("Unexpected response 0x%02X to command 0x%02X.\n", 
		       answer, cmdbyte);
		return GP_OK;
	};

 wait_finish:
	error = gp_port_read(camera->port, &answer, 1);

	if (error < 0){
		DC210_DEBUG("Command 0x%02X produced port read error %d\n",
		       cmdbyte, error);
		DC210_DEBUG("Probably the command expects a command package.\n");
		DC210_DEBUG("Sending one...\n");
		dc210_write_command_packet(camera, dummy_cmd_packet);
		goto wait_finish;
	};

	switch(answer){
	case DC210_COMMAND_COMPLETE:
		DC210_DEBUG("Okay. Command has been accepted and executed.\n");
		return GP_OK;
	case DC210_EXECUTION_ERROR:
		DC210_DEBUG("Sorry. An error occurred while executing command.\n");
		return GP_OK;
	case DC210_CORRECT_PACKET:
		DC210_DEBUG("Okay. Packet successfully received.\n");
		goto wait_finish;
	case DC210_PACKET_FOLLOWING:
		DC210_DEBUG("My, my, you got more than you bargained for. ");
		DC210_DEBUG("Awaiting packet...\n");
		dc210_read_dummy_packet(camera);
		DC210_DEBUG("Read %d packets.\n", ++packetcount);
		goto wait_finish;
	case DC210_BUSY:
		DC210_DEBUG("Camera is busy.\n");
		goto wait_finish;
	default:
		DC210_DEBUG("Unexpected response 0x%02X to command 0x%02X.\n", 
		       answer, cmdbyte);
		return GP_OK;
	};
};

int dc210_debug_callback(Camera * camera, CameraWidget * widget, GPContext * context){

	/*CameraFile *file;*/
	CameraWidget * window;
	char * s_param1, *s_param2, *s_param3;
	int param1, param2, param3;
	int value;
	unsigned char params[4];
	dc210_status status;

	memset(params, 0, 4);

	gp_widget_get_root(widget, &window);

	gp_widget_get_child_by_label (window, _("Parameter 1"), &widget);
	gp_widget_get_value (widget, &s_param1);
	param1 = atoi(s_param1);

	gp_widget_get_child_by_label (window, _("Parameter 2"), &widget);
	gp_widget_get_value (widget, &s_param2);
	param2 = atoi(s_param2);

	gp_widget_get_child_by_label (window, _("Parameter 3"), &widget);
	gp_widget_get_value (widget, &s_param3);
	param3 = atoi(s_param3);

	/* Put the stuff you want to test here */
	dc210_get_status(camera, &status);
	dc210_test_command(camera, 0x85, params);
	dc210_get_status(camera, &status);

	return GP_OK;

};

#endif
