/* blink2.c
 *
 * Copyright 2004 Olivier Fauchon <olivier@aixmarseille.com>
 *
 * 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 <unistd.h>

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

#include "libgphoto2/i18n.h"


#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#define sleep(x)
#define usleep(x)
#endif


#define ENIGMA13_BLK_FLASH_ALIGN 0x2000
#define ENIGMA13_BLK_CARD_ALIGN 0x4000
#define ENIGMA13_USB_TIMEOUT_MS 5000
#define ENIGMA13_WAIT_FOR_READY_TIMEOUT_S 5
#define ENIGMA13_WAIT_TOC_DELAY_MS 500
#define ENIGMA13_WAIT_IMAGE_READY_MS 300
#define GP_MODULE "enigma13"


#define CHECK(result) {int res; res = result; if (res < 0) return (res);}
#define CHECK_AND_FREE(result, buf) {int res; res = result; if (res < 0) { free(buf); return (res); }}


/* the table of content of the camera
   it's filled in enigma_get_toc, and used in enigma_download_img */
static char* enigma13_static_toc=NULL;




static int enigma13_about (Camera *camera, CameraText *about, GPContext *context)
{
	strcpy (about->text, _("Download program for Digital Dream Enigma 1.3. "
		"by <olivier@aixmarseille.com>, and adapted from spca50x driver. "
		"Thank you, spca50x team, it was easy to port your driver on this cam! "));
	return (GP_OK);
}


#if 0
/*
 * This function probes the camera to get all the storages media
 * supported (sdram, flash, card)
 *
 * Status: Working but not used
 */
static int enigma13_detect_storage_type (Camera *camera)
{
	int i;
	char buf[3];

	for (i=0;i<3;i++)
	{
		CHECK (gp_port_usb_msg_read (camera->port, 0x28, 0x0000, i, &buf[i], 0x01));
	}
	gp_log(GP_LOG_DEBUG, "enigma13","Camera storage information sdram: 0x%x flash 0x%x card: 0x%x\n"
			, buf[0], buf[1], buf[2]);
	return GP_OK;
}
#endif





/*
 * This function deletes all the images
 * supported (sdram, flash, card)
 *
 * Status: not verified
 */
static int
enigma13_flash_delete_all(CameraFilesystem *fs, const char *folder, void *data,
                 GPContext *context)
{
	Camera *cam;
	gp_log(GP_LOG_DEBUG, "enigma13", "Delete all files");
	cam = data;
	CHECK (gp_port_usb_msg_write (cam->port, 0x52, 0x0, 0x0, NULL, 0x0));
	return GP_OK;
}




/*
 * This function waits camera is ready
 *
 * Status: Not sure what it really does, but the camera replies
 * Can this be some kind of ping for keepalive, or really a ready status ?
 */
static int enigma13_wait_for_ready(Camera *camera)
{
	int timeout = ENIGMA13_WAIT_FOR_READY_TIMEOUT_S;
	char ready = 1;
	while (timeout--) {
		sleep(1);
		CHECK (gp_port_usb_msg_read (camera->port, 0x21, 0x0000, 0x0000, (char*)&ready, 0x01));
		if (ready==0)
			return GP_OK;
	}
	return GP_ERROR;
}



#if 0
/*
 * Get the number of images in cam
 *
 * Status: Should work, but not used as enigma13_get_TOC does all.
 *
 */
static int enigma13_get_filecount (Camera *camera, int *filecount)
{
	uint16_t response = 0;
	CHECK(enigma13_wait_for_ready(camera));
	CHECK (gp_port_usb_msg_read (camera->port, 0x54, 0x0000, 0x0000, (char*)&response, 0x02));
	le16toh (response);
	*filecount = response;
	return GP_OK;
}
#endif




/*
 * Get the table of contents (directory) from the cam
 *
 * Verified: Yes
 */
static int enigma13_get_toc(Camera *camera, int *filecount, char** toc)
{
	char* flash_toc=NULL;
	int toc_size = 0;
	char buf[10];
	uint16_t response = 0;
	int ret=1;

	CHECK(enigma13_wait_for_ready(camera));
	CHECK (gp_port_usb_msg_read (camera->port, 0x54, 0x0000, 0x0000, (char*)&response, 0x02));
	le16toh (response);
	*filecount = response;

	/* Calc toc size */
	toc_size = (response) * 0x20;

	if (toc_size % 0x200 != 0)
		toc_size = ((toc_size / 0x200) + 1) * 0x200;

	CHECK(enigma13_wait_for_ready(camera));

	CHECK (gp_port_usb_msg_write (camera->port, 0x54, response, 0x0001, NULL, 0x0000));
	/* Wait until cam is ready to send the T.O.C */
	usleep(ENIGMA13_WAIT_TOC_DELAY_MS * 1000);

	CHECK (gp_port_usb_msg_read (camera->port, 0x21, 0x0000, 0x0000, buf, 0x01));
	if (buf[0]!=0x41) return GP_ERROR;

	CHECK (gp_port_usb_msg_read (camera->port, 0x21, 0x0000, 0x0002, buf, 0x01));
	if (buf[0]!=0x01) return GP_ERROR;


	flash_toc = (char*)malloc(toc_size);
	if (!flash_toc)
		return GP_ERROR_NO_MEMORY;

	ret=gp_port_read (camera->port, flash_toc, toc_size);
	*toc= flash_toc;
				gp_log(GP_LOG_DEBUG, "enigma13","Byte transferred :%d ", ret);
	return ret;

}


/*
 * Function that downloads image
 *
 * Camera camera  : camera structure
 * int    index   : image index ( in gphoto point of view) -1 for real index
 * char*  toc     : pointer to toc
 * char** img_buf : returned image data
 * int* img_size      : returned image data size
 *
 */
