/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
 *
 * Copyright (C) 2020, Rob Norris <rw_norris@hotmail.com>
 *
 * 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-1307  USA
 *
 */

#include "kml.h"
#include "viking.h"
#include <expat.h>
#include "ctype.h"

typedef enum {
	KML_COLOR_MODE_NORMAL=0,
	KML_COLOR_MODE_RANDOM
} colorModeEnum;

// ATM Not supporting various other style characteristics such as
// <BalloonStyle> / <gx:balloonVisibility>, <LabelStyle>,<PolyStyle> or <ListStyle>
// ATM only a subset of <LineStyle> & <IconStyle> i.e. just the colour
//  that translates easily into properties that Viking supports already
typedef struct {
	// ColorStyle
	gchar *color; // Should be aabbggrr in hex
	//colorModeEnum colorMode;
	// LineStyle
	//gdouble width; // Width in pixels - probably should be greater 0...!
	// <gx:outerColor>, <gx:outerWidth>, <gx:physicalWidth>, <gx:labelVisibility>
	// IconStyle
	//gdouble scale;
	// <heading>, <icon>, <hotspot>
} AnyStyle;

typedef struct {
	GString *c_cdata;
	gboolean use_cdata;
	gchar *name;
	gchar *snippet;
	gchar *desc;
	gchar *styleUrl;
	GHashTable *styles; // of AnyStyle
	gchar *styleName; // Temp holding area before storing in HashTable
	gchar *color;     // Temp holding area before storing in a style
	//gdouble width;    // Temp holding area before storing in a style
	//gdouble scale;    // Temp holding area before storing in a style
	gboolean vis;
	gdouble timestamp; // Waypoints only
	VikTrwLayer *vtl;
	VikWaypoint *waypoint;
	VikTrack *track;
	VikTrackpoint *trackpoint;
	GList *tracks;     // VikTracks
	GList *timestamps; // gdoubles
	GList *hrs;        // guints
	GList *cads;       // guints
	GList *temps;      // gdoubles
	GQueue *gq_start;
	GQueue *gq_end;
	gboolean layer_has_been_named;
	gboolean layer_has_description;
	XML_Parser parser;
} xml_data;

static guint unnamed_waypoints = 0;
static guint unnamed_tracks = 0;
static guint unnamed_routes = 0;

// Various helper functions

static void track_set_color ( VikTrack *trk, gchar *color )
{
	// ATM must be kml:color - i.e. 'aabbggrr'
	// so any other values are ignored
	if ( strlen(color) == 8 ) {
		//Create GdkColor string of form: #rrggbb
		gchar gcol[8];
		gcol[0] = '#';
		gcol[1] = color[6];
		gcol[2] = color[7];
		gcol[3] = color[4];
		gcol[4] = color[5];
		gcol[5] = color[2];
		gcol[6] = color[3];
		gcol[7] = '\0';
		trk->has_color = gdk_color_parse ( gcol, &trk->color);
	}
}

static void parse_tag_reset ( xml_data *xd )
{
	XML_SetElementHandler ( xd->parser, (XML_StartElementHandler)g_queue_pop_head(xd->gq_start), (XML_EndElementHandler)g_queue_pop_head(xd->gq_end) );
}

static void end_leaf_tag ( xml_data *xd )
{
	g_string_erase ( xd->c_cdata, 0, -1 );
	xd->use_cdata = FALSE;
	XML_SetEndElementHandler ( xd->parser, (XML_EndElementHandler)g_queue_pop_head(xd->gq_end) );
}

static void setup_to_read_next_level_tag ( xml_data *xd, gpointer old_start_func, gpointer old_end_func, gpointer new_start_func, gpointer new_end_func )
{
	if ( g_queue_peek_head(xd->gq_start) != old_start_func )
		g_queue_push_head ( xd->gq_start, old_start_func );
	if ( g_queue_peek_head(xd->gq_end) != old_end_func )
		g_queue_push_head ( xd->gq_end, old_end_func );
	XML_SetElementHandler ( xd->parser, (XML_StartElementHandler)new_start_func, (XML_EndElementHandler)new_end_func );
}

static const char *get_attr ( const char **attr, const char *key )
{
  while ( *attr ) {
    if ( g_strcmp0(*attr,key) == 0 )
      return *(attr + 1);
    attr += 2;
  }
  return NULL;
}

// Start of all the tag processing elements

static void name_end ( xml_data *xd, const char *el )
{
	xd->name = g_strdup ( xd->c_cdata->str );
	end_leaf_tag ( xd );
}

