/*
 * libdivecomputer
 *
 * Copyright (C) 2008 Jef Driesen
 *
 * 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.1 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 <assert.h>
#include <stdlib.h>
#include <string.h>

#include "suunto_d9.h"
#include "suunto_eon.h"
#include "suunto_eonsteel.h"
#include "suunto_solution.h"
#include "suunto_vyper2.h"
#include "suunto_vyper.h"
#include "reefnet_sensus.h"
#include "reefnet_sensuspro.h"
#include "reefnet_sensusultra.h"
#include "uwatec_aladin.h"
#include "uwatec_memomouse.h"
#include "uwatec_smart.h"
#include "oceanic_atom2.h"
#include "oceanic_veo250.h"
#include "oceanic_vtpro.h"
#include "mares_darwin.h"
#include "mares_iconhd.h"
#include "mares_nemo.h"
#include "mares_puck.h"
#include "hw_frog.h"
#include "hw_ostc.h"
#include "hw_ostc3.h"
#include "cressi_edy.h"
#include "cressi_leonardo.h"
#include "cressi_goa.h"
#include "zeagle_n2ition3.h"
#include "atomics_cobalt.h"
#include "shearwater_petrel.h"
#include "shearwater_predator.h"
#include "diverite_nitekq.h"
#include "citizen_aqualand.h"
#include "divesystem_idive.h"
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "deepblu.h"

#include "device-private.h"
#include "context-private.h"

dc_device_t *
dc_device_allocate (dc_context_t *context, const dc_device_vtable_t *vtable)
{
	dc_device_t *device = NULL;

	assert(vtable != NULL);
	assert(vtable->size >= sizeof(dc_device_t));

	// Allocate memory.
	device = (dc_device_t *) malloc (vtable->size);
	if (device == NULL) {
		ERROR (context, "Failed to allocate memory.");
		return device;
	}

	device->vtable = vtable;

	device->context = context;

	device->event_mask = 0;
	device->event_callback = NULL;
	device->event_userdata = NULL;

	device->cancel_callback = NULL;
	device->cancel_userdata = NULL;

	memset (&device->devinfo, 0, sizeof (device->devinfo));
	memset (&device->clock, 0, sizeof (device->clock));

	return device;
}

void
dc_device_deallocate (dc_device_t *device)
{
	free (device);
}

dc_status_t
dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, dc_iostream_t *iostream)
{
	dc_status_t rc = DC_STATUS_SUCCESS;
	dc_device_t *device = NULL;

	if (out == NULL || descriptor == NULL)
		return DC_STATUS_INVALIDARGS;

	switch (dc_descriptor_get_type (descriptor)) {
	case DC_FAMILY_SUUNTO_SOLUTION:
		rc = suunto_solution_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_SUUNTO_EON:
		rc = suunto_eon_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_SUUNTO_VYPER:
		rc = suunto_vyper_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_SUUNTO_VYPER2:
		rc = suunto_vyper2_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_SUUNTO_D9:
		rc = suunto_d9_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_SUUNTO_EONSTEEL:
		rc = suunto_eonsteel_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_UWATEC_ALADIN:
		rc = uwatec_aladin_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_UWATEC_MEMOMOUSE:
		rc = uwatec_memomouse_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_UWATEC_SMART:
		rc = uwatec_smart_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_REEFNET_SENSUS:
		rc = reefnet_sensus_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_REEFNET_SENSUSPRO:
		rc = reefnet_sensuspro_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_REEFNET_SENSUSULTRA:
		rc = reefnet_sensusultra_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_OCEANIC_VTPRO:
		rc = oceanic_vtpro_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_OCEANIC_VEO250:
		rc = oceanic_veo250_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_OCEANIC_ATOM2:
		rc = oceanic_atom2_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_MARES_NEMO:
		rc = mares_nemo_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_MARES_PUCK:
		rc = mares_puck_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_MARES_DARWIN:
		rc = mares_darwin_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_MARES_ICONHD:
		rc = mares_iconhd_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_HW_OSTC:
		rc = hw_ostc_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_HW_FROG:
		rc = hw_frog_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_HW_OSTC3:
		rc = hw_ostc3_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_CRESSI_EDY:
		rc = cressi_edy_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_CRESSI_LEONARDO:
		rc = cressi_leonardo_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_CRESSI_GOA:
		rc = cressi_goa_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_ZEAGLE_N2ITION3:
		rc = zeagle_n2ition3_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_ATOMICS_COBALT:
		rc = atomics_cobalt_device_open (&device, context);
		break;
	case DC_FAMILY_SHEARWATER_PREDATOR:
		rc = shearwater_predator_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_SHEARWATER_PETREL:
		rc = shearwater_petrel_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_DIVERITE_NITEKQ:
		rc = diverite_nitekq_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_CITIZEN_AQUALAND:
		rc = citizen_aqualand_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_DIVESYSTEM_IDIVE:
		rc = divesystem_idive_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
		break;
	case DC_FAMILY_COCHRAN_COMMANDER:
		rc = cochran_commander_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
		rc = tecdiving_divecomputereu_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_GARMIN:
		rc = garmin_device_open (&device, context, iostream);
		break;
	case DC_FAMILY_DEEPBLU:
		rc = deepblu_device_open (&device, context, iostream);
		break;
	default:
		return DC_STATUS_INVALIDARGS;
	}

	*out = device;

	return rc;
}


int
dc_device_isinstance (dc_device_t *device, const dc_device_vtable_t *vtable)
{
	if (device == NULL)
		return 0;

	return device->vtable == vtable;
}


dc_family_t
dc_device_get_type (dc_device_t *device)
{
	if (device == NULL)
		return DC_FAMILY_NULL;

	return device->vtable->type;
}


dc_status_t
dc_device_set_cancel (dc_device_t *device, dc_cancel_callback_t callback, void *userdata)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	device->cancel_callback = callback;
	device->cancel_userdata = userdata;

	return DC_STATUS_SUCCESS;
}


dc_status_t
dc_device_set_events (dc_device_t *device, unsigned int events, dc_event_callback_t callback, void *userdata)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	device->event_mask = events;
	device->event_callback = callback;
	device->event_userdata = userdata;

	return DC_STATUS_SUCCESS;
}


dc_status_t
dc_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsigned int size)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->set_fingerprint == NULL)
		return DC_STATUS_UNSUPPORTED;

	return device->vtable->set_fingerprint (device, data, size);
}


dc_status_t
dc_device_read (dc_device_t *device, unsigned int address, unsigned char data[], unsigned int size)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->read == NULL)
		return DC_STATUS_UNSUPPORTED;

	return device->vtable->read (device, address, data, size);
}


dc_status_t
dc_device_write (dc_device_t *device, unsigned int address, const unsigned char data[], unsigned int size)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->write == NULL)
		return DC_STATUS_UNSUPPORTED;

	return device->vtable->write (device, address, data, size);
}


dc_status_t
dc_device_dump (dc_device_t *device, dc_buffer_t *buffer)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->dump == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (buffer == NULL)
		return DC_STATUS_INVALIDARGS;

	dc_buffer_clear (buffer);

	return device->vtable->dump (device, buffer);
}


dc_status_t
device_dump_read (dc_device_t *device, unsigned char data[], unsigned int size, unsigned int blocksize)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->read == NULL)
		return DC_STATUS_UNSUPPORTED;

	// Enable progress notifications.
	dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
	progress.maximum = size;
	device_event_emit (device, DC_EVENT_PROGRESS, &progress);

	unsigned int nbytes = 0;
	while (nbytes < size) {
		// Calculate the packet size.
		unsigned int len = size - nbytes;
		if (len > blocksize)
			len = blocksize;

		// Read the packet.
		dc_status_t rc = device->vtable->read (device, nbytes, data + nbytes, len);
		if (rc != DC_STATUS_SUCCESS)
			return rc;

		// Update and emit a progress event.
		progress.current += len;
		device_event_emit (device, DC_EVENT_PROGRESS, &progress);

		nbytes += len;
	}

	return DC_STATUS_SUCCESS;
}


dc_status_t
dc_device_foreach (dc_device_t *device, dc_dive_callback_t callback, void *userdata)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->foreach == NULL)
		return DC_STATUS_UNSUPPORTED;

	return device->vtable->foreach (device, callback, userdata);
}


dc_status_t
dc_device_timesync (dc_device_t *device, const dc_datetime_t *datetime)
{
	if (device == NULL)
		return DC_STATUS_UNSUPPORTED;

	if (device->vtable->timesync == NULL)
		return DC_STATUS_UNSUPPORTED;

	return device->vtable->timesync (device, datetime);
}


dc_status_t
dc_device_close (dc_device_t *device)
{
	dc_status_t status = DC_STATUS_SUCCESS;

	if (device == NULL)
		return DC_STATUS_SUCCESS;

	// Disable the cancellation callback.
	device->cancel_callback = NULL;
	device->cancel_userdata = NULL;

	if (device->vtable->close) {
		status = device->vtable->close (device);
	}

	dc_device_deallocate (device);

	return status;
}


void
device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data)
{
	const dc_event_progress_t *progress = (const dc_event_progress_t *) data;

	// Check the event data for errors.
	switch (event) {
	case DC_EVENT_WAITING:
		assert (data == NULL);
		break;
	case DC_EVENT_PROGRESS:
		assert (progress != NULL);
		assert (progress->maximum != 0);
		assert (progress->maximum >= progress->current);
		break;
	case DC_EVENT_DEVINFO:
		assert (data != NULL);
		break;
	case DC_EVENT_CLOCK:
		assert (data != NULL);
		break;
	default:
		break;
	}

	if (device == NULL)
		return;

	// Cache the event data.
	switch (event) {
	case DC_EVENT_DEVINFO:
		device->devinfo = *(const dc_event_devinfo_t *) data;
		break;
	case DC_EVENT_CLOCK:
		device->clock = *(const dc_event_clock_t *) data;
		break;
	default:
		break;
	}

	// Check if there is a callback function registered.
	if (device->event_callback == NULL)
		return;

	// Check the event mask.
	if ((event & device->event_mask) == 0)
		return;

	device->event_callback (device, event, data, device->event_userdata);
}


int
device_is_cancelled (dc_device_t *device)
{
	if (device == NULL)
		return 0;

	if (device->cancel_callback == NULL)
		return 0;

	return device->cancel_callback (device->cancel_userdata);
}
