﻿package{
	import flash.display.Sprite;
	import flash.geom.Vector3D;	
	import com.physicscodes.math.Vector3DX;
	import com.physicscodes.motion.Forces3DX;	
	import com.physicscodes.objects.Ball;
	import flash.events.Event;
	import com.physicscodes.constants.*;
	import flash.geom.Matrix3D;		
	import com.physicscodes.math.Graph;	
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;	
	import flash.net.URLRequest;	
		
	public class SolarSystem extends Sprite{
		// time-keeping variables
		private var _dt:Number = 1/24; // simulation time unit is 1 day; time-step is 1 hr
		//private var _numSteps:Number = 8760; // 1 year; 365*24 
		private var _numSteps:Number = 8808; // 367 days; 367*24 
		//private var _numSteps:Number = 8568; // 357 days
		private var _animFreq:Number = 168; // once per week; 24*7
		private var _t:Number = 0;
		
		// gravitational constant
		private var _G:Number;

		// sun variables 
		private var _center:Vector3DX;
		private var _massSun:Number;
		private var _radiusSun:Number=30;

		// velocity and position vectors for all planets
		private var _v:Vector.<Vector3DX>;	
		private var _s:Vector.<Vector3DX>;		

		// visual objects
		private var _sprite:Sprite;		
		private var _sun:Ball;				
		private var _planets:Vector.<Ball>;
		private var _numPlanets:uint=4;

		// planets' properties
		private var _colors:Array;				
		private var _radiuses:Array;		
		private var _masses:Array;	
		
		// scaling factors
		private var _scaleTime:Number;
		private var _scaleDist:Number;
		private var _scaleMass:Number;
		private var _scaleVelo:Number;
		
		// graphs
		private var _graphX:Graph;
		private var _graphY:Graph;
		private var _graphZ:Graph;		
		
		// arrays to hold NASA data
		private var _arrData:Array = new Array();	
		private var _tArray:Array = new Array();
		private var _xArray:Array = new Array();			
		private var _yArray:Array = new Array();			
		private var _zArray:Array = new Array();				
		// color of data plots
		private var _colData:uint = 0x333333;

		private var _planetNames:Array = ['Mercury','Venus','Earth','Mars','Jupiter','Saturn','Uranus','Neptune'];
		// choose which planet's graphs to plot
		private var _ip:uint = 4; // choose from {1,2,3,4,5,6,7,8}, ordered corresponding to increasing distance of planet from Sun; e.g. Earth is 3  
		
		public function SolarSystem():void{
			init();
		}

		private function init():void{
			setupScaling();
			setupPlanetData();
			setInitialConditions();			
			setupDataFile();	
		}
		
		private function setupDataFile():void{
			var file:String = 'planets_data/' + _planetNames[_ip-1] + '_XYZ.csv';
			loadFile(file);
		}
		
		private function loadFile(pFile:String):void{
			var dataLoader:URLLoader = new URLLoader();
			dataLoader.dataFormat = URLLoaderDataFormat.TEXT;
			dataLoader.addEventListener(Event.COMPLETE, csvLoaded);
			dataLoader.load(new URLRequest(pFile));			
		}		
		
		private function csvLoaded(e:Event):void {
			_arrData = e.target.data.split(/\r/); 
    		for (var i:int=0; i<_arrData.length; i++) {
				_tArray[i] = i;
        		var arr:Array = _arrData[i].split(",");
				_xArray[i] = arr[0]*1000/_scaleDist;
				_yArray[i] = arr[1]*1000/_scaleDist;
				_zArray[i] = arr[2]*1000/_scaleDist;				
			}		
			initSetup();			
		}		

		private function initSetup():void{
			_sprite = new Sprite();
			addChild(_sprite);
			setupObjects();
			setupGraphs();
			plotData();			
			simulate();			
		}
		
		private function setupScaling():void{
			_scaleMass = Astro.EARTH_MASS;			
			_scaleTime = Astro.EARTH_DAY;
			_scaleDist = 1e9; // 1 million km or 1 billion meters: 1 px
			_scaleVelo = _scaleDist/_scaleTime; // million km per day

			_massSun = Astro.SUN_MASS/_scaleMass;

			var G:Number = Phys.GRAVITATIONAL_CONSTANT;
			_G = _scaleMass*_scaleTime*_scaleTime*G/(_scaleDist*_scaleDist*_scaleDist);
		}
		
		private function setupPlanetData():void{
			_colors = [0xffffcc, 0xffcc00, 0x0099ff, 0xff6600];			
//			_radiuses = [3.8, 9.5, 10, 5.3];
			_radiuses = [1.9, 4.7, 5, 2.7];
			_masses = new Array();
			_masses[0] = Astro.MERCURY_MASS/_scaleMass;			
			_masses[1] = Astro.VENUS_MASS/_scaleMass;			
			_masses[2] = Astro.EARTH_MASS/_scaleMass;			
			_masses[3] = Astro.MARS_MASS/_scaleMass;		
		}
		
		private function setInitialConditions():void{
			_center = new Vector3DX(300,300,0);

			_s = new Vector.<Vector3DX>();						
			_s[0] = new Vector3DX(-5.673486551269988E+07, -2.905807776472880E+07,  2.831471548856726E+06); // mercury
			_s[1] = new Vector3DX(1.083622101258184E+08,  5.186812728386815E+06, -6.182877404899519E+06); // venus
			_s[2] = new Vector3DX(-2.501567084587771E+07,  1.449614303354543E+08, -4.572052182107447E+03); // earth
			_s[3] = new Vector3DX(-1.779441811545570E+08,  1.720857121437973E+08,  7.974975344806875E+06); // mars		
			for (var i:uint=0; i<_s.length; i++){
				_s[i] = _center.addScaled(_s[i],1000/_scaleDist); // distances in NASA data are in km
			}
			
			_v = new Vector.<Vector3DX>();			
			_v[0] = new Vector3DX(1.216453506755825E+01, -4.123145442643666E+01, -4.484987038755303E+00); // mercury
			_v[1] = new Vector3DX(-1.818140449649583E+00,  3.482184715827766E+01,  5.820179514330701E-01); // venus
			_v[2] = new Vector3DX(-2.983875373861814E+01, -5.188096364047718E+00,  6.015483423878356E-04); // earth
			_v[3] = new Vector3DX(-1.592816222793181E+01, -1.535286664845714E+01,  6.941063066996438E-02); // mars			
			for (var j:uint=0; j<_v.length; j++){
				_v[j].scaleBy(1000/_scaleVelo); // velocities in NASA data are in km/s
			}
		}
		
		private function setupObjects():void{
			// sun
			_sun = new Ball(_radiusSun,0xffff00);
			_sun.pos = _center;
			_sprite.addChild(_sun);
			
			// planets
			_planets = new Vector.<Ball>();
			for (var n:uint=0; n<_numPlanets; n++){
				var planet:Ball = new Ball(_radiuses[n],_colors[n],_masses[n]);
				planet.pos = _s[n];
				planet.velo = _v[n];
				_sprite.addChild(planet);
				_planets.push(planet);
			}
		}

		private function simulate():void{
			for (var i:uint=0; i<_numSteps; i++){ // time-stepping
				_t += _dt; 
				for (var n:uint=0; n<_numPlanets; n++){
					RK4(n);
					if (i%_animFreq==0){
						animate(n);
						//plotGraphs(n);
					}
				}
				plotGraphs(_ip-1); 
			}
		}
		
		private function animate(n:uint):void{
			_planets[n].pos = _s[n];			
			clonePlanet(n);
		}		

		private function clonePlanet(n:uint):void{
			var pcopy:Ball = _planets[n].clone();
			pcopy.pos = _planets[n].pos;
			_sprite.addChild(pcopy);			
		}		
		
		private function setupGraphs():void {
			with (this.graphics){
				beginFill(0xeeeeee);
				drawRect(600,0,400,600);
				endFill();
			}
			
			//_graph= new Graph(xmin,xmax,ymin,ymax,xorig,yorig,xwidth,ywidth);	
			_graphX= new Graph(0,400,-250,250,650,100,300,100);					
			_graphX.drawgrid(100,10,250,50);			
			_graphX.drawaxes(' t (days)',' '+_planetNames[_ip-1] + ' X (million km)');	
			this.stage.addChild(_graphX);
			_graphY= new Graph(0,400,-250,250,650,300,300,100);					
			_graphY.drawgrid(100,10,250,50);			
			_graphY.drawaxes(' t (days)',' '+_planetNames[_ip-1] + ' Y (million km)');	
			this.stage.addChild(_graphY);			
			_graphZ= new Graph(0,400,-20,20,650,500,300,100);					
			_graphZ.drawgrid(100,10,20,5);			
			_graphZ.drawaxes(' t (days)',' '+_planetNames[_ip-1] + ' Z (million km)');	
			this.stage.addChild(_graphZ);			
		}
		
		private function plotData():void{
			_graphX.plot(_tArray, _xArray, _colData, false, true, 1, 1);	
			_graphY.plot(_tArray, _yArray, _colData, false, true, 1, 1);	
			_graphZ.plot(_tArray, _zArray, _colData, false, true, 1, 1);				
		}	
		
		private function plotGraphs(pn:uint):void{
			_graphX.plot([_t], [_s[pn].x-_center.x], _colors[pn], false, true, 3, 0.02);	
			_graphY.plot([_t], [_s[pn].y-_center.y], _colors[pn], false, true, 3, 0.02);	
			_graphZ.plot([_t], [_s[pn].z-_center.z], _colors[pn], false, true, 3, 0.02);				
		}			
		
		private function getAcc(ppos:Vector3DX,pvel:Vector3DX,pn:uint):Vector3DX{
			var massPlanet:Number = _planets[pn].mass;
			var r:Vector3DX = Vector3DX.convert(ppos.subtract(_center));
			// force exerted by sun
			var force:Vector3DX = Forces3DX.gravity(_G,_massSun,massPlanet,r);
			// forces exerted by other planets
			for (var n:uint=0; n<_numPlanets; n++){
				if (n!=pn){ // exclude the current planet itself!
					r = Vector3DX.convert(ppos.subtract(_s[n]));
					var gravity:Vector3DX = Forces3DX.gravity(_G,_masses[n],massPlanet,r);;
					force = Forces3DX.add([force, gravity]);
				}
			}
			// acceleration
			return force.multiply(1/massPlanet);
		}		
		
		private function RK4(n:uint):void{			
			// step 1
			var pos1:Vector3DX = _s[n];
			var vel1:Vector3DX = _v[n];
			var acc1:Vector3DX = getAcc(pos1,vel1,n); 
			// step 2
			var pos2:Vector3DX = pos1.addScaled(vel1,_dt/2); 
			var vel2:Vector3DX = vel1.addScaled(acc1,_dt/2);
			var acc2:Vector3DX = getAcc(pos2,vel2,n); 
			// step 3
			var pos3:Vector3DX = pos1.addScaled(vel2,_dt/2); 
			var vel3:Vector3DX = vel1.addScaled(acc2,_dt/2);
			var acc3:Vector3DX = getAcc(pos3,vel3,n); 
			// step 4
			var pos4:Vector3DX = pos1.addScaled(vel3,_dt); 
			var vel4:Vector3DX = vel1.addScaled(acc3,_dt);
			var acc4:Vector3DX = getAcc(pos4,vel4,n); 
			// sum vel and acc
			var velsum:Vector3DX = vel1.addScaled(vel2,2).addScaled(vel3,2).addScaled(vel4,1);
			var accsum:Vector3DX = acc1.addScaled(acc2,2).addScaled(acc3,2).addScaled(acc4,1);
			// update pos and velo
			_s[n] = pos1.addScaled(velsum,_dt/6);
			_v[n] = vel1.addScaled(accsum,_dt/6);			
		}
		
	}
}
