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

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

(C) 2008 Harald Kirsch (pifpafpuf at gmx dot de)
************************************************************************/

function toRadians(angle) {
  return angle*Math.PI/180.0;
}
function greatCircleDistance(point, other) {
  var plon = toRadians(point.lon);
  var plat = toRadians(point.lat);
  var qlon = toRadians(other.lon);
  var qlat = toRadians(other.lat);

  return Math.acos(Math.sin( plat ) * Math.sin(qlat) 
		   + 
		   Math.cos(plat) 
		   *Math.cos(qlat)
		   *Math.cos(toRadians(point.lon-other.lon))) * 6378135;
}
function roundedKm(meter) {
  return (meter*0.001).toFixed(3);
}
//------------------------------------------------------------
/**
 * Define class TrackMarker.
 */
var m = {
  initialize : function(track, lonlat, options) {
    this.name = "";		// set only explicitly for waypoints
    this.iswp = false;
    this.track = track;
    this.pop = null		// == track.pop when up for us
    this.map = track.map;		// needed by Handler
    var params = [lonlat, this.track.newIcon(), options];
    OpenLayers.Marker.prototype.initialize.apply(this, params);

    this.dragHandler = new OpenLayers.Handler.Drag(this, 
						   {move: this.moveTo, 
						       done: this.moveTo,
						       up: this.deAct});
    
    this.events.register('mouseover', this, this.mouseOver);
    this.events.register('mouseout', this, this.mouseOut);
    this.events.register('click', this, this.click);    
  },
  deAct : function(evt) {
    this.dragHandler.deactivate();
    if( this.myLine ) {
      this.movePoint(1, this.lonlat);
    }
    if( this.nextMark ) {
      this.nextMark.movePoint(0, this.lonlat);
    }
    this.track.updateTracklengthDisplay();
  },
  mouseOver : function(evt) {
    this.setOpacity(0.6);
    this.dragHandler.activate();
  },
  mouseOut : function(evt) {
    this.setOpacity(1.0);
    if(!this.dragHandler.dragging) {
      this.dragHandler.deactivate();
    }
    this.lock = false;
  },
  moveTo : function(xy) { 
    if( this.pop && this.pop.visible() ) return;
    var xyl = this.map.getLayerPxFromViewPortPx(xy);
    xyl.x -= 8;
    xyl.y -= 8;
    OpenLayers.Marker.prototype.moveTo.apply(this, [xyl]);
    this.lock = true;
  },
  click : function(evt) {
    if( this.lock ) {
      this.lock = false;
      return;
    }
    if( this.pop ) {
      // the popup is up for us, so popdown
      this.track.popdown.call(this.pop);
      return;
    }    
    if( evt.ctrlKey ) {
      this.dragHandler.deactivate();
      this.track.delMarker(this);
      this.track.updateTracklengthDisplay();
      return;
    }

    this.pop = this.track.pop;
    if( this.pop.targetMarker ) {
      // its up for some other marker, get it down
      this.track.popdown.call(this.pop);
    }
    this.pop.targetMarker = this;
    var e = document.getElementById('mname');
    e.value = this.name;
    e.disabled = !this.iswp;
    e = document.getElementById('miswp');
    e.checked = this.iswp;
    e = document.getElementById('trackkm');
    e.innerHTML = roundedKm(this.distanceFromStart())
    this.pop.updateSize();

    var q = this.map.getExtent().determineQuadrant(this.lonlat);
    var xy = this.map.getLayerPxFromLonLat(this.lonlat);
    if( q.charAt(0)=='b' ) {
      xy.y = xy.y - (this.pop.size.h + this.icon.size.h/2);
    } else {
      xy.y = xy.y + this.icon.size.h/2;
    }
    if( q.charAt(1)=='r' ) {
      xy.x = xy.x - (this.pop.size.w + this.icon.size.w/2);
    } else {
      xy.x = xy.x +  this.icon.size.w/2;
    }

    this.pop.moveTo(xy);
    this.pop.show();
  },

  // return distance from start of track in meter
  distanceFromStart : function() {
    if( ! this.prevMark ) return 0.0;
    var d = this.prevMark.distanceFromStart();
    var p = gpxTrack.toGrad(this.lonlat);
    var q = gpxTrack.toGrad(this.prevMark.lonlat);
    d = d + greatCircleDistance(p, q);
    return d;
  },

  movePoint : function(which, lonlat) {
    var p = this.myLine.geometry.components[which];
    p.x = lonlat.lon;
    p.y = lonlat.lat;
    p.move(0,0);
    this.track.vectors.drawFeature(this.myLine);
  },
  
};
TrackMarker = OpenLayers.Class(OpenLayers.Marker, m);