static void snippet_end ( xml_data *xd, const char *el )
{
	xd->snippet = g_strdup ( xd->c_cdata->str );
	end_leaf_tag ( xd );
}

static void description_end ( xml_data *xd, const char *el )
{
	xd->desc = g_strdup ( xd->c_cdata->str );
	end_leaf_tag ( xd );
}

static void styleUrl_end ( xml_data *xd, const char *el )
{
	g_free ( xd->styleUrl );
	if ( strlen(xd->c_cdata->str) > 2 ) {
		// Only really support 'anchor link' styles
		// i.e. those in the same kml file
		if ( xd->c_cdata->str[0] == '#' )
			xd->styleUrl = g_strdup ( &xd->c_cdata->str[1] );
		else
			xd->styleUrl = g_strdup ( xd->c_cdata->str );
	} else
		xd->styleUrl = NULL;
	end_leaf_tag ( xd );
}

static void visibility_end ( xml_data *xd, const char *el )
{
	xd->vis = TRUE;
	if ( g_strcmp0(xd->c_cdata->str, "0") == 0 )
		xd->vis = FALSE;
	end_leaf_tag ( xd );
}

// A tag which should only contain cdata (i.e. no further tags)
static void setup_to_read_leaf_tag ( xml_data *xd, gpointer old_end_func, gpointer new_end_func )
{
	// Save old end function if different
	if ( g_queue_peek_head(xd->gq_end) != old_end_func )
		g_queue_push_head ( xd->gq_end, old_end_func );
	// Register new end function
	XML_SetEndElementHandler ( xd->parser, (XML_EndElementHandler)new_end_func );
	// Clear buffer and turn on
	g_string_erase ( xd->c_cdata, 0, -1 );
	xd->use_cdata = TRUE;
}

static void timestamp_when_end ( xml_data *xd, const char *el )
{
	GTimeVal gtv;
	if ( g_time_val_from_iso8601(xd->c_cdata->str, &gtv) ) {
		gdouble d1 = gtv.tv_sec;
		gdouble d2 = (gdouble)gtv.tv_usec/G_USEC_PER_SEC;
		xd->timestamp = (d1 < 0) ? d1 - d2 : d1 + d2;
	}
	end_leaf_tag ( xd );
}

static void timestamp_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "TimeStamp" ) == 0 ) {
		parse_tag_reset ( xd );
	}
}

static void timestamp_start ( xml_data *xd, const char *el, const char **attr )
{
	// Ignore 'extrude' and 'altitudeMode'
	if ( g_strcmp0 ( el, "when" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, timestamp_end, timestamp_when_end );
	}
}

static void set_vc_to_ll ( xml_data *xd, VikCoord *vc, VikTrwLayer *vtl, gdouble lat, gdouble lon )
{
	// Remember KML coordinates are the 'lon,lat(,alt)' order
	struct LatLon c_ll;
	if ( lat < -90.0 || lat > 90.0 ) {
		g_warning ( "%s: Invalid latitude value %f at line %ld", G_STRLOC, lat, XML_GetCurrentLineNumber(xd->parser) );
		c_ll.lat = 0.0;
	} else
		c_ll.lat = lat;

	if ( lon < -180.0 || lon > 180.0 ) {
		g_warning ( "%s: Invalid longitude value %f at line %ld", G_STRLOC, lon, XML_GetCurrentLineNumber(xd->parser) );
		c_ll.lon = 0.0;
	} else
		c_ll.lon = lon;

	vik_coord_load_from_latlon ( vc, vik_trw_layer_get_coord_mode(vtl), &c_ll );
}

static void point_coordinates_end ( xml_data *xd, const char *el )
{
	if ( xd->waypoint ) {
		gchar **vals = g_strsplit ( xd->c_cdata->str, ",", -1 );
		guint nn = g_strv_length ( vals );
		if ( nn < 2 || nn > 3  )
			g_warning ( "%s: expected 2 or 3 coordinate parts but got %d at line %ld", G_STRLOC, nn, XML_GetCurrentLineNumber(xd->parser) );
		else {
			// Remember KML coordinates are the 'lon,lat(,alt)' order
			gdouble lat = g_ascii_strtod ( vals[1], NULL );
			gdouble lon = g_ascii_strtod ( vals[0], NULL );
			set_vc_to_ll ( xd, &(xd->waypoint->coord), xd->vtl, lat, lon );
			if ( nn == 3 )
				// ATM altitude is always interpreted to be in absolute mode (to sea level)
				xd->waypoint->altitude = g_ascii_strtod ( vals[2], NULL );
		}
		g_strfreev ( vals );
	}
	else
		g_warning ( "%s: no waypoint", G_STRLOC );

	end_leaf_tag ( xd );
}

