 /*

    Support for Raymarine Waypoint File (.rwf).

    Copyright (C) 2006,2007 Olaf Klein, o.b.klein@gpsbabel.org

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
*/

/*  
    Known format limits:

    	Waypoint name: max. 16 characters
    	Route name:    max. 16 characters
	Routes:        max. 50 waypoints per route
	???:           the character set may be only a subset of std. ASCII

    History:
    	
    	2006/10/30: Initial release (not yet in GPSBabel source tree)
    	2006/11/08: 
	2007/03/17: Remove GUIDs from writer (not really valid)
	            Fix "PredictedTwa" output
		    Initialize location with "My Waypoints"
		    Change default value for RcCount and RelSet (now 0)
	2007/04/18: Limit route names also to 16 characters
		    Bug-fix - add missing comma (write_route_wpt_cb/items)
		    Change line feeds to fixed CRLF
		    Sort waypoints by name (not really needed, but nice)
		    Add some MapSource icon names to icon mappings
		    Remove unused id from icon table
*/

#include "defs.h"
#include "inifile.h"
#include "csv_util.h"

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

typedef unsigned long long guid_t;

static inifile_t *fin;
static gbfile *fout;
static waypoint **waypt_table;
static short_handle hshort_wpt, hshort_rte;
static int waypt_table_sz, waypt_table_ct;
static int rte_index, rte_wpt_index;
static char *opt_location;

#define MYNAME "raymarine"

static
arglist_t raymarine_args[] = 
{
	{ "location", &opt_location, "Default location", "My Waypoints", ARGTYPE_STRING, ARG_NOMINMAX },
	ARG_TERMINATOR
};

/* from csv_util.c: convert excel time (days since 1900) to time_t and back again */

#define EXCEL_TO_TIMET(a) ((a - 25569.0) * 86400.0)
#define TIMET_TO_EXCEL(a) ((a / 86400.0) + 25569.0)

#define LINE_FEED "\r\n"

/* Bitmaps */

typedef struct {
	char *name;
	char *mps_name;
} raymarine_symbol_mapping_t;

static raymarine_symbol_mapping_t raymarine_symbols[] = {
	{ /* 0 */  "Unknown Symbol 0" },
	{ /* 1 */  "Unknown Symbol 1" },
	{ /* 2 */  "Unknown Symbol 2" },
	{ /* 3 */  "Red Square" },
	{ /* 4 */  "Big Fish" },
	{ /* 5 */  "Anchor" },
	{ /* 6 */  "Smiley", "Contact, Smiley" },
	{ /* 7 */  "Sad" },
	{ /* 8 */  "Red Button", "Navaid, Red" },
	{ /* 9 */  "Sailfish" },
	{ /* 10 */ "Danger", "Skull and Crossbones" },
	{ /* 11 */ "Attention" },
	{ /* 12 */ "Black Square" },
	{ /* 13 */ "Intl. Dive Flag", "Diver Down Flag 2" },
	{ /* 14 */ "Vessel", "Marina" },
	{ /* 15 */ "Lobster" },
	{ /* 16 */ "Buoy", "Buoy, White" },
	{ /* 17 */ "Exclamation" },
	{ /* 18 */ "Red X" },
	{ /* 19 */ "Check Mark" },
	{ /* 20 */ "Black Plus" },
	{ /* 21 */ "Black Cross" },
	{ /* 22 */ "MOB" },
	{ /* 23 */ "Billfish" },
	{ /* 24 */ "Bottom Mark" },
	{ /* 25 */ "Circle", "Circle, Red" },
	{ /* 26 */ "Diamond", "Block, Red" },
	{ /* 27 */ "Diamond Quarters", "Diamond, Red" },
	{ /* 28 */ "U.S. Dive Flag", "Diver Down Flag 1" },
	{ /* 29 */ "Dolphin" },
	{ /* 30 */ "Few Fish" },
	{ /* 31 */ "Multiple Fish" },
	{ /* 32 */ "Many Fish" },
	{ /* 33 */ "Single Fish" },
	{ /* 34 */ "Small Fish" },
	{ /* 35 */ "Marker" },
	{ /* 36 */ "Cocktails", "Bar" },
	{ /* 37 */ "Red Box Marker" },
	{ /* 38 */ "Reef" },
	{ /* 39 */ "Rocks" },
	{ /* 40 */ "Fish School" },
	{ /* 41 */ "Seaweed", "Weed Bed" },
	{ /* 42 */ "Shark" },
	{ /* 43 */ "Sportfisher" },
	{ /* 44 */ "Swimmer", "Swimming Area" },
	{ /* 45 */ "Top Mark" },
	{ /* 46 */ "Trawler" },
	{ /* 47 */ "Tree" },
	{ /* 48 */ "Triangle", "Triangle, Red" },
	{ /* 49 */ "Wreck", "Shipwreck" }
};