static int enigma13_download_img(Camera *camera, char *toc, int index, char **img_data, int *img_size)
{
	uint8_t *p;
	uint32_t file_size = 0, aligned_size = 0;
	char* buf=NULL;
	int align=0;
	char  retbuf[2];

	gp_log(GP_LOG_DEBUG, "enigma13","DOWNLOADING IMAGE NO %d",index);

	/* Offset for image informations .
	Each image has 16 bytes for name and 16 for size, and others*/
	p = (uint8_t *)toc + (index*2)*32;

	/* real size from toc*/
	aligned_size = file_size =
		(p[0x1c] & 0xff)
		+ (p[0x1d] & 0xff) * 0x100
		+ (p[0x1e] & 0xff) * 0x10000;

	CHECK (gp_port_usb_msg_read (camera->port, 0x23,
		0x0000, 0x64,
		retbuf, 0x01));
	if (retbuf[0]==0x20){
		align=ENIGMA13_BLK_CARD_ALIGN;
		gp_log(GP_LOG_DEBUG, "enigma13"," Image from card, alignment is set to %d bytes ",align);
	} else if (retbuf[0]==0x10){
		align=ENIGMA13_BLK_FLASH_ALIGN;
		gp_log(GP_LOG_DEBUG, "enigma13"," Image from flash, alignment is set to %d bytes",align);
	} else {
		return GP_ERROR;
	}

	if (file_size % align != 0)
		aligned_size = ((file_size / align) + 1) * align;

	buf = (char*) malloc (aligned_size);
	if (!buf)
		return GP_ERROR_NO_MEMORY;

	CHECK_AND_FREE (gp_port_usb_msg_write (camera->port,
	0x54, index+1, 2, NULL, 0x00), buf);
	usleep(ENIGMA13_WAIT_IMAGE_READY_MS * 1000);

	CHECK_AND_FREE (gp_port_usb_msg_read (camera->port, 0x21,
		0x0000, 0x0000,
		buf, 0x01), buf);
	if (buf[0]!=0x41) {
		free (buf);
		return GP_ERROR;
	}

	CHECK_AND_FREE (gp_port_usb_msg_read (camera->port, 0x21,
		0x0000, 0x0002,
		buf, 0x01), buf);
	if (buf[0]!=0x01) {
		free (buf);
		return GP_ERROR;
	}

	CHECK_AND_FREE (gp_port_usb_msg_read (camera->port, 0x21,
		0x0000, 0x0002,
		buf, 0x01), buf);
	if (buf[0]!=0x01) {
		free (buf);
		return GP_ERROR;
	}
	gp_log(GP_LOG_DEBUG, "enigma13","READY FOR TRANSFER");
	CHECK_AND_FREE (gp_port_read (camera->port, buf, aligned_size), buf);
	*img_data=buf;
	*img_size=file_size;
	return GP_OK;
}


/*
 * GPHOTO list files
 */

static int file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
		void *data, GPContext *context)
{
	Camera *camera = data;
	int	i, numpics;
	char tmp[20];

	CHECK(enigma13_get_toc(camera,&numpics,&enigma13_static_toc));

	for ( i=0; i < numpics; i=i+2 ) {
		sprintf(tmp,"sunp%04d.jpg",(i/2));
		gp_list_append( list, tmp, NULL);
	}
	return (GP_OK);
}



/*
 * File operations
 */
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;

	char* img_data=NULL;
	int   img_size=-1;

	image_no = gp_filesystem_number(fs, folder, filename, context);
	gp_log(GP_LOG_DEBUG, "enigma13","Index of image %d is %s",image_no, filename);
	switch (type) {
	case GP_FILE_TYPE_NORMAL:
	{
		gp_log(GP_LOG_DEBUG, "enigma13","Downloading raw image");

		CHECK(enigma13_download_img(camera, enigma13_static_toc, image_no, &img_data, &img_size));
		result = gp_file_append( file, img_data, img_size);

		break;
	}
	default:
		result = GP_ERROR_NOT_SUPPORTED;
		break;
	}
	if (result < 0)
		return result;
	return (GP_OK);
}

int
camera_abilities (CameraAbilitiesList *list)
{
	CameraAbilities a;

	memset(&a, 0, sizeof(a));
	strcpy(a.model,	"DigitalDream:Enigma1.3");
	a.status = GP_DRIVER_STATUS_EXPERIMENTAL;
	a.port     		= GP_PORT_USB;
	a.speed[0] 		= 0;
	a.usb_vendor		= 0x05da;
	a.usb_product		= 0x1018;
	a.operations		=  GP_OPERATION_NONE;
	a.file_operations	=  GP_FILE_OPERATION_NONE;
	a.folder_operations	=  GP_FOLDER_OPERATION_DELETE_ALL;
	gp_abilities_list_append(list, a);
	return (GP_OK);
}

int
camera_id (CameraText *id)
{
	strcpy(id->text, "Digital Dream Enigma 1.3");
	return (GP_OK);
}

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

int
camera_init (Camera *camera, GPContext *context)
{
	GPPortSettings settings;

	camera->functions->about        = enigma13_about;

	CHECK(gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera));
	CHECK(gp_port_get_settings( camera->port, &settings));
	settings.usb.inep = 0x82;
	settings.usb.outep = 0x03;
	settings.usb.config = 1;
	settings.usb.interface = 0;
	settings.usb.altsetting = 0;
	CHECK(gp_port_set_timeout (camera->port, ENIGMA13_USB_TIMEOUT_MS));
	CHECK(gp_port_set_settings( camera->port, settings));
	return GP_OK;


}
