// path_tour.js
// requires geplugin-helpers.js ( & therefore math3d.js)

/**
 * @fileoverview This is the main file for the TourSimulator class, which
 * simulates a tour of a given path in a Google Earth plugin instance
 * @author (adapted by) Romyn Perret-Green (www.TheKMZ.co.uk)
 */

/****************************************************************************
								NOTES
*****************************************************************************
This is a modified version of the original simulator.js and the related index.js
that were created by Roman Nurik which are Copyright 2008 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

The original javascript files on which this is based can be found at

    http://earth-api-samples.googlecode.com/svn/trunk/demos/drive-simulator/simulator.js
    http://earth-api-samples.googlecode.com/svn/trunk/demos/drive-simulator/index.js

*/

					
// The amount of real time to simulate per simulator tick - @type {number}
TourSimulator.TICK_SIM_MS = 66;

// Current location of the car in the simulation - @type {google.maps.LatLng}
//TourSimulator.prototype.currentLoc = null;

TourSimulator.speedFactor = 1.0;


var arr_tour = [];
var t_options = [];


/**
 * Constructs a simulator object
 * @param {Object} ge The Google Earth instance
 * @param {Array.<Object>} path The path to simulate, as an array of vertex
 *     objects of the form:
 *   {google.maps.LatLng} loc The vertex location of vertex,
 *   {number} duration The time duration before the next vertex, in seconds
 *   {number} distance The distance to the next vertex, in meters
 * @param {Object?} opt_opts An object with the following optional fields:
 *   {Function?} on_tick A callback function, called after one tick of the
 *       simulation completes
 * @constructor
 */
function TourSimulator(ge, path, t_options, opt_opts) {
  this.ge = ge;
  this.path = path;
  this.options = opt_opts || {};
  
  this.altitude = t_options.view_altitude;
  this.range = t_options.view_range;
  this.tilt = t_options.view_tilt;
  this.rotation = t_options.view_rotation;
  this.Model_URL = t_options.Model_URL;
  
  this.currentLoc = this.path[0].loc;
  
  // private vars
  this.geHelpers_ = new GEHelpers(ge);
  this.doTick_ = false;
  this.pathIndex_ = 0;
  this.currentSpeed_ = 0;
  this.segmentTime_ = 0;
  this.segmentAdvance_ = 0;
  this.headingSegmentStart_ = 0;
  this.headingSegmentEnd_ = 0;
  this.headingCurrent_ = 0;
  this.segmentPercent_ = 0;
  this.tickAdvance_ = 0;
  this.tickStart_ = 0;
  this.tickEnd_ = 0;
  this.timerAdjustment_ = 1.0;
  this.loop_ = false;
  this.totalDistance_ = 0;
  this.totalTimeReal_ = 0;
  this.pathIndexNext1_ = 1;
  this.pathIndexNext2_ = 2;
}

// Initializes the simulator UI - @param {Function?} opt_cb Optional callback
TourSimulator.prototype.initUI = function(opt_cb) {
  var me = this;

  this.headingSegmentStart_ = this.geHelpers_.getHeading(this.path[0].loc, this.path[1].loc);
  this.headingSegmentEnd_ = this.geHelpers_.getHeading(this.path[1].loc, this.path[2].loc);
  this.headingCurrent_ = this.headingSegmentStart_ + this.geHelpers_.fixAngle(this.headingSegmentEnd_ - this.headingSegmentStart_);
  
  if (this.path[0].loc = this.path[this.path.length - 1].loc) {
	this.loop_ = true;  
  }
  

  this.moveLookAt_(this.path[0].loc, this.path[1].loc);
 
  this.tickListener = function() {
                        if (me.doTick_)
                          me.tick_();
                      };
  
  window.google.earth.addEventListener(this.ge, 'frameend', this.tickListener);
  
  if (this.Model_URL) me.initModel();
  
  if (opt_cb) opt_cb();
}

TourSimulator.prototype.initModel = function() {
  var me = this;
  window.google.earth.fetchKml(
      this.ge,
      this.Model_URL,
      function(obj) {
        me.finishInitModel_(obj);
      });
}

/**
 * Completes the UI initialization (i.e. once the car model is loaded); called
 * after fetchKml() is called on the car model URL
 * @private
 * @param {Object} kml The car model KML object
 * @param {Function?} opt_cb Optional callback
 */
