/*
 * Course Generator
 * Copyright (C) 2016 Pierre Delore
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package course_generator;

import static course_generator.utils.Utils.CalcDistance;

import java.awt.Color;
import java.awt.Component;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;

import javax.swing.JOptionPane;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.joda.time.DateTime;

import com.sun.xml.txw2.output.IndentingXMLStreamWriter;

import course_generator.param.ParamData;
import course_generator.settings.CgSettings;
import course_generator.utils.CgConst;
import course_generator.utils.CgLog;
import course_generator.utils.StatData;
import course_generator.utils.Utils;
import course_generator.utils.Utils.CalcLineResult;

/**
 * @author pierre.delore
 */
public class TrackData {

	private static TrackData instance;

	/** Slope/Speed parameters **/
	public ParamData param = null;
	/** Parameters file name **/
	public String Paramfile = "";

	/** Arraylist containing the main data **/
	public ArrayList<CgData> data;

	/** Statistics data for 'in night' **/
	public StatData tInNight;
	/** Statistics data for 'in day' **/
	public StatData tInDay;

	/** Statistics data for 'slope' **/
	public StatData[] StatSlope;
	/** Statistics data for 'elevation' **/
	public StatData[] StatElev;
	/** Statistics data for 'elevation during night' **/
	public StatData[] StatElevNight;
	/** Statistics data for 'elevation during day' **/
	public StatData[] StatElevDay;

	/** Track name **/
	public String Name; // Track name
	/** Full track name with **/
	public String FullName; // Track name
	/** New track **/
	public boolean isNewTrack;

	/** Name of the track that appear in the track setting **/
	public String CourseName = "";
	/** Total distance in meters **/
	private double TotalDistance = 0.0;
	/** Total time in seconde **/
	public int TotalTime = 0;
	/** Indicate if during the last loading the time field as been loaded **/
	public boolean isTimeLoaded = false;
	/**
	 * Indicate if the data has been calculated. false=They must be calculate
	 **/
	public boolean isCalculated = false;
	/** If 'true' this indicate that the data has been modified **/
	public boolean isModified = false;
	/** Contain the ascent climb of the whole track (in m) **/
	private double ClimbP = 0.0;
	/** Contain the descent climb of the whole track (in m) **/
	private double ClimbM = 0.0;
	/** Contain the ascent time of the whole track (in s) **/
	public int AscTime = 0;
	/** Contain the descent time of the whole track (in s) **/
	public int DescTime = 0;
	/** Description of the track **/
	public String Description = "";
	/** Indicate if the autocalc function is active **/
	public boolean isAutoCalc = false;
	/** Start time of the track **/
	public DateTime StartTime = new DateTime();
	/** Minimum elevation of the track (in m) **/
	private double MinElev = -1;
	/** Maximum elevation of the track (in m) **/
	private double MaxElev = -1;
	/** Filter value for the elevation filter **/
	public int SmoothFilter = 0;
	/** Global start health coefficient **/
	public double StartGlobalCoeff = 100;
	/** Global end health coefficient **/
	public double EndGlobalCoeff = 100;
	/** If 'true' this indicate that the time limit has been reached **/
	public boolean isTimeLimit = false;
	/** Indicate where the timelimit has been reached (-1 if none) **/
	public int TimeLimit_Line = -1;
	/** Timezone offset for the sunrise/sunset calculation **/
	public int timeZoneOffsetHours = 0;
	/** Time Zone Id **/
	public String timeZoneId = "";
	/** Are we using the daylight saving time for the sunrise/sunset calculation **/
	public boolean TrackUseDaylightSaving = false;
	public double StartSpeed = 0.0;
	public double EndSpeed = 0.0;
	/** Number of meter of road in the track **/
	private double DistRoad = 0.0;
	/** Indicate the type of error during the file reading **/
	public int ReadError = 0;
	/** Indicate the at which line the error appear **/
	public int ReadLineError = 0;

	// -- Night Coeff --
	/** Start night time **/
	public DateTime StartNightTime;
	/** End night time **/
	public DateTime EndNightTime;
	private final static DateTime defaultSunriseSunsetTime = new DateTime(2010, 1, 1, 0, 0, 0);
	/** If 'true' this indicate that the night coefficients are used ***/
	public boolean bNightCoeff = false;
	/** Ascent nigth coefficient (100%=normal) **/
	public double NightCoeffAsc = 100.0;
	/** Descent nigth coefficient (100%=normal) **/
	public double NightCoeffDesc = 100.0;

	// -- Elevation coeff
	/** Indicate that the elevation effect is used during the calculation **/
	public boolean bElevEffect = false;

	// -- Profil mini-roadbook
	/** Width of the mini roadbook (in pixels) **/
	public int MrbSizeW = 1000;
	/** Height of the mini roadbook (in pixels) **/
	public int MrbSizeH = 480;
	/** Curve filter in the mini roadbook **/
	public int CurveFilter = 1;
	/** Number of characters before the line is word wrapped **/
	public int WordWrapLength = 25;
	/** Position of the label. 'true'=bottom **/
	public boolean LabelToBottom = false;
	/** Type of profil in the mini roadbook **/
	public int MRBType = 0;
	/** Top margin size in pixels in the mini roadbook **/
	public int TopMargin = 100;
	/** Show the night and day on the mrb profil **/
	public boolean bShowNightDay = true;
	// ** Indicate this track is read only mode **/
	public boolean ReadOnly = false;

	// -- Profil colors
	public Color clProfil_Simple_Fill = Color.BLACK;
	public Color clProfil_Simple_Border = Color.BLACK;
	public Color clProfil_RS_Road = Color.BLACK;
	public Color clProfil_RS_Path = Color.BLACK;
	public Color clProfil_RS_Border = Color.BLACK;

	public Color clProfil_SlopeInf5 = Color.BLACK;
	public Color clProfil_SlopeInf10 = Color.BLACK;
	public Color clProfil_SlopeInf15 = Color.BLACK;
	public Color clProfil_SlopeSup15 = Color.BLACK;
	public Color clProfil_SlopeBorder = Color.BLACK;

	private static CgSettings Settings;

	// -- Constructor --
	public TrackData(CgSettings settings) {
		Name = "";
		FullName = "";
		isNewTrack = true;
		param = new ParamData();
		Paramfile = "Default";
		data = new ArrayList<CgData>();
		tInNight = new StatData();
		tInDay = new StatData();
		StatSlope = new StatData[13]; // : Array [0..12] of TStat;
		for (int i = 0; i < 13; i++) {
			StatSlope[i] = new StatData();
		}

		StatElev = new StatData[6]; // : Array [0..5] of TStat;
		StatElevNight = new StatData[6]; // : Array [0..5] of TStat;
		StatElevDay = new StatData[6]; // : Array [0..5] of TStat;

		for (int i = 0; i < 6; i++) {
			StatElev[i] = new StatData();
			StatElevNight[i] = new StatData();
			StatElevDay[i] = new StatData();
		}

		timeZoneOffsetHours = 0;
		timeZoneId = "";
		StartNightTime = defaultSunriseSunsetTime;
		EndNightTime = defaultSunriseSunsetTime;

		MrbSizeW = settings.DefMrbWidth;
		MrbSizeH = settings.DefMrbHeight;
		CurveFilter = 1;
		WordWrapLength = 25;
		SmoothFilter = 0;

		DefaultMRBProfilSimpleColor();
		DefaultMRBProfilRSColor();
		DefaultMRBProfilSlopeColor();

		ReadOnly = false;
		Settings = settings;
	}

	public static synchronized TrackData getInstance() {
		if (instance == null) {
			instance = new TrackData(Settings);
		}
		return instance;
	}

	/**
	 * Initialize the mini roadbook profil color (simple mode)
	 */
	public void DefaultMRBProfilSimpleColor() {
		clProfil_Simple_Fill = CgConst.CL_PROFIL_SIMPLE_FILL;
		clProfil_Simple_Border = CgConst.CL_PROFIL_SIMPLE_BORDER;
	}

	/**
	 * Initialize the mini roadbook profil color (Road and track mode)
	 */
	public void DefaultMRBProfilRSColor() {
		clProfil_RS_Road = CgConst.CL_PROFIL_RS_ROAD;
		clProfil_RS_Path = CgConst.CL_PROFIL_RS_PATH;
		clProfil_RS_Border = CgConst.CL_PROFIL_RS_BORDER;
	}

	/**
	 * Initialize the mini roadbook profil color (Slope mode)
	 */
	public void DefaultMRBProfilSlopeColor() {
		clProfil_SlopeInf5 = CgConst.CL_PROFIL_SLOPE_INF5;
		clProfil_SlopeInf10 = CgConst.CL_PROFIL_SLOPE_INF10;
		clProfil_SlopeInf15 = CgConst.CL_PROFIL_SLOPE_INF15;
		clProfil_SlopeSup15 = CgConst.CL_PROFIL_SLOPE_SUP15;
		clProfil_SlopeBorder = CgConst.CL_PROFIL_SLOPE_BORDER;
	}

	/**
	 * Return the total distance in meter
	 * 
	 * @return
	 */
	public double getTotalDistance() {
		return TotalDistance;
	}

