//  
//  lfsclient.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;
using LapperThreads;
using Configurator;
using System.Collections;
namespace LFSLapper
{
    partial class LFSClient
    {
        public class votation
        {
            public int lastVotedRestart = 0;
            public int lastVotedQualify = 0;
            public int lastVotedEnd = 0;
            public bool allowRestart = false;
            public bool allowQualify = false;
            public bool allowEnd = false;
        }
        public class posQualUser
        {
            public string userName;
            public string nickName;
            public int posUser;
            public int totalUser;
            public int groupUser;

            public posQualUser(string userName, string nickName,int pos, int total, int group)
            {
                this.userName = userName;
                this.nickName = nickName;
                this.posUser = pos;
                this.totalUser = total;
                this.groupUser = group;
            }
        }
        public class infoHandicap
        {
            public int H_Mass = 0;
            public int H_TRes = 0;
            public infoHandicap(int H_Mass, int H_TRes)
            {
                this.H_Mass = H_Mass;
                this.H_TRes = H_TRes;
            }
        }
        public class raceInfo
        {
            public bool inRace = false;
            public int lapsDone = 0;
            public bool allowIdleOnTrack = false;
            public DateTime started;
            public bool autoRestartOn = false;
            public DateTime nextRestart = DateTime.Now.AddSeconds(3); // Autorestart on boot 3 s after to have status
            public int racesDone = 0;
            public bool isFreeRoaming = false;
            public bool isRaceRunning = false;
            public bool isAutoRestartOn = false;
            public int currRotateTrack = 0;
            public int currRotateCar = 0;
            public int raceLaps = 0;
            public int raceLapsDone = 0;
            public int raceLapsLeft = 0;
            public int qualMins = 0;
            public int raceMins = 0;
            public int raceFlags = 0;
            public int timing = 0;

        }

        raceInfo currRace = new raceInfo();
        votation currVotation = new votation();
        InSim.Connect insimConnection;
        public LFSDbs.gripDbs gripSqlDbs;
        public LFSDbs.driftDbs driftSqlDbs;
        public LFSDbs.storedDbs storedSqlDbs;

        public string connectIP = "";
        public string HostName = "";
        public int connectPort = 0;
        public string connectName = "";
        public string connectGroupID = "";

        public string uniqueConnectionId = "";

        cfguser cfu = new cfguser();
//        clSyncUploadDownload threadSyncDownload;
        WebCmds objWebCmd;
        PubStatUser objPubStatUser;
        registerWeb objregisterWeb;
        DiscordBot objDiscordBot;
        LFSRestAPI objLFSRestAPI;
        trackCarData trackInfo;
        System.Collections.Hashtable playerFilter = new System.Collections.Hashtable();
        System.Collections.Hashtable playerAlwaysAuth = new System.Collections.Hashtable();

        public wr currWr;
        private InSim.Encoder myEncoder = new InSim.Encoder();

        //string barGraphChar = InSim.CodePage.chr('L', 145);
    

        bool wrLoaded = false;
        public bool TermProg = false;
        public int ForceRotation = 0;
        public bool RestartProg = false;
        public bool startOk = false;


        public lexConfigurator newCfg;

        #region swearwords
        swearwords swearWordObj;
        #endregion


        #region handicap options
        System.Collections.Hashtable carTrackHandicap = new System.Collections.Hashtable();
        System.Collections.Hashtable carHandicap = new System.Collections.Hashtable();
        System.Collections.Hashtable playerHandicap = new System.Collections.Hashtable();
        #endregion


        int InRaceLapsVoteMin = 0;
        int InRaceLapsVoteMax = 9999;

        #region Qualification Option
        int MaxGroupQual = 0;
        int MaxUserGroupQual = 0;
        int MinUserGroupQual = 0;
        #endregion


        #region flags players allowed
        #endregion


        System.Collections.ArrayList shortCarNames = new System.Collections.ArrayList();
        System.Collections.ArrayList longCarNames = new System.Collections.ArrayList();

        InSim.Decoder.STA currState = new InSim.Decoder.STA();
//        string currentTrackName = "";
        string currentHName = "";

        System.Timers.Timer timer;

        System.Collections.ArrayList Tasks;
        public Queue incommingCmdStack = new Queue(2048);
        public Queue outgoingCmdStack = new Queue(2048);
        //Globalmsgs list.
        public Queue incomingdiscordmsgstack = new Queue(512);

        listPlayers listOfPlayers;

