/*
	Created by Alan Bellows for DamnInteresting.com
	
	If you're here to pilfer this code, please consider linking to our article 
	rather than resorting to theft.  You can also link directly to this simulator 
	at: http://www.damninteresting.com/?page_id=959
	
	There's probably a smarter way to do all this, but I'm no orbital mechanics 
	engineer, nor a... math-talkin guy.
*/

var sim = new AldrinCyclerSimulation();


function AldrinCyclerSimulation () {

	this.animationDelay = null;
	this.orbitsMidpoint = new Point(175,175);
	this.framesPerYear = 70;
	this.canvas;
	this.tv;
	this.dateDiv;
	this.animationTimeout;
	this.currentDate = new Date();
	this.monthNames = [ 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec' ];
	this.frameTime;

	this.cyclerOrbits = [];


	this.referenceCyclerOrbit = new Orbit(
		new Point(242,208),new Point(243,201),new Point(245,194),new Point(246,187),new Point(247,180),
		new Point(247,173),new Point(246,165),new Point(245,158),new Point(244,151),new Point(242,143),
		new Point(239,136),new Point(236,130),new Point(233,124),new Point(229,118),new Point(224,112),
		new Point(220,106),new Point(215,101),new Point(210,96),new Point(204,92),new Point(199,88),
		new Point(194,84),new Point(188,81),new Point(182,78),new Point(176,75),new Point(169,72),
		new Point(164,70),new Point(158,69),new Point(152,68),new Point(145,67),new Point(140,66),
		new Point(134,66),new Point(128,65),new Point(123,65),new Point(117,66),new Point(111,67),
		new Point(106,67),new Point(101,68),new Point(96,69),new Point(91,70),new Point(86,72),
		new Point(82,74),new Point(77,76),new Point(73,78),new Point(69,80),new Point(65,82),
		new Point(61,84),new Point(57,87),new Point(54,89),new Point(50,92),new Point(48,94),
		new Point(44,97),new Point(42,99),new Point(39,102),new Point(36,105),new Point(33,108),
		new Point(31,110),new Point(28,114),new Point(26,117),new Point(24,120),new Point(22,123),
		new Point(20,126),new Point(19,129),new Point(17,132),new Point(16,135),new Point(15,138),
		new Point(13,142),new Point(12,145),new Point(11,148),new Point(11,151),new Point(10,155),
		new Point(10,158),new Point(9,161),new Point(9,164),new Point(9,168),new Point(8,171),
		new Point(8,175),new Point(8,178),new Point(8,181),new Point(9,184),new Point(9,188),
		new Point(9,191),new Point(10,194),new Point(10,198),new Point(11,201),new Point(12,204),
		new Point(13,208),new Point(14,211),new Point(15,214),new Point(16,217),new Point(18,221),
		new Point(20,224),new Point(21,227),new Point(23,230),new Point(25,233),new Point(27,236),
		new Point(30,239),new Point(32,242),new Point(34,245),new Point(37,248),new Point(40,251),
		new Point(43,254),new Point(46,257),new Point(49,259),new Point(52,262),new Point(56,264),
		new Point(60,266),new Point(64,269),new Point(67,271),new Point(71,273),new Point(76,275),
		new Point(80,277),new Point(85,278),new Point(89,280),new Point(94,282),new Point(100,283),
		new Point(105,284),new Point(110,285),new Point(115,285),new Point(121,285),new Point(127,286),
		new Point(132,286),new Point(138,285),new Point(145,285),new Point(150,284),new Point(157,283),
		new Point(163,281),new Point(169,279),new Point(175,277),new Point(181,274),new Point(187,271),
		new Point(193,268),new Point(199,264),new Point(204,260),new Point(210,256),new Point(215,251),
		new Point(220,246),new Point(224,240),new Point(228,234),new Point(231,229),new Point(236,222),
		new Point(239,216)
	);


	this.earth = new Planet(
		this,
		'earth',
		-5,
		-11,
		74,
		1					
	);

	this.mars = new Planet(
		this,
		'mars',
		-5,
		-11,
		113,
		1.871428571
	);

	//1.8808

	this.cycler1 = new Cycler(
		this,
		'cycler1',
		-4,
		-4,
		0
	);				

	this.cycler2 = new Cycler(
		this,
		'cycler2',
		-4,
		-4,
		7
	);	


	this.buildCyclerOrbits = function () {
		var refPoint;
		var refAngle;
		var circle;
		var newPoint;

		//fill out the last few items in the reference orbit
		for (var i=0; i < (this.framesPerYear/7)-1; i++) {
			this.referenceCyclerOrbit.addPoint(this.referenceCyclerOrbit.get(i));
		}

		for (var i=0; i < this.referenceCyclerOrbit.length; i++) {
			refPoint = this.referenceCyclerOrbit.get(i);
			refAngle = refPoint.getAngleFrom(this.orbitsMidpoint);
			circle = new Circle(this.orbitsMidpoint, refPoint.distanceFrom(this.orbitsMidpoint));

			for (var j=0; j < 14; j++) {
				newPoint = circle.getPointAt(refAngle - ((360/14) * j), true);
				if (!this.cyclerOrbits[j]) {
					this.cyclerOrbits[j] = new Orbit();
				}
				this.cyclerOrbits[j].addPoint(newPoint, 'cyclerDots' + j);
			}
		}
		this.cycler1.setOrbit(this.cyclerOrbits[this.cycler1.cyclerOrbitIndex]);
		this.cycler2.setOrbit(this.cyclerOrbits[this.cycler2.cyclerOrbitIndex]);
	};


	this.init = function () {
		var msPerYear = 365 * 24 * 60 * 60 * 1000;
	
		this.canvas = document.getElementById('canvas');
		this.tv = document.getElementById('tv');
		this.dateDiv = document.getElementById('date');
		this.buildCyclerOrbits();
		this.earth.buildCircularOrbit( ((360/7)/2) + 90 );
		this.mars.buildCircularOrbit( 61.9 );

		this.earth.moveToIndex(1);
		this.mars.moveToIndex(1);
		this.cycler1.moveToIndex(1);
		this.cycler2.moveToIndex(106);

		this.cycler1.fudgeMarsIndex = 28;
		
		this.frameTime = msPerYear / this.framesPerYear;
		this.currentDate.setFullYear(2020,4,15);
		this.updateDate();
	};


	this.animate = function (delay) {
		if (this.animationDelay != delay) {
			var body = document.getElementById('body');
			this.animationDelay = delay;
			clearTimeout(this.animationTimeout);
			this.move();
			if (delay == 0) {
				this.tv.style.backgroundImage = "url(images/buzz.jpg)";
				this.tv.style.backgroundRepeat = "no-repeat";
			}
			else {
				this.tv.style.backgroundImage = "url(images/canvas.png)";
				this.tv.style.backgroundRepeat = "repeat";				
			}
		}
	};


	this.pause = function () {
		this.animationDelay = null;
		this.tv.style.backgroundImage = "url(images/canvas.png)";
		clearTimeout(this.animationTimeout);
	};


	this.move = function () {
		var sim = this;

		this.earth.advance();
		this.mars.advance();
		this.cycler1.advance();
		this.cycler2.advance();
		
		this.incrementDate();

		this.animationTimeout = setTimeout(function () { sim.move(); }, this.animationDelay);					
	};
	
	
	this.incrementDate = function () {
		var time = this.currentDate.getTime();
		this.currentDate.setTime(time + this.frameTime);
		
		this.updateDate();
	};
	
	this.updateDate = function () {
		var day = this.currentDate.getDate();
		/*
		var dateString = 
			(day < 10 ? "0" : "") + 
			day + " " + 
			this.monthNames[this.currentDate.getMonth()] + " " +
			this.currentDate.getFullYear();
		*/
		
		var dateString = 
			"Date: " + 
			this.twoDigit(this.currentDate.getMonth()+1) + "/" +
			this.twoDigit(this.currentDate.getDate()) + "/" +
			this.currentDate.getFullYear();
		
		
		this.dateDiv.innerHTML = dateString;
	};
	
	
	this.twoDigit = function (num) {
		if (num < 10) {
			return "0" + num;
		}
		return num;
	};
}


function Planet (simulation, elementId, positionTweakX, positionTweakY, orbitDistance, orbitYears) {

	this.simulation = simulation;
	this.elementId = elementId;
	this.orbitDistance = orbitDistance;
	this.orbitYears = orbitYears;
	this.positionTweakX = positionTweakX;
	this.positionTweakY = positionTweakY;
	this.currentOrbitIndex = 0;
	this.orbit;


	this.buildCircularOrbit = function (startDegrees) {
		this.orbit = new Orbit();

		var circle = new Circle(this.simulation.orbitsMidpoint, this.orbitDistance);
		var degreesPerFrame = 360/(this.simulation.framesPerYear * this.orbitYears);
		for (var i=360; i > 0; i-=degreesPerFrame) {
			this.orbit.addPoint(circle.getPointAt(i+startDegrees));
		}
		this.moveToIndex(this.currentOrbitIndex);
	};


	this.moveToIndex = function (index) {
		var el = document.getElementById(this.elementId);
		index = validItem(index, this.orbit.length);
		var bgx = this.orbit.get(index).x + this.positionTweakX;
		var bgy = this.orbit.get(index).y + this.positionTweakY;

		el.style.backgroundPosition = bgx + "px " + bgy + "px";		
		this.currentOrbitIndex = index;
	};	


	this.fudge = function (targetAngle) {
		//var currentAngle = this.orbit.get(this.currentOrbitIndex);
		//var diff = targetAngle - currentAngle;
		var degreesPerFrame = 360/(this.simulation.framesPerYear * this.orbitYears);
		var newStartAngle = validAngle(targetAngle + (degreesPerFrame * this.currentOrbitIndex));
		this.buildCircularOrbit(newStartAngle);
	};
}		


function Cycler (simulation, elementId, positionTweakX, positionTweakY, cyclerOrbitIndex) {

	this.simulation = simulation;
	this.elementId = elementId;
	this.positionTweakX = positionTweakX;
	this.positionTweakY = positionTweakY;
	this.cyclerOrbitIndex = cyclerOrbitIndex;
	this.currentOrbitIndex = 0;
	this.orbit;
	this.fudgeMarsIndex = -1;

	this.setOrbit = function (orbit) {
		if (this.orbit) {
			this.orbit.visible(false);
		}
		this.orbit = orbit;
		this.orbit.visible(true);
	};


	this.moveToIndex = function (index) {
		var el = document.getElementById(this.elementId);
		var realIndex = validItem(index, this.orbit.length);
		var pos;

		if (realIndex == 0) {
			//earth intersection... time to rotate the nodes
			this.cyclerOrbitIndex+=2;
			this.cyclerOrbitIndex = validItem(this.cyclerOrbitIndex, this.simulation.cyclerOrbits.length);
			this.setOrbit(this.simulation.cyclerOrbits[this.cyclerOrbitIndex]);
		}

		pos = this.orbit.get(realIndex);
		bgx = pos.x + this.positionTweakX;
		bgy = pos.y + this.positionTweakY;

		el.style.backgroundPosition = bgx + "px " + bgy + "px";				

		if (this.fudgeMarsIndex > 0 && realIndex == this.fudgeMarsIndex) {
			//we have to constantly fudge the mars orbit to keep things aligned... javascript math isn't precise enough
			refAngle = pos.getAngleFrom(this.simulation.orbitsMidpoint);
			this.simulation.mars.fudge(refAngle);
		}
		this.currentOrbitIndex = index;
	};				

}		


Planet.prototype.advance = Cycler.prototype.advance = function () {
	this.moveToIndex(this.currentOrbitIndex+1);
};


function Orbit () {
	this.points = [];
	this.dotsLayer;
	this.canvas = document.getElementById('canvas');
	this.length = 0;

	this.addPoints = function (pointsArray) {
		for (var i=0; i < pointsArray.length; i++) {
			this.addPoint(pointsArray[i]);
		}
	};

	this.addPoint = function (point, dotLayerId) {
		this.points.push(point);
		this.length++;

		if (dotLayerId) {
			if (!this.dotsLayer) {
				this.dotsLayer = document.getElementById(dotLayerId);
			}
			var dotsLayer = document.getElementById(dotLayerId);
			var dot = document.createElement('div');
			dot.className = 'orbitDot';
			dot.style.left = point.x + "px";
			dot.style.top = point.y + "px";
			dotsLayer.appendChild(dot);
		}
	};			


	this.get = function (index) {
		return this.points[validItem(index, this.points.length)];
	};


	this.visible = function (visible) {
		this.dotsLayer.style.display = (visible) ? "block" : "none";
	};

	if (arguments.length) {
		this.addPoints(arguments);
	}				
}


function Point (x,y) {
	this.x = x;
	this.y = y;

	this.distanceFrom = function (p) {
		var dist = Math.sqrt(Math.pow((this.x - p.x),2) + Math.pow((this.y - p.y),2));
		return Math.round(dist);
	};


	this.getAngleFrom = function (point) {
		var workingX = this.x - point.x;
		var workingY = this.y - point.y;
		var radians = Math.atan2(workingX, workingY);
		return validAngle(toDegrees(-1 * radians) - 180);
	};				
}



function Circle (midpoint,radius) {
	this.midpoint = midpoint;
	this.radius = radius;

	this.getPointAt = function (degrees) {
		radians = toRadians(degrees+180);
		var x = this.midpoint.x + Math.round(this.radius * Math.sin(-1 * radians));
		var y = this.midpoint.y + Math.round(this.radius * Math.cos(-1 * radians));
		return new Point(x,y);
	};
}


function validAngle (d) {
	return validItem(d, 360);
}


function validItem (index, maxItems) {
	if (index >= maxItems) {
		return index % maxItems;
	}
	if (index < 0) {
		return maxItems + (index % maxItems);
	}
	return index;
}


function toRadians (degrees) {
	return validAngle(degrees) * Math.PI / 180;
}			

function toDegrees (radians) {
	return validAngle((radians * 180 / Math.PI));
}	

