// Polyline with arrows
//
// Bill Chadwick May 2008
//
// Free for any use
//

// Constructor params exactly as GPolyline then 
// 1) arrow spacing in pixels, 
// 2) arrow head length in pixels
// 3) arrow colour
// 4) arrow thickness in pixels
// 5) arrow opacity

function BDCCArrowedEncodedPolyline(encodedPolyline, encodedLevels, color, weight, opacity, opts, gapPx, headLength, headColor, headWeight, headOpacity) {	
    
    this.gapPx = gapPx;
	this.encodedPolyline = encodedPolyline;
	this.encodedLevels = encodedLevels;
    this.points = null;
    this.color = color;
    this.weight = weight;
    this.opacity = opacity;
    this.headLength = headLength;
    this.headColor = headColor;
    this.headWeight = headWeight;
    this.headOpacity = headOpacity;
    this.opts = opts;
    this.heads = new Array();
    this.line = null;
	this.bShow = true;
    
}
BDCCArrowedEncodedPolyline.prototype = new GOverlay();


BDCCArrowedEncodedPolyline.prototype.initialize = function(map) {

    this.map = map;   
    this.prj = map.getCurrentMapType().getProjection();
    var rdrw = GEvent.callback(this,this.recalc );
  	this.lstnMoveEnd = GEvent.addListener(map,"zoomend",function(){rdrw ();});
  	this.lstnType = GEvent.addListener(map,"maptypechanged",function(){rdrw ();});

  	this.recalc();//first draw
}

BDCCArrowedEncodedPolyline.prototype.remove = function() {

    try{
        if (this.line) {
            this.map.removeOverlay(this.line);
		}
        for(var i=0; i<this.heads.length; i++) {
            this.map.removeOverlay(this.heads[i]); 
		}
    }
    catch(ex)
    {
    }
}

BDCCArrowedEncodedPolyline.prototype.redraw = function(force) {
    return;//do nothing, the GPolyline line and heads draw themselves
}

BDCCArrowedEncodedPolyline.prototype.show = function() {
	this.bShow = true;
	this.line.show();
	for (i in this.heads) {
		this.heads[i].show();
	}
    return;
}

BDCCArrowedEncodedPolyline.prototype.hide = function() {
	this.bShow = false;
	this.line.hide();
	for (i in this.heads) {
		this.heads[i].hide();
	}
    return;
}

BDCCArrowedEncodedPolyline.prototype.copy = function(map) {
    return new BDCCArrowedEncodedPolyline(this.points,this.color,this.weight,this.opacity,this.opts,this.gapPx, this.headLength, this.headColor, this.headWeight, this.headOpacity);
}


BDCCArrowedEncodedPolyline.prototype.recalc = function() {

   var zoom = this.map.getZoom();

   this.remove();

   //the main polyline
   this.line = new GPolyline.fromEncoded({
		color: this.color,
		weight: this.weight,
		points: this.encodedPolyline, 
		levels: this.encodedLevels,
		zoomFactor: 32,
		numLevels: 4,
		opacity : 0.75
		
	});
   //this.line = new GPolyline(this.points,this.color,this.weight,this.opacity,this.opts);
	(this.bShow) ? this.line.show() : this.line.hide();
	this.map.addOverlay(this.line);
	
   
   this.points = this.decode(this.encodedPolyline, this.encodedLevels);

   // the arrow heads
   this.heads = new Array();

   var p1 = this.prj.fromLatLngToPixel(this.points[0],  zoom);//first point
   var p2;//next point
   var dx;
   var dy;
   var sl;//segment length
   var theta;//segment angle
   var ta;//distance along segment for placing arrows
      
   ta = this.gapPx;
   for (var i=1; i<this.points.length; i++){
            
      p2 = this.prj.fromLatLngToPixel(this.points[i],  zoom)
      dx = p2.x-p1.x;
      dy = p2.y-p1.y;
      sl = Math.sqrt((dx*dx)+(dy*dy)); 
      theta = Math.atan2(-dy,dx);
      j=1;
      
	if(this.gapPx == 0){
		//just put one arrow at the end of the line
        	this.addHead(p2.x,p2.y,theta,zoom);
	}
	else if(this.gapPx == 1) {
		//just put one arrow in the middle of the line
        	var x = p1.x + ((sl/2) * Math.cos(theta)); 
        	var y = p1.y - ((sl/2) * Math.sin(theta));
        	this.addHead(x,y,theta,zoom);        
	}
	else{
      	//iterate along the line segment placing arrow markers
      	//don't put an arrow within gapPx of the beginning or end of the segment 

		if (ta < sl) {
			var times = 0;
			while((times * this.gapPx + ta) < sl){
				var x = p1.x + ((times * this.gapPx + ta) * Math.cos(theta)); 
				var y = p1.y - ((times * this.gapPx + ta) * Math.sin(theta));
				this.addHead(x,y,theta,zoom);
				times++;
			}  
			ta = (times * this.gapPx + ta) - sl;
		} else if (ta > sl) {
			ta -= sl;
		} else {
        	var x = p1.x + (ta * Math.cos(theta)); 
        	var y = p1.y - (ta * Math.sin(theta));
        	this.addHead(x,y,theta,zoom);
			ta = this.gapPx;
      	}
	}
      
      p1 = p2;   
   }
}

