// /home/heron/projekte/mameBot/ABot/Framework1/Framework1/FramePacket.cs 
// User: heron at 23:59 21.04.2008

using System;
using System.Collections.Generic;

namespace Framework1
{
	
	public class FramePacket
	{
		private enum ObCodes 
		{
			VCTR0 = 0x0000, VCTR1 = 0x1000, VCTR2 = 0x2000, VCTR3 = 0x3000, VCTR4 = 0x4000, 
			VCTR5 = 0x5000, VCTR6 = 0x6000, VCTR7 = 0x7000, VCTR8 = 0x8000, VCTR9 = 0x9000,
			LABS  = 0xA000,
			HALT  = 0xB000,
			JSRL  = 0xC000,
			RTSL  = 0xD000,
			JMPL  = 0xE000,
			SVEC  = 0xF000,
			UNKN  = 0x0001 // Unknown Obcode
		}
		public enum VectorROMPrograms
		{
			CopyRight  = 0x0852, 
			Explosion3 = 0x0880,
			Explosion2 = 0x0896,
			Explosion1 = 0x08B5,
			Explosion0 = 0x08D0,
			Asteroid1  = 0x08F3,
			Asteroid2  = 0x08FF,
			Asteroid3  = 0x090D,
			Asteroid4  = 0x091A,
			Saucer     = 0x0929,
			ShipUp     = 0x0A6D, 
			CharacterA = 0x0A78, 
			Character9 = 0x0B63,
			Character  = 0x2000, // not reacable by code
			Unknown    = 0x1000  // dito.
		}
		private enum Characters
		{
			A = 0x0A78,
			B = 0x0A80,
			C = 0x0A8D,
			D = 0x0A93,
			E = 0x0A9B,
			F = 0x0AA3,
			G = 0x0AAA,
			H = 0x0AB3,
			I = 0x0ABA,
			J = 0x0AC1,
			K = 0x0AC7,
			L = 0x0ACD,
			M = 0x0AD2,
			N = 0x0AD8,
			O = 0x0ADD,
			P = 0x0AE3,
			Q = 0x0AEA,
			R = 0x0AF3,
			S = 0x0AFB,
			T = 0x0B02,
			U = 0x0B08,
			V = 0x0B0E,
			W = 0x0B13,
			X = 0x0B1A,
			Y = 0x0B1F,
			Z = 0x0B26,
			SPACE = 0x0B2C,
			ONE = 0x0B2E,
			TWO = 0x0B32,
			THREE = 0x0B3A,
			FOUR = 0x0B41,
			FIVE = 0x0B48,
			SIX = 0x0B4F,
			SEVEN = 0x0B56,
			EIGHT = 0x0B5B,
			NINE = 0x0B63
		}
			
		//Fields
		private const int theFrameLength = 513;
		private byte[] theVectorRam = new byte[2*theFrameLength];
		private int[] theGlobalScale = new int[theFrameLength];
		private UInt16[] theVectorRam2 = new UInt16[theFrameLength];
		private char theFrameNo = (char)0;  // wird bei jedem Frame inkrementiert
		private char thePing = (char)0;     // Der Server schickt das letzte empfangene ping-Byte zurück
		private int theRemainingLives = 0;
		private System.Collections.Generic.List<Candidate> theAsteroidCandidates = new List<Candidate>();
		private System.Collections.Generic.List<Candidate> theShipCandidates = new List<Candidate>();
		private System.Collections.Generic.List<Candidate> theShotCandidates = new List<Candidate>();
		private System.Collections.Generic.List<Candidate> theSaucerCandidates = new List<Candidate>();
		//Properties
		//TODO add sanity tests here. e.g. need exactly two Ship candidates...
		
		public List<Candidate> AsteroidCandidates { get{ return theAsteroidCandidates;} }
		public List<Candidate> ShipCandidates { get{ return theShipCandidates; } }
		public List<Candidate> ShotCandidates { get{ return theShotCandidates; } }
		public List<Candidate> SaucerCandidates { get{ return theSaucerCandidates; } }
		public char FrameNo{ get{ return theFrameNo; } }
		public char Ping{ get{ return thePing; } }
		public int[] GlobalScale{ get{ return theGlobalScale; } }
		
