// /home/epsilon/Projects/Asteroid/ABot/Framework1/Framework1/Playfield.cs created with MonoDevelop
// User: epsilon at 16:26 19.04.2008
//

using System;
using System.Collections.Generic;

namespace Framework1
{
	
	
	public class Playfield
	{
		// consts
		public const double WORLD_WIDTH = 1023 - 0;
		public const double WORLD_HEIGHT = 895 - 128;		

		//Fields
		private System.Collections.Generic.List<Asteroid> theAsteroids;
		private System.Collections.Generic.List<Shot> theShots;
		private Saucer theSaucer;
		private Ship theShip;
		private FramePacket theFrame;
		private bool theGameEnded = false;
		
		//Properties
		public bool ShipPresent { get{ return theShip != null;} }
		public bool SaucerPresent { get{ return theSaucer != null;} }
		public Saucer SaucerSprite { get{ return theSaucer; } }
		public Ship ShipSprite { get{ return theShip; } }
		public int FrameNumber { get{ return (int)theFrame.FrameNo; } }
		public string ScreenText { get{ if(theFrame != null) return  theFrame.StringOnScreen; else return ""; } }
		public List<Asteroid> Asteroids { get{ return theAsteroids; } }
		public List<Shot> Shots { get{ return theShots; } }
		public List<Asteroid> AsteroidsByDistance 
		{ 
			get
			{
				if(ShipPresent)
				{
					theAsteroids.Sort(delegate(Asteroid a1, Asteroid a2){ return (a1 - theShip).Abs().CompareTo((a2 - theShip).Abs()) ; });
					return theAsteroids;
				}
				else
				{
					LogOutput.LogWarning("Playfield","AstroidsByDistance not defiend because no Ship is present!");
					return null;
				}
				
			}
		}
		public bool GameEnded
		{
			get
			{
				if (theGameEnded)
				{
					return true;
				}
				else
				{
					if(theFrame != null)
					{
						// search for game over output
						string screenString = theFrame.StringOnScreen;
						//Nullen statt Os!!
						return screenString.IndexOf("GAME 0VER") != -1 || screenString.IndexOf("Y0UR SC0RE IS 0NE 0F THE TEN BEST") != -1;
					}
					else
					{
						return false;
					}
				}
				 
			}
		}
		//Methods
		public Playfield()
		{
			theAsteroids = new List<Asteroid>();
			theShots = new List<Shot>();
		}
		///updates the Playfield with a new framepaket
		public void Update(byte[] packet)
		{
			try
			{
				theFrame = new FramePacket(packet);

				LogOutput.LogDebug("Playfield","retrieved packet  : \n"+theFrame.ToString());
				LogOutput.LogDebug("Playfield","disassebled packet: \n"+theFrame.Disassable());
				//LogOutput.LogDebug("Playfield","retrieved packet: \n"+theFrame.StringOnScreen);
				InterpretScreen();
				LogOutput.LogInfo("Playfield",this.ToString());
			}
			catch(Exception e)
			{
				LogOutput.LogError("Playfield", "Error while parsing FramePacket: " + e.Message);
				if (e.Message == "erste Befehl im Vectorram ist nicht JMPL")
				{
					LogOutput.LogInfo("Game has ended.");
					theGameEnded = true;
				}
			}			
		}
		private void InterpretScreen()
		{
			List<Asteroid> updatedAsteroids = new List<Asteroid>();
			int countNotMatched = 0;
				
			foreach(FramePacket.Candidate cand in theFrame.AsteroidCandidates)
			{
				bool matched = false;
				foreach( Asteroid asteroid in theAsteroids )
				{
					if(!matched)
					{
						if( matched = asteroid.Match(cand) )
							updatedAsteroids.Add(asteroid);
					}
				}
				if( !matched )
				{
					Asteroid asteroid = new Asteroid(cand);
					asteroid.Type = theFrame.GetAsteroidClass(cand);
					updatedAsteroids.Add(asteroid);
					countNotMatched++;
				}
			}
			if(countNotMatched > 0)
				LogOutput.LogInfo("Playfield" , countNotMatched +"Asteroids notMatched, "+updatedAsteroids.Count+" updated");
			
			theAsteroids.Clear();
			theAsteroids = updatedAsteroids;
			
			List<Shot> updatedShots = new List<Shot>();
			countNotMatched = 0;
			
			foreach(FramePacket.Candidate cand in theFrame.ShotCandidates)
			{
				bool matched = false;
				foreach( Shot shot in theShots )
				{
					if(!matched)
					{
						if( matched = shot.Match(cand) )
							updatedShots.Add(shot);
					}
				}
				if( !matched )
				{
					Shot shot = new Shot(cand);
					updatedShots.Add(shot);
					countNotMatched++;
				}				
			}			
				
			if(countNotMatched > 0)
				LogOutput.LogInfo("Playfield" , countNotMatched +" Shots notMatched, "+updatedShots.Count+" updated");
			
			theShots.Clear();
			theShots = updatedShots;
			
			if(theFrame.SaucerCandidates.Count == 1)
			{
				
				if( theSaucer == null || ! theSaucer.Match( theFrame.SaucerCandidates[0] ) )
					theSaucer = new Saucer( theFrame.SaucerCandidates[0] );
			}
			else
				theSaucer = null;
			
			theShip = null;
			if(theFrame.ShipCandidates.Count == 2)
			{
				theShip = new Ship(theFrame.ShipCandidates[0]);
				theShip.Phi = theFrame.GetShipPhi(theFrame.ShipCandidates[0].Address,theFrame.ShipCandidates[1].Address,
				                                  theFrame.ShipCandidates[0].GlobalScale);
			}
		}

		
		///returns description of the Playfield in a string
		public override string ToString()
		{
			string result = "[";
			if(ShipPresent)
				result += "Ship: " + ShipSprite.ToString() + "\n";
			if(SaucerPresent)
				result += "Saucer: " + SaucerSprite.ToString() + "\n";
			result += "Asteroids: ";
			if(ShipPresent)
			{
				foreach(Asteroid a in AsteroidsByDistance)
					result += a.ToString()+"\n";
			}
			else
			{
				foreach(Asteroid a in theAsteroids)
					result += a.ToString()+"\n";
			}
			result += "\nShots";
			foreach(Shot s in theShots)
				result += s.ToString()+"\n"; 
			return result+"]";
		}
		public static void Test()
		{
			LogOutput.LogInfo("Playfield","Testing Playfield");
			Playfield testField = new Playfield();
			byte[] testFrame = { 0,1,2,3 };
			testField.Update(testFrame);
			LogOutput.LogInfo("Playfield","   "+testField.ToString());
			LogOutput.LogInfo("Playfield","Done.");
		}
			
	}
				