#define RAYMARINE_SYMBOL_CT  sizeof(raymarine_symbols) / sizeof(raymarine_symbol_mapping_t)
#define RAYMARINE_STD_SYMBOL 3

static int
find_symbol_num(const char *descr)
{
	if ((descr != NULL) && (*descr)) {
		
		int i;
		raymarine_symbol_mapping_t *a;
		
		a = &raymarine_symbols[0];
		
		for (i = 0; i < RAYMARINE_SYMBOL_CT; i++, a++) {
			if (case_ignore_strcmp(descr, a->name) == 0) return i;
			if (a->mps_name && (case_ignore_strcmp(descr, a->mps_name) == 0)) return i;
		}
	}
	
	return RAYMARINE_STD_SYMBOL;
}

/* ============================================= */
/* %%%    R A Y M A R I N E   R E A D E R    %%% */
/* ============================================= */

static void
raymarine_rd_init(const char *fname)
{
	fin = inifile_init(fname, MYNAME);
}

static void
raymarine_rd_done(void)
{
	inifile_done(fin);
}

static void
raymarine_read(void)
{
	waypoint *wpt;
	int ix, rx;
	
	/* Read all waypoints */
	
	for (ix = 0; ix < 0x3FFF; ix++) {
		char sect[10];
		char *str, *name, *lat, *lon;
		
		/* built section identifier */
		snprintf(sect, sizeof(sect), "Wp%d", ix);
		
		/* try to read our most expected values */
		if (NULL == (name = inifile_readstr(fin, sect, "Name"))) break;
		if (NULL == (lat = inifile_readstr(fin, sect, "Lat"))) break;
		if (NULL == (lon = inifile_readstr(fin, sect, "Long"))) break;
		
		wpt = waypt_new();
		wpt->shortname = xstrdup(name);
		wpt->latitude = atof(lat);
		wpt->longitude = atof(lon);
		waypt_add(wpt);
		
		/* try to read optional values */
		if (((str = inifile_readstr(fin, sect, "Notes"))) && *str) wpt->notes = xstrdup(str);
		if (((str = inifile_readstr(fin, sect, "Time"))) && *str) wpt->creation_time = EXCEL_TO_TIMET(atof(str));
		if (((str = inifile_readstr(fin, sect, "Bmp"))) && *str) {
			int symbol = atoi(str);
			
			if ((symbol < 3) && (symbol >= RAYMARINE_SYMBOL_CT))
				symbol = RAYMARINE_STD_SYMBOL;
			wpt->icon_descr = raymarine_symbols[symbol].name;
		}
	}
	
	/* Read all routes */
	
	for (rx = 0; rx < 0x3FFF; rx++) {
		char sect[10];
		char *name;
		route_head *rte;
		int wx;

		snprintf(sect, sizeof(sect), "Rt%d", rx);
		if (NULL == (name = inifile_readstr(fin, sect, "Name"))) break;
		
		rte = route_head_alloc();
		rte->rte_name = xstrdup(name);
		route_add_head(rte);
		
		for (wx = 0; wx < 0x3FFF; wx++) {
			char buff[32];
			char *str;
			waypoint * wpt;
			
			snprintf(buff, sizeof(buff), "Mk%d", wx);
			str = inifile_readstr(fin, sect, buff);
			if ((str == NULL) || (*str == '\0')) break;
			
			wpt = find_waypt_by_name(str);
			if (wpt == NULL)
				fatal(MYNAME ": No associated waypoint for route point %s (Route %s)!\n",
					str, rte->rte_name);
					
			route_add_wpt(rte, waypt_dupe(wpt));
		}
	}
}