static void point_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "Point" ) == 0 ) {
		if ( xd->waypoint ) {
			if ( xd->name && strlen(xd->name) > 0 ) {
				vik_waypoint_set_name ( xd->waypoint, xd->name );
			} else {
				xd->waypoint->hide_name = TRUE;
				gchar *name = g_strdup_printf ( "WP%04d", unnamed_waypoints++ );
				vik_waypoint_set_name ( xd->waypoint, name );
				g_free ( name );
			}
			if ( xd->snippet ) {
				vik_waypoint_set_comment ( xd->waypoint, xd->snippet );
			}
			if ( xd->desc ) {
				vik_waypoint_set_description ( xd->waypoint, xd->desc );
			}
			// TODO: consider styleUrl...
			// ATM as we don't support individual waypoint sizes nor colour
			//  (these are both done at the layer level)
			//  so we won't try to apply the scale to the default waypoint size
			xd->waypoint->visible = xd->vis;
			xd->waypoint->timestamp = xd->timestamp;
			vik_trw_layer_filein_add_waypoint ( xd->vtl, NULL, xd->waypoint );
		}
		parse_tag_reset ( xd );
	}
}

static void point_start ( xml_data *xd, const char *el, const char **attr )
{
	// Ignore 'extrude' and 'altitudeMode'
	if ( g_strcmp0 ( el, "coordinates" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, point_end, point_coordinates_end );
	}
	xd->waypoint = vik_waypoint_new();
}

static void linestring_coordinates_end ( xml_data *xd, const char *el )
{
	if ( xd->track ) {
		gchar *ptr = xd->c_cdata->str;
		if ( ptr ) {
			int len = strlen(ptr);
			gchar *endptr = ptr + len;
			gchar *cp;
			for ( cp = ptr; cp < endptr; cp++ )
				if ( !isspace(*cp) )
					break;

			gboolean newseg = TRUE;
			int val = 0;
			gdouble values[3];
			gchar *vp;
			for ( vp = cp; cp <= endptr; cp++ ) {
				if ( *cp == ',' ) {
					// Get string before this comma
					gchar *str = g_malloc0 ( cp-vp+1 );
					strncpy ( str, vp, cp-vp );
					values[val] = g_ascii_strtod ( str, NULL );
					g_free ( str );
					val++;
					vp = cp + 1; // +1 for the next one after the comma
				} else if ( cp == NULL || isspace(*cp) ) {
					if ( val < 1 || val > 2 )
						// Not enough or too many coordinate parts
						goto end;
					// Otherwise the value is to end of text block
					//  (should be the last coordinate part)
					gchar *str = g_malloc0 ( cp-vp+1 );
					strncpy ( str, vp, cp-vp );
					values[val] = g_ascii_strtod ( str, NULL );
					g_free ( str );

					VikTrackpoint *tp = vik_trackpoint_new();
					// Remember KML coordinates are the 'lon,lat(,alt)' order
					set_vc_to_ll ( xd, &(tp->coord), xd->vtl, values[1], values[0] );
					if ( val == 2 )
						// ATM altitude is always interpreted to be in absolute mode (to sea level)
						tp->altitude = values[2];
					if ( newseg ) {
						tp->newsegment = TRUE;
						newseg = FALSE;
					}

					xd->track->trackpoints = g_list_prepend ( xd->track->trackpoints, tp );

					// Consume any extra space to get to the next coordinate part
					while ( cp != NULL && isspace(*cp) )
						cp++;
					val = 0;
					vp = cp;
				}
			}
		}
	}
	else
		g_warning ( "%s: no track", G_STRLOC );

end:
	end_leaf_tag ( xd );
}

static void add_track ( xml_data *xd )
{
	if ( xd->track ) {
		if ( xd->name && strlen(xd->name) > 0 ) {
			vik_track_set_name ( xd->track, xd->name );
		} else {
			gchar *name = g_strdup_printf ( "TRK%04d", unnamed_tracks++ );
			vik_track_set_name ( xd->track, name );
			g_free ( name );
		}
		if ( xd->snippet ) {
			vik_track_set_comment ( xd->track, xd->snippet );
		}
		if ( xd->desc ) {
			vik_track_set_description ( xd->track, xd->desc );
		}
		if ( xd->styleUrl ) {
			AnyStyle *as = g_hash_table_lookup ( xd->styles, xd->styleUrl );
			if ( as ) {
				// Set colour for track
				track_set_color ( xd->track, as->color );
				// ATM not going to use the width - since we don't support a per track width
				//  we only do line width applying to the whole layer
			}
			if ( vik_debug && !xd->track->comment )
				vik_track_set_comment ( xd->track, xd->styleUrl );
		}
		xd->track->trackpoints = g_list_reverse ( xd->track->trackpoints );
		xd->track->visible = xd->vis;
		vik_trw_layer_filein_add_track ( xd->vtl, NULL, xd->track );
	}
}

