/*
 * 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 <stdlib.h>
#include <string.h>	// memcmp

#include <libdivecomputer/units.h>

#include "uwatec_smart.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"

#define ISINSTANCE(parser) dc_parser_isinstance((parser), &uwatec_smart_parser_vtable)

#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))

#define NBITS 8

#define SMARTPRO          0x10
#define GALILEO           0x11
#define ALADINTEC         0x12
#define ALADINTEC2G       0x13
#define SMARTCOM          0x14
#define ALADIN2G          0x15
#define ALADINSPORTMATRIX 0x17
#define SMARTTEC          0x18
#define GALILEOTRIMIX     0x19
#define SMARTZ            0x1C
#define MERIDIAN          0x20
#define ALADINSQUARE      0x22
#define CHROMIS           0x24
#define MANTIS2           0x26
#define G2                0x32
#define G2HUD             0x42

#define UNSUPPORTED 0xFFFFFFFF

#define NEVENTS   3
#define NGASMIXES 10

#define HEADER  1
#define PROFILE 2

#define FRESH 1.000
#define SALT  1.025

#define FREEDIVE  0x00000080
#define GAUGE     0x00001000
#define SALINITY  0x00100000

#define EPOCH 946684800 // 2000-01-01 00:00:00 UTC

typedef enum {
	PRESSURE_DEPTH,
	RBT,
	TEMPERATURE,
	PRESSURE,
	DEPTH,
	HEARTRATE,
	BEARING,
	ALARMS,
	TIME,
	APNEA,
	MISC,
} uwatec_smart_sample_t;

typedef enum {
	EV_WARNING,          /* Warning (yellow buzzer) */
	EV_ALARM,            /* Alarm (red buzzer) */
	EV_WORKLOAD,         /* Workload */
	EV_WORKLOAD_WARNING, /* Increased workload (lung symbol) */
	EV_BOOKMARK,         /* Bookmark / safety stop timer started */
	EV_GASMIX,           /* Active gasmix */
	EV_UNKNOWN,
} uwatec_smart_event_t;

typedef struct uwatec_smart_header_info_t {
	unsigned int maxdepth;
	unsigned int divetime;
	unsigned int gasmix;
	unsigned int ngases;
	unsigned int temp_minimum;
	unsigned int temp_maximum;
	unsigned int temp_surface;
	unsigned int tankpressure;
	unsigned int timezone;
	unsigned int settings;
} uwatec_smart_header_info_t;

typedef struct uwatec_smart_sample_info_t {
	uwatec_smart_sample_t type;
	unsigned int absolute;
	unsigned int index;
	unsigned int ntypebits;
	unsigned int ignoretype;
	unsigned int extrabytes;
} uwatec_smart_sample_info_t;

typedef struct uwatec_smart_event_info_t {
	uwatec_smart_event_t type;
	unsigned int mask;
	unsigned int shift;
} uwatec_smart_event_info_t;

typedef struct uwatec_smart_gasmix_t {
	unsigned int id;
	unsigned int oxygen;
	unsigned int helium;
} uwatec_smart_gasmix_t;

typedef struct uwatec_smart_tank_t {
	unsigned int id;
	unsigned int beginpressure;
	unsigned int endpressure;
	unsigned int gasmix;
} uwatec_smart_tank_t;

typedef struct uwatec_smart_parser_t uwatec_smart_parser_t;

struct uwatec_smart_parser_t {
	dc_parser_t base;
	unsigned int model;
	unsigned int devtime;
	dc_ticks_t systime;
	const uwatec_smart_sample_info_t *samples;
	const uwatec_smart_header_info_t *header;
	unsigned int headersize;
	unsigned int nsamples;
	const uwatec_smart_event_info_t *events[NEVENTS];
	unsigned int nevents[NEVENTS];
	unsigned int trimix;
	// Cached fields.
	unsigned int cached;
	unsigned int ngasmixes;
	uwatec_smart_gasmix_t gasmix[NGASMIXES];
	unsigned int ntanks;
	uwatec_smart_tank_t tank[NGASMIXES];
	dc_water_t watertype;
	dc_divemode_t divemode;
};

static dc_status_t uwatec_smart_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t uwatec_smart_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t uwatec_smart_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t uwatec_smart_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);

static dc_status_t uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback, void *userdata);

static const dc_parser_vtable_t uwatec_smart_parser_vtable = {
	sizeof(uwatec_smart_parser_t),
	DC_FAMILY_UWATEC_SMART,
	uwatec_smart_parser_set_data, /* set_data */
	uwatec_smart_parser_get_datetime, /* datetime */
	uwatec_smart_parser_get_field, /* fields */
	uwatec_smart_parser_samples_foreach, /* samples_foreach */
	NULL /* destroy */
};