/* ============================================= */
/* %%%    R A Y M A R I N E   W R I T E R    %%% */
/* ============================================= */

/* make waypoint shortnames unique */

static char
same_points(const waypoint *A, const waypoint *B)
{
	return (	/* !!! We are case-sensitive !!! */
		(strcmp(A->shortname, B->shortname) == 0) &&
		(A->latitude == B->latitude) &&
		(A->longitude == B->longitude));
}

static void
register_waypt(const waypoint *ref, const char is_rtept)
{
	int i;
	waypoint *wpt = (waypoint *) ref;
	
	for (i = 0; i < waypt_table_ct; i++) {
		waypoint *cmp = waypt_table[i];
		
		if (same_points(wpt, cmp)) {
			wpt->extra_data = cmp->extra_data;
			return;
		}
	}
	
	if (waypt_table_ct >= waypt_table_sz) {
		waypt_table_sz += 32;
		if (waypt_table)
			waypt_table = (void *) xrealloc(waypt_table, waypt_table_sz * sizeof(wpt));
		else
			waypt_table = (void *) xmalloc(waypt_table_sz * sizeof(wpt));
	}
	
	wpt->extra_data = (void *)mkshort(hshort_wpt, wpt->shortname);

	waypt_table[waypt_table_ct] = (waypoint *)wpt;
	waypt_table_ct++;
}

static void
enum_waypt_cb(const waypoint *wpt)
{
	register_waypt((waypoint *) wpt, 0);
}

static void
enum_rtept_cb(const waypoint *wpt)
{
	register_waypt((waypoint *) wpt, 1);
}

static int 
qsort_cb(const void *a, const void *b)
{
	const waypoint *wa = *(waypoint **)a;
	const waypoint *wb = *(waypoint **)b;
	
	return strcmp(wa->shortname, wb->shortname);
}

static void
write_waypoint(gbfile *fout, const waypoint *wpt, const int waypt_no, const char *location)
{
	char *notes;
	char *name;
	double time;
		
	notes = wpt->notes;
	if (notes == NULL) {
		notes = wpt->description;
		if (notes == NULL) notes = "";
	}
	notes = csv_stringclean(notes, LINE_FEED);
	time = (wpt->creation_time > 0) ? TIMET_TO_EXCEL(wpt->creation_time) : TIMET_TO_EXCEL(gpsbabel_time);
	name = (char *)wpt->extra_data;

	gbfprintf(fout, "[Wp%d]" LINE_FEED
			"Loc=%s" LINE_FEED
			"Name=%s" LINE_FEED
			"Lat=%.15f" LINE_FEED
			"Long=%.15f" LINE_FEED,
		waypt_no, location, name, wpt->latitude, wpt->longitude
	);
	gbfprintf(fout, "Rng=%.15f" LINE_FEED
			"Bear=%.15f" LINE_FEED
			"Bmp=%d" LINE_FEED
			"Fixed=1" LINE_FEED
			"Locked=0" LINE_FEED
			"Notes=%s" LINE_FEED,
		0.0, 0.0, 
		find_symbol_num(wpt->icon_descr), 
		notes
	);
	gbfprintf(fout, "Rel=" LINE_FEED
			"RelSet=0" LINE_FEED
			"RcCount=0" LINE_FEED
			"RcRadius=%.15f" LINE_FEED
			"Show=1" LINE_FEED
			"RcShow=0" LINE_FEED
			"SeaTemp=%.15f" LINE_FEED
			"Depth=%.15f" LINE_FEED
			"Time=%.10f00000" LINE_FEED,
		0.0, -32678.0, 65535.0, time
	);
	xfree(notes);
}