static void linestring_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "LineString" ) == 0 ) {
		add_track ( xd );
		parse_tag_reset ( xd );
	}
}

static void linestring_start ( xml_data *xd, const char *el, const char **attr )
{
	// ATM ignoring at least 'extrude', 'tessellate' & 'altitudeMode'
	if ( g_strcmp0 ( el, "coordinates" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, linestring_end, linestring_coordinates_end );
	}
}

// No different to linestring handling
// Notionally a LinearRing is closed line string;
//  i.e. the last coordinate should be the same as the first
//  but ATM we do not enforce that
static void linearring_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "LinearRing" ) == 0 ) {
		add_track ( xd );
		parse_tag_reset ( xd );
	}
}

static void linearring_start ( xml_data *xd, const char *el, const char **attr )
{
	// ATM ignoring at least 'extrude', 'tessellate' & 'altitudeMode'
	if ( g_strcmp0 ( el, "coordinates" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, linearring_end, linestring_coordinates_end );
	}
}

static void reset_xd ( xml_data *xd )
{
	xd->vis = TRUE;
	xd->timestamp = NAN;
	g_free ( xd->name );
	xd->name = NULL;
	g_free ( xd->snippet );
	xd->snippet = NULL;
	g_free ( xd->desc );
	xd->desc = NULL;
	g_free ( xd->styleUrl );
	xd->styleUrl = NULL;
	g_free ( xd->color );
	xd->color = NULL;
	g_free ( xd->styleName );
	xd->styleName = NULL;
	//xd->width = 1.0;
	//xd->scale = 1.0;
}

static void placemark_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "Placemark" ) == 0 ) {
		reset_xd ( xd );
		parse_tag_reset ( xd );
	}
}

// For some unknown reason Track coordinates use a ' ' seperator,
//  whereas linestrings (and points) use a ','
static void track_coordinates_end ( xml_data *xd, const char *el )
{
	if ( xd->trackpoint && xd->track ) {
		gchar **vals = g_strsplit ( xd->c_cdata->str, " ", -1 );
		guint nn = g_strv_length ( vals );
		if ( nn < 2 || nn > 3  )
			g_warning ( "%s: expected 2 or 3 coordinate parts but got %d at line %ld", G_STRLOC, nn, XML_GetCurrentLineNumber(xd->parser) );
		else {
			// Remember KML coordinates are the 'lon,lat(,alt)' order
			gdouble lat = g_ascii_strtod ( vals[1], NULL );
			gdouble lon = g_ascii_strtod ( vals[0], NULL );
			set_vc_to_ll ( xd, &(xd->trackpoint->coord), xd->vtl, lat, lon );
			if ( nn == 3 )
				// ATM altitude is always interpreted to be in absolute mode (to sea level)
				xd->trackpoint->altitude = g_ascii_strtod ( vals[2], NULL );

			xd->track->trackpoints = g_list_prepend ( xd->track->trackpoints, xd->trackpoint );
			xd->track->visible = xd->vis;
			xd->vis = TRUE;
		}
		g_strfreev ( vals );
	}
	else
		g_warning ( "%s: no trackpoint", G_STRLOC );

	end_leaf_tag ( xd );
}