static const
uwatec_smart_header_info_t uwatec_smart_pro_header = {
	18,
	20,
	24, 1,
	22, /* temp_minimum */
	UNSUPPORTED, /* temp_maximum */
	UNSUPPORTED, /* temp_surface */
	UNSUPPORTED, /* tankpressure */
	UNSUPPORTED, /* timezone */
	UNSUPPORTED, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_galileo_header = {
	22,
	26,
	44, 3,
	30, /* temp_minimum */
	28, /* temp_maximum */
	32, /* temp_surface */
	50, /* tankpressure */
	16, /* timezone */
	92, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_trimix_header = {
	22, /* maxdepth */
	26, /* divetime */
	UNSUPPORTED, 0, /* gasmixes */
	30, /* temp_minimum */
	28, /* temp_maximum */
	32, /* temp_surface */
	UNSUPPORTED, /* tankpressure */
	16, /* timezone */
	68, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_aladin_tec_header = {
	22,
	24,
	30, 1,
	26, /* temp_minimum */
	28, /* temp_maximum */
	32, /* temp_surface */
	UNSUPPORTED, /* tankpressure */
	16, /* timezone */
	52, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_aladin_tec2g_header = {
	22,
	26,
	34, 3,
	30, /* temp_minimum */
	28, /* temp_maximum */
	32, /* temp_surface */
	UNSUPPORTED, /* tankpressure */
	16, /* timezone */
	60, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_com_header = {
	18,
	20,
	24, 1,
	22, /* temp_minimum */
	UNSUPPORTED, /* temp_maximum */
	UNSUPPORTED, /* temp_surface */
	30, /* tankpressure */
	UNSUPPORTED, /* timezone */
	UNSUPPORTED, /* settings */
};

static const
uwatec_smart_header_info_t uwatec_smart_tec_header = {
	18,
	20,
	28, 3,
	22, /* temp_minimum */
	UNSUPPORTED, /* temp_maximum */
	UNSUPPORTED, /* temp_surface */
	34, /* tankpressure */
	UNSUPPORTED, /* timezone */
	UNSUPPORTED, /* settings */
};

static const
uwatec_smart_sample_info_t uwatec_smart_pro_samples[] = {
	{DEPTH,          0, 0, 1, 0, 0}, // 0ddddddd
	{TEMPERATURE,    0, 0, 2, 0, 0}, // 10dddddd
	{TIME,           1, 0, 3, 0, 0}, // 110ddddd
	{ALARMS,         1, 0, 4, 0, 0}, // 1110dddd
	{DEPTH,          0, 0, 5, 0, 1}, // 11110ddd dddddddd
	{TEMPERATURE,    0, 0, 6, 0, 1}, // 111110dd dddddddd
	{DEPTH,          1, 0, 7, 1, 2}, // 1111110d dddddddd dddddddd
	{TEMPERATURE,    1, 0, 8, 0, 2}, // 11111110 dddddddd dddddddd
};

static const
uwatec_smart_sample_info_t uwatec_smart_galileo_samples[] = {
	{DEPTH,          0, 0, 1, 0, 0}, // 0ddd dddd
	{RBT,            0, 0, 3, 0, 0}, // 100d dddd
	{PRESSURE,       0, 0, 4, 0, 0}, // 1010 dddd
	{TEMPERATURE,    0, 0, 4, 0, 0}, // 1011 dddd
	{TIME,           1, 0, 4, 0, 0}, // 1100 dddd
	{HEARTRATE,      0, 0, 4, 0, 0}, // 1101 dddd
	{ALARMS,         1, 0, 4, 0, 0}, // 1110 dddd
	{ALARMS,         1, 1, 8, 0, 1}, // 1111 0000 dddddddd
	{DEPTH,          1, 0, 8, 0, 2}, // 1111 0001 dddddddd dddddddd
	{RBT,            1, 0, 8, 0, 1}, // 1111 0010 dddddddd
	{TEMPERATURE,    1, 0, 8, 0, 2}, // 1111 0011 dddddddd dddddddd
	{PRESSURE,       1, 0, 8, 0, 2}, // 1111 0100 dddddddd dddddddd
	{PRESSURE,       1, 1, 8, 0, 2}, // 1111 0101 dddddddd dddddddd
	{PRESSURE,       1, 2, 8, 0, 2}, // 1111 0110 dddddddd dddddddd
	{HEARTRATE,      1, 0, 8, 0, 1}, // 1111 0111 dddddddd
	{BEARING,        1, 0, 8, 0, 2}, // 1111 1000 dddddddd dddddddd
	{ALARMS,         1, 2, 8, 0, 1}, // 1111 1001 dddddddd
	{APNEA,          1, 0, 8, 0, 0}, // 1111 1010 (8 bytes)
	{MISC,           1, 0, 8, 0, 1}, // 1111 1011 dddddddd (n-1 bytes)
};


static const
uwatec_smart_sample_info_t uwatec_smart_aladin_samples[] = {
	{DEPTH,          0, 0, 1, 0, 0}, // 0ddddddd
	{TEMPERATURE,    0, 0, 2, 0, 0}, // 10dddddd
	{TIME,           1, 0, 3, 0, 0}, // 110ddddd
	{ALARMS,         1, 0, 4, 0, 0}, // 1110dddd
	{DEPTH,          0, 0, 5, 0, 1}, // 11110ddd dddddddd
	{TEMPERATURE,    0, 0, 6, 0, 1}, // 111110dd dddddddd
	{DEPTH,          1, 0, 7, 1, 2}, // 1111110d dddddddd dddddddd
	{TEMPERATURE,    1, 0, 8, 0, 2}, // 11111110 dddddddd dddddddd
	{ALARMS,         1, 1, 9, 0, 0}, // 11111111 0ddddddd
};

static const
uwatec_smart_sample_info_t uwatec_smart_com_samples[] = {
	{PRESSURE_DEPTH, 0, 0,  1, 0, 1}, // 0ddddddd dddddddd
	{RBT,            0, 0,  2, 0, 0}, // 10dddddd
	{TEMPERATURE,    0, 0,  3, 0, 0}, // 110ddddd
	{PRESSURE,       0, 0,  4, 0, 1}, // 1110dddd dddddddd
	{DEPTH,          0, 0,  5, 0, 1}, // 11110ddd dddddddd
	{TEMPERATURE,    0, 0,  6, 0, 1}, // 111110dd dddddddd
	{ALARMS,         1, 0,  7, 1, 1}, // 1111110d dddddddd
	{TIME,           1, 0,  8, 0, 1}, // 11111110 dddddddd
	{DEPTH,          1, 0,  9, 1, 2}, // 11111111 0ddddddd dddddddd dddddddd
	{PRESSURE,       1, 0, 10, 1, 2}, // 11111111 10dddddd dddddddd dddddddd
	{TEMPERATURE,    1, 0, 11, 1, 2}, // 11111111 110ddddd dddddddd dddddddd
	{RBT,            1, 0, 12, 1, 1}, // 11111111 1110dddd dddddddd
};

static const
uwatec_smart_sample_info_t uwatec_smart_tec_samples[] = {
	{PRESSURE_DEPTH, 0, 0,  1, 0, 1}, // 0ddddddd dddddddd
	{RBT,            0, 0,  2, 0, 0}, // 10dddddd
	{TEMPERATURE,    0, 0,  3, 0, 0}, // 110ddddd
	{PRESSURE,       0, 0,  4, 0, 1}, // 1110dddd dddddddd
	{DEPTH,          0, 0,  5, 0, 1}, // 11110ddd dddddddd
	{TEMPERATURE,    0, 0,  6, 0, 1}, // 111110dd dddddddd
	{ALARMS,         1, 0,  7, 1, 1}, // 1111110d dddddddd
	{TIME,           1, 0,  8, 0, 1}, // 11111110 dddddddd
	{DEPTH,          1, 0,  9, 1, 2}, // 11111111 0ddddddd dddddddd dddddddd
	{TEMPERATURE,    1, 0, 10, 1, 2}, // 11111111 10dddddd dddddddd dddddddd
	{PRESSURE,       1, 0, 11, 1, 2}, // 11111111 110ddddd dddddddd dddddddd
	{PRESSURE,       1, 1, 12, 1, 2}, // 11111111 1110dddd dddddddd dddddddd
	{PRESSURE,       1, 2, 13, 1, 2}, // 11111111 11110ddd dddddddd dddddddd
	{RBT,            1, 0, 14, 1, 1}, // 11111111 111110dd dddddddd
};

static const
uwatec_smart_event_info_t uwatec_smart_tec_events_0[] = {
	{EV_WARNING,          0x01, 0},
	{EV_ALARM,            0x02, 1},
	{EV_WORKLOAD_WARNING, 0x04, 2},
	{EV_WORKLOAD,         0x38, 3},
	{EV_UNKNOWN,          0xC0, 6},
};

static const
uwatec_smart_event_info_t uwatec_smart_aladintec_events_0[] = {
	{EV_WARNING,          0x01, 0},
	{EV_ALARM,            0x02, 1},
	{EV_BOOKMARK,         0x04, 2},
	{EV_UNKNOWN,          0x08, 3},
};

static const
uwatec_smart_event_info_t uwatec_smart_aladintec_events_1[] = {
	{EV_UNKNOWN,          0xFF, 0},
};

static const
uwatec_smart_event_info_t uwatec_smart_aladintec2g_events_0[] = {
	{EV_WARNING,          0x01, 0},
	{EV_ALARM,            0x02, 1},
	{EV_BOOKMARK,         0x04, 2},
	{EV_UNKNOWN,          0x08, 3},
};

static const
uwatec_smart_event_info_t uwatec_smart_aladintec2g_events_1[] = {
	{EV_UNKNOWN,          0x07, 0},
	{EV_GASMIX,           0x18, 3},
};

static const
uwatec_smart_event_info_t uwatec_smart_galileo_events_0[] = {
	{EV_WARNING,          0x01, 0},
	{EV_ALARM,            0x02, 1},
	{EV_WORKLOAD_WARNING, 0x04, 2},
	{EV_BOOKMARK,         0x08, 3},
};

static const
uwatec_smart_event_info_t uwatec_smart_galileo_events_1[] = {
	{EV_WORKLOAD,         0x07, 0},
	{EV_UNKNOWN,          0x18, 3},
	{EV_GASMIX,           0x60, 5},
	{EV_UNKNOWN,          0x80, 7},
};

static const
uwatec_smart_event_info_t uwatec_smart_galileo_events_2[] = {
	{EV_UNKNOWN,          0xFF, 0},
};

static const
uwatec_smart_event_info_t uwatec_smart_trimix_events_2[] = {
	{EV_UNKNOWN,          0x0F, 0},
	{EV_GASMIX,           0xF0, 4},
};

static unsigned int
uwatec_smart_find_gasmix (uwatec_smart_parser_t *parser, unsigned int id)
{
       unsigned int i = 0;
       while (i < parser->ngasmixes) {
               if (id == parser->gasmix[i].id)
                       break;
               i++;
       }

       return i;
}

static unsigned int
uwatec_smart_find_tank (uwatec_smart_parser_t *parser, unsigned int id)
{
       unsigned int i = 0;
       while (i < parser->ntanks) {
               if (id == parser->tank[i].id)
                       break;
               i++;
       }

       return i;
}

static dc_status_t
uwatec_smart_parser_cache (uwatec_smart_parser_t *parser)
{
	const unsigned char *data = parser->base.data;
	unsigned int size = parser->base.size;

	if (parser->cached) {
		return DC_STATUS_SUCCESS;
	}

	if (parser->model == GALILEO || parser->model == GALILEOTRIMIX) {
		if (size < 44)
			return DC_STATUS_DATAFORMAT;

		if (data[43] & 0x80) {
			parser->trimix = 1;
			parser->headersize = 84;
			parser->header = &uwatec_smart_trimix_header;
			parser->events[2] = uwatec_smart_trimix_events_2;
			parser->nevents[2] = C_ARRAY_SIZE (uwatec_smart_trimix_events_2);
		} else {
			parser->trimix = 0;
			parser->headersize = 152;
			parser->header = &uwatec_smart_galileo_header;
			parser->events[2] = uwatec_smart_galileo_events_2;
			parser->nevents[2] = C_ARRAY_SIZE (uwatec_smart_galileo_events_2);
		}
	}

	const uwatec_smart_header_info_t *header = parser->header;

	// Get the settings.
	dc_divemode_t divemode = DC_DIVEMODE_OC;
	dc_water_t watertype = DC_WATER_FRESH;
	if (header->settings != UNSUPPORTED) {
		unsigned int settings = array_uint32_le (data + header->settings);

		// Get the freedive/gauge bits.
		unsigned int freedive = 0;
		unsigned int gauge = (settings & GAUGE) != 0;
		if (parser->model != ALADINTEC && parser->model != ALADINTEC2G) {
			freedive = (settings & FREEDIVE) != 0;
		}

		// Get the dive mode. The freedive bit needs to be checked
		// first, because freedives have both the freedive and gauge
		// bits set.
		if (freedive) {
			divemode = DC_DIVEMODE_FREEDIVE;
		} else if (gauge) {
			divemode = DC_DIVEMODE_GAUGE;
		} else {
			divemode = DC_DIVEMODE_OC;
		}

		// Get the water type.
		if (settings & SALINITY) {
			watertype = DC_WATER_SALT;
		}
	}

	// Get the gas mixes and tanks.
	unsigned int ntanks = 0;
	unsigned int ngasmixes = 0;
	uwatec_smart_tank_t tank[NGASMIXES] = {{0}};
	uwatec_smart_gasmix_t gasmix[NGASMIXES] = {{0}};
	if (header->gasmix != UNSUPPORTED) {
		for (unsigned int i = 0; i < header->ngases; ++i) {
			unsigned int idx = DC_GASMIX_UNKNOWN;
			unsigned int o2 = 0;
			if (parser->model == ALADINTEC2G) {
				o2 = data[header->gasmix + i];
			} else {
				o2 = array_uint16_le (data + header->gasmix + i * 2);
			}

			if (o2 != 0) {
				idx = ngasmixes;
				gasmix[ngasmixes].id = i;
				gasmix[ngasmixes].oxygen = o2;
				gasmix[ngasmixes].helium = 0;
				ngasmixes++;
			}

			unsigned int beginpressure = 0;
			unsigned int endpressure = 0;
			if (header->tankpressure != UNSUPPORTED &&
				divemode != DC_DIVEMODE_FREEDIVE) {
				if (parser->model == GALILEO || parser->model == GALILEOTRIMIX ||
					parser->model == ALADIN2G || parser->model == MERIDIAN ||
					parser->model == CHROMIS || parser->model == MANTIS2 ||
					parser->model == G2 || parser->model == ALADINSPORTMATRIX ||
					parser->model == ALADINSQUARE || parser->model == G2HUD) {
					unsigned int offset = header->tankpressure + 2 * i;
					endpressure   = array_uint16_le(data + offset);
					beginpressure = array_uint16_le(data + offset + 2 * header->ngases);
				} else {
					unsigned int offset = header->tankpressure + 4 * i;
					beginpressure = array_uint16_le(data + offset);
					endpressure   = array_uint16_le(data + offset + 2);
				}
			}
			if ((beginpressure != 0 || endpressure != 0) &&
				(beginpressure != 0xFFFF) && (endpressure != 0xFFFF)) {
				tank[ntanks].id = i;
				tank[ntanks].beginpressure = beginpressure;
				tank[ntanks].endpressure = endpressure;
				tank[ntanks].gasmix = idx;
				ntanks++;
			}
		}
	}

	// Cache the data for later use.
	parser->ngasmixes = ngasmixes;
	for (unsigned int i = 0; i < ngasmixes; ++i) {
		parser->gasmix[i] = gasmix[i];
	}
	parser->ntanks = ntanks;
	for (unsigned int i = 0; i < ntanks; ++i) {
		parser->tank[i] = tank[i];
	}
	parser->watertype = watertype;
	parser->divemode = divemode;
	parser->cached = HEADER;

	return DC_STATUS_SUCCESS;
}


dc_status_t
uwatec_smart_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int devtime, dc_ticks_t systime)
{
	dc_status_t status = DC_STATUS_SUCCESS;
	uwatec_smart_parser_t *parser = NULL;

	if (out == NULL)
		return DC_STATUS_INVALIDARGS;

	// Allocate memory.
	parser = (uwatec_smart_parser_t *) dc_parser_allocate (context, &uwatec_smart_parser_vtable);
	if (parser == NULL) {
		ERROR (context, "Failed to allocate memory.");
		return DC_STATUS_NOMEMORY;
	}

	// Set the default values.
	parser->model = model;
	parser->devtime = devtime;
	parser->systime = systime;
	parser->trimix = 0;
	for (unsigned int i = 0; i < NEVENTS; ++i) {
		parser->events[i] = NULL;
		parser->nevents[i] = 0;
	}
	switch (model) {
	case SMARTPRO:
		parser->headersize = 92;
		parser->header = &uwatec_smart_pro_header;
		parser->samples = uwatec_smart_pro_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_pro_samples);
		parser->events[0] = uwatec_smart_tec_events_0;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_tec_events_0);
		break;
	case GALILEO:
	case GALILEOTRIMIX:
	case ALADIN2G:
	case MERIDIAN:
	case CHROMIS:
	case MANTIS2:
	case ALADINSQUARE:
		parser->headersize = 152;
		parser->header = &uwatec_smart_galileo_header;
		parser->samples = uwatec_smart_galileo_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_galileo_samples);
		parser->events[0] = uwatec_smart_galileo_events_0;
		parser->events[1] = uwatec_smart_galileo_events_1;
		parser->events[2] = uwatec_smart_galileo_events_2;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_galileo_events_0);
		parser->nevents[1] = C_ARRAY_SIZE (uwatec_smart_galileo_events_1);
		parser->nevents[2] = C_ARRAY_SIZE (uwatec_smart_galileo_events_2);
		break;
	case G2:
	case G2HUD:
	case ALADINSPORTMATRIX:
		parser->headersize = 84;
		parser->header = &uwatec_smart_trimix_header;
		parser->samples = uwatec_smart_galileo_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_galileo_samples);
		parser->events[0] = uwatec_smart_galileo_events_0;
		parser->events[1] = uwatec_smart_galileo_events_1;
		parser->events[2] = uwatec_smart_trimix_events_2;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_galileo_events_0);
		parser->nevents[1] = C_ARRAY_SIZE (uwatec_smart_galileo_events_1);
		parser->nevents[2] = C_ARRAY_SIZE (uwatec_smart_trimix_events_2);
		parser->trimix = 1;
		break;
	case ALADINTEC:
		parser->headersize = 108;
		parser->header = &uwatec_smart_aladin_tec_header;
		parser->samples = uwatec_smart_aladin_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_aladin_samples);
		parser->events[0] = uwatec_smart_aladintec_events_0;
		parser->events[1] = uwatec_smart_aladintec_events_1;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_aladintec_events_0);
		parser->nevents[1] = C_ARRAY_SIZE (uwatec_smart_aladintec_events_1);
		break;
	case ALADINTEC2G:
		parser->headersize = 116;
		parser->header = &uwatec_smart_aladin_tec2g_header;
		parser->samples = uwatec_smart_aladin_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_aladin_samples);
		parser->events[0] = uwatec_smart_aladintec2g_events_0;
		parser->events[1] = uwatec_smart_aladintec2g_events_1;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_aladintec2g_events_0);
		parser->nevents[1] = C_ARRAY_SIZE (uwatec_smart_aladintec2g_events_1);
		break;
	case SMARTCOM:
		parser->headersize = 100;
		parser->header = &uwatec_smart_com_header;
		parser->samples = uwatec_smart_com_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_com_samples);
		parser->events[0] = uwatec_smart_tec_events_0;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_tec_events_0);
		break;
	case SMARTTEC:
	case SMARTZ:
		parser->headersize = 132;
		parser->header = &uwatec_smart_tec_header;
		parser->samples = uwatec_smart_tec_samples;
		parser->nsamples = C_ARRAY_SIZE (uwatec_smart_tec_samples);
		parser->events[0] = uwatec_smart_tec_events_0;
		parser->nevents[0] = C_ARRAY_SIZE (uwatec_smart_tec_events_0);
		break;
	default:
		status = DC_STATUS_INVALIDARGS;
		goto error_free;
	}

	parser->cached = 0;
	parser->ngasmixes = 0;
	parser->ntanks = 0;
	for (unsigned int i = 0; i < NGASMIXES; ++i) {
		parser->gasmix[i].id = 0;
		parser->gasmix[i].oxygen = 0;
		parser->gasmix[i].helium = 0;
		parser->tank[i].id = 0;
		parser->tank[i].beginpressure = 0;
		parser->tank[i].endpressure = 0;
		parser->tank[i].gasmix = 0;
	}
	parser->watertype = DC_WATER_FRESH;
	parser->divemode = DC_DIVEMODE_OC;

	*out = (dc_parser_t*) parser;

	return DC_STATUS_SUCCESS;