        ListNodeEvent listOfNodeEvent = new ListNodeEvent();
        ListZoneEvent listOfZoneEvent = new ListZoneEvent();
        ListStreetEvent listOfStreetEvent = new ListStreetEvent(); //Streetcode Yisc[NL]

        string sFlagsRequired = "";

        Thread TRegisterWeb = null;
        Thread TwebCmd = null;
        Thread TPubStatUserPB = null;
        Thread TDiscordBot = null;
        Thread TLFSRestAPI = null;

        public string HName;
        public string scriptFileName;

        public Auth SystemAuth;

        public GLDebug.Debug myDebug;
        userGroups uGroup;
        lang lfsLang;

        public int PlayerLayoutInfoRequest;

        
        public void inputHandler(ConsoleCtrl.ConsoleEvent consoleEvent)
        {
            if (consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlC
                || consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlClose
                || consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlBreak
                || consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlLogoff
                || consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlShutdown)
            {
                byte[] cl = myEncoder.IS_TINY((byte)InSim.TypePack.ISP_TINY, 0, (byte)InSim.TypeTiny.TINY_CLOSE);
                insimConnection.Send(cl, cl.Length);
                System.Environment.Exit(-1);
            }
        }
        
        /*************************************/
        /*************************************/
        /*************************************/
        public LFSClient(string pGroupID, string pName, string pip, int pport, string pWorkingDir, string pInifile, string pSuperUsers, int timeOutScript )
        {
            myDebug = new GLDebug.Debug(pWorkingDir + "/logs");
            myDebug.AddDebugOutput("err", pip + "-" + pport + "-ERR.log");
            myDebug.AddDebugOutput("mss", pip + "-" + pport + "-MSS.log");

            lfsLang = new lang(myDebug);
            
            uGroup = new userGroups(myDebug);
            SystemAuth = new Auth(myDebug);
            try
            {
                myDebug.WriteSeparator("mss");
                myDebug.WriteLineDate("mss");
                myDebug.WriteSeparator("mss");
                init(pGroupID, pName , pip, pport, pWorkingDir, pInifile, pSuperUsers, timeOutScript );
            }
            catch (InSim.Connect.ConnectException ex)
            {
//                myDebug.WriteLine("err", ex.Message);
                finalize();
                throw (ex);
            }
            catch (Exception ex)
            {
/*
                Console.WriteLine("\nLapper Instance " + pip + "/" + pport + " abort. Look at log file :" + myDebug.getFileName("err"));
                myDebug.printDateOnEachLine = false;
                myDebug.WriteSeparator("err");
                myDebug.WriteLineDate("err");
                myDebug.WriteLine("err", "\nLapper Instance " + pip + "/" + pport + " 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;
*/
                UTILS.utils.sendErrorMail(newCfg.varsLapper.adminEmail,
                                                    newCfg.varsLapper.smtpServer,
                                                    newCfg.varsLapper.loginMail,
                                                    newCfg.varsLapper.passMail,
                scriptFileName, ex);
                System.Threading.Thread.Sleep(1000);
                // Close All Lapper's Threads
                finalize();
                throw;
            }
            finally
            {
                myDebug.WriteSeparator("mss");
                myDebug.WriteSeparator("mss");
            }
        }
        private void init(string pGroupID, string pName,  string pip, int pport, string pWorkingDir, string pIniFile, string pSuperUsers, int timeOutScript )
        {
            // Only for crash test
//                            infoPlayer toto = null;
//                            toto.absangle = 1;

            this.connectIP = pip;
            this.connectPort = pport;
            this.connectName = pName;
            this.connectGroupID = pGroupID;
            this.uniqueConnectionId = connectIP.Replace('.', '_') + "_" + connectPort;
            readIni myReadIni = null;
            try
            {
                myReadIni = new readIni(pWorkingDir, pIniFile);
            }
            catch
            {
                throw new Exception("Error on inifile " + pIniFile );
            }
            if( myReadIni.configFile == "" )
                throw new Exception("Error on inifile " + pIniFile + " you must indicate a configfile" );

            this.scriptFileName = pWorkingDir + "/" + myReadIni.configFile;
            // Here to intercept keys Ctrl
            
            try
            {
                ConsoleCtrl cc = new ConsoleCtrl();
                cc.ControlEvent += new ConsoleCtrl.ControlEventHandler(inputHandler);
            }
            catch { myDebug.WriteLine("mss","Intercept don't work"); }
            
            #region Car Names Tables
            shortCarNames.Add("TBO"); longCarNames.Add("TURBO");
            shortCarNames.Add("LRF"); longCarNames.Add("LRF");
            shortCarNames.Add("GTR"); longCarNames.Add("GTR");
            shortCarNames.Add("UF1"); longCarNames.Add("UF 1000");
            shortCarNames.Add("XFG"); longCarNames.Add("XF GTI");
            shortCarNames.Add("XRG"); longCarNames.Add("XR GT");
            shortCarNames.Add("XRT"); longCarNames.Add("XR GT TURBO");
            shortCarNames.Add("RB4"); longCarNames.Add("RB4 GT");
            shortCarNames.Add("FXO"); longCarNames.Add("FXO TURBO");
            shortCarNames.Add("LX4"); longCarNames.Add("LX4");
            shortCarNames.Add("LX6"); longCarNames.Add("LX6");
            shortCarNames.Add("RAC"); longCarNames.Add("RA");
            shortCarNames.Add("FZ5"); longCarNames.Add("FZ50");
            shortCarNames.Add("MRT"); longCarNames.Add("MRT5");
            shortCarNames.Add("XFR"); longCarNames.Add("XF GTR");
            shortCarNames.Add("UFR"); longCarNames.Add("UF GTR");
            shortCarNames.Add("FOX"); longCarNames.Add("FORMULA XR");
            shortCarNames.Add("FO8"); longCarNames.Add("FORMULA V8");
            shortCarNames.Add("FXR"); longCarNames.Add("FXO GTR");
            shortCarNames.Add("XRR"); longCarNames.Add("XR GTR");
            shortCarNames.Add("FZR"); longCarNames.Add("FZ50 GTR");
            shortCarNames.Add("BF1"); longCarNames.Add("BMW SAUBER");
            shortCarNames.Add("FBM"); longCarNames.Add("BMW FB02");
            #endregion

            #region Configuring LFSLapper

            newCfg = new lexConfigurator( myDebug,lfsLang);
			newCfg.CurrApp.funcTimeOut = timeOutScript;
            initScriptFunction(); // Add all LFSLapper dedicated function into GLScript

            cfu.load();
            myDebug.Write("mss","Parsing config file...");
            if (!newCfg.load(this.scriptFileName))
            {
                throw new Exception("Error on script");
            }
            myDebug.printDateOnEachLine = false;
            myDebug.WriteLine("mss","Ok");
            myDebug.printDateOnEachLine = true;
            newCfg.varsLapper.Password = myReadIni.password;
            newCfg.varsLapper.WorkingDir = pWorkingDir;
            newCfg.varsLapper.superUsers = pSuperUsers;
            newCfg.varsLapper.GripDatabase = pWorkingDir + "/" + newCfg.varsLapper.GripDatabase;
            newCfg.varsLapper.DriftDatabase = pWorkingDir + "/" + newCfg.varsLapper.DriftDatabase;
            newCfg.varsLapper.StoredValueDbs = pWorkingDir + "/" + newCfg.varsLapper.StoredValueDbs;
            newCfg.varsLapper.TrackInfoFile = pWorkingDir + "/" + newCfg.varsLapper.TrackInfoFile;

            if (newCfg.varsLapper.LapTimeUsedForPb < 1)
            {
                myDebug.WriteLine("mss","Wrong value for $LapTimeUsedForPb, set to 1");
                newCfg.varsLapper.LapTimeUsedForPb = 1;
            }
            if (newCfg.varsLapper.LapTimeUsedForPb > 10)
            {
                myDebug.WriteLine("mss","Wrong value for $LapTimeUsedForPb, set to 10");
                newCfg.varsLapper.LapTimeUsedForPb = 10;
            }

            #region PubStat et wr get cfg
            myDebug.Write("mss","Loading WR...");
            currWr = new wr();
            if (currWr.load("", "", newCfg.varsLapper.PubStatIdk))
            {
                wrLoaded = true;
                myDebug.printDateOnEachLine = false;
                myDebug.WriteLine("mss","Ok");
                myDebug.printDateOnEachLine = true;
            }
            else
            {
                wrLoaded = false;
                myDebug.printDateOnEachLine = false;
                myDebug.WriteLine("mss", "Not Loaded");
                myDebug.printDateOnEachLine = true;

            }
            #endregion

            #region track info cfg
            trackInfo = new trackCarData();
            if (!trackInfo.load(newCfg.varsLapper.TrackInfoFile))
                myDebug.WriteLine("mss",newCfg.varsLapper.TrackInfoFile + " Not present, some features won't works");
            #endregion


            #region swearwords action get cfg
            swearWordObj = new swearwords();
            myDebug.Write("mss","Loading swearwords...");
            myDebug.printDateOnEachLine = false;
            if (swearWordObj.load(newCfg.varsLapper.WorkingDir, newCfg.varsLapper.SwearWordsList) == false)
                myDebug.WriteLine("mss","Not Loaded");
            else
                myDebug.WriteLine("mss","Ok");
            myDebug.printDateOnEachLine = true;
            #endregion

            #region Authorisation option
            if (newCfg.varsLapper.AuthAllowPlayer == "" || newCfg.varsLapper.AuthAllowPlayer.ToLower() == "all")
            {
                SystemAuth.AllowAllPlayer = true;
            }
            else
            {
                SystemAuth.AllowAllPlayer = false;
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth1);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth2);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth3);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth4);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth5);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth6);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth7);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth8);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth9);
                SystemAuth.AddAuthString(newCfg.varsLapper.Auth10);
                SystemAuth.updAllowedLevel( newCfg.varsLapper.AuthAllowPlayer );
            }

            #endregion


            #region Rotations et autorestart options

            if (newCfg.varsLapper.AutoRestartRaceSec > 0)
                currRace.nextRestart = DateTime.Now.AddMinutes(newCfg.varsLapper.AutoRestartRaceSec);
            if (newCfg.varsLapper.AutoRestartRaceSec == 0)
                newCfg.varsLapper.EnableRotation = false;

            if (newCfg.varsLapper.InRaceLapsVoteMinMax == "")
                newCfg.varsLapper.InRaceLapsVoteMinMax = "-";
            {
                string[] tmpSplit = newCfg.varsLapper.InRaceLapsVoteMinMax.Split('-');
                try
                {
                    if (tmpSplit[0] == "")
                        tmpSplit[0] = "0";
                    InRaceLapsVoteMin = int.Parse(tmpSplit[0]);
                    if (tmpSplit[1] == "")
                        tmpSplit[1] = "99999999";
                    InRaceLapsVoteMax = int.Parse(tmpSplit[1]);

                }
                catch
                {
                    myDebug.WriteLine("mss","Error on cfg : InRaceLapsVoteMinMax = " + newCfg.varsLapper.InRaceLapsVoteMinMax);
                    InRaceLapsVoteMin = 0;
                    InRaceLapsVoteMax = 9999999;
                }
            }
            #endregion


            #region Handicap
            UpdateHandicapUsers(newCfg.varsLapper.HandicapUsers);
            UpdateHandicapCars(newCfg.varsLapper.HandicapCars);
            UpdateHandicapCarsTracks(newCfg.varsLapper.HandicapCarsTracks);
            #endregion

            timer = new System.Timers.Timer(10000);
            timer.AutoReset = false;
            timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);


            #region load scheduled tasks

            Tasks = newCfg.Tasks;

            #endregion


            #endregion

            insimConnection = new InSim.Connect( myDebug );

            insimConnection.insimConnect(pip, pport, newCfg.varsLapper.Password, "U", "LFSLapper", false, newCfg.varsLapper.TCPmode, newCfg.varsLapper.ReceiveJoinRequest, newCfg.varsLapper.DisableAI);
            listOfPlayers = new listPlayers(myDebug, insimConnection, lfsLang);
            string info = "Product:" + insimConnection.Product + " Version:" + insimConnection.Version + " InSim Version:" + insimConnection.InSimVersion;
            myDebug.WriteLine("mss",info);
            Console.WriteLine(info);
            if (newCfg.varsLapper.GripDatabase.ToLower() == newCfg.varsLapper.DriftDatabase.ToLower()
                    || newCfg.varsLapper.GripDatabase.ToLower() == newCfg.varsLapper.StoredValueDbs.ToLower()
                    || newCfg.varsLapper.DriftDatabase.ToLower() == newCfg.varsLapper.StoredValueDbs.ToLower())
            {
                throw new Exception("All DBS must have different Name, correct your config file");
            }
            gripSqlDbs = new LFSDbs.gripDbs(myDebug,uniqueConnectionId, newCfg.varsLapper.GripDatabase, newCfg.varsLapper.LapTimeUsedForPb, newCfg.varsLapper.FtpServer, newCfg.varsLapper.FtpLogin, newCfg.varsLapper.FtpPasswd, newCfg.varsLapper.FtpRemotePath, newCfg.varsLapper.DateFormat);
            driftSqlDbs = new LFSDbs.driftDbs(myDebug, uniqueConnectionId,newCfg.varsLapper.DriftDatabase, newCfg.varsLapper.FtpServer, newCfg.varsLapper.FtpLogin, newCfg.varsLapper.FtpPasswd, newCfg.varsLapper.FtpRemotePath, newCfg.varsLapper.DateFormat);
            storedSqlDbs = new LFSDbs.storedDbs(myDebug, newCfg.varsLapper.StoredValueDbs);


            #region Qualification
            UpdateQualUsers(newCfg.varsLapper.QualUsers);
            #endregion
 
            byte[] intervalReq = myEncoder.NLI(50);    //receive status every 50 ms
            insimConnection.Send(intervalReq, intervalReq.Length);

            // Start Thread for cmd via Web
            
            objWebCmd = new WebCmds(myDebug, newCfg.varsLapper.PubStatIdk);
            TwebCmd = new Thread(new ThreadStart(objWebCmd.TWebCmds));
            TwebCmd.Start();
            
            // Start Thread for User PB
            objPubStatUser = new PubStatUser(myDebug, newCfg.varsLapper.PubStatIdk);
            TPubStatUserPB = new Thread(new ThreadStart(objPubStatUser.ThreadUserPubStat));
            TPubStatUserPB.Start();

            // Start Thread for discordbot
            objDiscordBot = new DiscordBot(newCfg, myDebug, newCfg.varsLapper.DiscordToken, newCfg.varsLapper.DiscordChannelReceive);
            TDiscordBot = new Thread(new ThreadStart(objDiscordBot.TDiscordBot));
            TDiscordBot.Start();

            // Start Thread for Register Web
            if (newCfg.varsLapper.EnableRegisterWeb)
            {
                objregisterWeb = new registerWeb( myDebug );
                TRegisterWeb = new Thread(new ThreadStart(objregisterWeb.RegisterLoop));
                TRegisterWeb.Start();
            }
            myDebug.WriteLine("mss","LFSLapper is running...");
            myDebug.WriteSeparator("mss");

            //Request Raceinfo like CarReset/Laps/QualMins etc
            byte[] sstreq = myEncoder.SST();
            insimConnection.Send(sstreq, sstreq.Length);
        }
        public void doloop(){

            try
            {
                // Main loop
                startOk = true;
                SendMsg("/msg ^8LFSLapper Connected: " + LFSLapper.getShortVersion(4) + " " + paramLapper.LapperState);
                Loop(insimConnection);
            }

            catch (InSim.Connect.ConnectException ex )
            {
                throw (ex);
            }
            catch (Exception ex)
            {
               Console.WriteLine("\nLapper Instance [" + this.connectIP + "/" + this.connectPort + "] failed to connected!");
/*                
                myDebug.printDateOnEachLine = false;
                myDebug.WriteSeparator("err");
                myDebug.WriteLineDate("err");
                myDebug.WriteLine("err", "\nLapper Instance " + this.connectIP + "/" + this.connectPort + " 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;
*/
                UTILS.utils.sendErrorMail(newCfg.varsLapper.adminEmail,
                                                    newCfg.varsLapper.smtpServer,
                                                    newCfg.varsLapper.loginMail,
                                                    newCfg.varsLapper.passMail,
                                                    scriptFileName, ex);
                throw;
            }
            finally
            {
// Close All Lapper's Threads
                finalize();
            }
        }
        public void finalize(){
            // Close All Lapper's Threads
                if ((TDiscordBot != null) && (DiscordBot.ConnectedToDiscord == 1))
                {
                    DiscordBot.StopDiscordBot();
                    TDiscordBot.Abort();
                }
                if (TLFSRestAPI != null)
                    TLFSRestAPI.Abort();
                if (TRegisterWeb != null)
                    TRegisterWeb.Abort();
                if (TwebCmd != null)
                    TwebCmd.Abort();
                if (TPubStatUserPB != null)
                    TPubStatUserPB.Abort();
                if (gripSqlDbs != null)
                    gripSqlDbs.killThreads();
                if (driftSqlDbs != null)
                    driftSqlDbs.killThreads();
                if (storedSqlDbs != null)
                {
                    storedSqlDbs.dbCon.dbCon.Close();
                    storedSqlDbs.dbCon.dbCon.Dispose();
                }
                byte[] terreq = myEncoder.ISC();
                if (insimConnection != null)
                {
                    insimConnection.Send(terreq, terreq.Length);
                    insimConnection.Close();
                }
                if (timer != null)
                {
                    timer.Close();
                    timer.Dispose();
                }
        }
    }
}