static void track_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "gx:Track" ) == 0 ) {
		if ( xd->track ) {
			if ( xd->name && strlen(xd->name) > 0 ) {
				vik_track_set_name ( xd->track, xd->name );
				g_free ( xd->name );
				xd->name = NULL;
			} else {
				gchar *name = g_strdup_printf ( "TRK%04d", unnamed_tracks++ );
				vik_track_set_name ( xd->track, name );
				g_free ( name );
			}
			if ( xd->desc ) {
				vik_track_set_description ( xd->track, xd->desc );
				g_free ( xd->desc );
				xd->desc = NULL;
			}
			xd->track->trackpoints = g_list_reverse ( xd->track->trackpoints );
			xd->timestamps = g_list_reverse ( xd->timestamps );
			xd->hrs = g_list_reverse ( xd->hrs );
			xd->cads = g_list_reverse ( xd->cads );
			xd->temps = g_list_reverse ( xd->temps );

			gulong num_points = g_list_length ( xd->track->trackpoints );

			// Assign times
			gulong ntimes = g_list_length ( xd->timestamps );
			if ( ntimes ) {
				if ( ntimes == num_points ) {
					GList *lts = xd->timestamps;
					for (GList *ltp = xd->track->trackpoints; ltp != NULL; ltp = ltp->next ) {
						gdouble *dd = (gdouble*)lts->data;
						VIK_TRACKPOINT(ltp->data)->timestamp = *dd;
						lts = lts->next;
					}
				} else
					g_warning ( "%s: trackpoint count vs timestamp count differ %ld vs %ld at line %ld",
					            G_STRLOC, num_points, ntimes, XML_GetCurrentLineNumber(xd->parser) );
			}
			g_list_free_full ( xd->timestamps, g_free );
			xd->timestamps = NULL;

			// Assign heart rate
			gulong nhrs = g_list_length ( xd->hrs );
			if ( nhrs ) {
				if ( nhrs == num_points ) {
					GList *lts = xd->hrs;
					for (GList *ltp = xd->track->trackpoints; ltp != NULL; ltp = ltp->next ) {
						VIK_TRACKPOINT(ltp->data)->heart_rate = GPOINTER_TO_UINT(lts->data);
						lts = lts->next;
					}
				} else
					g_warning ( "%s: trackpoint count vs heart rate count differ %ld vs %ld at line %ld",
					            G_STRLOC, num_points, nhrs, XML_GetCurrentLineNumber(xd->parser) );
			}
			g_list_free ( xd->hrs ); // NB no data in list has been allocated

			// Assign cadence
			gulong ncads = g_list_length ( xd->cads );
			if ( ncads ) {
				if ( ncads == num_points ) {
					GList *lts = xd->cads;
					for (GList *ltp = xd->track->trackpoints; ltp != NULL; ltp = ltp->next ) {
						VIK_TRACKPOINT(ltp->data)->cadence = GPOINTER_TO_UINT(lts->data);
						lts = lts->next;
					}
				} else
					g_warning ( "%s: trackpoint count vs cadence count differ %ld vs %ld at line %ld",
					            G_STRLOC, num_points, ncads, XML_GetCurrentLineNumber(xd->parser) );
			}
			g_list_free ( xd->cads ); // NB no data in list has been allocated

			// Assign temps
			gulong ntemps = g_list_length ( xd->temps );
			if ( ntemps ) {
				if ( ntemps == num_points ) {
					GList *lts = xd->temps;
					for (GList *ltp = xd->track->trackpoints; ltp != NULL; ltp = ltp->next ) {
						gdouble *dd = (gdouble*)lts->data;
						VIK_TRACKPOINT(ltp->data)->temp = *dd;
						lts = lts->next;
					}
				} else
					g_warning ( "%s: trackpoint count vs temp count differ %ld vs %ld at line %ld",
					            G_STRLOC, num_points, ntemps, XML_GetCurrentLineNumber(xd->parser) );
			}
			g_list_free_full ( xd->temps, g_free );
			xd->temps = NULL;

			// Set first (and only) segment
			VikTrackpoint *tpt = vik_track_get_tp_first ( xd->track );
			if ( tpt )
				tpt->newsegment = TRUE;

			// Add it or wait if reading multi tracks
			if ( !xd->tracks )
				vik_trw_layer_filein_add_track ( xd->vtl, NULL, xd->track );
		}
		parse_tag_reset ( xd );
	}
}

// Tricky to reuse timestamp_when_end()
// Since for tracks the <when></when> should be repeated for each trackpoint
//  so need to add to a list rather then a singular instance.
static void track_when_end ( xml_data *xd, const char *el )
{
	gdouble *tt = g_malloc0 ( sizeof(gdouble) );
	GTimeVal gtv;
	if ( g_time_val_from_iso8601(xd->c_cdata->str, &gtv) ) {
		gdouble d1 = gtv.tv_sec;
		gdouble d2 = (gdouble)gtv.tv_usec/G_USEC_PER_SEC;
		*tt = (d1 < 0) ? d1 - d2 : d1 + d2;
	} else {
		*tt = NAN;
	}
	xd->timestamps = g_list_prepend ( xd->timestamps, tt );
	end_leaf_tag ( xd );
}

static void value_cad_end ( xml_data *xd, const char *el )
{
	gdouble val = g_ascii_strtod ( xd->c_cdata->str, NULL );
	guint ival;
	if ( isnan(val) )
		ival = VIK_TRKPT_CADENCE_NONE;
	else
		ival = round ( val );
	xd->cads = g_list_prepend ( xd->cads, GUINT_TO_POINTER(ival) );
	end_leaf_tag ( xd );
}