	public class Asteroid : Sprite
	{
		private int theType = 0;
		
		public int Type 
		{ 
			get{ return theType; } 
			set
			{
				if(value > 0 && value < 5) 
					theType = value;
				else
					throw new Exception("trying to set Asteroid ty to "+ value +". must be in [1,4]");
			} 
		}
		
		//FIXME calculate right hit and crashradii
		public Asteroid(FramePacket.Candidate cand):
			base(cand,0.1*cand.GlobalScale,1*cand.GlobalScale)
		{
			
		}
		public override string ToString()
		{
			//FIXME: how can I access the Sprite.ToString()?
			string speed = "unknown";
			if(SpeedKnown)
				speed = Speed.ToString();
			return "[ "+Position.ToString()+" Speed: "+speed+" Type: "+Type+" ]";
		}
		/// test Asteroid's capabilities and reports to LogInfo
		public static void Test()
		{
			LogOutput.LogInfo("Testing Asteroid");
			LogOutput.LogInfo("  Generating type 1 at [0, 0] scale 10 => r_h=1 r_c=10");
			Asteroid aster = new Asteroid( FramePacket.Candidate.Test() );
			aster.Type = 3;
			LogOutput.LogInfo("  "+aster.ToString());
			LogOutput.LogInfo("Done.");
		}
	}
	
	public class Saucer : Sprite
	{
		//FIXME calculate right hit and crashradii
		public Saucer(FramePacket.Candidate cand):
			base(cand,0.1*cand.GlobalScale,1*cand.GlobalScale)
		{
			
		}
		/// test Saucers's capabilities and reports to LogInfo
		public static void Test()
		{
			LogOutput.LogInfo("Testing Saucer");
			LogOutput.LogInfo("  Generating saucer at [0, 0] scale 10 => r_h=1 r_c=10");
			Saucer saucer = new Saucer( FramePacket.Candidate.Test() );
			LogOutput.LogInfo("  "+saucer.ToString());
			LogOutput.LogInfo("Done.");
		}
	}
	
	public class Shot : Sprite
	{
		//FIXME calculate right hit and crashradii
		public Shot(FramePacket.Candidate cand):
			base(cand, 0.1*cand.GlobalScale, 1*cand.GlobalScale)
		{
			
		}
		/// test Shots's capabilities and reports to LogInfo
		public static void Test()
		{
			LogOutput.LogInfo("Testing Shot");
			LogOutput.LogInfo("  Generating shot at [0, 0] => r_h=0 r_c=0");
			Shot shot = new Shot( FramePacket.Candidate.Test() );
			LogOutput.LogInfo("  "+shot.ToString());
			LogOutput.LogInfo("Done.");
		}
	}
	