error_free:
	dc_parser_deallocate ((dc_parser_t *) parser);
	return status;
}


static dc_status_t
uwatec_smart_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
	uwatec_smart_parser_t *parser = (uwatec_smart_parser_t *) abstract;

	// Reset the cache.
	parser->cached = 0;
	parser->ngasmixes = 0;
	parser->ntanks = 0;
	for (unsigned int i = 0; i < NGASMIXES; ++i) {
		parser->gasmix[i].id = 0;
		parser->gasmix[i].oxygen = 0;
		parser->gasmix[i].helium = 0;
		parser->tank[i].id = 0;
		parser->tank[i].beginpressure = 0;
		parser->tank[i].endpressure = 0;
		parser->tank[i].gasmix = 0;
	}
	parser->watertype = DC_WATER_FRESH;
	parser->divemode = DC_DIVEMODE_OC;

	return DC_STATUS_SUCCESS;
}


static dc_status_t
uwatec_smart_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
	uwatec_smart_parser_t *parser = (uwatec_smart_parser_t *) abstract;
	const uwatec_smart_header_info_t *table = parser->header;
	const unsigned char *data = abstract->data;

	if (abstract->size < parser->headersize)
		return DC_STATUS_DATAFORMAT;

	unsigned int timestamp = array_uint32_le (abstract->data + 8);

	dc_ticks_t ticks = EPOCH + timestamp / 2;

	if (table->timezone != UNSUPPORTED) {
		// For devices with timezone support, the UTC offset of the
		// device is used. The UTC offset is stored in units of 15
		// minutes (or 900 seconds).
		int utc_offset = (signed char) data[table->timezone];

		ticks += utc_offset * 900;

		if (!dc_datetime_gmtime (datetime, ticks))
			return DC_STATUS_DATAFORMAT;

		datetime->timezone = utc_offset * 900;
	} else {
		// For devices without timezone support, the current timezone of
		// the host system is used.
		if (!dc_datetime_localtime (datetime, ticks))
			return DC_STATUS_DATAFORMAT;
	}

	return DC_STATUS_SUCCESS;
}