static void value_hr_end ( xml_data *xd, const char *el )
{
	gdouble val = g_ascii_strtod ( xd->c_cdata->str, NULL );
	guint ival;
	if ( isnan(val) )
		ival = 0;
	else
		ival = round ( val );
	xd->hrs = g_list_prepend ( xd->hrs, GUINT_TO_POINTER(ival) );
	end_leaf_tag ( xd );
}

static void value_temp_end ( xml_data *xd, const char *el )
{
	gdouble *val = g_malloc0 ( sizeof(gdouble) );
	*val = g_ascii_strtod ( xd->c_cdata->str, NULL );
	xd->temps = g_list_prepend ( xd->temps, val );
	end_leaf_tag ( xd );
}

static void simplearraydata_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "gx:SimpleArrayData" ) == 0 )
		parse_tag_reset ( xd );
}

static void simplearraydata_cad_start ( xml_data *xd, const char *el, const char **attr )
{
	setup_to_read_leaf_tag ( xd, simplearraydata_end, value_cad_end );
}

static void simplearraydata_hr_start ( xml_data *xd, const char *el, const char **attr )
{
	setup_to_read_leaf_tag ( xd, simplearraydata_end, value_hr_end );
}

static void simplearraydata_temp_start ( xml_data *xd, const char *el, const char **attr )
{
	setup_to_read_leaf_tag ( xd, simplearraydata_end, value_temp_end );
}

static void schemadata_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "SchemaData" ) == 0 )
		parse_tag_reset ( xd );
}

static void schemadata_start ( xml_data *xd, const char *el, const char **attr )
{
	// Looking for cadence or heartrate or temperature
	if ( g_strcmp0 ( el, "gx:SimpleArrayData" ) == 0 ) {
		const gchar *name = get_attr ( attr, "name" );
		if ( g_strcmp0 ( name, "cadence" ) == 0 )
			setup_to_read_next_level_tag ( xd, schemadata_start, schemadata_end, simplearraydata_cad_start, simplearraydata_end );
		else if ( g_strcmp0 ( name, "heartrate" ) == 0 )
			setup_to_read_next_level_tag ( xd, schemadata_start, schemadata_end, simplearraydata_hr_start, simplearraydata_end );
		else if ( g_strcmp0 ( name, "temperature" ) == 0 )
			setup_to_read_next_level_tag ( xd, schemadata_start, schemadata_end, simplearraydata_temp_start, simplearraydata_end );
	}
}

static void extendeddata_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "ExtendedData" ) == 0 )
		parse_tag_reset ( xd );
}

static void extendeddata_start ( xml_data *xd, const char *el, const char **attr )
{
	if ( g_strcmp0 ( el, "SchemaData" ) == 0 )
		setup_to_read_next_level_tag ( xd, extendeddata_start, extendeddata_end, schemadata_start, schemadata_end );
}

static void track_start ( xml_data *xd, const char *el, const char **attr )
{
	// Ignore ''altitudeMode', 'gx:angles', 'Model'
	// Read values from when + ExtendedData into separate lists
	//  merging into the main trackpoints list once the track end is reached
	if ( g_strcmp0 ( el, "gx:coord" ) == 0 ) {
		xd->trackpoint = vik_trackpoint_new();
		setup_to_read_leaf_tag ( xd, track_end, track_coordinates_end );
	} else if ( g_strcmp0 ( el, "when" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, track_end, track_when_end );
	} else if ( g_strcmp0 ( el, "ExtendedData" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, track_start, track_end, extendeddata_start, extendeddata_end );
	}
}

static void multitrack_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "gx:MultiTrack" ) == 0 ) {
		// Join tracks together (but keep segments)
		parse_tag_reset ( xd );
		if ( xd->tracks ) {
			// Copy first track - so its not freed when the list of tracks are
			xd->track = vik_track_copy ( VIK_TRACK(xd->tracks->data), TRUE );
			guint count = 1;
			for ( GList *gl = xd->tracks; gl != NULL; gl = gl->next ) {
				// Don't append the first track to itself
				if ( count > 1 ) {
					vik_track_steal_and_append_trackpoints ( xd->track, VIK_TRACK(gl->data) );
				}
				count++;
			}
			vik_trw_layer_filein_add_track ( xd->vtl, NULL, xd->track );
			xd->track = NULL;
			g_list_free_full ( xd->tracks, (GDestroyNotify)vik_track_free );
		}
	}
}

