package org.herac.tuxguitar.io.gpx;

import java.io.InputStream;
import java.math.BigDecimal;

import javax.xml.parsers.DocumentBuilderFactory;

import org.herac.tuxguitar.io.gpx.score.GPXAutomation;
import org.herac.tuxguitar.io.gpx.score.GPXBar;
import org.herac.tuxguitar.io.gpx.score.GPXBeat;
import org.herac.tuxguitar.io.gpx.score.GPXChord;
import org.herac.tuxguitar.io.gpx.score.GPXDocument;
import org.herac.tuxguitar.io.gpx.score.GPXMasterBar;
import org.herac.tuxguitar.io.gpx.score.GPXNote;
import org.herac.tuxguitar.io.gpx.score.GPXRhythm;
import org.herac.tuxguitar.io.gpx.score.GPXTrack;
import org.herac.tuxguitar.io.gpx.score.GPXVoice;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class GPXDocumentReader {
	
	public static final Integer GP6 = 6;
	public static final Integer GP7 = 7;
	
	private Integer version;
	private Document xmlDocument;
	private GPXDocument gpxDocument;
	
	public GPXDocumentReader(InputStream stream, Integer version) throws GPXFormatException {
		this.version = version;
		this.xmlDocument = getDocument(stream);
		this.gpxDocument = new GPXDocument();
	}
	
	private Document getDocument(InputStream stream) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		// CVE-2020-14940
		try {
			factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
			factory.setXIncludeAware(false);
		} catch (Throwable throwable) {
		}
		
		try {
			return factory.newDocumentBuilder().parse(stream);
		} catch (Throwable throwable) {
			throw new GPXFormatException("Invalid file format", throwable);
		}
	}
	
	public GPXDocument read() {
		if( this.xmlDocument != null ) {
			this.readScore();
			this.readAutomations();
			this.readTracks();
			this.readMasterBars();
			this.readBars();
			this.readVoices();
			this.readBeats();
			this.readNotes();
			this.readRhythms();
		}
		return this.gpxDocument;
	}
	
	public void readScore(){
		if( this.xmlDocument != null ){
			Node scoreNode = getChildNode(this.xmlDocument.getFirstChild(), "Score");
			if( scoreNode != null ){
				this.gpxDocument.getScore().setTitle( getChildNodeContent(scoreNode, "Title"));
				this.gpxDocument.getScore().setSubTitle( getChildNodeContent(scoreNode, "SubTitle"));
				this.gpxDocument.getScore().setArtist( getChildNodeContent(scoreNode, "Artist"));
				this.gpxDocument.getScore().setAlbum( getChildNodeContent(scoreNode, "Album"));
				this.gpxDocument.getScore().setWords( getChildNodeContent(scoreNode, "Words"));
				this.gpxDocument.getScore().setMusic( getChildNodeContent(scoreNode, "Music"));
				this.gpxDocument.getScore().setWordsAndMusic( getChildNodeContent(scoreNode, "WordsAndMusic"));
				this.gpxDocument.getScore().setCopyright( getChildNodeContent(scoreNode, "Copyright"));
				this.gpxDocument.getScore().setTabber( getChildNodeContent(scoreNode, "Tabber"));
				this.gpxDocument.getScore().setInstructions( getChildNodeContent(scoreNode, "Instructions"));
				this.gpxDocument.getScore().setNotices( getChildNodeContent(scoreNode, "Notices"));
			}
		}
	}
	
	public void readAutomations(){
		if( this.xmlDocument != null ){
			Node masterTrackNode = getChildNode(this.xmlDocument.getFirstChild(), "MasterTrack");
			if( masterTrackNode != null ){
				NodeList automationNodes = getChildNodeList(masterTrackNode, "Automations");
				for( int i = 0 ; i < automationNodes.getLength() ; i ++ ){
					Node automationNode = automationNodes.item( i );
					if( automationNode.getNodeName().equals("Automation") ){
						GPXAutomation automation = new GPXAutomation();
						automation.setType( getChildNodeContent(automationNode, "Type"));
						automation.setBarId( getChildNodeIntegerContent(automationNode, "Bar"));
						automation.setValue( getChildNodeIntegerContentArray(automationNode, "Value"));
						automation.setLinear( getChildNodeBooleanContent(automationNode, "Linear"));
						automation.setPosition( getChildNodeIntegerContent(automationNode, "Position"));
						automation.setVisible( getChildNodeBooleanContent(automationNode, "Visible"));
						
						this.gpxDocument.getAutomations().add( automation );
					}
				}
			}
		}
	}
	
	public void readTracks(){
		if( this.xmlDocument != null ){
			NodeList trackNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Tracks");
			for( int i = 0 ; i < trackNodes.getLength() ; i ++ ){
				Node trackNode = trackNodes.item( i );
				if( trackNode.getNodeName().equals("Track") ){
					GPXTrack track = new GPXTrack();
					track.setId( getAttributeIntegerValue(trackNode, "id") );
					track.setName(getChildNodeContent(trackNode, "Name" ));
					track.setColor(getChildNodeIntegerContentArray(trackNode, "Color"));
					
					if( this.version == GP6 ) {
						Node gmNode = getChildNode(trackNode, "GeneralMidi");
						if( gmNode != null ){
							track.setGmProgram(getChildNodeIntegerContent(gmNode, "Program"));
							track.setGmChannel1(getChildNodeIntegerContent(gmNode, "PrimaryChannel", this.getFreeGmChannel(null)));
							track.setGmChannel2(getChildNodeIntegerContent(gmNode, "SecondaryChannel", this.getFreeGmChannel(track)));
						}
					} else if (this.version == GP7) {
						Node midiConnectionNode = getChildNode(trackNode, "MidiConnection");
						if( midiConnectionNode != null ){
							track.setGmChannel1(getChildNodeIntegerContent(midiConnectionNode, "PrimaryChannel", this.getFreeGmChannel(null)));
							track.setGmChannel2(getChildNodeIntegerContent(midiConnectionNode, "SecondaryChannel", this.getFreeGmChannel(track)));
						}
						NodeList soundsNodes = getChildNodeList(trackNode, "Sounds");
						if( soundsNodes != null ){
							for( int s = 0 ; s < soundsNodes.getLength() ; s ++ ){
								Node soundNode = soundsNodes.item( s );
								if( soundNode.getNodeName().equals("Sound")) {
									Node midiNode = getChildNode(soundNode, "MIDI");
									if( midiNode != null ) {
										track.setGmProgram(getChildNodeIntegerContent(midiNode, "Program"));
									}
								}
							}
						}
					}
					
					NodeList propertiesNode = null;
					if( this.version == GP6 ) {
						propertiesNode = getChildNodeList(trackNode, "Properties");
					} else if (this.version == GP7) {
						Node stavesNode = getChildNode(trackNode, "Staves");
						if( stavesNode != null ) {
							Node staffNode = getChildNode(stavesNode, "Staff");
							if( staffNode != null ) {
								propertiesNode = getChildNodeList(staffNode, "Properties");
							}
						}
					}
					
					if( propertiesNode != null ){
						for( int p = 0 ; p < propertiesNode.getLength() ; p ++ ){
							Node propertyNode = propertiesNode.item( p );
							if (propertyNode.getNodeName().equals("Property") ){ 
								if( getAttributeValue(propertyNode, "name").equals("Tuning") ){
									track.setTunningPitches( getChildNodeIntegerContentArray(propertyNode, "Pitches") );
								}
							}
						}
					}
					
					this.gpxDocument.getTracks().add( track );
					this.readChords(propertiesNode);
				}
			}
		}
	}
	
	public void readChords(NodeList propertiesNode) {
		if( propertiesNode != null ){
			for( int p = 0 ; p < propertiesNode.getLength() ; p ++ ){
				Node propertyNode = propertiesNode.item( p );
				if (propertyNode.getNodeName().equals("Property") ){ 
					if( getAttributeValue(propertyNode, "name").equals("DiagramCollection") ) {
						NodeList itemsNode = getChildNodeList(propertyNode, "Items");
						if( itemsNode != null ) {
							for( int i = 0 ; i < itemsNode.getLength() ; i ++ ){
								Node itemNode = itemsNode.item( i );
								if (itemNode.getNodeName().equals("Item")) {
									Node diagramNode = getChildNode(itemNode, "Diagram");
									NodeList fretsNode = getChildNodeList(itemNode, "Diagram");
									if( diagramNode != null && fretsNode != null ) {
										GPXChord chord = new GPXChord();
										
										chord.setId(getAttributeIntegerValue(itemNode, "id"));
										chord.setName(getAttributeValue(itemNode, "name"));
										chord.setStringCount(getAttributeIntegerValue(diagramNode, "stringCount"));
										chord.setFretCount(getAttributeIntegerValue(diagramNode, "fretCount"));
										chord.setBaseFret(getAttributeIntegerValue(diagramNode, "baseFret"));
										if( chord.getFretCount() != null ) {
											chord.setFrets(new Integer[chord.getFretCount()]);
											for( int f = 0 ; f < fretsNode.getLength() ; f ++ ){
												Node fretNode = fretsNode.item( f );
												if (fretNode.getNodeName().equals("Fret")) {
													Integer string = getAttributeIntegerValue(fretNode, "string");
													if( string != null && string > 0 && string <= chord.getFretCount() ) {
														chord.getFrets()[string - 1] = getAttributeIntegerValue(fretNode, "fret");
													}
												}
											}
											this.gpxDocument.getChords().add(chord);
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	
	public void readMasterBars(){
		if( this.xmlDocument != null ){
			NodeList masterBarNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "MasterBars");
			for( int i = 0 ; i < masterBarNodes.getLength() ; i ++ ){
				Node masterBarNode = masterBarNodes.item( i );
				if( masterBarNode.getNodeName().equals("MasterBar") ){
					GPXMasterBar masterBar = new GPXMasterBar();
					masterBar.setBarIds( getChildNodeIntegerContentArray(masterBarNode, "Bars"));
					masterBar.setTime( getChildNodeIntegerContentArray(masterBarNode, "Time", "/"));
					masterBar.setTripletFeel(getChildNodeContent(masterBarNode, "TripletFeel"));
					
					Node repeatNode = getChildNode(masterBarNode, "Repeat");
					if( repeatNode != null ){
						masterBar.setRepeatStart(getAttributeBooleanValue(repeatNode, "start"));
						if( getAttributeBooleanValue(repeatNode, "end") ){
							masterBar.setRepeatCount( getAttributeIntegerValue(repeatNode, "count") - 1);
						}
					}
					
					Node keyNode = getChildNode(masterBarNode, "Key");
					if (keyNode != null) {
						masterBar.setAccidentalCount(this.getChildNodeIntegerContent(keyNode, "AccidentalCount") ); 
						masterBar.setMode(this.getChildNodeContent(keyNode, "Mode") ); 
					}

					masterBar.setAlternateEndings(getChildNodeIntegerContentArray(masterBarNode, "AlternateEndings"));

					this.gpxDocument.getMasterBars().add( masterBar );
				}
			}
		}
	}
	
	public void readBars(){
		if( this.xmlDocument != null ){
			NodeList barNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Bars");
			for( int i = 0 ; i < barNodes.getLength() ; i ++ ){
				Node barNode = barNodes.item( i );
				if( barNode.getNodeName().equals("Bar") ){
					GPXBar bar = new GPXBar();
					bar.setId(getAttributeIntegerValue(barNode, "id"));
					bar.setVoiceIds( getChildNodeIntegerContentArray(barNode, "Voices"));
					bar.setClef(getChildNodeContent(barNode, "Clef"));
					bar.setSimileMark(getChildNodeContent(barNode,"SimileMark"));
					
					this.gpxDocument.getBars().add( bar );
				}
			}
		}
	}
	
	public void readVoices(){
		if( this.xmlDocument != null ){
			NodeList voiceNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Voices");
			for( int i = 0 ; i < voiceNodes.getLength() ; i ++ ){
				Node voiceNode = voiceNodes.item( i );
				if( voiceNode.getNodeName().equals("Voice") ){
					GPXVoice voice = new GPXVoice();
					voice.setId(getAttributeIntegerValue(voiceNode, "id"));
					voice.setBeatIds( getChildNodeIntegerContentArray(voiceNode, "Beats"));
					
					this.gpxDocument.getVoices().add( voice );
				}
			}
		}
	}
	
	public void readBeats(){
		if( this.xmlDocument != null ){
			NodeList beatNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Beats");
			for( int i = 0 ; i < beatNodes.getLength() ; i ++ ){
				Node beatNode = beatNodes.item( i );
				if( beatNode.getNodeName().equals("Beat") ){
					GPXBeat beat = new GPXBeat();
					beat.setId(getAttributeIntegerValue(beatNode, "id"));
					beat.setDynamic(getChildNodeContent(beatNode, "Dynamic"));
					String text = getChildNodeContent(beatNode, "FreeText");
					if( text != null ){
						beat.setText(text);
					}
					beat.setRhythmId(getAttributeIntegerValue(getChildNode(beatNode, "Rhythm"), "ref"));
					beat.setTremolo( getChildNodeIntegerContentArray(beatNode, "Tremolo", "/"));
					beat.setNoteIds( getChildNodeIntegerContentArray(beatNode, "Notes"));
					beat.setChordId( getChildNodeIntegerContent(beatNode, "Chord", null));
					beat.setFadding( getChildNodeContent(beatNode, "Fadding"));
					
					NodeList propertyNodes = getChildNodeList(beatNode, "Properties");
					if( propertyNodes != null ){
						for( int p = 0 ; p < propertyNodes.getLength() ; p ++ ){
							Node propertyNode = propertyNodes.item( p );
							if (propertyNode.getNodeName().equals("Property") ){ 
								String propertyName = getAttributeValue(propertyNode, "name");
								
								if( propertyName.equals("WhammyBar") ){
									beat.setWhammyBarEnabled( getChildNode(propertyNode, "Enable") != null );
								}
								if( propertyName.equals("WhammyBarOriginValue") ){
									beat.setWhammyBarOriginValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarMiddleValue") ){
									beat.setWhammyBarMiddleValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarDestinationValue") ){
									beat.setWhammyBarDestinationValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarOriginOffset") ){
									beat.setWhammyBarOriginOffset( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarMiddleOffset1") ){
									beat.setWhammyBarMiddleOffset1( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarMiddleOffset2") ){
									beat.setWhammyBarMiddleOffset2( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("WhammyBarDestinationOffset") ){
									beat.setWhammyBarDestinationOffset( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("Brush") ){
									beat.setBrush( getChildNodeContent(propertyNode, "Direction") );
								}
							}
						}
					}
					
					this.gpxDocument.getBeats().add( beat );
				}
			}
		}
	}
	
	public void readNotes(){
		if( this.xmlDocument != null ){
			NodeList noteNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Notes");
			for( int i = 0 ; i < noteNodes.getLength() ; i ++ ){
				Node noteNode = noteNodes.item( i );
				if( noteNode.getNodeName().equals("Note") ){
					GPXNote note = new GPXNote();
					note.setId( getAttributeIntegerValue(noteNode, "id") );
					
					Node tieNode = getChildNode(noteNode, "Tie");
					note.setTieDestination( tieNode != null ? getAttributeValue(tieNode, "destination").equals("true") : false);
					
					String ghostNodeContent = getChildNodeContent(noteNode, "AntiAccent");
					if( ghostNodeContent != null ){
						note.setGhost(ghostNodeContent.equals("Normal"));
					}
					
					note.setAccent(getChildNodeIntegerContent(noteNode, "Accent"));
					note.setTrill(getChildNodeIntegerContent(noteNode, "Trill"));

					note.setVibrato( getChildNode(noteNode, "Vibrato") != null );
					
					NodeList propertyNodes = getChildNodeList(noteNode, "Properties");
					if( propertyNodes != null ){
						for( int p = 0 ; p < propertyNodes.getLength() ; p ++ ){
							Node propertyNode = propertyNodes.item( p );
							if (propertyNode.getNodeName().equals("Property") ){ 
								String propertyName = getAttributeValue(propertyNode, "name");
								if( propertyName.equals("String") ){
									note.setString( getChildNodeIntegerContent(propertyNode, "String") );
								}
								if( propertyName.equals("Fret") ){
									note.setFret( getChildNodeIntegerContent(propertyNode, "Fret") );
								}
								if( propertyName.equals("Midi") ){
									note.setMidiNumber( getChildNodeIntegerContent(propertyNode, "Number") );
								}
								if( propertyName.equals("Tone") ){
									note.setTone( getChildNodeIntegerContent(propertyNode, "Step") );
								}
								if( propertyName.equals("Octave") ){
									note.setOctave( getChildNodeIntegerContent(propertyNode, "Number") );
								}
								if( propertyName.equals("Element") ){
									note.setElement( getChildNodeIntegerContent(propertyNode, "Element") );
								}
								if( propertyName.equals("Variation") ){
									note.setVariation( getChildNodeIntegerContent(propertyNode, "Variation") );
								}
								if( propertyName.equals("Muted") ){
									note.setMutedEnabled( getChildNode(propertyNode, "Enable") != null );
								}
								if( propertyName.equals("PalmMuted") ){
									note.setPalmMutedEnabled( getChildNode(propertyNode, "Enable") != null );
								}
								if( propertyName.equals("Slide") ){
									note.setSlide( true );
									note.setSlideFlags( getChildNodeIntegerContent(propertyNode, "Flags") );
								}
								if( propertyName.equals("Tapped") ){
									note.setTapped( getChildNode(propertyNode, "Enable") != null );
								}
								if( propertyName.equals("Bended") ){
									note.setBendEnabled( getChildNode(propertyNode, "Enable") != null );
								}
								if( propertyName.equals("BendOriginValue") ){
									note.setBendOriginValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendMiddleValue") ){
									note.setBendMiddleValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendDestinationValue") ){
									note.setBendDestinationValue( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendOriginOffset") ){
									note.setBendOriginOffset( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendMiddleOffset1") ){
									note.setBendMiddleOffset1( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendMiddleOffset2") ){
									note.setBendMiddleOffset2( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("BendDestinationOffset") ){
									note.setBendDestinationOffset( Integer.valueOf(getChildNodeIntegerContent(propertyNode, "Float")) );
								}
								if( propertyName.equals("HopoOrigin") ){
									note.setHammer(true);
								}
								if( propertyName.equals("HopoDestination") ){
	//								this is a hammer-on or pull-off
								}
								if( propertyName.equals("HarmonicFret") ){
									note.setHarmonicFret( ( getChildNodeIntegerContent(propertyNode, "HFret") ) );
								}
								if( propertyName.equals("HarmonicType") ){
									note.setHarmonicType( getChildNodeContent (propertyNode, "HType"));
								}
							}
						}
					}
					
					this.gpxDocument.getNotes().add( note );
				}
			}
		}
	}
	
	public void readRhythms(){
		if( this.xmlDocument != null ){
			NodeList rhythmNodes = getChildNodeList(this.xmlDocument.getFirstChild(), "Rhythms");
			for( int i = 0 ; i < rhythmNodes.getLength() ; i ++ ){
				Node rhythmNode = rhythmNodes.item( i );
				if( rhythmNode.getNodeName().equals("Rhythm") ){
					Node primaryTupletNode = getChildNode(rhythmNode, "PrimaryTuplet");
					Node augmentationDotNode = getChildNode(rhythmNode, "AugmentationDot");
					
					GPXRhythm rhythm = new GPXRhythm();
					rhythm.setId( getAttributeIntegerValue(rhythmNode, "id") );
					rhythm.setNoteValue(getChildNodeContent(rhythmNode, "NoteValue") );
					rhythm.setPrimaryTupletDen(primaryTupletNode != null ? getAttributeIntegerValue(primaryTupletNode, "den") : 1);
					rhythm.setPrimaryTupletNum(primaryTupletNode != null ? getAttributeIntegerValue(primaryTupletNode, "num") : 1);
					rhythm.setAugmentationDotCount(augmentationDotNode != null ? getAttributeIntegerValue(augmentationDotNode, "count") : 0);
					
					this.gpxDocument.getRhythms().add( rhythm );
				}
			}
		}
	}
	
	private String getAttributeValue(Node node, String attribute ){
		if( node != null ){
			return node.getAttributes().getNamedItem( attribute ).getNodeValue();
		}
		return null;
	}
	
	private int getAttributeIntegerValue(Node node, String attribute ){
		try {
			return new BigDecimal(this.getAttributeValue(node, attribute)).intValue();
		} catch( Throwable throwable ){ 
			return 0;
		}
	}
	
	private boolean getAttributeBooleanValue(Node node, String attribute ){
		String value = this.getAttributeValue(node, attribute);
		if( value != null ){
			return value.equals("true");
		}
		return false;
	}
	
	private Node getChildNode(Node node, String name ){
		NodeList childNodes = node.getChildNodes();
		for( int i = 0 ; i < childNodes.getLength() ; i ++ ){
			Node childNode = childNodes.item( i );
			if( childNode.getNodeName().equals( name ) ){
				return childNode;
			}
		}
		return null;
	}
	
	private NodeList getChildNodeList(Node node, String name ){
		Node childNode = getChildNode(node, name);
		if( childNode != null ){
			return childNode.getChildNodes();
		}
		return null;
	}
	
	private String getChildNodeContent(Node node, String name ){
		Node childNode = getChildNode(node, name);
		if( childNode != null ){
			return childNode.getTextContent();
		}
		return null;
	}
	
	private boolean getChildNodeBooleanContent(Node node, String name ){
		String value = this.getChildNodeContent(node, name);
		if( value != null ){
			return value.equals("true");
		}
		return false;
	}
	
	private int getChildNodeIntegerContent(Node node, String name){
		return this.getChildNodeIntegerContent(node, name, 0);
	}
	
	private Integer getChildNodeIntegerContent(Node node, String name, Integer defaultValue){
		try {
			return new BigDecimal(this.getChildNodeContent(node, name)).intValue();
		} catch( Throwable throwable ){
			return defaultValue;
		}
	}
	
	private int[] getChildNodeIntegerContentArray(Node node, String name , String regex){
		String rawContents = this.getChildNodeContent(node, name);
		if( rawContents != null ){
			String[] contents = rawContents.trim().split(regex);
			int[] intContents = new int[contents.length];
			for( int i = 0 ; i < intContents.length; i ++ ){
				try {
					intContents[i] = new BigDecimal( contents[i].trim() ).intValue();
				} catch( Throwable throwable ){
					intContents[i] = 0;
				}
			}
			return intContents;
		}
		return null;
	}
	
	private int[] getChildNodeIntegerContentArray(Node node, String name ){
		return getChildNodeIntegerContentArray(node, name, (" ") );
	}

	private int getFreeGmChannel(GPXTrack trackToCheck) {
		int gmChannel = 0;

		boolean isGmChannelUsed;
		do {
			gmChannel += 1;

			isGmChannelUsed = trackToCheck != null && ((trackToCheck.getGmChannel1() == gmChannel) || (trackToCheck.getGmChannel2() == gmChannel));
			for (GPXTrack track : this.gpxDocument.getTracks()) {
				isGmChannelUsed |= ((track.getGmChannel1() == gmChannel) || (track.getGmChannel2() == gmChannel));
			}
		} while (isGmChannelUsed);

		return gmChannel;
	}
}
