//  
//  LapperInstance.cs
//  
//  Author:
//       Robert BRACCAGNI alias Gai-Luron <lfsgailuron@free.fr>
// 
//  Copyright (c) 2010 Gai-Luron
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Threading;

namespace LapperInstances
{
	public enum status
	{
		NOT_CONNECTED,	//  0
		STANDBY,   		//  1
		WORK,   		//  2
		TRY_TO_CONNECT,	//  4
		TRY_TO_LAUNCH,	//  8
	}
	class LapperInstance
	{

		public infoInstance infos;
		Thread LapperInstanceThread;
		private InSim.Connect insimConnection = null;
		private InSim.Encoder myEncoder = new InSim.Encoder();
        GLDebug.Debug myDebug;
		public LFSLapper.LFSClient myClient = null;
		private bool terminateThread = false;
		static object obj = new object();
		static DateTime NextStartWork = DateTime.Now;
		private DateTime LastDateError = DateTime.Now;
		private int cumuledError = 0;
		public status currentStatus = status.NOT_CONNECTED;

		public LapperInstance(infoInstance pinfos)
		{
			infos = pinfos;
            myDebug = new GLDebug.Debug( infos.workingDir + "/logs" );
            myDebug.AddDebugOutput( "err",infos.ip + "-" + infos.port + "-ERR.log" );
            myDebug.AddDebugOutput( "mss",infos.ip + "-" + infos.port + "-MSS.log" );

			this.LapperInstanceThread = new Thread(new ThreadStart(this.threadInstance));
			this.LapperInstanceThread.Start();
		}

		void SendMsgToConnection(int UCID, string msg, int Sound)
		{
			if (msg.Length > 0)
			{
				if (UCID != -1)
				{
					byte[] outMsg = myEncoder.MTC(UCID, 0, msg, Sound);
					insimConnection.Send(outMsg, outMsg.Length);
				}
			}
		}
		void SendMsg(string msg)
		{
			if (msg.Length > 0)
			{
				byte[] outMsg = myEncoder.MST(msg);
				insimConnection.Send(outMsg, outMsg.Length);
			}
		}
		private void openStbMode()
		{
			byte[] sstreq = myEncoder.SST();

			insimConnection = new InSim.Connect(myDebug);
            insimConnection.insimConnect(infos.ip, infos.port, infos.password, "U", "LFSLapper", false, true, -1, -1);
            insimConnection.Send(sstreq, sstreq.Length);
			insimConnection.waitReceive = false;
			// Get All Connections
			System.Threading.Thread.Sleep(100);
			byte[] ismncn = myEncoder.NCN();
			insimConnection.Send(ismncn, ismncn.Length);
		}
		private void closeStbMode()
		{
			insimConnection.Close();
		}