static void multitrack_start ( xml_data *xd, const char *el, const char **attr )
{
	// Ignore ''altitudeMode', 'gx:interpolate'
	if ( g_strcmp0 ( el, "gx:Track" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, multitrack_start, multitrack_end, track_start, track_end );
		xd->track = vik_track_new();
		xd->tracks = g_list_append ( xd->tracks, xd->track );
	}
}

static void placemark_start ( xml_data *xd, const char *el, const char **attr )
{
	// NB ignores <MultiGeometry> levels and reads anything found in it anyway
	if ( g_strcmp0 ( el, "name" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, placemark_end, name_end );
	} else if ( g_strcmp0 ( el, "visibility" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, placemark_end, visibility_end );
	} else if ( g_strcmp0 ( el, "snippet" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, placemark_end, snippet_end );
	} else if ( g_strcmp0 ( el, "description" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, placemark_end, description_end );
	} else if ( g_strcmp0 ( el, "styleUrl" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, placemark_end, styleUrl_end );
	} else if ( g_strcmp0 ( el, "TimeStamp" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, timestamp_start, timestamp_end );
	} else if ( g_strcmp0 ( el, "Point" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, point_start, point_end );
	} else if ( g_strcmp0 ( el, "LineString" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, linestring_start, linestring_end );
		xd->track = vik_track_new();
		xd->track->is_route = TRUE;
	} else if ( g_strcmp0 ( el, "LinearRing" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, linearring_start, linearring_end );
		xd->track = vik_track_new();
		xd->track->is_route = TRUE;
	} else if ( g_strcmp0 ( el, "gx:Track" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, track_start, track_end );
		xd->track = vik_track_new();
	} else if ( g_strcmp0 ( el, "gx:MultiTrack" ) == 0 ) {
		setup_to_read_next_level_tag ( xd, placemark_start, placemark_end, multitrack_start, multitrack_end );
	}
}

static void top_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "kml" ) == 0 )
		XML_SetEndElementHandler ( xd->parser, (XML_EndElementHandler)g_queue_pop_head(xd->gq_end) );
}

// Fwd declarations
static void document_start ( xml_data *xd, const char *el, const char **attr );
static void document_end ( xml_data *xd, const char *el );

static void top_start ( xml_data *xd, const char *el, const char **attr )
{
	// NB also finds Placemarks whereever they may be in Document or Folder levels as well
	if ( g_strcmp0 ( el, "Placemark" ) == 0 )
		setup_to_read_next_level_tag ( xd, top_start, top_end, placemark_start, placemark_end );

	if ( g_strcmp0 ( el, "Document" ) == 0 )
		setup_to_read_next_level_tag ( xd, top_start, top_end, document_start, document_end );
}

static void color_end ( xml_data *xd, const char *el )
{
	g_free ( xd->color );
	xd->color = g_strdup ( xd->c_cdata->str );
	end_leaf_tag ( xd );
}

static void linestyle_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "LineStyle" ) == 0 )
		parse_tag_reset ( xd );
}

static void linestyle_start ( xml_data *xd, const char *el, const char **attr )
{
	if ( g_strcmp0 ( el, "color" ) == 0 )
		setup_to_read_leaf_tag ( xd, linestyle_end, color_end );
	// else read "width"...
}

static void style_end ( xml_data *xd, const char *el )
{
	// set style color
	if ( g_strcmp0 ( el, "Style" ) == 0 ) {
		// If color hasn't been defined then don't create a style
		if ( xd->color ) {
			AnyStyle *as = g_malloc ( sizeof(AnyStyle) );
			as->color = g_strdup ( xd->color );
			//as->width = xd->width;
			//as->scale = xd->scale;
			(void)g_hash_table_insert ( xd->styles, g_strdup(xd->styleName), as );
			g_debug ( "%s: inserting style %s", __FUNCTION__, xd->styleName );
		}
		parse_tag_reset ( xd );
	}
}

static void style_start ( xml_data *xd, const char *el, const char **attr )
{
	if ( g_strcmp0 ( el, "LineStyle" ) == 0 )
		setup_to_read_next_level_tag ( xd, style_start, style_end, linestyle_start, linestyle_end );
	// else "IconStyle"...
}

static void doc_name_end ( xml_data *xd, const char *el )
{
	// Only do the first encountered name
	// (hopefully applying to top level document and thus equivalent to the vtl,
	//  rather than any other documents and/or folders)
	if ( !xd->layer_has_been_named ) {
		vik_layer_rename ( VIK_LAYER(xd->vtl), xd->c_cdata->str );
		xd->layer_has_been_named = TRUE;
	}
	end_leaf_tag ( xd );
}

