/* library.c
 *
 * Copyright (C) 2003 Theodore Kilgore <kilgota@auburn.edu>
 *
 * 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
 */

#define _DEFAULT_SOURCE

#include <config.h>

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

#include <libgphoto2/bayer.h>
#include <libgphoto2/gamma.h>

#include <gphoto2/gphoto2.h>

#include "libgphoto2/i18n.h"

#include "aox.h"
#include <gphoto2/gphoto2-port.h>

#define GP_MODULE "aox"

struct _CameraPrivateLibrary {
	Model model;
	Info info[2];
};

static struct {
	char *name;
	CameraDriverStatus status;
	unsigned short idVendor;
	unsigned short idProduct;
} models[] = {
	{"Concord EyeQMini_1", GP_DRIVER_STATUS_EXPERIMENTAL, 0x03e8, 0x2182},
	{"Concord EyeQMini_2", GP_DRIVER_STATUS_EXPERIMENTAL, 0x03e8, 0x2180},
	{"D-MAX DM3588", GP_DRIVER_STATUS_EXPERIMENTAL, 0x03e8, 0x2130},
	{NULL,0,0,0}
};

int
camera_id (CameraText *id)
{
	strcpy (id->text, "Aox chipset camera");
	return GP_OK;
}

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

	for (i = 0; models[i].name; i++) {
		memset (&a, 0, sizeof(a));
		strcpy (a.model, models[i].name);
		a.status = models[i].status;
		a.port = GP_PORT_USB;
		a.speed[0] = 0;
		a.usb_vendor = models[i].idVendor;
		a.usb_product = models[i].idProduct;
		if (a.status == GP_DRIVER_STATUS_EXPERIMENTAL)
			a.operations = GP_OPERATION_NONE;
		else
			a.operations = GP_OPERATION_CAPTURE_IMAGE;
		a.folder_operations = GP_FOLDER_OPERATION_NONE;
		a.file_operations = GP_FILE_OPERATION_PREVIEW;
		gp_abilities_list_append (list, a);
	}
	return GP_OK;
}

static int
camera_summary (Camera *camera, CameraText *summary, GPContext *context)
{

	int num_lo_pics = aox_get_num_lo_pics(camera->pl->info);
	int num_hi_pics = aox_get_num_hi_pics(camera->pl->info);

	sprintf (summary->text, _("Your USB camera has an Aox chipset.\n"
			"Number of lo-res PICs = %i\n"
			"Number of hi-res PICs = %i\n"
			"Number of PICs = %i\n"
			), num_lo_pics, num_hi_pics, num_lo_pics + num_hi_pics);

	return GP_OK;
}


static int
camera_about (Camera *camera, CameraText *about, GPContext *context)
{
	strcpy (about->text, _("Aox generic driver\n"
			"Theodore Kilgore <kilgota@auburn.edu>\n"));
	return GP_OK;
}

/*************** File and Downloading Functions *******************/