//------------------------------------------------------------
/**
 * Define class GpxTrack
 */
var m = {
  initialize : function(map, options) {
    this.map = map;
    OpenLayers.Control.prototype.initialize.apply(this, [options]);
    map.addControl(this);


    // create the icon to use as a marker for trackmarks. This icon is
    // reused by each track marker by cloning. 
    this.icon = 
    new OpenLayers.Icon("/cycleroute/static/redcirc13.png", 
			new OpenLayers.Size(10, 10),
			new OpenLayers.Pixel(-5,-5));
    this.icon.setOpacity(0.8);    

    var e = document.getElementById('popuptemplate');
    var pophtml = e.innerHTML;
    e.innerHTML = "";

    var pop = 
    new OpenLayers.Popup(null,
			 new OpenLayers.LonLat(0,50),
			 new OpenLayers.Size(290,70),
			 pophtml, false, this.popdown);
    var pop = new OpenLayers.Popup(null, null, null, pophtml, 
				   false, this.popdown);
    pop.autoSize = true;
    pop.setBorder("2px dotted grey");
    pop.setBackgroundColor("gold");
    pop.padding = new OpenLayers.Bounds(5, 0, 5, 0);
    this.pop = pop;

    // TODO: change this to lazy init, because otherwise map.setCenter()
    // needs to be called to early
    this.map.addPopup(this.pop); 
    this.pop.hide();

    e = document.getElementById('mname');
    e.onkeypress = this.popKeyPress;

    // initialize the style to use for drawing the lines between tracks
    this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
    this.style.strokeColor = "gold";
    this.style.strokeColor = "#9e2525";
    this.style.strokeWidth = 3;
    this.style.cursor = "default";
    this.style.hoverStrokeColor = "#bbaaaa";
    this.style.hoverStrokeWidth = 3;

    // initialize the marker counter to generate consecutive default
    // marker names.
    this.mID = 0;

    this.polySelector = 
    new OpenLayers.Handler.Feature(this,
				   this.vectors,
				   {click: this.polygonClick,
				       over: this.polygonOver,
				       out: this.polygonOut,});
    this.keyHandler = 
    new OpenLayers.Handler.Keyboard(this,
				    {keyup: this.keyPress});
    this.keyHandler.activate();
  },

  polygonClick : function(feature) {
    this.deactivatePoly();
    var bounds = feature.geometry.getBounds();
    var lonlat = bounds.getCenterLonLat();
    var mark = feature.forMarker;
    this.setMark(lonlat, mark.prevMark, mark);
  },
  polygonOver : function(feature) {
    this.currentFeature = feature;
    var color = this.style.strokeColor;
    this.style.strokeColor = "#bbaaaa";
    this.vectors.drawFeature(feature, this.style);
    this.style.strokeColor = color;
  },
  polygonOut : function(feature) {
    this.vectors.drawFeature(feature, this.style);
    this.currentFeature = null;
  },

  keyPress : function(evt) {
    if( this.currentFeature ) return;
    if( this.markers.markers.length<2 ) return;
    if( evt.keyCode==65 ) {
      if( !this.polySelector.active ) this.activatePoly();
      else this.deactivatePoly();
    } else if( evt.keyCode==27 ) {
      this.deactivatePoly();
    }
  },

  activatePoly : function() {
    this.polySelector.activate();
    setCursor("map", "crosshair");
  },

  deactivatePoly : function() {
    this.currentFeature = null;
    this.polySelector.deactivate();
    setCursor("map", "default");
  },
    
  draw : function() {
    this.map.events.register("click", this, this.setMarkEvent);
  },

  newIcon : function() {
    return this.icon.clone();
  },
  popKeyPress : function(evt) {
    if( evt.keyCode!=evt.DOM_VK_RETURN ) return true;

    // ooompf, this function called in HTML context, need global
    // reference because 'this' points the wrong way.
    gpxTrack.nameMarker();
    return false;
  },
  createLine : function(llfrom, llto) {
    var p1 = new OpenLayers.Geometry.Point(llfrom.lon, llfrom.lat);
    var p2 = new OpenLayers.Geometry.Point(llto.lon, llto.lat);
    var geom = new OpenLayers.Geometry.LineString([p1, p2]);
    var v = new OpenLayers.Feature.Vector(geom);
    v.style = this.style;
    this.vectors.addFeatures(v); 
    return v;   
  },
  setMarkEvent : function(evt) {
    if (!OpenLayers.Event.isLeftClick(evt)) return;
    if( evt.ctrlKey ) return;
    var lonlat = this.map.getLonLatFromViewPortPx(evt.xy); 
    var l = this.markers.markers.length;
    var previous = null;
    if( l>0 ) {
      previous = this.markers.markers[l-1];
    }
    this.setMark(lonlat, previous, null);
    this.updateTracklengthDisplay();
  },
  
  setMark : function(lonlat, previous, next) {
    var mark = new TrackMarker(this, lonlat);
    this.markers.addMarker(mark);
    if( previous ) {
      mark.myLine = this.createLine(previous.lonlat, lonlat);
      mark.myLine.forMarker = mark;
      previous.nextMark = mark;
      mark.prevMark = previous;
    }
    if( next ) {
      // get the order right (insert method missing on marker layer)
      var l = this.markers.markers.length-2;
      while( this.markers.markers[l]!=next ) {
	this.markers.markers[l+1] = this.markers.markers[l];
	l -= 1;
      }
      this.markers.markers[l+1] = this.markers.markers[l];
      this.markers.markers[l] = mark;
      mark.nextMark = next;
      next.prevMark = mark;
      if( !next.myLine ) {
	// next was the first and only mark until now
	next.myLine = this.createLine(lonlat, next.lonlat);
      }
      next.movePoint(0, mark.lonlat);
    }
  },
  updateTracklengthDisplay : function() {
    var l = this.markers.markers.length;
    if( l==0 ) {
      hideLayer("tracklength");
    } else {
      showLayer("tracklength", "block");
      var e = document.getElementById("tracklength");
      var lastMarker = this.markers.markers[l-1];
      var distanceFromStart = lastMarker.distanceFromStart();
      e.innerHTML = ""+roundedKm(distanceFromStart)+" km";
    }
  },

  // supports upload of marks. The server sends an array of elements
  // with lon, lat and name to run over
  marksFromList : function(marks) {
    for(i=0; i<marks.length; i++) {
      var minfo = marks[i];             // again a list: lon, lat, name
      var lonlat = this.fromGrad(minfo[0], minfo[1]);
      var previous = i>0 ? this.markers.markers[i-1] : null;
      this.setMark(lonlat, previous, null);
    }
    this.updateTracklengthDisplay();
  },
  findMarker : function(id) { 
    var markers = this.markers.markers;
    for(var i=0; i<markers.length; i++) {
      if( markers[i].icon.imageDiv.id==id ) return markers[i];
    }
    return null;
  },
  nameMarker : function() {
    var m = this.pop.targetMarker;
    if( !m ) return;
    var e = document.getElementById('miswp');
    if( e.checked ) {
      e = document.getElementById('mname');
      m.name = e.value;
      m.iswp = true;
    } else {
      m.name = "";
      m.iswp = false;
    }
    this.popdown.call(this.pop);
  },
  deleteMarker : function() {
    var m = this.pop.targetMarker;
    if( !m ) return;
    this.popdown.call(this.pop);
    this.delMarker(m);
    this.updateTracklengthDisplay();
  },
  popdown : function() {
    // must be called with this==the popup 
    this.targetMarker.pop = null;
    this.targetMarker = null;
    this.hide();
  },
  /**
   * delete marker given by the ID of its icon
   */
  delMarkerByID : function(id) {
    var m = this.findMarker(id);
    if( !m ) return;    
    m.pop.hide();
    this.delMarker(m);
  },

  delMarker : function(m) {
    this.markers.removeMarker(m);
    if( m.prevMark ) {
      m.prevMark.nextMark = m.nextMark;
    }
    if( m.nextMark ) {
      var n = m.nextMark;
      n.prevMark = m.prevMark;
      if( m.prevMark ) {
	n.movePoint(0, m.prevMark.lonlat);
      } else {
	n.myLine.destroy();
	n.myLine = null;
      }
    }
    if( m.myLine ) {
      m.myLine.destroy();
    }
    m.destroy();
  },
  clear : function() {
    this.pop.hide();
    var markers = this.markers.markers;
    for(var i=markers.length; i>0; ) {
      this.delMarker(markers[--i]);
    }
    this.mID = 0;
    this.updateTracklengthDisplay();
  },
  save : function(hint) {
    var markers = this.markers.markers;
    if( markers.length==0 ) {
      showLayer("warnEmpty", "block");
      showLayer("help", "block");
      return;
    }
    hideLayer("warnEmpty", "block");

    var routename = document.getElementById('routename').value;
    var isTrack = true;
    if( document.getElementById('trackoption').value=="route" ) {
      xmlEl = "rte";
      xmlSegOn = "";
      xmlSegOff = "";
    } else {
      xmlEl = "trk";
      xmlSegOn = "<trkseg>"
      xmlSegOff = "</trkseg>"
    }
    var html = '<?xml version="1.0"?>\n'
    + '<gpx version="1.1"\n'
    + '  creator="route/track editor on http://'+location.hostname+location.pathname+' and OpenLayers.js"\n'
    + '  xmlns="http://www.topografix.com/GPX/1/1"\n'
    + '  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n'
    + '  xsi:schemaLocation="http://www.topografix.com/GPX/1/1\n' 
    + '  http://www.topografix.com/GPX/1/1/gpx.xsd">\n'
    ;
    
    for(var i=0; i<markers.length; i++) {
      var m = markers[i];
      if( ! m.iswp ) continue;
      var ll = this.toGrad(m.lonlat);
      html = html
	+ '  <wpt lat="'+ll.lat+'" lon="'+ll.lon+'">\n'
	+ '    <name>'+m.name+'</name>\n'
	+ '  </wpt>\n'
	;
    }

    html = html 
    + '  <'+xmlEl+'>\n'
    + '    <name>'+routename+'</name>\n'
    + '    '+xmlSegOn+'\n'
    ;
    for(var i=0; i<markers.length; i++) {
      var m = markers[i];
      var ll = this.toGrad(m.lonlat);
      html += '      <'+xmlEl+'pt lat="'+ll.lat+'" lon="'+ll.lon+'"';
      if( m.name!="" ) {
	html += '>\n        <name>'+m.name+'</name>\n'
	  + '      </'+xmlEl+'pt>\n';
      } else {
	html += " />\n";
      }
    }
    html += '    '+xmlSegOff+'\n'
    +'  </'+xmlEl+'>\n</gpx>\n';

    html = html.replace(/</g, '&lt;').replace(/>/g,'&gt;');    
    e = document.getElementById('gpx');
    e.innerHTML = html; // '<pre>'+html+'</pre>'

	  e = document.getElementById('routename');
	  v = e.value;
	  e = document.getElementById('hiddenroutename');
	  e.value = v;
    e = document.getElementById('getgpx');
    e.click();
  },
  fromGrad : function(lon, lat) {
    var xy = {x:lon, y:lat};
    xy = Proj4js.transform(p4326, p900913, xy);
    return new OpenLayers.LonLat(xy.x, xy.y);
  },
  toGrad : function(lonlat) {
    var xy = {x: lonlat.lon, y:lonlat.lat};
    xy = Proj4js.transform(p900913, p4326, xy);
    return new OpenLayers.LonLat(xy.x, xy.y);
  }
  
};
GpxTrack = OpenLayers.Class(OpenLayers.Control, m);
//----------------------------------------------------------------------

var options = {
  minResolution: 1.15,
  maxResolution: 150000
};
var markers = new OpenLayers.Layer.Markers("GPX points",options);
var vectors = new OpenLayers.Layer.Vector("Lines", options);
	
map.addLayer(vectors);
map.addLayer(markers);
gpxTrack = new GpxTrack(map, {"markers":markers, "vectors": vectors});