TourSimulator.prototype.finishInitModel_ = function(kml) {
  this.modelPlacemark = kml.getFeatures().getChildNodes().item(0);
  this.model = this.modelPlacemark.getGeometry();
  this.model.setAltitudeMode(this.ge.ALTITUDE_RELATIVE_TO_GROUND);
  this.ge.getFeatures().appendChild(this.modelPlacemark);
    
  this.moveModel_(this.path[0].loc, this.path[1].loc);

}

// Destroy the UI and detach from the Earth instance
TourSimulator.prototype.destroy = function() {
  this.stop();
  
  if (this.modelPlacemark) this.ge.getFeatures().removeChild(this.modelPlacemark);

  window.google.earth.removeEventListener(this.ge, 'frameend',
                                          this.tickListener);
}

// Start/resume the simulation clock
TourSimulator.prototype.start = function() {
  if (this.doTick_)
    return;
  
  this.oldFlyToSpeed = this.ge.getOptions().getFlyToSpeed();
  this.ge.getOptions().setFlyToSpeed(this.ge.SPEED_TELEPORT);
  
  this.doTick_ = true;
  this.tick_();
}

// Stop/pause the simulation clock
TourSimulator.prototype.stop = function() {
  if (!this.doTick_)
    return;
  
  this.tickStart_ = null;

  this.ge.getOptions().setFlyToSpeed(this.oldFlyToSpeed);
  
  this.doTick_ = false;
}

/**
 * Position the Model
 * @private
 * @param {google.maps.LatLng} loc Location to move the car to
 */
TourSimulator.prototype.moveModel_ = function(loc) {
	
  this.model.getOrientation().setHeading(this.headingCurrent_);
  this.model.getLocation().setLatLngAlt(loc.lat(), loc.lng(), 0);
 
}

/**
 * Position the LookAt
 * @private
 * @param {google.maps.LatLng} loc Move to location
 */
TourSimulator.prototype.moveLookAt_ = function(loc) {
  
  // latitude, longitude, altitude, altitudeMode, heading, tilt, range

  var la = this.ge.createLookAt('');
  la.set(loc.lat(), loc.lng(),
      this.altitude, // altitude
      this.ge.ALTITUDE_RELATIVE_TO_GROUND,
	  this.headingCurrent_ + this.rotation,
	  this.tilt, // tilt
	  this.range //range
      );

  this.ge.getView().setAbstractView(la);
}

TourSimulator.prototype.moveCamera_ = function(loc) {
  
  // latitude, longitude, altitude, altitudeMode, heading, tilt, roll, 
  var cam = this.ge.createCamera('');
  
  var locOffset = offsetLoc(loc, -this.range, this.headingCurrent_);

  cam.set(locOffset.lat(), locOffset.lng(),  
  //cam.set(loc.lat(), loc.lng(),
      this.altitude,
      this.ge.ALTITUDE_RELATIVE_TO_GROUND,
	  this.headingCurrent_ + this.rotation,
	  this.tilt, 
	  0 //roll
      );
  this.ge.getView().setAbstractView(cam);
  
  var cam_offset_distance = geHelpers.distance(locOffset, this.currentLoc);
  //alert(cam_offset_distance);
 
}

/**
 * Simulate one unit of time, as specified by TICK_SIM_MS
 * @private
 */