	public class Ship : Sprite
	{
		private double thePhi;
		
		public double Phi 
		{ 
			get{ return thePhi; } 
			set
			{
				if(value >= -Math.PI && value <= Math.PI)
					thePhi = value;
				else
					throw new Exception("trying to set Ship Phi to "+value+" must be in [-pi,pi]");
			}
		}
		
		
		//FIXME calculate right hit and crashradii
		public Ship(FramePacket.Candidate cand):
			base(cand, 1*cand.GlobalScale,1*cand.GlobalScale)
		{
			
		}
		
		public override string ToString()
		{
			//FIXME: how can I access the Sprite.ToString()?
			return "[ "+Position.ToString()+" Phi: "+Phi+" ]";
		}
		
		/// test Shots's capabilities and reports to LogInfo
		public static void Test()
		{
			LogOutput.LogInfo("Testing Ship");
			LogOutput.LogInfo("  Generating ship at [0, 1] direction [10,11]} => r_h=10 r_c=10");
			Ship ship = new Ship( FramePacket.Candidate.Test() );
			ship.Phi = 0.5;
			LogOutput.LogInfo("  "+ship.ToString());
			LogOutput.LogInfo("Done.");
		}
	}
	
	
	public abstract class Sprite
	{
		//constants 
		//private const double MaxSpeedAngle = 0.75;
		private const double MaxSpeedDifference = 1;
		
		//Fields
		private FramePacket.Candidate theLastCand;
		private Point2D theSpeed = null;
		private double theCrashRadius = 0;
		private double theHitRadius = 0;
		private int theNFound = 0;
		//Properties
		public Point2D Position { get{ return theLastCand.Position; } }
		/// TODO Direction is depricated remove as soon as not use anymore
		public Point2D Direction { get{ return Speed; } }
		public bool SpeedKnown { get{ return theSpeed != null; } }
		public Point2D Speed 
		{ 
			get
			{
				if( theSpeed == null )
					throw new ArgumentNullException("the speed has not been initialised, properly this is th first time this object is found!");
				else
					return theSpeed;
			}
		}
		public double CrashRadius { get{ return theCrashRadius; } }
		public double HitRadius { get{ return theHitRadius; } }
		//Methods
		public Sprite(FramePacket.Candidate cand ,double crashRadius, double hitRadius)
		{
			theLastCand = cand;
			
			theNFound = 0;
			theSpeed = null;
			theHitRadius= hitRadius;
			theCrashRadius = crashRadius;
		}
		///matches a Sprite to a Candidate. [true] if matched then the Sprites Fields are updated
		public bool Match(FramePacket.Candidate cand)
		{
			bool result = ( cand.Equals( theLastCand ) && cand.FrameNumber > theLastCand.FrameNumber);
			if(result)
			{
				Point2D newSpeed = ( cand.Position - this.Position ) / ( cand.FrameNumber - theLastCand.FrameNumber);
				if( theNFound == 0 )
					theSpeed = newSpeed;
				else
				{
					LogOutput.LogDebug("Sprite" , "SpeedAngle: " +  Math.Abs( newSpeed.Phi() - theSpeed.Phi() ) );
					LogOutput.LogDebug("Sprite" , "SpeedDifference: " +  Math.Abs( newSpeed.Abs() - theSpeed.Abs() ) );
					                  
					//if( Math.Abs( newSpeed.Phi() - theSpeed.Phi() ) > MaxSpeedAngle )
					if( Math.Abs( newSpeed.Abs() - theSpeed.Abs() ) > MaxSpeedDifference )
						result = false;
					else
						theSpeed = (theSpeed*theNFound + newSpeed) / (theNFound +1);
				}
			}
			if( result )
			{
				theNFound++;
				theLastCand = cand;
			}
			LogOutput.LogDebug("Sprite","n = "+theNFound);
			return result;
		}
		/// returns ture if the Sprite is found in frame number [frameNo]
		public bool InFrame(int frameNo)
		{
			return theLastCand.FrameNumber == frameNo;
		}
		///returns String describing the Sprite
		public override string ToString()
		{
			string speed = "unknown";
			if(SpeedKnown)
				speed = Speed.ToString();
							
			return "[ Pos:"+Position.ToString()+" Speed:"+speed+" r_crash = "+theCrashRadius+" r_hit= "+theHitRadius+" ]";
		}
		
		public static Point2D operator-(Sprite s1, Sprite s2)
		{
			return s1.Position - s2.Position;
		}
		
	}
}