static int
file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
		void *data, GPContext *context)
{
	Camera *camera = data;
	int num_lo_pics = aox_get_num_lo_pics (camera->pl->info);
	int num_hi_pics = aox_get_num_hi_pics (camera->pl->info);
	int n = num_hi_pics + num_lo_pics;
	char name[30];
	int i;
	/* Low-resolution pictures are always downloaded first. We do not know
	 * yet how to process them, so they will remain in RAW format. */

	for (i=0; i< num_lo_pics; i++){
		snprintf( name, sizeof(name), "aox_pic%03i.raw", i+1 );
		gp_list_append(list, name, NULL);
	}

	for (i = num_lo_pics; i < n; i++){
		snprintf( name, sizeof(name), "aox_pic%03i.ppm", i+1 );
		gp_list_append(list, name, NULL);
	}
	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;

/* The camera will always download the low-resolution pictures first, if any.
 * As those are compressed, they are not of fixed size. Unfortunately, the
 * compression method is not known.
 * For a high-resolution picture, the size is always the same.
 * Every picture file has a header, which is of length 0x98. The high-
 * resolution pictures are just Bayer data; their headers will be discarded.
 */

	int i, j, k, n, num_lo_pics, num_hi_pics, w = 0, h = 0;
	unsigned char temp;
	unsigned char *data;
	unsigned char *p_data = NULL;
	unsigned char *output = NULL;
	int len;
	int header_len;
	char header[128];
	unsigned char gtable[256];

	k = gp_filesystem_number(camera->fs, "/", filename, context);


	num_lo_pics = aox_get_num_lo_pics(camera->pl->info);
	num_hi_pics = aox_get_num_hi_pics(camera->pl->info);


	GP_DEBUG("There are %i compressed photos\n", num_lo_pics);
	GP_DEBUG("There are %i hi-res photos\n", num_hi_pics);

	if ( (k < num_lo_pics) ) {
		n = k;
		w = 320;
		h = 240;
	} else {
		n = k - num_lo_pics;
		w = 640;
		h = 480;
	}

	len = aox_get_picture_size (camera->port, num_lo_pics,
						num_hi_pics, n, k);
	if (len < GP_OK) return len;

	GP_DEBUG("len = %i\n", len);
	data = malloc(len);
	if (!data) {
		printf("Malloc failed\n"); return 0;}
	aox_read_picture_data (camera->port, (char *)data, len, n);

	switch (type) {
	case GP_FILE_TYPE_EXIF:
		free (data);
		return (GP_ERROR_FILE_EXISTS);

	case GP_FILE_TYPE_PREVIEW:
	case GP_FILE_TYPE_NORMAL:
		if (w == 320) {
			gp_file_detect_mime_type (file); /* Detected as "raw"*/
			gp_file_set_data_and_size (file, (char *)data, len);
			gp_file_adjust_name_for_mime_type (file);
			break;
		}
		if (w == 640) {

			if (len < 0x98 + w*h) {
				GP_DEBUG("len %d is less than expected %d\n", len, 0x98+w*h);
				return GP_ERROR;
			}

			/* Stripping useless header */
			p_data = data + 0x98;
			/* Picture is mirror-imaged.*/
			for (i = 0; i < h; ++i) {
				for (j = 0; j < w / 2; j++) {
					temp = p_data[w * i + j];
					p_data[w * i + j] = p_data[w * i + w - 1 - j];
					p_data[w * i + w - 1 - j] = temp;
				}
			}
			/* Not only this, but some columns are
			 * interchanged, too. */
			for (i = 0; i < w*h/4; i++) {
				temp = p_data[4 * i + 1];
				p_data[4 * i + 1] = p_data[4 * i + 2];
				p_data[4 * i + 2] = temp;
			}
			/* And now create a ppm file, with our own header */
			header_len = snprintf(header, 127,
				"P6\n"
				"# CREATOR: gphoto2, aox library\n"
				"%d %d\n"
				"255\n", w, h);

			output = malloc(3*w*h);
			if(!output) {
				free(output);
				return GP_ERROR_NO_MEMORY;
			}
			if (camera->pl->model == AOX_MODEL_DMAX)
				gp_bayer_decode (p_data, w, h, output, BAYER_TILE_RGGB);
			else
				gp_bayer_decode (p_data, w, h, output, BAYER_TILE_GRBG);
			/* gamma correction of .70 may not be optimal. */
			gp_gamma_fill_table (gtable, .65);
			gp_gamma_correct_single (gtable, output, w * h);
			gp_file_set_mime_type (file, GP_MIME_PPM);
			gp_file_append (file, header, header_len);
			gp_file_append (file, (char *)output, 3*w*h);
		}
		free (data);
		free (output);
		return GP_OK;
	case GP_FILE_TYPE_RAW:
		gp_file_set_data_and_size (file, (char *)data, len);
		gp_file_set_mime_type (file, GP_MIME_RAW);
		gp_file_adjust_name_for_mime_type(file);
		break;
	default:
		free (data);
		return (GP_ERROR_NOT_SUPPORTED);
	}
	return GP_OK;
}

/*************** Exit and Initialization Functions ******************/

static int
camera_exit (Camera *camera, GPContext *context)
{
	GP_DEBUG ("Aox camera_exit");

	free (camera->pl);
	camera->pl = NULL;

	return GP_OK;
}

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

int
camera_init(Camera *camera, GPContext *context)
{
	GPPortSettings settings;
	CameraAbilities abilities;
	int ret = 0;

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

	GP_DEBUG ("Initializing the camera\n");
	ret = gp_port_get_settings(camera->port,&settings);
	if (ret < 0) return ret;

	ret = gp_camera_get_abilities(camera, &abilities);
	if (ret < 0) return ret;
	GP_DEBUG("product number is 0x%x\n", abilities.usb_product);

	switch (camera->port->type) {
		case GP_PORT_SERIAL:
			return ( GP_ERROR );
		case GP_PORT_USB:
			settings.usb.config = 1;
			settings.usb.altsetting = 0;
			settings.usb.interface = 1;
			settings.usb.inep = 0x84;
			settings.usb.outep =0x05;
			break;
		default:
			return ( GP_ERROR );
	}

	ret = gp_port_set_settings(camera->port,settings);
	if (ret < 0) return ret;

	GP_DEBUG("interface = %i\n", settings.usb.interface);
	GP_DEBUG("inep = %x\n", settings.usb.inep);
	GP_DEBUG("outep = %x\n", settings.usb.outep);

	/* Tell the CameraFilesystem where to get lists from */
	gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera);

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

	switch(abilities.usb_product) {
	case 0x2130:
		camera->pl->model = AOX_MODEL_DMAX;
		break;
	default:
		camera->pl->model = AOX_MODEL_MINI;
	}
	/* Connect to the camera */
	aox_init (camera->port, &camera->pl->model, camera->pl->info);

	return GP_OK;
}