		private UInt16 this[ int i ]
		{ 
			get
			{
				if(i <= theFrameLength)
					return theVectorRam2[i];
				else
					throw new Exception("FramePacket index out of bounds: "+i+" (max is "+theFrameLength);
			} 
		}
		///retruns the String displayed in this frame.
		public string StringOnScreen
		{
			get
			{
				string result = "";
				for(int addr = 0; addr < theFrameLength; addr++)
				{
					ObCodes code = GetObCode(addr);
					if(code == ObCodes.JSRL)
					{
						VectorROMPrograms prog = GetProgram(addr);
						if(prog == VectorROMPrograms.Character)
							result += GetCharacter(addr);
					}
					if(code == ObCodes.HALT)
						addr = theFrameLength;
				}
				return result;
			}
		}
		
		//methods
		public FramePacket(byte[] array)
		{	
			if (array[1] != 0xe0 && array[1] != 0xe2)
				throw new Exception("erste Befehl im Vectorram ist nicht JMPL"); // sollte nicht vorkommen; erster Befehl ist immer ein JMPL
			
			for (int addr = 0; addr < theFrameLength; addr++)
			{
				// Damit ich später nicht über einen Pointer auf das Vector-Ram zugreifen
				// muss, kopiere ich die Daten in ein ushort Array um.
				theVectorRam2[addr] = (ushort)(array[2 * addr] + array[(2 * addr) + 1] * 256);
				theVectorRam[addr] = array[addr];
				theGlobalScale[addr] = -1;
			}
			theFrameNo = (char)array[1024];
			thePing = (char)array[1025];
			
			int intermediateGSF = 0;
			Point2D position = new Point2D(0,0);
			for (int addr = 0; addr < theFrameLength; addr++)
			{
//				LogOutput.LogInfo("FramePacket","Address = "+addr);
				ObCodes code = GetObCode(addr);
				if( code == ObCodes.LABS)
				{
					int[] point = GetPosition(addr,0);
					position.X = point[0];
					position.Y = point[1];
					intermediateGSF = point[2];
				}
				if( code >= ObCodes.VCTR0 && code <= ObCodes.VCTR9)
				{
					int[] point = GetPosition(addr,intermediateGSF);
					if(code == ObCodes.VCTR6 && point[2] == 12)
						theShipCandidates.Add(new Candidate(position, intermediateGSF, addr, this[addr], theFrameNo));
					if(code == ObCodes.VCTR7 && point[0] == 0 && point[1] == 0 && point[2] == 15)
						theShotCandidates.Add(new Candidate(position, intermediateGSF, addr, this[addr], theFrameNo));
				}
				if( code == ObCodes.JSRL || code == ObCodes.JMPL )
				{
					VectorROMPrograms prog = GetProgram(addr);
					if(prog >= VectorROMPrograms.Asteroid1 && prog <= VectorROMPrograms.Asteroid4)
						theAsteroidCandidates.Add(new Candidate(position, intermediateGSF, addr, this[addr], theFrameNo));
					if(prog == VectorROMPrograms.Saucer)
						theSaucerCandidates.Add(new Candidate(position, intermediateGSF, addr, this[addr], theFrameNo));
					if(prog == VectorROMPrograms.ShipUp)
						theRemainingLives++;
				}

				if((UInt16)code <= 0xA000 )
					addr++;
				
				if( code == ObCodes.HALT )
					addr = theFrameLength;
				
			}
		
		}
		///returns description of the FramePacket
		public override string ToString()
		{
			string result ="[ VectorRam Frame: "+(int)FrameNo+", Ping: "+(int)Ping+"\n";
			result += "  "+theRemainingLives+" Lives remain\n"; 
			foreach(Candidate cand in theShipCandidates)
				result += "  ShipPart  "+cand.ToString()+"\n";
			foreach(Candidate cand in theAsteroidCandidates)
				result += "  Asteroid  "+cand.ToString()+"\n";
			foreach(Candidate cand in theSaucerCandidates)
				result += "  Saucer    "+cand.ToString()+"\n";
			foreach(Candidate cand in theShotCandidates)
				result += "  Shot      "+cand.ToString()+"\n";
					
			return result+"]";
		}
		///Disassebles the FramePacket to homan readable form
		public string Disassable()
		{
			string result ="[ VectorRam Disassbeled: "+(int)FrameNo+", Ping: "+(int)Ping;
			
			int len = 0;
			int globalScalingFactor=0;
			int shipCandidate = -1;
			for(int addr = 0; addr < theFrameLength; addr++)
			{
				UInt16 word = theVectorRam2[addr];	
				result += "\n"+addr+"\t"+word.ToString("X");
				ObCodes code = GetObCode(addr);
				result += "\t"+code.ToString();
				if( code >= ObCodes.VCTR0 && code <= ObCodes.VCTR9)
				{
					int[] point = GetPosition(addr,globalScalingFactor);
					result += "\t( ["+point[0]+", "+point[1]+"] Z: "+point[2]+" )";
					if(code == ObCodes.VCTR6 && point[2] == 12)
					{
						if(shipCandidate == -1)
						{
							shipCandidate = addr;
							result += " ShipCandidate!";
						}
						else
						{
							result += " Ship! phi: "+GetShipPhi(shipCandidate,addr,globalScalingFactor); 
						}
					}
				}
				
				if( code == ObCodes.LABS)
				{
					int[] point = GetPosition(addr,0);
					result += "\t( ["+point[0]+", "+point[1]+"] GSF: "+point[2]+" )";
					globalScalingFactor = point[2];
					shipCandidate = -1; // only ships in one LABS block.
				}
				if( code == ObCodes.JSRL || code == ObCodes.JMPL )
				{
					VectorROMPrograms prog = GetProgram(addr);
					result += "\t( "+prog;
					if(prog == VectorROMPrograms.Character)
						result += ": "+GetCharacter(addr);
					result += " )";
				}
				if(code <= ObCodes.LABS)
				{
					addr++;
//				result += "\t( "+this[addr].ToString("X")+" )";
				}
				
				if( code == ObCodes.HALT )
					addr = theFrameLength;
				len++;
			}
			return result += "\n"+len+"]";
		}
		///returns the ObJode at address [i]
		private ObCodes GetObCode(int i)
		{			
			UInt16 word = this[i];
			ObCodes result = ObCodes.UNKN;
			foreach(ObCodes code in ObCodes.GetValues(typeof(ObCodes)))
			{
				if((word & 0xF000) == (UInt16)code)
					result = code;
			}
			return result;
		}
		///returns the Programm called by JSRL or JMPL at address [i]
		private VectorROMPrograms GetProgram(int i)
		{
			VectorROMPrograms result = VectorROMPrograms.Unknown;
			UInt16 word = this[i];
			if( (word & 0xF000) != (UInt16)ObCodes.JSRL && (word & 0xF000) != (UInt16)ObCodes.JMPL ) 
				throw new Exception("GetProgram: trying to get VectorROMProgram for non Jump ObCode "+GetObCode(i));
			
			word &= 0x0FFF; // delete ObCode from word.
			foreach(VectorROMPrograms prog in VectorROMPrograms.GetValues(typeof(VectorROMPrograms)))
			{
				if( word == (UInt32)prog)
					result = prog;
			}
			if( word >= (UInt16)VectorROMPrograms.CharacterA && word <= (UInt16)VectorROMPrograms.Character9)
				result = VectorROMPrograms.Character;
			return result;
		}
		///returns a character displayed by JSRL or JMPL at [i]
		private string GetCharacter(int i)
		{			
			UInt16 word = this[i];
			if( (word & 0xF000) != (UInt16)ObCodes.JSRL && (word & 0xF000) != (UInt16)ObCodes.JMPL )
				throw new Exception("GetCharacter: trying to get VectorROMProgram for non Jump ObCode "+GetObCode(i));
			word &= 0x0FFF; // delete ObCode from word.
			if(!( word >= (UInt16)VectorROMPrograms.CharacterA && word <= (UInt16)VectorROMPrograms.Character9 ) )
				throw new Exception("trying to get Chararacter for non Character ROM: "+word.ToString("X"));
			
			string result = "?";
			foreach(Characters c in Characters.GetValues(typeof(Characters)))
			{
				if( word == (UInt16)c)
					result = c.ToString();
			}
			switch(result)
			{
			case "SPACE":
				result = " "; break;
			case "O":
				result = "0"; break;
			case "ONE":
						result = "1"; break;
			case "TWO":
				result = "2"; break;
			case "THREE":
				result = "3"; break;
			case "FOUR":
				result = "4"; break;
			case "FIVE":
				result = "5"; break;
			case "SIX":
				result = "6"; break;
			case "SEVEN":
				result = "7"; break;
			case "EIGHT":
				result = "8"; break;
			case "NINE":
				result = "9"; break;
			}
				return result;
		}
		