static dc_status_t
uwatec_smart_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
	uwatec_smart_parser_t *parser = (uwatec_smart_parser_t *) abstract;

	// Cache the parser data.
	dc_status_t rc = uwatec_smart_parser_cache (parser);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	// Cache the profile data.
	if (parser->cached < PROFILE) {
		rc = uwatec_smart_parse (parser, NULL, NULL);
		if (rc != DC_STATUS_SUCCESS)
			return rc;
	}

	const uwatec_smart_header_info_t *table = parser->header;
	const unsigned char *data = abstract->data;

	double salinity = (parser->watertype == DC_WATER_SALT ? SALT : FRESH);

	dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
	dc_tank_t *tank = (dc_tank_t *) value;
	dc_salinity_t *water = (dc_salinity_t *) value;

	if (value) {
		switch (type) {
		case DC_FIELD_DIVETIME:
			*((unsigned int *) value) = array_uint16_le (data + table->divetime) * 60;
			break;
		case DC_FIELD_MAXDEPTH:
			*((double *) value) = array_uint16_le (data + table->maxdepth) / 100.0 / salinity;
			break;
		case DC_FIELD_GASMIX_COUNT:
			*((unsigned int *) value) = parser->ngasmixes;
			break;
		case DC_FIELD_GASMIX:
			gasmix->helium = parser->gasmix[flags].helium / 100.0;
			gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0;
			gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
			break;
		case DC_FIELD_TANK_COUNT:
			*((unsigned int *) value) = parser->ntanks;
			break;
		case DC_FIELD_TANK:
			tank->type = DC_TANKVOLUME_NONE;
			tank->volume = 0.0;
			tank->workpressure = 0.0;
			tank->beginpressure = parser->tank[flags].beginpressure / 128.0;
			tank->endpressure   = parser->tank[flags].endpressure   / 128.0;
			tank->gasmix = parser->tank[flags].gasmix;
			break;
		case DC_FIELD_TEMPERATURE_MINIMUM:
			*((double *) value) = (signed short) array_uint16_le (data + table->temp_minimum) / 10.0;
			break;
		case DC_FIELD_TEMPERATURE_MAXIMUM:
			if (table->temp_maximum == UNSUPPORTED)
				return DC_STATUS_UNSUPPORTED;
			*((double *) value) = (signed short) array_uint16_le (data + table->temp_maximum) / 10.0;
			break;
		case DC_FIELD_TEMPERATURE_SURFACE:
			if (table->temp_surface == UNSUPPORTED)
				return DC_STATUS_UNSUPPORTED;
			*((double *) value) = (signed short) array_uint16_le (data + table->temp_surface) / 10.0;
			break;
		case DC_FIELD_DIVEMODE:
			if (table->settings == UNSUPPORTED)
				return DC_STATUS_UNSUPPORTED;
			*((dc_divemode_t *) value) = parser->divemode;
			break;
		case DC_FIELD_SALINITY:
			if (table->settings == UNSUPPORTED)
				return DC_STATUS_UNSUPPORTED;
			water->type = parser->watertype;
			water->density = salinity * 1000.0;
			break;
		default:
			return DC_STATUS_UNSUPPORTED;
		}
	}

	return DC_STATUS_SUCCESS;
}