TourSimulator.prototype.tick_ = function() {

  if (this.pathIndex_ == this.path.length - 1 ) {
	if (this.loop_ == true) {
		this.pathIndex_= 0;
	} else {
		this.doTick_ = false;
		this.tickStart_ = null;
		return;	
	}
  }
  
  // move up TICK_SIM_MS milliseconds
  //this.totalTime += TourSimulator.TICK_SIM_MS;
  this.segmentTime_ += TourSimulator.TICK_SIM_MS;
 
  var segmentDistance = this.path[this.pathIndex_].distance;
  //this.currentSpeed_ = this.path[this.pathIndex_].speed * TourSimulator.speedFactor;
  this.currentSpeed_ = (this.path[this.pathIndex_].speed + (this.path[this.pathIndex_ + 1 ].speed - this.path[this.pathIndex_].speed) * this.segmentPercent_) * TourSimulator.speedFactor;
  
  if (this.currentSpeed_) {
    this.tickAdvance_ = this.currentSpeed_ * TourSimulator.TICK_SIM_MS / 1000 * this.timerAdjustment_;
  } else {
    this.tickAdvance_ = 0.0;
  }
  
  if (!this.segmentAdvance_) this.segmentAdvance_ = 0.0;
   
  // move to next segment(s) if we pass one or more in this tick
	 while (this.pathIndex_ < this.path.length - 1 &&
		(this.segmentAdvance_ + this.tickAdvance_) >= segmentDistance) {

		// adjust distances
		this.tickAdvance_ -= (segmentDistance - this.segmentAdvance_);
		this.totalDistance_ += (segmentDistance - this.segmentAdvance_);
		this.segmentAdvance_ = 0;
		
		// update new position in path array
		this.pathIndex_++;
		this.pathIndexNext1_++;
		this.pathIndexNext2_++;
		
		if (this.pathIndex_ == this.path.length - 1 ) {
			if (this.loop_ == true) {
				this.pathIndex_= 0;
			} else {
				this.doTick_ = false;
				this.tickStart_ = null;
				return;	
			}
		}

		if (this.pathIndexNext1_ == this.path.length - 1 ) this.pathIndexNext1_ = 0;
		if (this.pathIndexNext2_ == this.path.length - 1 ) this.pathIndexNext2_ = 0;
		
		segmentDistance = this.path[this.pathIndex_].distance;
		//this.currentSpeed_ = this.path[this.pathIndex_].speed * TourSimulator.speedFactor;
		this.currentSpeed_ = (this.path[this.pathIndex_].speed + (this.path[this.pathIndex_ + 1 ].speed - this.path[this.pathIndex_].speed) * this.segmentPercent_) * TourSimulator.speedFactor;
	}
		
	this.segmentAdvance_ += this.tickAdvance_;
	this.totalDistance_ += this.tickAdvance_;
  
	this.headingSegmentStart_ = this.geHelpers_.getHeading(this.path[this.pathIndex_].loc, this.path[this.pathIndexNext1_].loc);
	this.headingSegmentEnd_ = this.geHelpers_.getHeading(this.path[this.pathIndexNext1_].loc, this.path[this.pathIndexNext2_].loc);
	this.segmentPercent_ = this.segmentAdvance_ / segmentDistance;
	this.headingCurrent_ = this.headingSegmentStart_ + (this.geHelpers_.fixAngle(this.headingSegmentEnd_ - this.headingSegmentStart_) * this.segmentPercent_);
  
  // update the current location
  
  this.currentLoc = this.geHelpers_.interpolateLoc(
      this.path[this.pathIndex_].loc,
      this.path[this.pathIndexNext1_].loc,
      this.segmentPercent_);

  if (this.Model_URL) this.moveModel_(this.currentLoc);
  if (view_mode == 'la') this.moveLookAt_(this.currentLoc);
  if (view_mode == 'cam') this.moveCamera_(this.currentLoc);
  
  var endDT = new Date();
  this.tickEnd_ = endDT.getTime();
  if (this.tickStart_) {
	var tickDuration = this.tickEnd_ - this.tickStart_;
	this.timerAdjustment_ = tickDuration/TourSimulator.TICK_SIM_MS;
	this.totalTimeReal_ += tickDuration;
  }
  this.tickStart_ = this.tickEnd_;

  // fire the callback if one is provided
  if (this.options.on_tick)
    this.options.on_tick();

}

function offsetLoc(loc, dist, heading) {
	/* Helper function, courtesy of
     http://www.movable-type.co.uk/scripts/latlong.html */
	var lat = loc.lat();
	var lng = loc.lng();
	
    lat *= Math.PI / 180;
    lng *= Math.PI / 180;
    heading *= Math.PI / 180;
    dist /= 6371000; // angular dist
    
    var lat2 = Math.asin(Math.sin(lat) * Math.cos(dist) + 
                         Math.cos(lat) * Math.sin(dist) * Math.cos(heading));
  
	var lat_new = 180 / Math.PI * lat2;
	var lng_new = 180 / Math.PI *
					(lng + Math.atan2(Math.sin(heading) * Math.sin(dist) * Math.cos(lat2),
                    Math.cos(dist) - Math.sin(lat) * Math.sin(lat2)));

				
	return new GLatLng(lat_new,lng_new);
				
  }