		///gets Point from VCTR or LABS x,y and Z(VCTR) or GSF(LABS) at [i]. the global scale [gsf] is ignored for LABS.
		private int[] GetPosition(int i, int gsf)
		{
			UInt16 word = this[i];
			UInt16 word2 = this[i+1];
			if(!( ( (word & 0xF000) >= (UInt16)ObCodes.VCTR0 && (word & 0xF000) <= (UInt16)ObCodes.VCTR9 ) 
			     || ( (word & 0xF000) == (UInt16)ObCodes.LABS ) ) )
				throw new Exception("trying to get Position for non Point ObCode "+GetObCode(i));
			
			int[] result={0,0,0};
			//read Y
			result[1] = word & 0x03FF;
			if((word & 0x0400) != 0)
				result[1] *= -1;
			//read X
			result[0] = word2 & 0x03FF;
			if((word2 & 0x0400) != 0)
				result[0] *= -1;
			//read Z (VCTR) or GSF (LABS) 
			result[2]= word2 >> 12;
			if( (word & 0xF000) != (UInt16)ObCodes.LABS )
			{
				int scale = (word & 0xF000) >> 12;
				LogOutput.LogDebug("FramePacket","scale: "+scale+", gsf: "+gsf+" => "+(1 << (9 -scale)));
				scale += gsf;
				scale %= 8;
				
				result[0] /= 1 << (9 -scale); // 2^(9-scale)
				result[1] /= 1 << (9 -scale); // 2^(9-scale)
			}
			
			return result;
		}
				