BDCCArrowedEncodedPolyline.prototype.addHead = function(x,y,theta,zoom) {

    //add an arrow head at the specified point
    var t = theta + (Math.PI/4) ;
    if(t > Math.PI)
        t -= 2*Math.PI;
    var t2 = theta - (Math.PI/4) ;
    if(t2 <= (-Math.PI))
        t2 += 2*Math.PI;
    var pts = new Array();
    var x1 = x-Math.cos(t)*this.headLength;
    var y1 = y+Math.sin(t)*this.headLength;
    var x2 = x-Math.cos(t2)*this.headLength;
    var y2 = y+Math.sin(t2)*this.headLength;
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x1,y1), zoom));
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x,y), zoom));    
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x2,y2), zoom));
    this.heads.push(new GPolyline(pts,this.headColor,this.headWeight,this.headOpacity,this.opts));


	(this.bShow) ? this.heads[this.heads.length-1].show() : this.heads[this.heads.length-1].hide();
	this.map.addOverlay(this.heads[this.heads.length-1]);
}

BDCCArrowedEncodedPolyline.prototype.decode = function(encodedPolyline, encodedLevels) {
  var encoded_points = encodedPolyline;
  var encoded_levels = encodedLevels;

  if (encoded_points.length==0 || encoded_levels.length==0) {
    return;
  }

  var enc_points = this.decodeLine(encoded_points);
  var enc_levels = this.decodeLevels(encoded_levels);

  if (enc_points.length==0 || enc_levels.length==0) {
	alert("Points are empty.");
    return [];
  }

  if (enc_points.length != enc_levels.length) {
    alert('Point count and level count do not match');
    return [];
  }

  var points = [];

  for (var i = 0; i < enc_points.length; ++i) {
    //createPoint(enc_points[i][0], enc_points[i][1], enc_levels[i]);
	points.push(new GLatLng(enc_points[i][0], enc_points[i][1]))
  }

  return points;
}

// Decode an encoded polyline into a list of lat/lng tuples.
BDCCArrowedEncodedPolyline.prototype.decodeLine = function(encoded) {
  var len = encoded.length;
  var index = 0;
  var array = [];
  var lat = 0;
  var lng = 0;

  while (index < len) {
    var b;
    var shift = 0;
    var result = 0;
    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);
    var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
    lat += dlat;

    shift = 0;
    result = 0;
    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);
    var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
    lng += dlng;

    array.push([lat * 1e-5, lng * 1e-5]);
  }

  return array;
}

// Decode an encoded levels string into a list of levels.
BDCCArrowedEncodedPolyline.prototype.decodeLevels = function(encoded) {
  var levels = [];

  for (var pointIndex = 0; pointIndex < encoded.length; ++pointIndex) {
    var pointLevel = encoded.charCodeAt(pointIndex) - 63;
    levels.push(pointLevel);
  }

  return levels;
}