static unsigned int
uwatec_smart_identify (const unsigned char data[], unsigned int size)
{
	unsigned int count = 0;
	for (unsigned int i = 0; i < size; ++i) {
		unsigned char value = data[i];
		for (unsigned int j = 0; j < NBITS; ++j) {
			unsigned char mask = 1 << (NBITS - 1 - j);
			if ((value & mask) == 0)
				return count;
			count++;
		}
	}

	return (unsigned int) -1;
}


static unsigned int
uwatec_galileo_identify (unsigned char value)
{
	// Bits: 0ddd dddd
	if ((value & 0x80) == 0)
		return 0;

	// Bits: 100d dddd
	if ((value & 0xE0) == 0x80)
		return 1;

	// Bits: 1XXX dddd
	if ((value & 0xF0) != 0xF0)
		return (value & 0x70) >> 4;

	// Bits: 1111 XXXX
	return (value & 0x0F) + 7;
}


static unsigned int
uwatec_smart_fixsignbit (unsigned int x, unsigned int n)
{
	if (n <= 0 || n > 32)
		return 0;

	unsigned int signbit = (1 << (n - 1));
	unsigned int mask = (signbit - 1);

	// When turning a two's-complement number with a certain number
	// of bits into one with more bits, the sign bit must be repeated
	// in all the extra bits.
	if ((x & signbit) == signbit)
		return x | ~mask;
	else
		return x & mask;
}