		private void doConnection(){ // Called by the thread
						#region 1
			while (true)
			{
				if (infos.autoStart == infoInstance.autoStartVal.AUTO_NONE)
				{
                    System.Threading.Thread.Sleep(1000);
					currentStatus = status.NOT_CONNECTED;
                    if (terminateThread == true)
                    return;
                    
					//continue;
				}
				if (infos.autoStart == infoInstance.autoStartVal.AUTO_STBY)
				{
					#region stby
					int nbCnt = 0;
					byte[] recvPacket;
					byte[] stiny;
					System.Collections.Hashtable listOfPlayers = new System.Collections.Hashtable();
					LFSLapper.userGroups ugroup = new LFSLapper.userGroups(myDebug);

					openStbMode();
					SendMsg("LFSLapper in Stand By State");
					currentStatus = status.STANDBY;
					while (true)
					{
						while (true)
						{
							recvPacket = null;
							InSim.Connect.objPacket objPack = insimConnection.Receive();
							if (objPack != null)
								recvPacket = objPack.recvPacket;

							if( terminateThread == true )
							{
								SendMsg("LFSLapper Closed");
								insimConnection.TInsimReceive.Abort();
								//System.Threading.Thread.Sleep(5000);
								closeStbMode();
								return;
							}
							if (recvPacket != null)
								break;
							else
							{
								System.Threading.Thread.Sleep(100);
								nbCnt++;
								if (nbCnt > 10)
								{
									nbCnt = 0;
									stiny = myEncoder.TINY_PING();
									insimConnection.Send(stiny, stiny.Length);
								}
							}
						}
						if( recvPacket.Length > 3 )
						{
							string packetHead;
							packetHead = insimConnection.packetHead(recvPacket);
							switch (packetHead)
							{
								case "TINY"://confirm ack with ack
									InSim.Decoder.TINY tiny = new InSim.Decoder.TINY(recvPacket);
									switch (tiny.SubT)
									{
										case "TINY_NONE":
											stiny = myEncoder.TINY_NONE();
											insimConnection.Send(stiny, stiny.Length);
											break;
									}
									break;
								case "NCN": // New Connection
									InSim.Decoder.NCN newConnection = new InSim.Decoder.NCN(recvPacket);
									listOfPlayers[newConnection.UCID] = newConnection.userName;
									break;
								case "CNL": // ConN Leave (end connection is moved down into this slot)
									InSim.Decoder.CNL lostConnection = new InSim.Decoder.CNL(recvPacket);
									if (listOfPlayers.ContainsKey(lostConnection.UCID))
										listOfPlayers.Remove(lostConnection.UCID);
									break;

								case "MSO": // Message Output
									InSim.Decoder.MSO mso = new InSim.Decoder.MSO(recvPacket);
									if (mso.UserType == 1 || mso.UserType == 2)
									{
										if (mso.message.ToLower() == "!status")
										{
											SendMsgToConnection(mso.UCID, "LFSLapper is on stand by state",1);
										}
										else if (mso.message.ToLower() == "!stop")
										{
											SendMsgToConnection(mso.UCID, "LFS Lapper is already in stand by mode!",1);
										}
										else if (mso.message.ToLower() == "!start")
										{
											ugroup.addUserFromFile("superUsers", infos.workingDir + "/" + infos.superUsers);
											if ((listOfPlayers.ContainsKey(mso.UCID) && ugroup.userExist("superUsers", (string)listOfPlayers[mso.UCID])) || (mso.UCID == 0 )){
												infos.autoStart = LapperInstances.infoInstance.autoStartVal.AUTO_WORK;
											}
											else
												SendMsgToConnection(mso.UCID, "Only for admins!",1);
										}
									}
									break;
								default:
									break;
							}
						}
						if( infos.autoStart != LapperInstances.infoInstance.autoStartVal.AUTO_STBY)
                        {
                            closeStbMode();
							break;
						}
					}
					#endregion stby
				}
				if (infos.autoStart == infoInstance.autoStartVal.AUTO_WORK)
				{
					#region workMode
					openStbMode();
					SendMsg("LFSLapper in Work state");
					closeStbMode();
					while (true)
					{
						try
						{
							while (true)
							{
								lock (obj)
								{
									if (NextStartWork < DateTime.Now)
									{
										NextStartWork = DateTime.Now.AddSeconds(LFSLapper.paramLapper.secToNextWorkMode);
										break;
									}
								}
								System.Threading.Thread.Sleep(500);
							}
							myClient = new LFSLapper.LFSClient(infos.groupId,infos.name ,infos.ip, infos.port, infos.workingDir, infos.iniFile, infos.superUsers, LapperInstances.LFSServers.timeOutScript);
							currentStatus = status.WORK;
                            byte[] ism = myEncoder.ISM();
                            insimConnection.Send(ism, ism.Length);
                            myClient.doloop();

							if (!myClient.RestartProg)
							{
								infos.autoStart = LapperInstances.infoInstance.autoStartVal.AUTO_STBY;
								myClient = null;
								break;
							}
							myClient = null;
						}
						catch (System.Net.Sockets.SocketException ex)
						{
							throw (ex);
						}
						catch (InSim.Connect.ConnectException ex)
						{
							throw (ex);
						}
						catch (System.Exception ex)
						{
							if (LastDateError.AddSeconds(10) > DateTime.Now)
								this.cumuledError++;
							else
								this.cumuledError = 0;
							this.LastDateError = DateTime.Now;
							currentStatus = status.TRY_TO_LAUNCH;
							Console.WriteLine("\nLapper Instance abort, Go in standBy mode. Look at log file :" + myDebug.getFileName("err"));
							myDebug.printDateOnEachLine = false;
							myDebug.WriteSeparator("err");
							myDebug.WriteLineDate("err");
							myDebug.WriteLine("err", "\nLapper Instance " + infos.ip + "/" + infos.port + " abort!");
							myDebug.WriteLine("err", "");
							myDebug.WriteLine("err", ex.Message);   // Print the error message.
							myDebug.WriteLine("err", ex.Source);    // Name of application or object that caused the error.
							myDebug.WriteLine("err", ex.StackTrace); //String that contains the stack trace for this exception.
							myDebug.WriteLine("err", ex.TargetSite.ToString()); //String that contains the stack trace for this exception.
							myDebug.WriteLine("err", "Closing Instance...");
							myDebug.WriteSeparator("err");
							myDebug.printDateOnEachLine = true;
							if (this.cumuledError > 4)
							{ // Too many error in little time, goto in standBy
								infos.autoStart = LapperInstances.infoInstance.autoStartVal.AUTO_STBY;
								Console.WriteLine("\nToo many errors during connecting!");
                                Console.WriteLine("\nLapper in Stand-By mode!");
                            }
						}
						if (infos.autoStart != LapperInstances.infoInstance.autoStartVal.AUTO_WORK)
							break;
					}
					#endregion
				}
			}
			#endregion
		}
		private void threadInstance()
		{
			System.Globalization.CultureInfo oldCult = System.Threading.Thread.CurrentThread.CurrentCulture;
			System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
			while (true)
			{

				try
				{
					doConnection();
					if (terminateThread == true)
					{
						System.Threading.Thread.CurrentThread.CurrentCulture = oldCult;
						currentStatus = status.NOT_CONNECTED;
                        return;
					}
				}
				catch (System.Net.Sockets.SocketException)
				{
					currentStatus = status.TRY_TO_CONNECT;
					Console.WriteLine("ERROR: Cannot connect to server [" + infos.name + "/" + infos.ip + ":" + infos.port + "] retry in 1 minute");
					System.Threading.Thread.Sleep(60000);
				}
				catch (InSim.Connect.ConnectException)
				{
					currentStatus = status.TRY_TO_CONNECT;
					Console.WriteLine("ERROR: Cannot connect to server [" + infos.name + "/" + infos.ip + ":" + infos.port + "] retry in 1 minute");
                    System.Threading.Thread.Sleep(60000);
				}
			}
		}
		public void stopLapperInstance() // Stop Lapper Instance
		{
			if (myClient != null)
				myClient.TermProg = true; // Stop LFS is needed
			terminateThread = true;			// Stop the thread
			LapperInstanceThread.Join();	// Wait for thread stop
		}
		public infoInstance goWorkMode() // Go in Work mode
		{
			if (myClient != null)
				return infos;
			infos.autoStart = infoInstance.autoStartVal.AUTO_WORK;
			return infos;
		}
		public infoInstance goStbMode() // Go in standBy mode mode
		{
			if (myClient != null)
				myClient.TermProg = true; // Stop LFS is needed
			infos.autoStart = infoInstance.autoStartVal.AUTO_STBY;
			return infos;
		}
		public infoInstance goAutoNoneMode() // Go in standBy mode mode
		{
			if (myClient != null)
				myClient.TermProg = true; // Stop LFS is needed
			infos.autoStart = infoInstance.autoStartVal.AUTO_NONE;
			return infos;
		}
    }
}