	/**
	 * Return the total distance. Unit depend of the 'unit'
	 * 
	 * @param unit Unit
	 * @return distance
	 */
	public double getTotalDistance(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return TotalDistance;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2uMiles(TotalDistance);
		default:
			return TotalDistance;
		}
	}

	public void setTotalDistance(double totalDistance) {
		TotalDistance = totalDistance;
	}

	/***
	 * Check the GPS point density on the track. If the result is too high, ask if
	 * CG need to filter the track
	 * 
	 * @return true : filter must be apply
	 */
	private boolean CheckPointDensity(double PosFilterAskThreshold) {
		java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("course_generator/Bundle");

		CalcDist();

		int nb = 0;
		boolean ok = false;

		// Scan the data
		for (CgData r : data) {
			if (r.getDist(CgConst.UNIT_METER) < 10.0)
				nb += 1;
		}
		double p = nb * 100.0 / data.size();
		CgLog.info("Point density calculation = " + p + "%");

		// Question?
		if (p >= PosFilterAskThreshold) {
			if (JOptionPane.showConfirmDialog(null,
					String.format(bundle.getString("TrackData.PositionFilterQuestion"), p), "",
					JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
				ok = true;
			}
		}

		return ok;
	}

	/**
	 * Read a GPX file and store the data in the array
	 * 
	 * @param name Full name of the file
	 * @param mode 0 = Replace the existing data by the new data 1 = Insert the new
	 *             data at the beginning of the existing data 2 = Add the new data
	 *             at the end of the existing data
	 * @return Return true if time data have been loaded
	 * @throws Exception
	 */
	public boolean OpenGPX(String name, int mode, double PosFilterAskThreshold) throws Exception {
		SaxGPXHandler GPXhandler = new SaxGPXHandler();

		int ret = GPXhandler.readDataFromGPX(name, this, mode);
		if (ret != 0)
			CgLog.error("TrackData.OpenGPX : Error while reading '" + name + "'. Line =" + GPXhandler.getErrLine());

		CgLog.info(data.size() + " positions loaded.");

		boolean filter = CheckPointDensity(PosFilterAskThreshold);

		// -- Positions filter
		if (filter) {
			PositionFilter();
			CgLog.info(data.size() + " positions after positions filter.");
		}

		// -- Set the line number
		int cmpt = 1;
		for (CgData r : data) {
			r.setNum(cmpt);
			cmpt++;
		}

		isCalculated = false;
		isModified = false;

		if (mode == 0)
			isNewTrack = true;

		// -- Calculate the main data of the track
		CalcMainData(true, false);

		// StartTime = data.get(0).getHour().equals(new DateTime(1970, 1, 1, 0, 0, 0)) ?
		// new DateTime(2010, 1, 1, 0, 0) : data.get(0).getHour();
		StartTime = data.get(0).getHour().equals(new DateTime(1970, 1, 1, 0, 0, 0)) ? DateTime.now()
				: data.get(0).getHour();

		timeZoneOffsetHours = 0;
		timeZoneId = "";
		StartNightTime = defaultSunriseSunsetTime;
		EndNightTime = defaultSunriseSunsetTime;

		SetNightBit();

		isCalculated = true;
		Name = new File(name).getName();

		switch (mode) {
		case 1:
			CgLog.info("TrackData.OpenGPX : '" + name + "' imported at the end of the data");
			break;
		case 2:
			CgLog.info("TrackData.OpenGPX : '" + name + "' imported at the start of the data");
			break;
		default:
			CgLog.info("TrackData.OpenGPX : '" + name + "' loaded");
		}

		return isTimeLoaded;
	} // -- OpenGPX

	// -- Save GPX file (complet or partial) --
	/**
	 * Save the track in GPX format
	 * 
	 * @param name  Name of the file
	 * @param start Index of the first point to save
	 * @param end   Index of the last point to save
	 */
	public void ExportGPX(String name, int start, int end) {
		/*
		 * <?xml version="1.0"?> <gpx creator=
		 * "GPS Visualizer http://www.gpsvisualizer.com/" version="1.0"
		 * xmlns="http://www.topografix.com/GPX/1/0"
		 * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
		 * "http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"
		 * > <trk> <name>Lamontagnhard</name> <desc>http://www.lamontagnhard.fr</desc>
		 * <trkseg> <trkpt lat="45.8547528" lon="6.7226378"> <ele>1180.4</ele>
		 * <time>2010-06-11T11:41:10.000Z</time> </trkpt> </trkseg> </trk> </gpx>
		 */

		if (data.size() <= 0) {
			return;
		}

		long ts = System.currentTimeMillis();

		// -- Save the data in the home directory
		XMLOutputFactory factory = XMLOutputFactory.newInstance();
		try {
			BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(name));
			XMLStreamWriter writer = new IndentingXMLStreamWriter(
					factory.createXMLStreamWriter(bufferedOutputStream, "UTF-8"));

			// writer.writeStartDocument("UTF-8", "1.0");
			// writer.writeComment("Course Generator (C) Pierre DELORE");
			// writer.writeStartElement("CONFIG");
			// Utils.WriteStringToXML(writer, "PARAMFILE", ParamFile);

			// DateTime dt = new DateTime();

			writer.writeStartDocument("UTF-8", "1.0");
			writer.writeStartElement("gpx");
			writer.writeAttribute("creator", "Course Generator http://www.techandrun.com");
			writer.writeAttribute("version", "1.1");
			writer.writeAttribute("xsi:schemaLocation",
					"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/ActivityExtension/v1 http://www8.garmin.com/xmlschemas/ActivityExtensionv1.xsd http://www.garmin.com/xmlschemas/AdventuresExtensions/v1 http://www8.garmin.com/xmlschemas/AdventuresExtensionv1.xsd http://www.garmin.com/xmlschemas/PressureExtension/v1 http://www.garmin.com/xmlschemas/PressureExtensionv1.xsd http://www.garmin.com/xmlschemas/TripExtensions/v1 http://www.garmin.com/xmlschemas/TripExtensionsv1.xsd http://www.garmin.com/xmlschemas/TripMetaDataExtensions/v1 http://www.garmin.com/xmlschemas/TripMetaDataExtensionsv1.xsd http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensions/v1 http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensionsv1.xsd http://www.garmin.com/xmlschemas/CreationTimeExtension/v1 http://www.garmin.com/xmlschemas/CreationTimeExtensionsv1.xsd http://www.garmin.com/xmlschemas/AccelerationExtension/v1 http://www.garmin.com/xmlschemas/AccelerationExtensionv1.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 http://www.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/VideoExtension/v1 http://www.garmin.com/xmlschemas/VideoExtensionv1.xsd");
			writer.writeAttribute("xmlns", "http://www.topografix.com/GPX/1/1");
			writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
			writer.writeAttribute("xmlns:wptx1", "http://www.garmin.com/xmlschemas/WaypointExtension/v1");
			writer.writeAttribute("xmlns:gpxtrx", "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			writer.writeAttribute("xmlns:gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1");
			writer.writeAttribute("xmlns:gpxx", "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			writer.writeAttribute("xmlns:trp", "http://www.garmin.com/xmlschemas/TripExtensions/v1");
			writer.writeAttribute("xmlns:adv", "http://www.garmin.com/xmlschemas/AdventuresExtensions/v1");
			writer.writeAttribute("xmlns:prs", "http://www.garmin.com/xmlschemas/PressureExtension/v1");
			writer.writeAttribute("xmlns:tmd", "http://www.garmin.com/xmlschemas/TripMetaDataExtensions/v1");
			writer.writeAttribute("xmlns:vptm",
					"http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensions/v1");
			writer.writeAttribute("xmlns:ctx", "http://www.garmin.com/xmlschemas/CreationTimeExtension/v1");
			writer.writeAttribute("xmlns:gpxacc", "http://www.garmin.com/xmlschemas/AccelerationExtension/v1");
			writer.writeAttribute("xmlns:gpxpx", "http://www.garmin.com/xmlschemas/PowerExtension/v1");
			writer.writeAttribute("xmlns:vidx1", "http://www.garmin.com/xmlschemas/VideoExtension/v1");

			// Utils.WriteStringToXML(writer, "creator", "Course Generator
			// http://www.techandrun.com");
			// Utils.WriteStringToXML(writer, "version", "1.1");
			//
			// Utils.WriteStringToXML(writer, "xsi:schemaLocation",
			// "http://www.topografix.com/GPX/1/1
			// http://www.topografix.com/GPX/1/1/gpx.xsd
			// http://www.garmin.com/xmlschemas/WaypointExtension/v1
			// http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd
			// http://www.garmin.com/xmlschemas/TrackPointExtension/v1
			// http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
			// http://www.garmin.com/xmlschemas/GpxExtensions/v3
			// http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd
			// http://www.garmin.com/xmlschemas/ActivityExtension/v1
			// http://www8.garmin.com/xmlschemas/ActivityExtensionv1.xsd
			// http://www.garmin.com/xmlschemas/AdventuresExtensions/v1
			// http://www8.garmin.com/xmlschemas/AdventuresExtensionv1.xsd");
			// Utils.WriteStringToXML(writer, "xmlns",
			// "http://www.topografix.com/GPX/1/1");
			// Utils.WriteStringToXML(writer, "xmlns:xsi",
			// "http://www.w3.org/2001/XMLSchema-instance");
			// Utils.WriteStringToXML(writer, "xmlns:wptx1",
			// "http://www.garmin.com/xmlschemas/WaypointExtension/v1");
			// Utils.WriteStringToXML(writer, "xmlns:gpxtrx",
			// "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			// Utils.WriteStringToXML(writer, "xmlns:gpxtpx",
			// "http://www.garmin.com/xmlschemas/TrackPointExtension/v1");
			// Utils.WriteStringToXML(writer, "xmlns:gpxx",
			// "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			// Utils.WriteStringToXML(writer, "xmlns:trp",
			// "http://www.garmin.com/xmlschemas/TripExtensions/v1");
			// Utils.WriteStringToXML(writer, "xmlns:adv",
			// "http://www.garmin.com/xmlschemas/AdventuresExtensions/v1");

			// <trk> node
			writer.writeStartElement("trk");

			// <name> node
			Utils.WriteStringToXML(writer, "name", CourseName);

			// <trkseg> node
			writer.writeStartElement("trkseg");

			NumberFormat nf = NumberFormat.getNumberInstance(Locale.ROOT);
			nf.setGroupingUsed(false);
			nf.setMaximumFractionDigits(7);

			int cmpt = 0;
			for (int i = start; i <= end; i++) {
				CgData r = data.get(i);

				// <trkpt>
				// <trkpt lat="45.8547528" lon="6.7226378">
				writer.writeStartElement("trkpt");
				// writer.writeAttribute("lat", String.format(Locale.ROOT,
				// "%1.14f", r.getLatitude()));
				// writer.writeAttribute("lon", String.format(Locale.ROOT,
				// "%1.14f", r.getLongitude()));
				nf.setMaximumFractionDigits(14);
				writer.writeAttribute("lat", nf.format(r.getLatitude()));
				writer.writeAttribute("lon", nf.format(r.getLongitude()));

				// <ele>1180.4</ele>
				// Utils.WriteStringToXML(writer, "ele",
				// String.format(Locale.ROOT, "%1.7f",
				// r.getElevation(CgConst.UNIT_METER)));
				nf.setMaximumFractionDigits(7);
				Utils.WriteStringToXML(writer, "ele", nf.format(r.getElevation(CgConst.UNIT_METER)));

				// <time>2010-08-18T07:57:07.000Z</time>
				// dt = r.getHour().ToUniversalTime();
				Utils.WriteStringToXML(writer, "time", r.getHour().toString());

				// <name>toto</name>
				Utils.WriteStringToXML(writer, "name", String.valueOf(i));

				writer.writeEndElement();// Trkpt

				cmpt++;
			} // for
			writer.writeEndElement();// Trkseg
			writer.writeEndElement();// trk
			writer.writeEndElement();// gpx
			writer.writeEndDocument();
			writer.flush();
			writer.close();
			bufferedOutputStream.close();

			isModified = false;
			// Name = new File(name).getName();

			CgLog.info("TrackData.SaveGPX : '" + name + "' saved");
			CgLog.info(cmpt + " positions saved.");
		} catch (XMLStreamException | IOException e) {
			e.printStackTrace();
		}

		CgLog.info("Save time : " + (System.currentTimeMillis() - ts) + "ms");
	}

	// -- Save tags as waypoint in a GPX file --
	public void SaveWaypoint(String name, int mask) {
		if (data.size() <= 0) {
			return;
		}

		// -- Save the data in the home directory
		XMLOutputFactory factory = XMLOutputFactory.newInstance();
		try {
			BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(name));
			XMLStreamWriter writer = new IndentingXMLStreamWriter(
					factory.createXMLStreamWriter(bufferedOutputStream, "UTF-8"));

			writer.writeStartDocument("UTF-8", "1.0");
			writer.writeStartElement("gpx");
			writer.writeAttribute("creator", "Course Generator http://www.techandrun.com");
			writer.writeAttribute("version", "1.1");
			writer.writeAttribute("xsi:schemaLocation",
					"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/ActivityExtension/v1 http://www8.garmin.com/xmlschemas/ActivityExtensionv1.xsd http://www.garmin.com/xmlschemas/AdventuresExtensions/v1 http://www8.garmin.com/xmlschemas/AdventuresExtensionv1.xsd http://www.garmin.com/xmlschemas/PressureExtension/v1 http://www.garmin.com/xmlschemas/PressureExtensionv1.xsd http://www.garmin.com/xmlschemas/TripExtensions/v1 http://www.garmin.com/xmlschemas/TripExtensionsv1.xsd http://www.garmin.com/xmlschemas/TripMetaDataExtensions/v1 http://www.garmin.com/xmlschemas/TripMetaDataExtensionsv1.xsd http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensions/v1 http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensionsv1.xsd http://www.garmin.com/xmlschemas/CreationTimeExtension/v1 http://www.garmin.com/xmlschemas/CreationTimeExtensionsv1.xsd http://www.garmin.com/xmlschemas/AccelerationExtension/v1 http://www.garmin.com/xmlschemas/AccelerationExtensionv1.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 http://www.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/VideoExtension/v1 http://www.garmin.com/xmlschemas/VideoExtensionv1.xsd");
			writer.writeAttribute("xmlns", "http://www.topografix.com/GPX/1/1");
			writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
			writer.writeAttribute("xmlns:wptx1", "http://www.garmin.com/xmlschemas/WaypointExtension/v1");
			writer.writeAttribute("xmlns:gpxtrx", "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			writer.writeAttribute("xmlns:gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1");
			writer.writeAttribute("xmlns:gpxx", "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
			writer.writeAttribute("xmlns:trp", "http://www.garmin.com/xmlschemas/TripExtensions/v1");
			writer.writeAttribute("xmlns:adv", "http://www.garmin.com/xmlschemas/AdventuresExtensions/v1");
			writer.writeAttribute("xmlns:prs", "http://www.garmin.com/xmlschemas/PressureExtension/v1");
			writer.writeAttribute("xmlns:tmd", "http://www.garmin.com/xmlschemas/TripMetaDataExtensions/v1");
			writer.writeAttribute("xmlns:vptm",
					"http://www.garmin.com/xmlschemas/ViaPointTransportationModeExtensions/v1");
			writer.writeAttribute("xmlns:ctx", "http://www.garmin.com/xmlschemas/CreationTimeExtension/v1");
			writer.writeAttribute("xmlns:gpxacc", "http://www.garmin.com/xmlschemas/AccelerationExtension/v1");
			writer.writeAttribute("xmlns:gpxpx", "http://www.garmin.com/xmlschemas/PowerExtension/v1");
			writer.writeAttribute("xmlns:vidx1", "http://www.garmin.com/xmlschemas/VideoExtension/v1");

			int i = 1;
			String s;
			for (CgData r : data) {
				if ((r.getTag() != 0) && ((r.getTag() & mask) != 0)) {
					// <wpt>
					writer.writeStartElement("wpt");
					writer.writeAttribute("lat", String.format(Locale.ROOT, "%1.14f", r.getLatitude()));
					writer.writeAttribute("lon", String.format(Locale.ROOT, "%1.14f", r.getLongitude()));

					// <time>2010-08-18T07:57:07.000Z</time>
					// Utils.WriteStringToXML(writer,"time",
					// r.getHour().toString()+"Z");

					// <name>toto</name>
					if (r.getName().isEmpty()) {
						Utils.WriteStringToXML(writer, "name", String.format("NoName%d", i));
						i++;
					} else
						Utils.WriteStringToXML(writer, "name", r.getName());

					// <sym>Flag, Green</sym>
					s = "Flag, Green"; // Par defaut
					if ((r.getTag() & CgConst.TAG_HIGH_PT) != 0)
						s = "Summit";
					if ((r.getTag() & CgConst.TAG_WATER_PT) != 0)
						s = "Bar";
					if ((r.getTag() & CgConst.TAG_EAT_PT) != 0)
						s = "Restaurant";

					Utils.WriteStringToXML(writer, "sym", s);

					// <type>user</type>
					Utils.WriteStringToXML(writer, "type", "user");

					writer.writeEndElement();// wpt
				} // if
			} // for
			writer.writeEndElement();// gpx
			writer.writeEndDocument();
			writer.flush();
			writer.close();
			CgLog.info("TrackData.SaveWaypoint : '" + name + "' saved");
		} catch (XMLStreamException | IOException e) {
			e.printStackTrace();
		}
	}

	public void ExportCGP(String name, int mask) {
		if (data.size() <= 0) {
			return;
		}

		// -- Save the data in the home directory
		XMLOutputFactory factory = XMLOutputFactory.newInstance();
		try {
			XMLStreamWriter writer = new IndentingXMLStreamWriter(
					factory.createXMLStreamWriter(new FileOutputStream(name), "UTF-8"));

			writer.writeStartDocument("UTF-8", "1.0");
			writer.writeStartElement("COURSEGENERATOR");
			writer.writeAttribute("VERSION", "1");

			// <Points> node
			writer.writeStartElement("Points");
			for (CgData r : data) {
				if ((((mask & CgConst.TAG_HIGH_PT) != 0) && ((r.getTag() & CgConst.TAG_HIGH_PT) != 0))
						|| (((mask & CgConst.TAG_LOW_PT) != 0) && ((r.getTag() & CgConst.TAG_LOW_PT) != 0))
						|| (((mask & CgConst.TAG_EAT_PT) != 0) && ((r.getTag() & CgConst.TAG_EAT_PT) != 0))
						|| (((mask & CgConst.TAG_WATER_PT) != 0) && ((r.getTag() & CgConst.TAG_WATER_PT) != 0))
						|| (((mask & CgConst.TAG_MARK) != 0) && ((r.getTag() & CgConst.TAG_MARK) != 0))
						|| (((mask & CgConst.TAG_COOL_PT) != 0) && ((r.getTag() & CgConst.TAG_COOL_PT) != 0))
						|| (((mask & CgConst.TAG_NOTE) != 0) && ((r.getTag() & CgConst.TAG_NOTE) != 0))
						|| (((mask & CgConst.TAG_ROADBOOK) != 0) && ((r.getTag() & CgConst.TAG_ROADBOOK) != 0))
						|| (((mask & CgConst.TAG_DROPBAG) != 0) && ((r.getTag() & CgConst.TAG_DROPBAG) != 0))
						|| (((mask & CgConst.TAG_CREW) != 0) && ((r.getTag() & CgConst.TAG_CREW) != 0))
						|| (((mask & CgConst.TAG_FIRST_AID) != 0) && ((r.getTag() & CgConst.TAG_FIRST_AID) != 0))
						|| (((mask & CgConst.TAG_INFO) != 0) && ((r.getTag() & CgConst.TAG_INFO) != 0))) {
					// <Pt>
					writer.writeStartElement("Pt");

					// <LatitudeDegrees>123.456</LatitudeDegrees>
					Utils.WriteStringToXML(writer, "LatitudeDegrees",
							String.format(Locale.ROOT, "%1.14f", r.getLatitude()));

					// <LongitudeDegrees>12345.678</LongitudeDegrees>
					Utils.WriteStringToXML(writer, "LongitudeDegrees",
							String.format(Locale.ROOT, "%1.14f", r.getLongitude()));

					// <AltitudeMeters>625.03515</AltitudeMeters>
					Utils.WriteStringToXML(writer, "AltitudeMeters",
							String.format(Locale.ROOT, "%1.1f", r.getElevation(CgConst.UNIT_METER)));

					// <Comment>AAA</Comment>
					String s = r.getComment().trim();
					if (s.equals(""))
						s = " ";
					Utils.WriteStringToXML(writer, "Comment", s);

					// <Name>AAA</Name>
					s = r.getName().trim();
					if (s.equals(""))
						s = " ";
					Utils.WriteStringToXML(writer, "Name", s);

					// <Tag>1234</Tag>
					Utils.WriteStringToXML(writer, "Tag", String.format("%d", r.getTag()));

					// <EatTime>1234</EatTime>
					Utils.WriteIntToXML(writer, "EatTime", r.getStation());

					// <TimeLimit>1234</TimeLimit>
					Utils.WriteIntToXML(writer, "TimeLimit", r.getTimeLimit());

					// <FmtLbMiniRoadbook>1234</FmtLbMiniRoadbook>
					Utils.WriteStringToXML(writer, "FmtLbMiniRoadbook", r.FmtLbMiniRoadbook);

					// <OptMiniRoadbook>1234</OptMiniRoadbook>
					Utils.WriteIntToXML(writer, "OptMiniRoadbook", r.OptionMiniRoadbook);

					// <VPosMiniRoadbook>1234</VPosMiniRoadbook>
					Utils.WriteIntToXML(writer, "VPosMiniRoadbook", r.VPosMiniRoadbook);

					// <CommentMiniRoadbook>1234</CommentMiniRoadbook>
					Utils.WriteStringToXML(writer, "CommentMiniRoadbook", r.CommentMiniRoadbook);

					// <FontSizeMiniRoadbook>1234</FontSizeMiniRoadbook>
					Utils.WriteIntToXML(writer, "FontSizeMiniRoadbook", r.FontSizeMiniRoadbook);

					writer.writeEndElement(); // Pt
				} // if

			} // for

			writer.writeEndElement(); // Points
			writer.writeEndElement(); // COURSEGENERATOR

			writer.writeEndDocument();
			writer.flush();
			writer.close();
		} catch (XMLStreamException | IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Class used to store the result of a point search
	 * 
	 * @author pierre
	 */
	public static class SearchPointResult {

		public double Distance; // Distance in meter
		public int Point; // -1= no point

		public SearchPointResult(int _point, double _distance) {
			Distance = _distance;
			Point = _point;
		}
	}

	/**
	 * Search the best point from the latitude and longitude
	 * 
	 * @param lat Latitude of the point to search
	 * @param lon Longitude of the point to search
	 * @return Object that contain the found point and the distance from the
	 *         searched point
	 */
	public SearchPointResult SearchPoint(double lat, double lon) {
		CgData cdata;
		double d, best;
		int p;

		best = 10000000000.0; // Big value. It's normal
		p = -1;

		if (data.size() >= 0) {
			for (int i = 0; i < data.size() - 1; i++) {
				cdata = data.get(i);
				d = Math.abs(Utils.CalcDistance(cdata.getLatitude(), cdata.getLongitude(), lat, lon));
				if (d < best) {
					best = d;
					p = i;
				}
			}
		}
		return new SearchPointResult(p, best);
	}

	/**
	 * Search the best point from the latitude and longitude
	 * 
	 * @param dist Distance to search
	 * 
	 * @return Index of the found point
	 */
	public int SearchDistance(double dist) {
		CgData cdata;
		double d, best;
		int p;

		double val = Utils.Distance2Meter(dist, Settings.Unit);

		best = 10000000000.0; // Big value. It's normal
		p = -1;

		if (data.size() >= 0) {
			for (int i = 0; i < data.size() - 1; i++) {
				cdata = data.get(i);
				d = Math.abs(val - cdata.getTotal(CgConst.UNIT_METER));
				if (d < best) {
					best = d;
					p = i;
				}
			}
		}
		return p;
	}

	/*
	 * private void AltitudeFilter() { if (data.size() <= 1) { return; } CgData r =
	 * null; double threshold = 4.0; double oldAlt =
	 * data.get(0).getElevation(CgConst.UNIT_METER); // We don't use the first and
	 * last point for (int i = 1; i < data.size() - 2; i++) { r = data.get(i); if
	 * (Math.abs(r.getElevation(CgConst.UNIT_METER) - oldAlt) < threshold)
	 * r.setElevation(oldAlt); else oldAlt = r.getElevation(CgConst.UNIT_METER); } }
	 */

	/**
	 * Position filter
	 */
	private void PositionFilter() {
		if (data.size() <= 0) {
			return;
		}

		int i = 0;
		CgData r = null;
		CgData r1 = null;
		CgData r2 = null;
		CgData r3 = null;
		CgData r4 = null;

		// Threshold. Point at a distance less than +- threshold are removed
		double threshold = 15.0; // 8.0;

		// Scan the data
		for (i = 0; i < data.size() - 6; i++) {
			r = data.get(i);
			if (r.ToDelete)
				continue;

			r1 = data.get(i + 1);
			r2 = data.get(i + 2);
			r3 = data.get(i + 3);
			r4 = data.get(i + 4);

			double dist1 = CalcDistance(r.getLatitude(), r.getLongitude(), r1.getLatitude(), r1.getLongitude());
			double dist2 = CalcDistance(r.getLatitude(), r.getLongitude(), r2.getLatitude(), r2.getLongitude());
			double dist3 = CalcDistance(r.getLatitude(), r.getLongitude(), r3.getLatitude(), r3.getLongitude());
			double dist4 = CalcDistance(r.getLatitude(), r.getLongitude(), r4.getLatitude(), r4.getLongitude());

			if (dist4 < threshold) {
				r4.ToDelete = true;
			}
			if (dist3 < threshold) {
				r3.ToDelete = true;
			}
			if (dist2 < threshold) {
				r2.ToDelete = true;
			}
			if (dist1 < threshold) {
				r1.ToDelete = true;
			}
		}

		// Removed the marked points
		for (i = data.size() - 1; i >= 0; i--) {
			if (data.get(i).ToDelete)
				data.remove(i);
		}

	}

	/**
	 * Calculate the main data of the track
	 * 
	 * @param CalcHour Indicate hour calculation need to be done
	 */
	public void CalcMainData(boolean CalcHour, boolean SpeedwithTime) {
		CalcDist();
		CalcSpeed(SpeedwithTime);
		CalcSlope();

		CalcClimbResult resClimb = new CalcClimbResult();
		CalcClimb(CgConst.ELEV_NORM, 0, data.size() - 1, resClimb);
		ClimbP = resClimb.cp;
		ClimbM = resClimb.cm;
		AscTime = resClimb.tp;
		DescTime = resClimb.tm;

		if (CalcHour)
			TotalTime = CalcHour();

		SearchMinMaxElevationResult resMinMaxElev = new SearchMinMaxElevationResult();
		resMinMaxElev = SearchMinMaxElevation(0, (data.size() - 1), resMinMaxElev);
		MinElev = resMinMaxElev.min;
		MaxElev = resMinMaxElev.max;
	}

	// -- Calculate Distance ---

	/**
	 * Calculate the distance of each portion of the track and the total distance
	 * The calculation take into account the elevation
	 */
	public void CalcDist() {
		double dist = 0.0;
		double mLat = 0.0;
		double mLon = 0.0;
		double mEle = 0.0;
		double v = 0.0;
		double Lat = 0;
		double Lon = 0;
		double Ele = 0;

		TotalDistance = 0;

		boolean b = false;
		for (CgData r : data) {
			Lat = r.getLatitude();
			Lon = r.getLongitude();
			Ele = r.getElevation(CgConst.UNIT_METER);
			if (b) {
				// -- Calculate the "flat" distance
				dist = CalcDistance(mLat, mLon, Lat, Lon);
				// -- A little bit of Pythagoras theorem in order to include the
				// difference of elevation between the points
				v = Math.sqrt(dist * dist + (Ele - mEle) * (Ele - mEle));

				TotalDistance = TotalDistance + v;
				r.setDist(v);
				r.setTotal(TotalDistance);
			} else {
				r.setDist(0.0);
				r.setTotal(0.0);
				b = true;
			} // if
			mLat = Lat;
			mLon = Lon;
			mEle = Ele;
		}
	} // Calcdist

	/**
	 * Calculate speed !!! To call after Calcdist()
	 */
	public void CalcSpeed(boolean withTime) {
		if (withTime)
//			CalcSpeedWithTime();
		{
			int prevTime = data.get(0).getTime();
			// -- Calculation loop --
			for (CgData r : data) {
				int dTime = r.getTime() - prevTime;

				//double dist = r.getDist(CgConst.UNIT_METER);
				if (dTime != 0.0) {
					r.setSpeed(r.getDist(CgConst.UNIT_METER) / dTime * 3.6);
				} else {
					r.setSpeed(0.0);
				}
				prevTime = r.getTime();
			} // End of the calculation loop --
		} else {
			boolean b = false;
			for (CgData r : data) {
				if (b) {
					if (r.getdTime_f() != 0.0) {
						r.setSpeed(r.getDist(CgConst.UNIT_METER) / r.getdTime_f() * 3.6);
					} else {
						r.setSpeed(0.0);
					}
				} else {
					r.setSpeed(0.0);
					b = true;
				} // if
			}
		}
	} // CalcSpeed

	/**
	 * Calculate slope
	 */
	public void CalcSlope() {
		double delta = 0;
		double dist = 0;
		double mLat = 0;
		double mLon = 0;
		double mEle = 0;
		double Lat = 0;
		double Lon = 0;
		double Ele = 0;

		boolean b = false;
		for (CgData r : data) {

			r.setSlope(0.0);
			Lat = (double) r.getLatitude();
			Lon = (double) r.getLongitude();
			Ele = (double) r.getElevation(CgConst.UNIT_METER);

			if (b) {
				dist = CalcDistance(mLat, mLon, Lat, Lon);

				delta = Ele - mEle;
				r.setdElevation(delta);
				if (dist != 0) {
					r.setSlope(delta / dist * 100);
				} else {
					r.setSlope(0);
				}
			}
			b = true;

			mLat = Lat;
			mLon = Lon;
			mEle = Ele;
		} // for i
	} // Calcslope

	public static class CalcAvrSlopeResult {

		/** Average positive slope **/
		public double AvrSlopeP;
		/** Average negative slope **/
		public double AvrSlopeM;

		/** Total distance with positive climb (stored in m) **/
		private double TotClimbP;
		/** Totale distance with flat (stored in m) **/
		private double TotFlat;
		/** Total distance with negative climb (stored in m) **/
		private double TotClimbM;

		public double getTotClimbP(int unit) {
			switch (unit) {
			case CgConst.UNIT_METER:
				return TotClimbP;
			case CgConst.UNIT_MILES_FEET:
				// meter to miles
				return Utils.Meter2uMiles(TotClimbP);
			default:
				return TotClimbP;
			}
		}

		public void setTotClimbP(double totClimbP) {
			TotClimbP = totClimbP;
		}

		public double getTotFlat(int unit) {
			switch (unit) {
			case CgConst.UNIT_METER:
				return TotFlat;
			case CgConst.UNIT_MILES_FEET:
				// meter to miles
				return Utils.Meter2uMiles(TotFlat);
			default:
				return TotFlat;
			}
		}

		public void setTotFlat(double totFlat) {
			TotFlat = totFlat;
		}

		public double getTotClimbM(int unit) {
			switch (unit) {
			case CgConst.UNIT_METER:
				return TotClimbM;
			case CgConst.UNIT_MILES_FEET:
				// meter to miles
				return Utils.Meter2uMiles(TotClimbM);
			default:
				return TotClimbM;
			}
		}

		public void setTotClimbM(double totClimbM) {
			TotClimbM = totClimbM;
		}

	}

	// -- Calculate climb - and + ---
	public CalcAvrSlopeResult CalcAvrSlope(int StartLine, int EndLine, CalcAvrSlopeResult r) {
		int i = 0;
		int ip = 0;
		int im = 0;
		double slope = 0.0;

		r.AvrSlopeP = 0.0;
		r.AvrSlopeM = 0.0;
		r.TotClimbP = 0.0;
		r.TotFlat = 0.0;
		r.TotClimbM = 0.0;

		if (data.size() > 0) {

			for (i = StartLine; i <= EndLine; i++) {
				slope = data.get(i).getSlope();
				if (slope > 0) {
					r.AvrSlopeP += slope;
					ip++;
				} else {
					r.AvrSlopeM += slope;
					im++;
				}

				if (slope <= -2) {
					r.TotClimbM += data.get(i).getDist(CgConst.UNIT_METER);
				} else if ((slope > -2) && (slope < 2)) {
					r.TotFlat += data.get(i).getDist(CgConst.UNIT_METER);
				} else if (slope >= 2) {
					r.TotClimbP += data.get(i).getDist(CgConst.UNIT_METER);
				}

			} // for i
		} // if
		r.AvrSlopeP = r.AvrSlopeP / ip;
		r.AvrSlopeM = r.AvrSlopeM / im;
		return r;
	} // CalcAvrSlope

	public static class CalcClimbResult {
		public double cp, cm;
		public int tp, tm;
				
		public double getAscendClimbSum(int unit) {
			switch (unit) {
			case CgConst.UNIT_METER:
				return cp;
			case CgConst.UNIT_MILES_FEET:
				// meter to feet
				return Utils.Meter2Feet(cp);
			default:
				return cp;
			}
		}
		
		
		public String getAscendClimbSumString(int unit, boolean withunit) {

			double e = getAscendClimbSum(unit);

			String s = "";
			switch (unit) {
			case CgConst.UNIT_METER:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "m";
				break;
			case CgConst.UNIT_MILES_FEET:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "feet";
				break;
			default:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "m";
				break;
			}
			return s;
		}

		public double getDescentClimbSum(int unit) {
			switch (unit) {
			case CgConst.UNIT_METER:
				return cm;
			case CgConst.UNIT_MILES_FEET:
				// meter to feet
				return Utils.Meter2Feet(cm);
			default:
				return cm;
			}
		}
		
		public String getDescentClimbSumString(int unit, boolean withunit) {

			double e = getDescentClimbSum(unit);

			String s = "";
			switch (unit) {
			case CgConst.UNIT_METER:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "m";
				break;
			case CgConst.UNIT_MILES_FEET:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "feet";
				break;
			default:
				s = String.format("%1.0f", e);
				if (withunit)
					s = s + "m";
				break;
			}
			return s;
		}
	}

	
	// -- Calculate climb - and + ---
	// cp: cumul D+ (m)
	// cm: cumul D- (m)
	// tp: cumul temps en montée (s)
	// tm: cumul temps en descente (s)
	public CalcClimbResult CalcClimb(int elevType, int StartLine, int EndLine, CalcClimbResult r) {
		int i = 0;
		int oldTime = 0;
		int dt = 0;
		double oldElev = 0.0;
		double de = 0.0;
		double elev = 0.0;
		int time = 0;

		r.cp = 0;
		r.cm = 0;
		r.tp = 0;
		r.tm = 0;

		if (data.size() > 0) {
			switch (elevType) {
			case CgConst.ELEV_NOTSMOOTHED:
				oldElev = data.get(StartLine).getElevationNotSmoothed(CgConst.UNIT_METER);
				break;
			case CgConst.ELEV_SMOOTHED:
				oldElev = data.get(StartLine).getElevationSmoothed(CgConst.UNIT_METER);
				break;
			default:
				oldElev = data.get(StartLine).getElevation(CgConst.UNIT_METER);
			}

			oldTime = data.get(StartLine).getTime();

			for (i = StartLine; i <= EndLine; i++) {
				switch (elevType) {
				case CgConst.ELEV_NOTSMOOTHED:
					elev = data.get(i).getElevationNotSmoothed(CgConst.UNIT_METER);
					break;
				case CgConst.ELEV_SMOOTHED:
					elev = data.get(i).getElevationSmoothed(CgConst.UNIT_METER);
					break;
				default:
					elev = data.get(i).getElevation(CgConst.UNIT_METER);
				}

				time = data.get(i).getTime();

				de = (elev - oldElev);
				dt = (time - oldTime);

				if (Math.abs(de) > (double) Settings.ClimbThresholdForCalculation) {
					if (de > 0) {
						r.cp += de;
						r.tp += dt;
					}
					if (de < 0) {
						r.cm -= de;
						r.tm += dt;
					}
					oldElev = elev;
					oldTime = time;
				}
			} // for i
		} // if
		return r;
	} // CalcClimb

	/**
	 * Class to store the result of Average speed calculation
	 * 
	 * @author pierre
	 */
	public static class CalcAvrSpeedResult {

		/** Average speed (km/h) **/
		private double avrspeed;

		public void setAvrspeed(double avrspeed) {
			this.avrspeed = avrspeed;
		}

		public double getAvrspeed() {
			return avrspeed;
		}
	}

	// -- Calculate the average speed between 2 points
	public CalcAvrSpeedResult CalcAvrSpeed(int StartLine, int EndLine, CalcAvrSpeedResult r) {
		int t = 0;

		if (data.size() > 0) {
			t = (data.get(EndLine).getTime() - data.get(StartLine).getTime());
			if (t != 0) {
				r.avrspeed = (data.get(EndLine).getTotal(CgConst.UNIT_METER)
						- data.get(StartLine).getTotal(CgConst.UNIT_METER)) * 3.600 / t;
			} else {
				r.avrspeed = 0.0;
			}
		} // if
		return r;
	} // CalcAvrSpeed

	
	// -- Calculate Hour --
	/**
	 * Calculate the hour for each point
	 * 
	 * @return the total time in second
	 */
	public int CalcHour() {
		int last = 0;
		for (CgData r : data) {
			r.setHour(StartTime.plusSeconds(r.getTime()));
			last = r.getTime();
		} // for i
		return last;
	}

	// -- Calculate road distance (in meter) --
	public double CalcRoad(int start, int end) {
		double road = 0.0;
		// DistRoad = 0.0;
		// for (CgData r : data) {
		for (int i = start; i < end; i++) {
			CgData r = data.get(i);
			if (r.getDiff() == 100) {
				// DistRoad = DistRoad + r.getDist(CgConst.UNIT_METER);
				road = road + r.getDist(CgConst.UNIT_METER);
			}
		}
		return road;
	}

	class SearchMinMaxElevationResult {

		public double min, max;
	}

	/**
	 * Search the minmimum and maximum elevation of the track betwwen two points
	 * 
	 * @param start Starting point
	 * @param end   Ending point
	 * @param r     SearchMinMaxElevationResult object
	 * @return Result in a SearchMinMaxElevationResult object
	 */
	private SearchMinMaxElevationResult SearchMinMaxElevation(int start, int end, SearchMinMaxElevationResult r) {
		r.min = 9999.0;
		r.max = -1.0;
		int i = 0;
		for (i = start; i < end; i++) {
			double e = data.get(i).getElevation(CgConst.UNIT_METER);
			if (e < r.min) {
				r.min = e;
			}
			if (e > r.max) {
				r.max = e;
			}
		}
		return r;
	}

	/*
	 * public void SearchMinMaxElevation(int start, int end, ref min, ref max) {
	 * MinElev = 9999; MaxElev = -1; foreach (cgData r in data) { double e =
	 * r.Elevation; if (e < MinElev) MinElev = e; if (e > MaxElev) MaxElev = e; } }
	 */

	/**
	 * Check if the limit time is reached
	 * 
	 * @return True if the limit time is reached
	 */
	public boolean CheckTimeLimit() {
		int i = 0;
		isTimeLimit = false;
		TimeLimit_Line = -1;
		for (CgData r : data) {
			if ((r.getTimeLimit() != 0) && (r.getTime() > r.getTimeLimit())) {
				isTimeLimit = true;
				TimeLimit_Line = i;
			}
			i++;
		}
		return isTimeLimit;
	}

	/**
	 * Calculate the time for each position of the track
	 */
	public void Calculate() {
		int j = 0;
		int k = 0;
		double ts = 0.0;
		double x = 0.0;
		double x1 = 0.0;
		double y1 = 0.0;
		double x2 = 0.0;
		double y2 = 0.0;
		double y = 0.0;
		double tmp = 0.0;
		double night = 0.0;
		double dt = 0.0;
		double ef = 1.0;
		boolean ok;

		if (param == null) {
			return;
		}

		isTimeLoaded = false;

		// String sParamfile = Utils.GetHomeDir() + "/" + CgConst.CG_DIR + "/" +
		// Paramfile + ".par";

		// -- Search the curve in a special order (user>min_miles>km_h)
		int FolderType = Utils.searchCurveFolder(Paramfile);
		String sParamfile = Utils.getSelectedCurveFolder(FolderType) + Paramfile + ".par";

		try {
			param.Load(sParamfile);
		} catch (Exception e) {
			return;
		}

		ef = 1;

		dt = 0;
		// -- Calculation loop --
		for (CgData r : data) {
			x = r.getSlope();
			if (x > CgConst.MAX_CLIMB) {
				x = CgConst.MAX_CLIMB;
			}
			if (x < CgConst.MIN_CLIMB) {
				x = CgConst.MIN_CLIMB;
			}
			ok = false;
			for (j = 0; j <= param.data.size() - 1; j++) // -2
			{
				tmp = param.data.get(j).getSlope();
				if (tmp >= x) {
					k = j - 1;
					if (j == 0) {
						k = 0;
					}
					x1 = param.data.get(k).getSlope();
					y1 = param.data.get(k).getSpeedNumber();
					x2 = param.data.get(j).getSlope();
					y2 = param.data.get(j).getSpeedNumber();
					ok = true;
					break;
				}
			} // for j

			if (ok) {
				// We give 2 points (slope/speed) and we get "a" and
				// "b" from the line equation
				if (x1 != x2) {
					Utils.CalcLineResult res = new CalcLineResult();
					res = Utils.CalcLine(x1, y1, x2, y2, res);
					// We calculate the line equation. "Y"=speed in km/h
					// and "X"=slope
					y = res.a * x + res.b;
				} else {
					y = y1;
				}
			} else {
				y = param.data.get(param.data.size() - 2).getSpeedNumber();
			}

			// --Night coeff --
			DateTime t = r.getHour();
			if (bNightCoeff
					&& ((Utils.CompareHMS(t, StartNightTime) >= 0) || (Utils.CompareHMS(t, EndNightTime) <= 0))) {
				if (r.getSlope() < -2.0) {
					night = 100.0 / NightCoeffDesc;
					r.setNight(true);
				} else {
					night = 100.0 / NightCoeffAsc;
					r.setNight(true);
				}
			} else {
				night = 1;
				r.setNight(false);
			}

			// --Elev effect --
			if (bElevEffect && ((double) r.getElevation(CgConst.UNIT_METER) > 1500.0)) {
				ef = 1.0 + (Math.round(((double) r.getElevation(CgConst.UNIT_METER) - 1500.0) / 100.0) / 100.0);
			} else {
				ef = 1.0;
			}
			// --
			double dist = r.getDist(CgConst.UNIT_METER);
			double coeff = 100.0;
			double diff = 100.0;

			if (coeff != 0.0) {
				coeff = 100.0 / r.getCoeff();
			}
			if (diff != 0) {
				diff = 100.0 / r.getDiff();
			}

			double station = (double) (r.getStation());

			if (y != 0.0) {
				// Calculate the travel time in second in a part of the track
				ts = (dist / (y / 3.6)) * coeff * diff * night * ef + station;
			} else {
				ts = 0.0;
			}
			dt = dt + ts;

			r.setdTime_f(ts);
			r.setTime((int) Math.round(dt));

			if (ts != 0.0) {
				r.setSpeed(dist * 3.6 / ts);
			} else {
				r.setSpeed(0.0);
			}
			r.setHour(StartTime.plusSeconds((int) (Math.round(dt))));
		} // End of the calculation loop --

		// -- Update the road distance for the track
		DistRoad = CalcRoad(0, data.size() - 1);

		TotalTime = (int) Math.round(dt);
		isCalculated = true;
		isModified = true;
	} // Calculate

	public void CalcSpeedWithTime() {
		if (param == null) {
			return;
		}

		isTimeLoaded = false;

		int prevTime = data.get(0).getTime();
		// -- Calculation loop --
		for (CgData r : data) {
			int dTime = r.getTime() - prevTime;

			double dist = r.getDist(CgConst.UNIT_METER);
			if (dTime != 0.0) {
				r.setSpeed(dist * 3.6 / dTime);
			} else {
				r.setSpeed(0.0);
			}
			prevTime = r.getTime();
		} // End of the calculation loop --
		isModified = true;
	} // Calculate

	/**
	 * Set night bit. Used when we load a track to avoid to launch a new calculation
	 * to have the night display on the map
	 */
	public void SetNightBit() {
		// -- Calculation loop --
		for (CgData r : data) {
			DateTime t = r.getHour();
			if (bNightCoeff
					&& ((Utils.CompareHMS(t, StartNightTime) >= 0) || (Utils.CompareHMS(t, EndNightTime) <= 0))) {
				r.setNight(true);
			} else {
				r.setNight(false);
			}
		}
	} // SetNightBit

	/**
	 * Search the min/max elevation of the track
	 */
	public void CalcMinMaxElevation() {
		int i = 0;
		int j = 0;
		int k = 0;
		double dist = 0.0;
		double valmax = 0.0;
		double valmin = 0.0;
		boolean minm = false;
		boolean maxm = false;
		boolean minp = false;
		boolean maxp = false;
		boolean findmax = false;
		boolean findmin = false;
		CgData r1 = null;
		CgData r2 = null;
		CgData r3 = null;

		// -- Reset bit 0 & 1 of 'Tag' variable
		for (CgData r : data)
			r.setTag((int) r.getTag() & 0xFC);

		// -- Main loop
		for (i = 0; i < data.size(); i++) {
			r1 = data.get(i);

			dist = 0;
			minm = true;
			maxm = true;
			findmax = false;
			findmin = false;
			valmax = -1;
			valmin = -1;

			for (j = i; j >= 0; j--) {
				r2 = data.get(j);

				if ((r2.getTag() & (CgConst.TAG_LOW_PT | CgConst.TAG_HIGH_PT)) == 1) {
					findmax = true;
					valmax = r2.getElevation(CgConst.UNIT_METER);
				}
				if ((r2.getTag() & (CgConst.TAG_LOW_PT | CgConst.TAG_HIGH_PT)) == 2) {
					findmin = true;
					valmin = r2.getElevation(CgConst.UNIT_METER);
				}

				if (r2.getElevation(CgConst.UNIT_METER) > r1.getElevation(CgConst.UNIT_METER)) {
					maxm = false;
				}

				if (r2.getElevation(CgConst.UNIT_METER) < r1.getElevation(CgConst.UNIT_METER)) {
					minm = false;
				}

				dist = dist + r2.getDist(CgConst.UNIT_METER);
				if (dist > CgConst.DIST_MAX_MINMAX) {
					break;
				}
			} // For j

			if ((r1 == null) || (r2 == null)) {
				return;
			}
			if (maxm && (findmax
					|| (Math.abs(r2.getElevation(CgConst.UNIT_METER) - valmax) < CgConst.MIN_ELEV_MINMAX))) {
				maxm = false;
			}
			if (minm && (findmin
					|| (Math.abs(r2.getElevation(CgConst.UNIT_METER) - valmin) < CgConst.MIN_ELEV_MINMAX))) {
				minm = false;
			}

			dist = 0;
			minp = true;
			maxp = true;

			for (k = i; k < data.size(); k++) {
				r3 = data.get(k);
				if (r3.getElevation(CgConst.UNIT_METER) > r1.getElevation(CgConst.UNIT_METER)) {
					maxp = false;
				}

				if (r3.getElevation(CgConst.UNIT_METER) < r1.getElevation(CgConst.UNIT_METER)) {
					minp = false;
				}

				if (k < data.size() - 1) {
					dist = dist + (double) data.get(k + 1).getDist(CgConst.UNIT_METER);
				}

				if (dist > CgConst.DIST_MAX_MINMAX) {
					break;
				}
			}

			if (maxm && maxp) {
				r1.setTag(r1.getTag() | CgConst.TAG_HIGH_PT);
			}
			if (minm && minp) {
				r1.setTag(r1.getTag() | CgConst.TAG_LOW_PT);
			}

		} // Main loop
	} // CalcMinMax

	/**
	 * Invert track
	 */
	public void Invert() {

		if (data.size() >= 0) {

			// -- Invert the list
			Collections.reverse(data);

			// -- Set the line number
			int n = 1;
			for (CgData r : data)
				r.setNum(n++);

			// -- Refresh
			CalcDist();
			CalcSlope();

			CalcClimbResult resClimb = new CalcClimbResult();
			CalcClimb(CgConst.ELEV_NORM, 0, data.size() - 1, resClimb);
			ClimbP = resClimb.cp;
			ClimbM = resClimb.cm;
			AscTime = resClimb.tp;
			DescTime = resClimb.tm;

			CalcHour();

			SearchMinMaxElevationResult resMinMaxElev = new SearchMinMaxElevationResult();
			resMinMaxElev = SearchMinMaxElevation(0, (data.size() - 1), resMinMaxElev);
			MinElev = resMinMaxElev.min;
			MaxElev = resMinMaxElev.max;

			isCalculated = true;
			isModified = true;
		}
	}

	/**
	 * Invert track
	 * 
	 * @param start Index of the starting point
	 */
	public void NewStartingPoint(int start) {

		if (data.size() >= 0) {

			ArrayList<CgData> datatmp;

			datatmp = new ArrayList<CgData>();

			int n = start;
			int nb = 0;
			while (nb < data.size()) {
				datatmp.add(new CgData((double) (nb + 1), data.get(n).getLatitude(), data.get(n).getLongitude(),
						data.get(n).getElevation(CgConst.UNIT_METER),
						data.get(n).getElevationNotSmoothed(CgConst.UNIT_METER),
						data.get(n).getElevationSmoothed(CgConst.UNIT_METER), data.get(n).getElevationMemo(),
						data.get(n).getTag(), data.get(n).getDist(CgConst.UNIT_METER),
						data.get(n).getTotal(CgConst.UNIT_METER), data.get(n).getDiff(), data.get(n).getCoeff(), 0.0,
						data.get(n).getSlope(), data.get(n).getSpeed(CgConst.UNIT_METER),
						data.get(n).getdElevation(CgConst.UNIT_METER), data.get(n).getTime(), data.get(n).getdTime_f(),
						data.get(n).getTimeLimit(), data.get(n).getHour(), data.get(n).getStation(),
						data.get(n).getName(), data.get(n).getComment(), 0.0, 0.0, data.get(n).FmtLbMiniRoadbook,
						data.get(n).OptionMiniRoadbook, data.get(n).VPosMiniRoadbook, data.get(n).CommentMiniRoadbook,
						data.get(n).FontSizeMiniRoadbook));

				nb++;
				n++;
				if (n >= data.size()) {
					n = 0;
				}
			}

			n = 0;
			for (CgData r : data) {
				r.setNum(datatmp.get(n).getNum());
				r.setLatitude(datatmp.get(n).getLatitude());
				r.setLongitude(datatmp.get(n).getLongitude());
				r.setElevation(datatmp.get(n).getElevation(CgConst.UNIT_METER));
				r.setElevationNotSmoothed(datatmp.get(n).getElevationNotSmoothed(CgConst.UNIT_METER));
				r.setElevationSmoothed(datatmp.get(n).getElevationSmoothed(CgConst.UNIT_METER));
				r.setElevationMemo(datatmp.get(n).getElevationMemo());
				r.setTag(datatmp.get(n).getTag());
				r.setDist(datatmp.get(n).getDist(CgConst.UNIT_METER));
				r.setTotal(datatmp.get(n).getTotal(CgConst.UNIT_METER));
				r.setDiff(datatmp.get(n).getDiff());
				r.setCoeff(datatmp.get(n).getCoeff());
				r.setSlope(datatmp.get(n).getSlope());
				r.setSpeed(datatmp.get(n).getSpeed(CgConst.UNIT_METER));
				r.setdElevation(datatmp.get(n).getdElevation(CgConst.UNIT_METER));
				r.setTime(datatmp.get(n).getTime());
				r.setdTime_f(datatmp.get(n).getdTime_f());
				r.setTimeLimit(datatmp.get(n).getTimeLimit());
				r.setHour(datatmp.get(n).getHour());
				r.setStation(datatmp.get(n).getStation());
				r.setName(datatmp.get(n).getName());
				r.setComment(datatmp.get(n).getComment());
				r.FmtLbMiniRoadbook = data.get(n).FmtLbMiniRoadbook;
				r.OptionMiniRoadbook = data.get(n).OptionMiniRoadbook;
				r.VPosMiniRoadbook = data.get(n).VPosMiniRoadbook;
				r.CommentMiniRoadbook = data.get(n).CommentMiniRoadbook;
				n++;
			}

			CalcDist();
			CalcSpeed(false);
			CalcSlope();

			CalcClimbResult resClimb = new CalcClimbResult();
			resClimb = CalcClimb(CgConst.ELEV_NORM, 0, data.size() - 1, resClimb);
			ClimbP = resClimb.cp;
			ClimbM = resClimb.cm;
			AscTime = resClimb.tp;
			DescTime = resClimb.tm;

			CalcHour();

			SearchMinMaxElevationResult resMinMaxElev = new SearchMinMaxElevationResult();
			resMinMaxElev = SearchMinMaxElevation(0, (data.size() - 1), resMinMaxElev);
			MinElev = resMinMaxElev.min;
			MaxElev = resMinMaxElev.max;

			isCalculated = true;
			isModified = true;
		}
	}

	/**
	 * Load a CGX file
	 * 
	 * @param name   name of the file
	 * @param mode   reading mode (0=complet 1=partial)
	 * @param backup Indicate if the load is a backup or not. If it's a backup the
	 *               name will not be updated
	 */
	public void OpenCGX(Component parent, String name, int mode, boolean backup) {
		SaxCGXHandler CGXhandler = new SaxCGXHandler();

		int ret = 0;
		try {
			ret = CGXhandler.readDataFromCGX(parent, name, this, mode);
		} catch (Exception e) {
		}

		if (ret != 0)
			CgLog.error("TrackData.OpenCGX : Error while reading '" + name + "'. Line =" + CGXhandler.getErrLine());

		CgLog.info(data.size() + " positions loaded.");

		/*
		 * Currently not used. Useful for CGX???? // -- Positions filter
		 * PositionFilter(); CgLog.info(data.size() +
		 * " positions after positions filter.");
		 */

		// -- Set the line number
		int cmpt = 1;
		for (CgData r : data) {
			r.setNum(cmpt);
			cmpt++;
		}

		isCalculated = false;
		isModified = false;
		if (mode == 0)
			isNewTrack = true;

		// -- Calculate the main data of the track
		CalcMainData(true, false);

		/*
		 * CalcDist(); CalcSpeed(); CalcSlope();
		 * 
		 * CalcClimbResult resClimb = new CalcClimbResult();
		 * CalcClimb(CgConst.ELEV_NORM, 0, data.size() - 1, resClimb); ClimbP =
		 * resClimb.cp; ClimbM = resClimb.cm; AscTime = resClimb.tp; DescTime =
		 * resClimb.tm;
		 * 
		 * TotalTime = CalcHour();
		 * 
		 * SearchMinMaxElevationResult resMinMaxElev = new
		 * SearchMinMaxElevationResult(); resMinMaxElev = SearchMinMaxElevation(0,
		 * (data.size() - 1), resMinMaxElev); MinElev = resMinMaxElev.min; MaxElev =
		 * resMinMaxElev.max;
		 */

		SetNightBit();

		CheckTimeLimit();
		isCalculated = true;

		if (!backup) {
			Name = new File(name).getName();
			//String Dir = new File(name).getAbsolutePath();
			switch (mode) {
			case 1:
				CgLog.info("TrackData.OpenCGX : '" + name + "' imported at the end of the data");
				break;
			case 2:
				CgLog.info("TrackData.OpenCGX : '" + name + "' imported at the start of the data");
				break;
			default:
				CgLog.info("TrackData.OpenCGX : '" + name + "' loaded");
			}
		}
		FullName = name;

	}// LoadCGX

	/**
	 * Save data in CGX format (complete and partial)
	 * 
	 * @param name  name of the file
	 * @param start first line to save
	 * @param end   last line to save
	 */
	public void SaveCGX(String name, int start, int end, boolean backup) {
		if (data.isEmpty()) {
			return;
		}

		long ts = System.currentTimeMillis();

		XMLOutputFactory factory = XMLOutputFactory.newInstance();
		try {
			BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(name));
			XMLStreamWriter writer = new IndentingXMLStreamWriter(
					factory.createXMLStreamWriter(bufferedOutputStream, "UTF-8"));

			writer.writeStartDocument("UTF-8", "1.0");
			writer.writeStartElement("COURSEGENERATOR");
			Utils.WriteStringToXML(writer, "VERSION", "5");

			double d = data.get(end).getTotal(CgConst.UNIT_METER) - data.get(start).getTotal(CgConst.UNIT_METER);
			Utils.WriteStringToXML(writer, "TOTALDISTANCE", String.format(Locale.ROOT, "%f", d));

			int j = data.get(end).getTime() - data.get(start).getTime();
			Utils.WriteStringToXML(writer, "TOTALSECOND", String.valueOf(j));

			Utils.WriteStringToXML(writer, "COURSENAME", CourseName);
			Utils.WriteStringToXML(writer, "DESCRIPTION", Description);
			Utils.WriteStringToXML(writer, "STARTTIME", StartTime.toString());
			Utils.WriteStringToXML(writer, "USEELEVEFFECT", (bElevEffect ? "1" : "0"));
			Utils.WriteStringToXML(writer, "USENIGHTCOEFF", (bNightCoeff ? "1" : "0"));
			Utils.WriteStringToXML(writer, "NIGHTSTARTTIME", StartNightTime.toString("HH:mm"));
			Utils.WriteStringToXML(writer, "NIGHTENDTIME", EndNightTime.toString("HH:mm"));
			Utils.WriteStringToXML(writer, "NIGHTCOEFF", String.format(Locale.ROOT, "%f", NightCoeffAsc));
			Utils.WriteStringToXML(writer, "NIGHTCOEFFDESC", String.format(Locale.ROOT, "%f", NightCoeffDesc));
			Utils.WriteStringToXML(writer, "STARTGLOBALCOEFF", String.format(Locale.ROOT, "%f", StartGlobalCoeff));
			Utils.WriteStringToXML(writer, "ENDGLOBALCOEFF", String.format(Locale.ROOT, "%f", EndGlobalCoeff));
			Utils.WriteIntToXML(writer, "TIMEZONE", timeZoneOffsetHours);
			Utils.WriteStringToXML(writer, "TIMEZONEID", timeZoneId);
			Utils.WriteStringToXML(writer, "USESUMMERTIME", (TrackUseDaylightSaving ? "1" : "0"));
			Utils.WriteStringToXML(writer, "CURVE", Paramfile);
			Utils.WriteIntToXML(writer, "MRBSIZEW", MrbSizeW);
			Utils.WriteIntToXML(writer, "MRBSIZEH", MrbSizeH);
			Utils.WriteStringToXML(writer, "MRBSHOWDAYNIGHT", (bShowNightDay ? "1" : "0"));

			Utils.WriteIntToXML(writer, "CLPROFILSIMPLEFILL", clProfil_Simple_Fill.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILSIMPLEBORDER", clProfil_Simple_Border.getRGB());

			Utils.WriteIntToXML(writer, "CLPROFILRSROAD", clProfil_RS_Road.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILRSPATH", clProfil_RS_Path.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILRSBORDER", clProfil_RS_Border.getRGB());

			Utils.WriteIntToXML(writer, "CLPROFILSLOPEINF5", clProfil_SlopeInf5.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILSLOPEINF10", clProfil_SlopeInf10.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILSLOPEINF15", clProfil_SlopeInf15.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILSLOPESUP15", clProfil_SlopeSup15.getRGB());
			Utils.WriteIntToXML(writer, "CLPROFILSLOPEBORDER", clProfil_SlopeBorder.getRGB());

			Utils.WriteIntToXML(writer, "MRBCURVEFILTER", CurveFilter);
			Utils.WriteIntToXML(writer, "WORDWRAPLENGTH", WordWrapLength);
			Utils.WriteStringToXML(writer, "LABELTOBOTTOM", (LabelToBottom ? "1" : "0"));
			Utils.WriteIntToXML(writer, "MRBTYPE", MRBType);
			Utils.WriteIntToXML(writer, "TOPMARGIN", TopMargin);

			Utils.WriteIntToXML(writer, "SMOOTHFILTER", SmoothFilter);

			NumberFormat nf = NumberFormat.getNumberInstance(Locale.ROOT);
			nf.setGroupingUsed(false);
			nf.setMaximumFractionDigits(7);

			writer.writeStartElement("TRACKPOINTS");
			int cmpt = 0;
			for (int i = start; i <= end; i++) {
				CgData r = data.get(i);
				writer.writeStartElement("TRACKPOINT");
				Utils.WriteStringToXML(writer, "LATITUDEDEGREES", nf.format(r.getLatitude()));
				Utils.WriteStringToXML(writer, "LONGITUDEDEGREES", nf.format(r.getLongitude()));
				Utils.WriteStringToXML(writer, "ALTITUDEMETERS", nf.format(r.getElevation(CgConst.UNIT_METER)));
				Utils.WriteStringToXML(writer, "ALTITUDEMETERSNOTSMOOTHED",
						nf.format(r.getElevationNotSmoothed(CgConst.UNIT_METER)));
				Utils.WriteStringToXML(writer, "ALTITUDEMETERSSMOOTHED",
						nf.format(r.getElevationSmoothed(CgConst.UNIT_METER)));
				Utils.WriteStringToXML(writer, "DISTANCEMETERS", nf.format(r.getDist(CgConst.UNIT_METER)));
				Utils.WriteStringToXML(writer, "DISTANCEMETERSCUMUL", nf.format(r.getTotal(CgConst.UNIT_METER)));
				Utils.WriteStringToXML(writer, "DIFF", nf.format(r.getDiff()));
				Utils.WriteStringToXML(writer, "COEFF", nf.format(r.getCoeff()));
				Utils.WriteStringToXML(writer, "RECUP", nf.format(r.getRecovery()));
				Utils.WriteIntToXML(writer, "TIMESECONDE", r.getTime());
				Utils.WriteIntToXML(writer, "EATTIME", r.getStation());
				Utils.WriteIntToXML(writer, "TIMELIMIT", r.getTimeLimit());
				Utils.WriteStringToXML(writer, "COMMENT", r.getComment());
				Utils.WriteStringToXML(writer, "NAME", r.getName());
				Utils.WriteIntToXML(writer, "TAG", r.getTag());
				Utils.WriteStringToXML(writer, "FMTLBMINIROADBOOK", r.FmtLbMiniRoadbook);
				Utils.WriteIntToXML(writer, "OPTMINIROADBOOK", r.OptionMiniRoadbook);
				Utils.WriteIntToXML(writer, "VPOSMINIROADBOOK", r.VPosMiniRoadbook);
				Utils.WriteStringToXML(writer, "COMMENTMINIROADBOOK", r.CommentMiniRoadbook);
				Utils.WriteIntToXML(writer, "FONTSIZEMINIROADBOOK", r.FontSizeMiniRoadbook);

				writer.writeEndElement();
				cmpt++;
			} // for
				// TRACKPOINT
			writer.writeEndElement();

			writer.writeEndElement();

			writer.writeEndDocument();
			writer.flush();
			writer.close();
			bufferedOutputStream.close();

			if (!backup)

			{
				isModified = false;
				Name = new File(name).getName();
				CgLog.info("TrackData.SaveCGX : '" + name + "' saved");
				CgLog.info(cmpt + " positions saved.");
			}
			FullName = name;
		} catch (XMLStreamException |

				IOException e) {
			e.printStackTrace();
		}

		CgLog.info("Save time : " + (System.currentTimeMillis() - ts) + "ms");
	}

	/**
	 * Save CSV file
	 * 
	 * @param name      name of the CSV file
	 * @param start     first line of the data to save
	 * @param end       last line of the data to save
	 * @param separator 0=dot 1=comma
	 */
	public void SaveCSV(String name, int start, int end, int unit, int separator) {
		if (data.size() <= 0)
			return;

		java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("course_generator/Bundle");
		StringBuilder s = new StringBuilder();

		long ts = System.currentTimeMillis();

		DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ROOT);
		if (separator == 0)
			symbols.setDecimalSeparator('.');
		else
			symbols.setDecimalSeparator(',');

		DecimalFormat decimalFormat7 = new DecimalFormat("##0.#######", symbols);
		DecimalFormat decimalFormat1 = new DecimalFormat("##0.#", symbols);

		// TODO May be export alo elevationNotSmoothed and elevationSmoothed?
		try {
			PrintWriter writer = new PrintWriter(name, "UTF-8");

			s.append(bundle.getString("frmMain.HeaderNum.text") + ";");
			s.append(bundle.getString("frmMain.HeaderLat.text") + ";");
			s.append(bundle.getString("frmMain.HeaderLon.text") + ";");
			s.append(bundle.getString("frmMain.HeaderElev.text") + ";");
			s.append(bundle.getString("frmMain.HeaderElevNotSmoothed.text") + ";"); // "ElevationNotSmoothed" + ";");
																					// //TODO translate
			s.append(bundle.getString("frmMain.HeaderElevSmoothed.text") + ";"); // "ElevationSmoothed" + ";"); //TODO
																					// translate
			s.append(bundle.getString("frmMain.HeaderTag.text") + ";");
			s.append(bundle.getString("frmMain.HeaderDist.text") + ";");
			s.append(bundle.getString("frmMain.HeaderTotal.text") + ";");
			s.append(bundle.getString("frmMain.HeaderDiff.text") + ";");
			s.append(bundle.getString("frmMain.HeaderCoeff.text") + ";");
			s.append(bundle.getString("frmMain.HeaderRecovery.text") + ";");
			s.append(bundle.getString("frmMain.HeaderTime.text") + ";");
			s.append(bundle.getString("frmMain.HeaderTimeLimit.text") + ";");
			s.append(bundle.getString("frmMain.HeaderHour.text") + ";");
			s.append(bundle.getString("frmMain.HeaderStation.text") + ";");
			s.append(bundle.getString("frmMain.HeaderName.text") + ";");
			s.append(bundle.getString("frmMain.HeaderComment.text") + ";");
			s.append(bundle.getString("frmMain.HeaderSpeed.text") + ";"); // "Speed;"); //TODO translate
			s.append(bundle.getString("frmMain.HeaderSlope.text") + ";"); // "Slope;"); //TODO translate
			writer.println(s);
			s.setLength(0);

			for (int i = start; i <= end; i++) {
				CgData d = data.get(i);
				s.append(d.getNumString() + ";");
				s.append(decimalFormat7.format(d.getLatitude()) + ";");
				s.append(decimalFormat7.format(d.getLongitude()) + ";");
				s.append(d.getElevationString(unit, false) + ";"); // No decimal
				s.append(d.getElevationNotSmoothedString(unit, false) + ";"); // No decimal
				s.append(d.getElevationSmoothedString(unit, false) + ";"); // No decimal
				s.append(d.getTag() + ";");
				s.append(d.getDistString(unit, false) + ";"); // No decimal
				s.append(decimalFormat1.format(d.getTotal(unit)) + ";");
				s.append(decimalFormat1.format(d.getDiff()) + ";");
				s.append(decimalFormat1.format(d.getCoeff()) + ";");
				s.append(decimalFormat1.format(d.getRecovery()) + ";");
				s.append(d.getTimeString() + ";");
				s.append(d.getTimeLimitString(false) + ";");
				s.append(d.getHourString() + ";");
				s.append(d.getStationString(false) + ";");

				String str = d.getName();
				str = str.replace("\n", "").replace("\r", "").trim();
				s.append(str + ";");

				str = d.getComment();
				str = str.replace("\n", "").replace("\r", "").trim();
				s.append(str + ";");

				s.append(decimalFormat1.format(d.getSpeed(unit)) + ";");
				s.append(decimalFormat1.format(d.getSlope()) + ";");

				writer.println(s);
				s.setLength(0);
			}
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		CgLog.info("Save time : " + (System.currentTimeMillis() - ts) + "ms");
	}

	/**
	 * Calculate the night and day time, speed and distance
	 */
	public void CalcStatNight() {
		if (data.size() <= 0) {
			return;
		}

		tInNight.Init();
		tInDay.Init();

		boolean first = true;
		for (CgData r : data) {
			if (!first) {
				if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
						|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
					tInNight.Time = tInNight.Time + r.getdTime_f();
					tInNight.setSpeed(tInNight.getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					tInNight.setDist(tInNight.getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					tInNight.Cmpt++;
				} else {
					tInDay.Time = tInDay.Time + r.getdTime_f();
					tInDay.setSpeed(tInDay.getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					tInDay.setDist(tInDay.getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					tInDay.Cmpt++;
				}
			} else {
				first = false;
			}
		}
	}

	/**
	 * Calculate the slope statistic
	 */
	public void CalcStatSlope() {
		if (data.size() <= 0) {
			return;
		}

		for (StatData st : StatSlope)
			st.Init();

		int j = 0;
		boolean first = true;
		for (CgData r : data) {
			if (!first) {
				if (r.getSlope() < -40) {
					j = 0;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -40) && (r.getSlope() < -30)) {
					j = 1;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -30) && (r.getSlope() < -20)) {
					j = 2;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -20) && (r.getSlope() < -10)) {
					j = 3;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -10) && (r.getSlope() < -5)) {
					j = 4;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -5) && (r.getSlope() < -2)) {
					j = 5;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() >= -2) && (r.getSlope() <= 2)) {
					j = 6;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() > 2) && (r.getSlope() <= 5)) {
					j = 7;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() > 5) && (r.getSlope() <= 10)) {
					j = 8;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() > 10) && (r.getSlope() <= 20)) {
					j = 9;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() > 20) && (r.getSlope() <= 30)) {
					j = 10;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if ((r.getSlope() > 30) && (r.getSlope() <= 40)) {
					j = 11;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				} else if (r.getSlope() > 40) {
					j = 12;
					StatSlope[j].setSpeed(StatSlope[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatSlope[j].setDist(StatSlope[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatSlope[j].Time = StatSlope[j].Time + r.getdTime_f();
					StatSlope[j].Cmpt++;
				}
			} else {
				first = false;
			}
		} // foreach
	}

	/**
	 * Calculate the elevation statistic
	 */
	public void CalcStatElev() {
		if (data.size() <= 0) {
			return;
		}

		for (StatData st : StatElev)
			st.Init();

		for (StatData st : StatElevNight)
			st.Init();

		for (StatData st : StatElevDay)
			st.Init();

		int j = 0;
		boolean first = true;
		for (CgData r : data) {
			if (!first) {
				if (r.getElevation(CgConst.UNIT_METER) < 1000) {
					j = 0;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;

					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				} else if ((r.getElevation(CgConst.UNIT_METER) >= 1000)
						&& (r.getElevation(CgConst.UNIT_METER) < 1500)) {
					j = 1;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;
					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				} else if ((r.getElevation(CgConst.UNIT_METER) >= 1500)
						&& (r.getElevation(CgConst.UNIT_METER) < 2000)) {
					j = 2;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;
					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				} else if ((r.getElevation(CgConst.UNIT_METER) >= 2000)
						&& (r.getElevation(CgConst.UNIT_METER) < 2500)) {
					j = 3;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;
					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				} else if ((r.getElevation(CgConst.UNIT_METER) >= 2500)
						&& (r.getElevation(CgConst.UNIT_METER) < 3000)) {
					j = 4;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;
					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				} else if ((r.getElevation(CgConst.UNIT_METER) >= 3000)) {
					j = 5;
					StatElev[j].setSpeed(StatElev[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
					StatElev[j].setDist(StatElev[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
					StatElev[j].Time = StatElev[j].Time + r.getdTime_f();
					StatElev[j].Cmpt++;
					// Night
					if (bNightCoeff && ((r.getHour().getSecondOfDay() >= StartNightTime.getSecondOfDay())
							|| (r.getHour().getSecondOfDay() <= EndNightTime.getSecondOfDay()))) {
						StatElevNight[j].Time = StatElevNight[j].Time + r.getdTime_f();
						StatElevNight[j].setSpeed(
								StatElevNight[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevNight[j]
								.setDist(StatElevNight[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevNight[j].Cmpt++;
					} else // Day
					{
						StatElevDay[j].Time = StatElevDay[j].Time + r.getdTime_f();
						StatElevDay[j]
								.setSpeed(StatElevDay[j].getSpeed(CgConst.UNIT_METER) + r.getSpeed(CgConst.UNIT_METER));
						StatElevDay[j]
								.setDist(StatElevDay[j].getDist(CgConst.UNIT_METER) + r.getDist(CgConst.UNIT_METER));
						StatElevDay[j].Cmpt++;
					}
				}
			} else {
				first = false;
			}
		}
	}

	/**
	 * Find the nearest point in the point list
	 * 
	 * @param lat latitude of the point
	 * @param lon longitude of the point
	 * @return index of the nearest point in the point list
	 */
	public int FindNearestPoint(double lat, double lon) {
		double a, c, dDistance, dLat1InRad, dLong1InRad, dLat2InRad, dLong2InRad, dLongitude, dLatitude;
		double kEarthRadiusKms;
		double min;
		int index = -1;
		int i = 0;

		min = 99999999;

		double k = Math.PI / 180.0;

		dLat1InRad = lat * k;
		dLong1InRad = lon * k;

		for (CgData r : data) {
			kEarthRadiusKms = 6378.14; // 6376.5

			dDistance = 0; // Double.MinValue
			dLat2InRad = r.getLatitude() * k;
			dLong2InRad = r.getLongitude() * k;

			dLongitude = dLong2InRad - dLong1InRad;
			dLatitude = dLat2InRad - dLat1InRad;

			// Intermediate result a.
			a = Math.pow(Math.sin(dLatitude / 2.0), 2.0)
					+ Math.cos(dLat1InRad) * Math.cos(dLat2InRad) * Math.pow(Math.sin(dLongitude / 2.0), 2.0);

			// Intermediate result c (great circle distance in Radians)
			c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));

			// Distance.
			dDistance = kEarthRadiusKms * c * 1000.0;
			if (dDistance < min) {
				min = dDistance;
				index = i;
			}

			i++;
		}

		// Résultat en mètres
		return index;
	}

	/**
	 * Return the minimum elevation of the track
	 * 
	 * @param unit Unit wanted (To get from the settings)
	 * @return Minimum elevation of the track
	 */
	public double getMinElev(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return MinElev;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2Feet(MinElev);
		default:
			return MinElev;
		}
	}

	/**
	 * Return the maximum elevation of the track
	 * 
	 * @param unit Unit wanted (To get from the settings)
	 * @return Maximum elevation of the track
	 */
	public double getMaxElev(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return MaxElev;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2Feet(MaxElev);
		default:
			return MaxElev;
		}
	}

	public double getClimbP(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return ClimbP;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2Feet(ClimbP);
		default:
			return ClimbP;
		}
	}

	/**
	 * Set climbP variable
	 * 
	 * @param climbP Value in meter
	 */
	public void setClimbP(double climbP) {
		ClimbP = climbP;
	}

	public double getClimbM(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return ClimbM;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2Feet(ClimbM);
		default:
			return ClimbM;
		}
	}

	/**
	 * Set climbM variable
	 * 
	 * @param climbM Value in meter
	 */
	public void setClimbM(double climbM) {
		ClimbM = climbM;
	}

	/**
	 * Return the road distance on the track
	 * 
	 * @param unit Unit for the returned value
	 * @return Road distance in meter
	 */
	public double getDistRoad(int unit) {
		switch (unit) {
		case CgConst.UNIT_METER:
			return DistRoad;
		case CgConst.UNIT_MILES_FEET:
			// meter to miles
			return Utils.Meter2uMiles(DistRoad);
		default:
			return DistRoad;
		}
	}

	/**
	 * Set distRoad variable
	 * 
	 * @param distRoad Value in meter
	 */
	public void setDistRoad(double distRoad) {
		DistRoad = distRoad;
	}

	/**
	 * Copy the current track to another
	 * 
	 * @param d track object where to copy the current track
	 * @return track object where the current is copied
	 */
	public TrackData CopyTo(TrackData d) {
		int i = 0;

		d.param = param;
		d.Paramfile = Paramfile;

		d.data.clear();
		for (CgData r : data) {
			CgData n = new CgData();
			n = r.CopyTo(n);

			d.data.add(n);
		}

		d.tInNight = tInNight.CopyTo(d.tInNight);
		d.tInDay = tInDay.CopyTo(d.tInDay);

		int n = StatSlope.length;
		d.StatSlope = new StatData[StatSlope.length];
		if (n >= 0) {
			for (i = 0; i < n; i++) {
				d.StatSlope[i] = new StatData();
				d.StatSlope[i] = StatSlope[i].CopyTo(d.StatSlope[i]);
			}
		}

		n = StatElev.length;
		d.StatElev = new StatData[StatElev.length];
		if (n >= 0) {
			for (i = 0; i < n; i++) {
				d.StatElev[i] = new StatData();
				d.StatElev[i] = StatElev[i].CopyTo(d.StatElev[i]);
			}
		}

		n = StatElevNight.length;
		d.StatElevNight = new StatData[StatElevNight.length];
		if (n >= 0) {
			for (i = 0; i < n; i++) {
				d.StatElevNight[i] = new StatData();
				d.StatElevNight[i] = StatElevNight[i].CopyTo(d.StatElevNight[i]);
			}
		}

		n = StatElevDay.length;
		d.StatElevDay = new StatData[StatElevDay.length];
		if (n >= 0) {
			for (i = 0; i < n; i++) {
				d.StatElevDay[i] = new StatData();
				d.StatElevDay[i] = StatElevDay[i].CopyTo(d.StatElevDay[i]);
			}
		}

		d.Name = d.Name;
		d.CourseName = CourseName;
		d.TotalDistance = TotalDistance;
		d.TotalTime = TotalTime;
		d.isTimeLoaded = isTimeLoaded;
		d.isCalculated = isCalculated;
		d.isModified = isModified;
		d.ClimbP = ClimbP;
		d.ClimbM = ClimbM;
		d.AscTime = AscTime;
		d.DescTime = DescTime;
		d.Description = Description;
		d.isAutoCalc = isAutoCalc;
		d.StartTime = StartTime;
		d.MinElev = MinElev;
		d.MaxElev = MaxElev;
		d.StartGlobalCoeff = StartGlobalCoeff;
		d.EndGlobalCoeff = EndGlobalCoeff;
		d.isTimeLimit = isTimeLimit;
		d.TimeLimit_Line = TimeLimit_Line;
		d.timeZoneOffsetHours = timeZoneOffsetHours;
		d.timeZoneId = timeZoneId;
		d.TrackUseDaylightSaving = TrackUseDaylightSaving;
		d.StartSpeed = StartSpeed;
		d.EndSpeed = EndSpeed;
		d.DistRoad = DistRoad;
		d.ReadError = ReadError;
		d.ReadLineError = ReadLineError;
		d.StartNightTime = StartNightTime;
		d.EndNightTime = EndNightTime;
		d.bNightCoeff = bNightCoeff;
		d.NightCoeffAsc = NightCoeffAsc;
		d.NightCoeffDesc = NightCoeffDesc;
		d.bElevEffect = bElevEffect;
		d.MrbSizeW = MrbSizeW;
		d.MrbSizeH = MrbSizeH;
		d.CurveFilter = CurveFilter;
		d.WordWrapLength = WordWrapLength;
		d.LabelToBottom = LabelToBottom;
		d.MRBType = MRBType;
		d.TopMargin = TopMargin;
		d.bShowNightDay = bShowNightDay;
		d.ReadOnly = ReadOnly;
		return d;
	}

	public void determineTrackTimeZone() {
		if (this.data == null || this.data.isEmpty())
			return;

		timeZoneId = Utils.getTimeZoneFromLatLon(this.data.get(0).getLatitude(), this.data.get(0).getLongitude())
				.getID();
		timeZoneOffsetHours = Utils.hoursUTCOffsetFromLatLon(this.data.get(0).getLatitude(),
				this.data.get(0).getLongitude());
	}

	public void determineSunriseSunsetTimes() {
		if (this.data == null || this.data.isEmpty())
			return;

		if (timeZoneId.equals(""))
			determineTrackTimeZone();

		EndNightTime = Utils.determineSunRiseTimes(StartTime, this.data.get(0).getLatitude(),
				this.data.get(0).getLongitude(), timeZoneId);
		StartNightTime = Utils.determineSunsetTimes(StartTime, this.data.get(0).getLatitude(),
				this.data.get(0).getLongitude(), timeZoneId);

	}

	/**
	 * Copy the smoothed elevation in the elevation field
	 */
	public void SelectSmoothedElevation() {
		if (this.data == null || this.data.isEmpty())
			return;

		for (CgData r : data) {
			r.setElevation(r.getElevationSmoothed(CgConst.UNIT_METER));
		}
	}

	/**
	 * Copy the not smoothed elevation in the elevation field
	 */
	public void SelectNotSmoothedElevation() {
		if (this.data == null || this.data.isEmpty())
			return;

		for (CgData r : data) {
			r.setElevation(r.getElevationNotSmoothed(CgConst.UNIT_METER));
		}
	}

	/**
	 * Copy the Not smoothed elevation in the smoothed elevation field
	 */
	public void CopyNotSmoothedInSmoothedElevation() {
		if (this.data == null || this.data.isEmpty())
			return;

		for (CgData r : data) {
			r.setElevationSmoothed(r.getElevationNotSmoothed(CgConst.UNIT_METER));
		}
	}

	/*
	 * Keep these 2 methods because they can be used to remove enormous slope. Maybe
	 * a bit of debug is necessary because input data are not the right ones (due
	 * the method of calculation of dist and slope)
	 * 
	 * public double CalcNewSlope(double elevPrev, double elevNext, double distPrev,
	 * double distNext) {
	 * 
	 * double de = elevNext-elevPrev; double dd = distPrev-distNext; double
	 * value=elevPrev+(de*distPrev/dd); return value; }
	 * 
	 * public void FixIncorrectSlope() { if (this.data == null ||
	 * this.data.isEmpty()) return;
	 * 
	 * for (int i=0; i<data.size()-1; i++) { CgData act = data.get(i); if
	 * (Math.abs(act.getSlope())>50) { CgData prev = data.get(i-1); CgData next =
	 * data.get(i+1); act.setElevation(CalcNewSlope(
	 * prev.getdElevation(CgConst.UNIT_METER),
	 * next.getdElevation(CgConst.UNIT_METER), prev.getDist(CgConst.UNIT_METER),
	 * next.getDist(CgConst.UNIT_METER) ) ); }
	 * //r.setElevationSmoothed(r.getElevationNotSmoothed(CgConst.UNIT_METER)); } }
	 */

} // TrackData