static dc_status_t
uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback, void *userdata)
{
	dc_parser_t *abstract = (dc_parser_t *) parser;

	const unsigned char *data = abstract->data;
	unsigned int size = abstract->size;

	const uwatec_smart_sample_info_t *table = parser->samples;
	unsigned int entries = parser->nsamples;

	// Get the maximum number of alarm bytes.
	unsigned int nalarms = 0;
	for (unsigned int i = 0; i < entries; ++i) {
		if (table[i].type == ALARMS &&
			table[i].index >= nalarms)
		{
			nalarms = table[i].index + 1;
		}
	}

	int complete = 0;
	int calibrated = 0;

	unsigned int time = 0;
	unsigned int rbt = 99;
	unsigned int tank = 0;
	unsigned int gasmix = 0;
	double depth = 0, depth_calibration = 0;
	double temperature = 0;
	double pressure = 0;
	unsigned int heartrate = 0;
	unsigned int bearing = 0;
	unsigned int bookmark = 0;

	// Previous gas mix - initialize with impossible value
	unsigned int gasmix_previous = 0xFFFFFFFF;

	double salinity = (parser->watertype == DC_WATER_SALT ? SALT : FRESH);

	unsigned int interval = 4;
	if (parser->divemode == DC_DIVEMODE_FREEDIVE) {
		interval = 1;
	}

	int have_depth = 0, have_temperature = 0, have_pressure = 0, have_rbt = 0,
		have_heartrate = 0, have_bearing = 0;

	unsigned int offset = parser->headersize;
	while (offset < size) {
		dc_sample_value_t sample = {0};

		// Process the type bits in the bitstream.
		unsigned int id = 0;
		if (parser->model == GALILEO || parser->model == GALILEOTRIMIX ||
			parser->model == ALADIN2G || parser->model == MERIDIAN ||
			parser->model == CHROMIS || parser->model == MANTIS2 ||
			parser->model == G2 || parser->model == ALADINSPORTMATRIX ||
			parser->model == ALADINSQUARE || parser->model == G2HUD) {
			// Uwatec Galileo
			id = uwatec_galileo_identify (data[offset]);
		} else {
			// Uwatec Smart
			id = uwatec_smart_identify (data + offset, size - offset);
		}
		if (id >= entries) {
			ERROR (abstract->context, "Invalid type bits.");
			return DC_STATUS_DATAFORMAT;
		}

		// Skip the processed type bytes.
		offset += table[id].ntypebits / NBITS;

		// Process the remaining data bits.
		unsigned int nbits = 0;
		unsigned int value = 0;
		unsigned int n = table[id].ntypebits % NBITS;
		if (n > 0) {
			nbits = NBITS - n;
			value = data[offset] & (0xFF >> n);
			if (table[id].ignoretype) {
				// Ignore any data bits that are stored in
				// the last type byte for certain samples.
				nbits = 0;
				value = 0;
			}
			offset++;
		}

		// Check for buffer overflows.
		if (offset + table[id].extrabytes > size) {
			ERROR (abstract->context, "Incomplete sample data.");
			return DC_STATUS_DATAFORMAT;
		}

		// Process the extra data bytes.
		for (unsigned int i = 0; i < table[id].extrabytes; ++i) {
			nbits += NBITS;
			value <<= NBITS;
			value += data[offset];
			offset++;
		}

		// Fix the sign bit.
		signed int svalue = uwatec_smart_fixsignbit (value, nbits);

		// Parse the value.
		unsigned int idx = 0;
		unsigned int subtype = 0;
		unsigned int nevents = 0;
		const uwatec_smart_event_info_t *events = NULL;
		switch (table[id].type) {
		case PRESSURE_DEPTH:
			pressure += ((signed char) ((svalue >> NBITS) & 0xFF)) / 4.0;
			depth += ((signed char) (svalue & 0xFF)) / 50.0;
			complete = 1;
			break;
		case RBT:
			if (table[id].absolute) {
				rbt = value;
				have_rbt = 1;
			} else {
				rbt += svalue;
			}
			break;
		case TEMPERATURE:
			if (table[id].absolute) {
				temperature = svalue / 2.5;
				have_temperature = 1;
			} else {
				temperature += svalue / 2.5;
			}
			break;
		case PRESSURE:
			if (table[id].absolute) {
				if (parser->trimix) {
					tank = (value & 0xF000) >> 12;
					pressure = (value & 0x0FFF) / 4.0;
				} else {
					tank = table[id].index;
					pressure = value / 4.0;
				}
				have_pressure = 1;
				gasmix = tank;
			} else {
				pressure += svalue / 4.0;
			}
			break;
		case DEPTH:
			if (table[id].absolute) {
				depth = value / 50.0;
				if (!calibrated) {
					calibrated = 1;
					depth_calibration = depth;
				}
				have_depth = 1;
			} else {
				depth += svalue / 50.0;
			}
			complete = 1;
			break;
		case HEARTRATE:
			if (table[id].absolute) {
				heartrate = value;
				have_heartrate = 1;
			} else {
				heartrate += svalue;
			}
			break;
		case BEARING:
			bearing = value;
			have_bearing = 1;
			break;
		case ALARMS:
			idx = table[id].index;
			if (idx >= NEVENTS || parser->events[idx] == NULL) {
				ERROR (abstract->context, "Unexpected event index.");
				return DC_STATUS_DATAFORMAT;
			}

			events = parser->events[idx];
			nevents = parser->nevents[idx];

			for (unsigned int i = 0; i < nevents; ++i) {
				uwatec_smart_event_t ev_type = events[i].type;
				unsigned int ev_value = (value & events[i].mask) >> events[i].shift;
				switch (ev_type) {
				case EV_BOOKMARK:
					bookmark = ev_value;
					break;
				case EV_GASMIX:
					gasmix = ev_value;
					break;
				default:
					break;
				}
			}
			break;
		case TIME:
			complete = value;
			break;
		case APNEA:
			if (offset + 8 > size) {
				ERROR (abstract->context, "Incomplete sample data.");
				return DC_STATUS_DATAFORMAT;
			}
			offset += 8;
			break;
		case MISC:
			if (value < 1 || offset + value - 1 > size) {
				ERROR (abstract->context, "Incomplete sample data.");
				return DC_STATUS_DATAFORMAT;
			}

			subtype = data[offset];
			if (subtype >= 32 && subtype <= 41) {
				if (value < 16) {
					ERROR (abstract->context, "Incomplete sample data.");
					return DC_STATUS_DATAFORMAT;
				}
				unsigned int mixid = subtype - 32;
				unsigned int mixidx = DC_GASMIX_UNKNOWN;
				unsigned int o2 = array_uint16_le (data + offset + 1);
				unsigned int he = array_uint16_le (data + offset + 3);
				unsigned int beginpressure = array_uint16_le (data + offset + 5);
				unsigned int endpressure   = array_uint16_le (data + offset + 7);

				if (o2 != 0 || he != 0) {
					idx = uwatec_smart_find_gasmix (parser, mixid);
					if (idx >= parser->ngasmixes) {
						if (idx >= NGASMIXES) {
							ERROR (abstract->context, "Maximum number of gas mixes reached.");
							return DC_STATUS_NOMEMORY;
						}
						parser->gasmix[idx].id = mixid;
						parser->gasmix[idx].oxygen = o2;
						parser->gasmix[idx].helium = he;
						parser->ngasmixes++;
					}
					mixidx = idx;
				}

				if ((beginpressure != 0 || endpressure != 0) &&
					(beginpressure != 0xFFFF) && (endpressure != 0xFFFF)) {
					idx = uwatec_smart_find_tank (parser, mixid);
					if (idx >= parser->ntanks) {
						if (idx >= NGASMIXES) {
							ERROR (abstract->context, "Maximum number of tanks reached.");
							return DC_STATUS_NOMEMORY;
						}
						parser->tank[idx].id = mixid;
						parser->tank[idx].beginpressure = beginpressure;
						parser->tank[idx].endpressure = endpressure;
						parser->tank[idx].gasmix = mixidx;
						parser->ntanks++;
					}
				}
			}

			offset += value - 1;
			break;
		default:
			WARNING (abstract->context, "Unknown sample type.");
			break;
		}

		while (complete) {
			sample.time = time;
			if (callback) callback (DC_SAMPLE_TIME, sample, userdata);

			if (parser->ngasmixes && gasmix != gasmix_previous) {
				idx = uwatec_smart_find_gasmix (parser, gasmix);
				if (idx >= parser->ngasmixes) {
					ERROR (abstract->context, "Invalid gas mix index.");
					return DC_STATUS_DATAFORMAT;
				}
				sample.gasmix = idx;
				if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata);
				gasmix_previous = gasmix;
			}

			if (have_temperature) {
				sample.temperature = temperature;
				if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
			}

			if (bookmark) {
				sample.event.type = SAMPLE_EVENT_BOOKMARK;
				sample.event.time = 0;
				sample.event.flags = 0;
				sample.event.value = 0;
				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
			}

			if (have_rbt || have_pressure) {
				sample.rbt = rbt;
				if (callback) callback (DC_SAMPLE_RBT, sample, userdata);
			}

			if (have_pressure) {
				idx = uwatec_smart_find_tank(parser, tank);
				if (idx < parser->ntanks) {
					sample.pressure.tank = idx;
					sample.pressure.value = pressure;
					if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
				}
			}

			if (have_heartrate) {
				sample.heartbeat = heartrate;
				if (callback) callback (DC_SAMPLE_HEARTBEAT, sample, userdata);
			}

			if (have_bearing) {
				sample.bearing = bearing;
				if (callback) callback (DC_SAMPLE_BEARING, sample, userdata);
				have_bearing = 0;
			}

			if (have_depth) {
				sample.depth = (depth - depth_calibration) / salinity;
				if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
			}

			time += interval;
			complete--;
		}
	}

	parser->cached = PROFILE;

	return DC_STATUS_SUCCESS;
}


static dc_status_t
uwatec_smart_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
	uwatec_smart_parser_t *parser = (uwatec_smart_parser_t *) abstract;

	// Cache the parser data.
	dc_status_t rc = uwatec_smart_parser_cache (parser);
	if (rc != DC_STATUS_SUCCESS)
		return rc;

	// Cache the profile data.
	if (parser->cached < PROFILE) {
		rc = uwatec_smart_parse (parser, NULL, NULL);
		if (rc != DC_STATUS_SUCCESS)
			return rc;
	}

	return uwatec_smart_parse (parser, callback, userdata);
}