static void doc_description_end ( xml_data *xd, const char *el )
{
	VikTRWMetadata *meta = vik_trw_layer_get_metadata ( xd->vtl );
	if ( meta ) {
		// Similar to the above name field;
		// Only do the first encountered description
		if ( !xd->layer_has_description ) {
			g_free (meta->description);
			meta->description = g_strdup ( xd->c_cdata->str );
			xd->layer_has_description = TRUE;
		}
	}
	end_leaf_tag ( xd );
}

static void document_start ( xml_data *xd, const char *el, const char **attr )
{
	if ( g_strcmp0 ( el, "name" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, document_end, doc_name_end );
	}
	if ( g_strcmp0 ( el, "description" ) == 0 ) {
		setup_to_read_leaf_tag ( xd, document_end, doc_description_end );
	}

	if ( g_strcmp0 ( el, "Placemark" ) == 0 )
		setup_to_read_next_level_tag ( xd, top_start, top_end, placemark_start, placemark_end );

	if ( g_strcmp0 ( el, "Style" ) == 0 ) {
		const gchar *name = get_attr ( attr, "id" );
		if ( name ) {
			g_free ( xd->styleName );
			xd->styleName = g_strdup ( name );
		}
		// Styles are within <Document>
		// ATM even if there are mulitple documents - styles are combined across all of them
		setup_to_read_next_level_tag ( xd, document_start, document_end, style_start, style_end );
	}
}

static void document_end ( xml_data *xd, const char *el )
{
	if ( g_strcmp0 ( el, "Document" ) == 0 )
		parse_tag_reset ( xd );
}

static void kml_start ( xml_data *xd, const char *el, const char **attr )
{
	if ( g_strcmp0 ( el, "kml" ) )
		XML_StopParser ( xd->parser, XML_FALSE );

	XML_SetStartElementHandler ( xd->parser, (XML_StartElementHandler)top_start );
}

static void kml_end ( xml_data *xd, const char *el )
{
	g_debug ( G_STRLOC );
}

static void kml_cdata ( xml_data *xd, const XML_Char *ss, int len )
{
	if ( xd->use_cdata ) {
		g_string_append_len ( xd->c_cdata, ss, len );
	}
}

/**
 * a_kml_read_file:
 * @FILE: The KML file to open
 * @VikTrwLayer: The Layer to put the geo data in
 *
 * Returns:
 *  TRUE on success
 */
gboolean a_kml_read_file ( VikTrwLayer *vtl, FILE *ff, gboolean external )
{
	gchar buffer[4096];
	XML_Parser parser = XML_ParserCreate(NULL);
	enum XML_Status status = XML_STATUS_ERROR;

	unnamed_waypoints = 1;
	unnamed_tracks = 1;
	unnamed_routes = 1;

	xml_data *xd = g_malloc0 ( sizeof (xml_data) );
	// Set default allocations / settings:
	xd->c_cdata = g_string_new ( "" );
	xd->vtl = vtl;
	xd->gq_start = g_queue_new();
	xd->gq_end = g_queue_new();
	xd->parser = parser;
	xd->styles = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_free );
	// Other default values
	reset_xd ( xd );

	// Always force V1.1, since we may read in 'extended' data like cadence, etc...
	vik_trw_layer_set_gpx_version ( vtl, GPX_V1_1 );

	// The premise of handling tags is thar for each level down the xml tree,
	//  we use an appropriate handler for that tag
	//   (which knows how to process the data being read in at that point)
	// And then when the end of the tag is reached, restore the previously active tag handlers
	g_queue_push_head ( xd->gq_start, kml_start );
	g_queue_push_head ( xd->gq_end, kml_end );
	XML_SetElementHandler ( parser, (XML_StartElementHandler)kml_start, (XML_EndElementHandler)kml_end );
	XML_SetUserData ( parser, xd );
	XML_SetCharacterDataHandler ( parser, (XML_CharacterDataHandler)kml_cdata);

	int done=0, len;
	while ( !done ) {
		len = fread ( buffer, 1, sizeof(buffer)-7, ff );
		done = feof ( ff ) || !len;
		status = XML_Parse ( parser, buffer, len, done );
	}

	gboolean ans = (status != XML_STATUS_ERROR);
	if ( !ans ) {
		g_warning ( "%s: XML error %s at line %ld", G_STRLOC, XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser) );
	}

	XML_ParserFree ( parser );

	g_hash_table_destroy ( xd->styles );
	g_queue_free ( xd->gq_start );
	g_queue_free ( xd->gq_end );
	g_string_free ( xd->c_cdata, TRUE );
	g_free ( xd );
	return ans;
}