		///returns Phi of ship candidates [a1] and [a2] in LABS-Block with global scaling [gsf]
		public double GetShipPhi(int a1, int a2, int gsf)
		{
			int[] cand1 = GetPosition(a1, gsf);
			int[] cand2 = GetPosition(a2, gsf);
			
			Point2D p1 = new Point2D(cand1[0], cand1[1]);
			Point2D p2 = new Point2D(cand2[0], cand2[1]);
			Point2D diff = p1 - p2;
			//LogOutput.LogDebug("FramePacket",diff.Phi()+"\t"+cand1[0]+"\t"+cand1[1]+"\t"+cand2[0]+"\t"+cand2[1]);
			LogOutput.LogDebug("FramePacket","Ship with l1 =\t"+p1.Abs()+" l2 =\t"+p2.Abs()+" diff =\t"+diff.Abs()+" phi =\t"+diff.Phi());
			return diff.Phi();
		}
		public int GetAsteroidClass(Candidate cand)
		{
			VectorROMPrograms prog = GetProgram(cand.Address);
			if(prog < VectorROMPrograms.Asteroid1 || prog > VectorROMPrograms.Asteroid4)
				throw new Exception("trying to get Asteroid type for non Asteroid "+prog);
			int result = 0;
			switch(prog)
			{
			case VectorROMPrograms.Asteroid1:
				result = 1; break;
			case VectorROMPrograms.Asteroid2:
				result = 2; break;
			case VectorROMPrograms.Asteroid3:
				result = 3; break;
			case VectorROMPrograms.Asteroid4:
				result = 4; break;
			}
			return result;
		}
		
		public class Candidate
		{
			private const double MaxDifference = 25.0; //FIXME should be calibrated not hardcoded, bigger for slow games
			
			private Point2D thePosition;
			private int theGlobalScale = -1;
			private int theAddress = -1;
			private int theFrameNumber = -1;
			private UInt16 theCode = 0x0000;
			
			public Point2D Position { get{ return thePosition; } }
			public int GlobalScale { get{ return theGlobalScale; } }
			public int Address { get{ return theAddress; } }
			public int FrameNumber{ get{ return theFrameNumber; } }
			public int Code{ get{ return theCode; } }
			
			public Candidate(Point2D p, int gsf, int addr, UInt16 code,int frameNo)
			{
				theCode = code;
				theFrameNumber = frameNo;
				thePosition = new Point2D(p);
				theGlobalScale = gsf;
				theAddress = addr;
			}
			
			public bool Equals( Candidate cand )
			{
				//LogOutput.LogInfo("Candidate","distance in matching: "+( this.Position - cand.Position ).Abs() );
				return this.Code == cand.Code && ( this.Position - cand.Position ).Abs() < MaxDifference && cand.GlobalScale == this.GlobalScale;
			}
			
			public override string ToString()
			{
				return "Candidate @ "+theAddress+", "+thePosition.ToString()+" with scale: "+theGlobalScale;
			}
			
			public static Candidate Test()
			{
				LogOutput.LogInfo("Candidate","Testing Candidate:");
				Candidate cand = new Candidate(Point2D.Test(),0,0,0x0000,0);
				LogOutput.LogInfo("Candidate","   "+cand.ToString());
				LogOutput.LogInfo("Candidate","Done.");
				return cand;
			}
		}
		
	}
}