static void
write_route_head_cb(const route_head *rte)
{
	char buff[32];
	char *name;
	
	name = rte->rte_name;
	if ((name == NULL) || (*name == '\0')) {
		snprintf(buff, sizeof(buff), "Route%d", rte_index);
		name = buff;
	}
	name = mkshort(hshort_rte, name);
	gbfprintf(fout, "[Rt%d]" LINE_FEED
			"Name=%s" LINE_FEED
			"Visible=1" LINE_FEED,
		rte_index,
		name
	);
	xfree(name);

	rte_index++;
	rte_wpt_index = 0;
}

static void
write_route_wpt_cb(const waypoint *wpt)
{
	int i;
	static char *items[] = {
		"Cog",
		"Eta",
		"Length",
		"PredictedDrift",
		"PredictedSet",
		"PredictedSog",
		"PredictedTime",
		"PredictedTwa",
		"PredictedTwd",
		"PredictedTws" };
		
	gbfprintf(fout, "Mk%d=%s" LINE_FEED, rte_wpt_index, (char *)wpt->extra_data);
	for (i = 0; i < sizeof(items) / sizeof(char *); i++)
		gbfprintf(fout, "%s%d=%.15f" LINE_FEED, items[i], rte_wpt_index, 0.0);
	
	rte_wpt_index++;
	return;
}

static void
enum_route_hdr_cb(const route_head *rte)
{
	is_fatal(rte->rte_waypt_ct > 50,
		MYNAME ": Routes with more than 50 points are not supported by Waymarine!");
}

static short_handle
raymarine_new_short_handle(void)
{
	short_handle res;
	
	res = mkshort_new_handle();

	setshort_length(res, 16);
	setshort_badchars(res, ",");
	setshort_mustupper(res, 0);
	setshort_mustuniq(res, 1);
	setshort_whitespace_ok(res, 1);
	setshort_repeating_whitespace_ok(res, 1);
	
	return res;
}

static void
raymarine_wr_init(const char *fname)
{
	fout = gbfopen(fname, "wb", MYNAME);

	hshort_wpt = raymarine_new_short_handle();
	hshort_rte = raymarine_new_short_handle();
}

static void
raymarine_wr_done(void)
{
	mkshort_del_handle(&hshort_wpt);
	mkshort_del_handle(&hshort_rte);

	gbfclose(fout);
}

static void
raymarine_write(void)
{
	int i;
	waypoint *wpt;
	
	waypt_table_sz = 0;
	waypt_table_ct = 0;
	waypt_table = NULL;

	/* enumerate all possible waypoints */
	waypt_disp_all(enum_waypt_cb);
	route_disp_all(enum_route_hdr_cb, NULL, enum_rtept_cb);
	
	if (waypt_table_ct == 0) return;
	
	qsort(waypt_table, waypt_table_ct, sizeof(*waypt_table), qsort_cb);

	/* write out waypoint summary */
	for (i = 0; i < waypt_table_ct; i++) {
		waypoint *wpt = waypt_table[i];
		write_waypoint(fout, wpt, i, opt_location);
	}
	
	/* write out all routes with their waypoints */
	rte_index = 0;
	route_disp_all(write_route_head_cb, NULL, write_route_wpt_cb);
	
	/* release local used data */	
	for (i = 0; i < waypt_table_ct; i++) {
		wpt = waypt_table[i];
		xfree(wpt->extra_data);
		wpt->extra_data = NULL;
	}
	xfree(waypt_table);
}

/* ================================================== */
/* %%%    M O D U L E   R E G I S T R A T I O N   %%% */
/* ================================================== */

ff_vecs_t raymarine_vecs = {
	ff_type_file,
	{ 
		ff_cap_read | ff_cap_write	/* waypoints */, 
		ff_cap_none 			/* tracks */, 
	  	ff_cap_read | ff_cap_write 	/* routes */, 
	},
	raymarine_rd_init,
	raymarine_wr_init,
	raymarine_rd_done,
	raymarine_wr_done,
	raymarine_read,
	raymarine_write,
	NULL,
	raymarine_args,
	CET_CHARSET_ASCII, 0	/* should we force this to 1 ? */
};
