//  
//  loop.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 LapperThreads;
using System.Threading;

namespace LFSLapper
{
    partial class LFSClient
    {
        private string closeConfigLang = "";
        public string StringLevelsAllowedPlayer(infoPlayer currInfoPlayer,bool libelMode )
        {
            string retString = "";
            for (int i = 0; i < 10; i++)
            {
//                if (SystemAuth.usedLevel[i])
//                {
                //                }
                if (SystemAuth.AuthString[i] != "")
                {
                    if (isPlayerLevelAllowed(currInfoPlayer.userName, currInfoPlayer.nickName, SystemAuth.AuthString[i]))
                    {
                        if( libelMode )
                            retString = retString + "," + SystemAuth.LibelLevel[i];
                        else
                            retString = retString + "," + i.ToString(); ;

                    }
                }
            }
            if( retString.Length > 0 )
                return retString.Substring(1);
            else
                return "None";
        }
        public bool isAllowedPlayer(infoPlayer currInfoPlayer)
        {
            if (SystemAuth.AllowAllPlayer || SystemAuth.AuthOn == false)
                return true;

            if (SystemAuth.ugroup.userExist("Allowed", currInfoPlayer.userName.ToLower()))
                return true;


            for (int i = 0; i < 10; i++)
            {
                if (SystemAuth.usedLevel[i])
                {
                    if( isPlayerLevelAllowed(currInfoPlayer.userName, currInfoPlayer.nickName, SystemAuth.AuthString[i]) )
                        return true;
                }
            }
            return false;
        }
        public bool isPlayerLevelAllowed(string userName, string nickName, string AuthString)
        {

            string[] OrAuth = AuthString.Split('|');
            for (int j = 0; j < OrAuth.Length; j++)
            {
                bool AndAllow = true;
                string[] AndAuth = OrAuth[j].Split('&');
                for (int k = 0; k < AndAuth.Length; k++)
                {
                    string[] arg = AndAuth[k].Split(':');
                    if (arg[0].ToLower() == "all")
                    {
                        AndAllow = true;
                        break;
                    }
                    if (arg.Length != 3)
                    {
                        myDebug.WriteLine("err","Error in Auth string format: Player Allowed, " + OrAuth[j]);
                        AndAllow = true;
                        break;
                    }
                    string cname = arg[0];
                    string tname = arg[1];
                    long time = unitConv.HMSToLong(arg[2]);
                    long PBTime = gripSqlDbs.retreivePB(userName, nickName, cname, tname);
                    if (PBTime == -1 || PBTime > time)
                    {
                        AndAllow = false;
                        break;
                    }
                }
                if (AndAllow == true)
                {
                    return true;
                }
            }
            return false;

        }

        string getUnitSpeed(bool unitSpeedKmh)
        {
            if (unitSpeedKmh)
                return "Km/h";
            else
                return "Mph";
        }
        string getUnitDist(bool unitSpeedKmh)
        {
            if (unitSpeedKmh)
                return "Km";
            else
                return "Miles";
        }
        bool UpdateQualUsers(string QualUsers)
        {
            int authidx;
// Clear Player filter
            gripSqlDbs.clearPlayerFilter();
            driftSqlDbs.clearPlayerFilter();

            if (QualUsers == "")
                return true;

            authidx = QualUsers.IndexOf("@"); // If list of user is in cfg file
            if (authidx != -1)
            {
                string[] ListUsers = QualUsers.Substring(authidx + 1).Split(',');
                gripSqlDbs.dbCon.executeNonQuery("BEGIN TRANSACTION");
                driftSqlDbs.dbCon.executeNonQuery("BEGIN TRANSACTION");
                foreach (string userinfo in ListUsers)
                {
                    if (userinfo.IndexOf("DefGroup") != -1)
                    {
                        string[] value = userinfo.Split(':');
                        try
                        {
                            MaxGroupQual = int.Parse(value[1]);
                            MaxUserGroupQual = int.Parse(value[2]);
                            MinUserGroupQual = int.Parse(value[3]);
                        }
                        catch (Exception)
                        {
                            MaxGroupQual = 0;
                            MaxUserGroupQual = 0;
                            MinUserGroupQual = 0;
                        }

                    }
                    else
                    {
                        string[] luserinfo = userinfo.Split(':');

                        if (luserinfo.Length > 1)
                        {
                            gripSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), luserinfo[1]);
                            driftSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), luserinfo[1]);
                        }
                        else
                        {
                            gripSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), "");
                            driftSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), "");
                        }
                    }
                }
                gripSqlDbs.dbCon.executeNonQuery("COMMIT TRANSACTION");
                driftSqlDbs.dbCon.executeNonQuery("COMMIT TRANSACTION");

                return true;
            }
            authidx = QualUsers.IndexOf("&"); // if QualUsers is a name of file containing list of user
            if (authidx != -1)
            {
                string userinfo;
                try
                {
                    using (System.IO.StreamReader sr = new System.IO.StreamReader(newCfg.varsLapper.WorkingDir + "/" + QualUsers.Substring(authidx + 1)))
                    {
                        gripSqlDbs.dbCon.executeNonQuery("BEGIN TRANSACTION");
                        driftSqlDbs.dbCon.executeNonQuery("BEGIN TRANSACTION");
                        while (true)
                        {
                            userinfo = sr.ReadLine();
                            if (userinfo == null)
                            {
                                gripSqlDbs.dbCon.executeNonQuery("COMMIT TRANSACTION");
                                driftSqlDbs.dbCon.executeNonQuery("COMMIT TRANSACTION");
                                return true;
                            }
                            if (userinfo.IndexOf("DefGroup") != -1)
                            {
                                // For compatibility with old system
                                userinfo = userinfo.Replace(";", ":");
                                string[] value = userinfo.Split(':');
                                try
                                {
                                    MaxGroupQual = int.Parse(value[1]);
                                    MaxUserGroupQual = int.Parse(value[2]);
                                    MinUserGroupQual = int.Parse(value[3]);
                                }
                                catch (Exception)
                                {
                                    MaxGroupQual = 0;
                                    MaxUserGroupQual = 0;
                                    MinUserGroupQual = 0;
                                }

                            }
                            else
                            {
                                string[] luserinfo = userinfo.Split(':');
                                if (luserinfo.Length > 1)
                                {
                                    gripSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), luserinfo[1]);
                                    driftSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), luserinfo[1]);
                                }
                                else
                                {
                                    gripSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), "");
                                    driftSqlDbs.addPlayerFilter(luserinfo[0].ToLower(), "");
                                }
                            }
                        }

//                        Console.WriteLine("Fin J'update");


                    }
                }
                catch (System.Exception ex)
                {
                    myDebug.WriteLine("err","(" + QualUsers + ")" + ex.Message);   // Print the error message.
                    return false;
                }
            }
            return false;
        }
        bool UpdateHandicapUsers(string HandicapUsers)
        {
            int authidx;

            playerHandicap.Clear();
            if (HandicapUsers == "")
                return true;

            authidx = HandicapUsers.IndexOf("@"); // If list of user is in cfg file
            if (authidx != -1)
            {
                string[] ListUsers = HandicapUsers.Substring(authidx + 1).Split(',');
                foreach (string userinfo in ListUsers)
                {
                    string[] usersplit = userinfo.Split(':');
                    int H_Mass = 0;
                    int H_TRes = 0;
                    try
                    {
                        H_Mass = int.Parse(usersplit[1]);
                    }
                    catch { H_Mass = 0; }
                    try
                    {
                        H_TRes = int.Parse(usersplit[2]);
                    }
                    catch { H_TRes = 0; }
                    playerHandicap[usersplit[0].ToLower()] = new infoHandicap(H_Mass, H_TRes);

                }
                return true;
            }
            authidx = HandicapUsers.IndexOf("&"); // if QualUsers is a name of file containing list of user
            if (authidx != -1)
            {
                string userinfo;
                try
                {
                    using (System.IO.StreamReader sr = new System.IO.StreamReader(newCfg.varsLapper.WorkingDir + "/" + newCfg.varsLapper.HandicapUsers.Substring(authidx + 1)))
                    {
                        while (true)
                        {
                            userinfo = sr.ReadLine();
                            if (userinfo == null)
                                return true;
                            string[] usersplit = userinfo.Split(':');
                            int H_Mass = 0;
                            int H_TRes = 0;
                            try
                            {
                                H_Mass = int.Parse(usersplit[1]);
                            }
                            catch { H_Mass = 0; }
                            try
                            {
                                H_TRes = int.Parse(usersplit[2]);
                            }
                            catch { H_TRes = 0; }
                            playerHandicap[usersplit[0].ToLower()] = new infoHandicap(H_Mass, H_TRes);
                        }
                    }
                }
                catch (System.Exception ex)
                {
                    myDebug.WriteLine("err","(" + newCfg.varsLapper.QualUsers + "):" + ex.Message);   // Print the error message.
                    return false;
                }
            }
            return false;
        }
        bool UpdateHandicapCars(string HandicapCars)
        {

            carHandicap.Clear();
            if (HandicapCars == "")
                return true;

            string[] ListCars = HandicapCars.Split(',');
            foreach (string carinfo in ListCars)
            {
                string[] carsplit = carinfo.Split(':');
                int H_Mass = 0;
                int H_TRes = 0;
                try
                {
                    H_Mass = int.Parse(carsplit[1]);
                }
                catch { H_Mass = 0; }
                try
                {
                    H_TRes = int.Parse(carsplit[2]);
                }
                catch { H_TRes = 0; }
                carHandicap[carsplit[0].ToLower()] = new infoHandicap(H_Mass, H_TRes);

            }
            return true;
        }
        bool UpdateHandicapCarsTracks(string HandicapCarsTracks)
        {

            carTrackHandicap.Clear();
            if (HandicapCarsTracks == "")
                return true;

            string[] ListCarsTracks = HandicapCarsTracks.Split(',');
            foreach (string carTrackinfo in ListCarsTracks)
            {
                string[] carTracksplit = carTrackinfo.Split(':');
                int H_Mass = 0;
                int H_TRes = 0;
                try
                {
                    H_Mass = int.Parse(carTracksplit[1]);
                }
                catch { H_Mass = 0; }
                try
                {
                    H_TRes = int.Parse(carTracksplit[2]);
                }
                catch { H_TRes = 0; }
                carTrackHandicap[carTracksplit[0].ToLower()] = new infoHandicap(H_Mass, H_TRes);

            }
            return true;
        }
        void SendMsg(string msg)
        {
            msg = msg.Trim();
            if (msg.Length > 0)
            {
                if (msg.IndexOf("/rcm_") != -1)
                {
                    RestartTimer();
                }

                byte[] outMsg =  myEncoder.MST(msg);
                insimConnection.Send(outMsg, outMsg.Length);
            }

        }
        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);
                }
            }
        }
        public string bufferToHexa(byte[] buffer, int buffLen)
        {
            string retString = "";
            string sep = "";
            for (int i = 0; i < buffLen; i++)
            {
                retString = retString + sep + string.Format("0x{0:X2}", buffer[i]);
                sep = ", ";
            }
            return retString;
        }
        public string stringToHexa(string str)
        {
            string retString = "";
            string sep = "";
            for (int i = 0; i < str.Length; i++)
            {
                retString = retString + sep + string.Format("0x{0:X2}", (byte)str[i]);
                sep = ", ";
            }
            return retString;
        }
        void showConfigPlayer( infoPlayer currInfoPlayer )
        {
            int styleSession = (int)InSim.BTN_style.ISB_LIGHT | 2;
            int stylePB = (int)InSim.BTN_style.ISB_LIGHT | 2;
            int styleWR = (int)InSim.BTN_style.ISB_LIGHT | 2;
            int P_L = 65;
            int P_T = 30;

            if (wrLoaded)
                P_L -= 10;

            switch (currInfoPlayer.viewSPBSplit)
            {
                case "S":
                    styleSession |= 3;
                    break;
                case "P":
                    stylePB |= 3;
                    break;
                case "W":
                    styleWR |= 3;
                    break;
            }

            currInfoPlayer.playerBox.create("sector", currInfoPlayer.UCID, P_L, P_T, 30, 5, 5, -1, 0, (int)InSim.BTN_style.ISB_DARK | (int)InSim.BTN_style.ISB_RIGHT, "", "Sector time relative to: ");
            currInfoPlayer.playerBox.show("sector", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("sector.sess", currInfoPlayer.UCID, P_L + 30, P_T, 20, 5, 5, -1, 0, styleSession, "", "Session", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("sector.sess", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("sector.server", currInfoPlayer.UCID, P_L + 50, P_T, 20, 5, 5, -1, 0, stylePB, "", "Server", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("sector.server", currInfoPlayer.UCID);
            if (wrLoaded)
            {
                currInfoPlayer.playerBox.create("sector.wr", currInfoPlayer.UCID, P_L + 70, P_T, 20, 5, 5, -1, 0, styleWR, "", "World Record", onChangeConfigPlayer);
                currInfoPlayer.playerBox.show("sector.wr", currInfoPlayer.UCID);
            }
            P_T += 5;
            int styleShow = (int)InSim.BTN_style.ISB_LIGHT | 2;
            int styleHide = (int)InSim.BTN_style.ISB_LIGHT | 2;

            if (currInfoPlayer.showSplitPB)
                styleShow |= 3;
            else
                styleHide |= 3;

            currInfoPlayer.playerBox.create("showSplit", currInfoPlayer.UCID, P_L, P_T, 30, 5, 5, -1, 0, (int)InSim.BTN_style.ISB_DARK | (int)InSim.BTN_style.ISB_RIGHT, "", "Show splitting info: ");
            currInfoPlayer.playerBox.show("showSplit", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("showSplit.show", currInfoPlayer.UCID, P_L + 30, P_T, 20, 5, 5, -1, 0, styleShow, "", "Show", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("showSplit.show", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("showSplit.hide", currInfoPlayer.UCID, P_L + 50, P_T, 20, 5, 5, -1, 0, styleHide, "", "Hide", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("showSplit.hide", currInfoPlayer.UCID);
            P_T += 5;

            int styleKm = (int)InSim.BTN_style.ISB_LIGHT | 2;
            int styleMph = (int)InSim.BTN_style.ISB_LIGHT | 2;

            if (currInfoPlayer.unitSpeedKmh)
                styleKm |= 3;
            else
                styleMph |= 3;

            currInfoPlayer.playerBox.create("unitSpeed", currInfoPlayer.UCID, P_L, P_T, 30, 5, 5, -1, 0, (int)InSim.BTN_style.ISB_DARK | (int)InSim.BTN_style.ISB_RIGHT , "", "Show splitting info: ");
            currInfoPlayer.playerBox.show("unitSpeed", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("unitSpeed.km", currInfoPlayer.UCID, P_L+30, P_T, 20, 5, 5, -1, 0, styleKm, "", "Km/h", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("unitSpeed.km", currInfoPlayer.UCID);
            currInfoPlayer.playerBox.create("unitSpeed.mph", currInfoPlayer.UCID, P_L+50, P_T, 20, 5, 5, -1, 0, styleMph, "", "Mph", onChangeConfigPlayer);
            currInfoPlayer.playerBox.show("unitSpeed.mph", currInfoPlayer.UCID);
            P_T += 5;

            string[] lang = lfsLang.getAllLang();
            currInfoPlayer.playerBox.create("language", currInfoPlayer.UCID, P_L, P_T, 30, 5, 5, -1, 0, (int)InSim.BTN_style.ISB_DARK | (int)InSim.BTN_style.ISB_RIGHT, "", "Show splitting info: ");
            currInfoPlayer.playerBox.show("language", currInfoPlayer.UCID);
            int decal = 30;
            int styleLang = 0;
            closeConfigLang = "lang_" + string.Join("&lang_",lang );
            for (int i = 0; i < lang.Length; i++)
            {
                styleLang =  (int)InSim.BTN_style.ISB_LIGHT | 2;
                if (currInfoPlayer.idLang == lang[i])
                    styleLang |= 3;

                currInfoPlayer.playerBox.create("lang_" + lang[i], currInfoPlayer.UCID, P_L + decal, P_T, 5, 5, 5, -1, 0, styleLang, "", lang[i].ToUpper(), onChangeConfigPlayer);
                currInfoPlayer.playerBox.show("lang_" + lang[i], currInfoPlayer.UCID);
                decal += 5;
            }

            P_T += 5;

            currInfoPlayer.playerBox.create("closeCfgPlayer", currInfoPlayer.UCID, 100 - 15, P_T, 30, 10, 5, -1, 0, (int)InSim.BTN_style.ISB_DARK , "", "Close", onCloseConfigPlayer);
            currInfoPlayer.playerBox.show("closeCfgPlayer", currInfoPlayer.UCID);

        }
        void onCloseConfigPlayer(ISMB.typExeFunc typ, infoPlayer currInfoPlayer, int click, string idButton)
        {
            currInfoPlayer.privateButtonClose("sector&sector.sess&sector.server&sector.wr");
            currInfoPlayer.privateButtonClose("showSplit&showSplit.show&showSplit.hide");
            currInfoPlayer.privateButtonClose("unitSpeed&unitSpeed.km&unitSpeed.mph");
            currInfoPlayer.privateButtonClose("language");
            currInfoPlayer.privateButtonClose(closeConfigLang);
            currInfoPlayer.privateButtonClose("closeCfgPlayer");

        }
        void onChangeConfigPlayer(ISMB.typExeFunc typ, infoPlayer currInfoPlayer, int click, string idButton)
        {
			currInfoPlayer.isConfigPlayerChanged = true;
            if (idButton.IndexOf("lang_") == 0)
            {
                string lang = idButton.Substring(5);
                currInfoPlayer.idLang = lang;
            }
            else
            {
                switch (idButton)
                {
                    case "sector.sess":
                        currInfoPlayer.viewSPBSplit = "S";
                        break;
                    case "sector.server":
                        currInfoPlayer.viewSPBSplit = "P";
                        break;
                    case "sector.wr":
                        currInfoPlayer.viewSPBSplit = "W";
                        break;
                    case "showSplit.show":
                        currInfoPlayer.showSplitPB = true;
                        break;
                    case "showSplit.hide":
                        currInfoPlayer.showSplitPB = false;
                        break;
                    case "unitSpeed.km":
                        currInfoPlayer.unitSpeedKmh = true;
                        break;
                    case "unitSpeed.mph":
                        currInfoPlayer.unitSpeedKmh = false;
                        break;
                }
            }
            showConfigPlayer(currInfoPlayer);
        }
/*
        void ClearButton(infoPlayer currInfoPlayer)
        {
            // Clear all buttons
            byte[] bfn = myEncoder.BFN(0, 1, currInfoPlayer.UCID, 0);
            insimConnection.Send(bfn, bfn.Length);
        }
*/
        bool SplitNotify(infoPlayer currInfoPlayer, InSim.Decoder.SPX splitdec)
        {
            string splitAction = "";

            splitAction = trackInfo.getSplitAction(currState.ShortTrackName, currInfoPlayer.CName, splitdec.Split, splitdec.STime);
            if (splitAction != "")
            {
                string[] args = new string[1];
                args[0] = currInfoPlayer.userName;
                newCfg.executeFunction(splitAction, currInfoPlayer, args);
            }
            try
            {
                if (newCfg.varsLapper.ShowSplitPB && currInfoPlayer.showSplitPB)
                {
                    string[] args = new string[1];
                    args[0] = currInfoPlayer.userName;
                    newCfg.executeFunction("OnSpbSplit" + splitdec.Split.ToString(), currInfoPlayer, args);
                }
            }
            catch
            {
                myDebug.WriteLine("err","Error on onSpbSplit: " + splitdec.Split.ToString());
            }
            try
            {
                string[] args = new string[1];
                args[0] = currInfoPlayer.userName;
                newCfg.executeFunction("OnSplit" + splitdec.Split.ToString(), currInfoPlayer, args);
            }
            catch
            {
                myDebug.WriteLine("err","Error on onSplit: " + splitdec.Split.ToString());
            }

            return true;

        }
        //Flooding textmessages detection
        void CheckForFlooding(infoPlayer currInfoPlayer)
        {
            try
            {
                //Ignore Host
                if ((currInfoPlayer.userName != "") || (currInfoPlayer.isHost == false))
                {
                    DateTime now = DateTime.Now;

                    System.TimeSpan ts = now - currInfoPlayer.LastLine;
                    currInfoPlayer.LastLine = now;

                    if (ts.TotalMilliseconds < newCfg.varsLapper.MaxFloodLinesTime) //Maximum time betweeen lines to count as flood
                        currInfoPlayer.floodcount++;
                    else
                    {
                        currInfoPlayer.floodcount = 1;
                        return;
                    }

                    if (currInfoPlayer.floodcount > newCfg.varsLapper.MaxFloodLines)   //max lines to tolerate
                    {
                        currInfoPlayer.floodcount = 0;
                        string[] args = new string[1];
                        args[0] = currInfoPlayer.userName;
                        newCfg.executeFunction("OnFlood", currInfoPlayer, args);
                    }
                }
            }
            catch (Exception e)
            {
                myDebug.WriteLine("err","Flood exception: " + e.ToString());
            }
        }
        void trVote( int percVoteNeed,int voted, string action, string actionReach, string pcmdLFS ){
// Stop Voting
            if (percVoteNeed == -1 )
                return;

            int PercVote;
            if (listOfPlayers.nbPlayerOnTrack == 0)
                PercVote = 0;
            else
                PercVote = (voted * 100) / listOfPlayers.nbPlayerOnTrack;
            string lstr = "";
            string cmdLfs = "";
            if (PercVote != 0)
            {
                lstr = "";
                for (int i = 0; i < voted; i++)
                    lstr += "L";
                if (PercVote >= percVoteNeed)
                {
                    action = actionReach;
                    listOfPlayers.voteClear();
                    cmdLfs = pcmdLFS;
                }
                int tmpint = listOfPlayers.nbPlayerOnTrack * percVoteNeed / 100;
                int tmpint2 = tmpint * 100 / percVoteNeed;
                if (tmpint2 < listOfPlayers.nbPlayerOnTrack)
                    tmpint++;
                string[] args = new string[3];
                args[0] = listOfPlayers.nbPlayerOnTrack.ToString();
                args[1] = voted.ToString();
                args[2] = tmpint.ToString();
                newCfg.executeFunction(action, null, args);

                if( cmdLfs != "" ){
                    byte[] outMsg = myEncoder.MST(cmdLfs);
                    insimConnection.Send(outMsg, outMsg.Length);
                }

            }
            if (newCfg.varsLapper.VoteRestart != -1 && listOfPlayers.voteRestart == 0)
                newCfg.executeFunction("OnVoteRestartZero", null, null);
            if (newCfg.varsLapper.VoteQualify != -1 && listOfPlayers.voteQualify == 0)
                newCfg.executeFunction("OnVoteQualifyZero", null, null);
            if (newCfg.varsLapper.VoteEnd != -1 && listOfPlayers.voteEnd == 0)
                newCfg.executeFunction("OnVoteEndZero", null, null);
        }
        void actionPlayerFlags(  int flags, infoPlayer currInfoPlayer, bool displayFlags )
        {
            string[] flagsPlayerAllow = new string[14];


            #region construct help flags relative to config file, construct each time because GLScript can modify value
            {
                string tmpStr;
                int i = 0;
                tmpStr = newCfg.varsLapper.SwapSide;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                flagsPlayerAllow[i++] = "*";    // RESERVED_2

                flagsPlayerAllow[i++] = "*";    // RESERVED_4

                tmpStr = newCfg.varsLapper.AutoGears;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.Shifter;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                flagsPlayerAllow[i++] = "*";    // RESERVED_32

                tmpStr = newCfg.varsLapper.HelpBrake;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.AxisClutch;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                flagsPlayerAllow[i++] = "*";    // INPITS

                tmpStr = newCfg.varsLapper.AutoClutch;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.Mouse;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.KbNoHelp;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.KbStabilised;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;

                tmpStr = newCfg.varsLapper.CustomView;
                if (tmpStr == "")
                    tmpStr = "*";
                flagsPlayerAllow[i++] = tmpStr;
            }
            for (int i = 0; i < 14; i++)
            {
                if (flagsPlayerAllow[i].ToLower() == "y")
                {
                    sFlagsRequired = sFlagsRequired
                                        + ((InSim.Decoder.NPL.PlayerFlags)(1 << i)).ToString()
                                        + " = Y ";
                }
                if (flagsPlayerAllow[i].ToLower() == "n")
                {
                    sFlagsRequired = sFlagsRequired
                                        + ((InSim.Decoder.NPL.PlayerFlags)(1 << i)).ToString()
                                        + " = N ";
                }
            }


            #endregion

            // Player Flags
            string addInfo = "";
            bool PlayerAllowedF = true;
            for (int i = 0; i < 14; i++)
            {
                if (((uint)(flags) & (uint)(1 << i)) != 0)
                    addInfo += ((InSim.Decoder.NPL.PlayerFlags)(1 << i)).ToString() + " ";

                if (flagsPlayerAllow[i].ToLower() == "y")
                {
                    if (((uint)(flags) & (uint)(1 << i)) == 0)
                        PlayerAllowedF = false;
                }
                if (flagsPlayerAllow[i].ToLower() == "n")
                {
                    if (((uint)(flags) & (uint)(1 << i)) != 0)
                        PlayerAllowedF = false;
                }
            }
            currInfoPlayer.sPlayerFlags = addInfo;
            if (addInfo.Length != 0 && newCfg.varsLapper.ShowPlayerControl && displayFlags )
                SendMsg( "/msg " + currInfoPlayer.sPlayerFlags );
            if (!PlayerAllowedF)
            {

                {
                    string[] args = new string[1];
                    args[0] = currInfoPlayer.userName;
                    newCfg.executeFunction("OnNotMatchFlags", currInfoPlayer, args);
                }
            }


        }
        void Loop(InSim.Connect insimConnection)
        {
            //            DateTime nextSaveDb = DateTime.Now.AddSeconds((double)lexCfg.paramLapper.delayedSave);
            DateTime nextSaveDriftDb = DateTime.Now.AddSeconds((double)paramLapper.delayedSave);
            DateTime nextTime100ms = DateTime.Now.AddMilliseconds((double)100);
            DateTime nextTime250ms = DateTime.Now.AddMilliseconds((double)250);
            DateTime nextTime500ms = DateTime.Now.AddMilliseconds((double)500);
			DateTime nextTime1000ms = DateTime.Now.AddMilliseconds((double)1000);
            infoPlayer currInfoPlayer = null;

            byte[] stiny;
            int nbCnt = 0;

            // Top generate an error for testing restart
            //            currInfoPlayer.userName = currInfoPlayer.userName;
            // Init the rotation if needed

            newCfg.executeFunction("OnLapperStart", null, null);
            {
                //Request RST packet to receive all raceinfo data from LFS
                RST_packet_From_NPL = 1; //This var is also used in NewPlayer packet in scriptfunctions.cs
                //Get info from LFS by these insimpackets below.
                currRace.started = DateTime.Now;
                byte[] GetRaceInfoFromLFS = myEncoder.RST();
                insimConnection.Send(GetRaceInfoFromLFS, GetRaceInfoFromLFS.Length);
                byte[] GetStateFromLFS = myEncoder.SST();
                insimConnection.Send(GetStateFromLFS, GetStateFromLFS.Length);
                /*
                if (newCfg.varsLapper.StoreOfflineData == 1)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("Warning: $StoreOfflineData = 1. Store Offline data enabled!");
                    Console.ForegroundColor = ConsoleColor.White;
                    myDebug.WriteLine("mss", "Warning: $StoreOfflineData = 1.Store Offline data enabled!");
                }
                */
            }

            byte[] recvPacket;
			DateTime datRecvPacket = DateTime.Now;
			insimConnection.waitReceive = false;
            DateTime initRotation = DateTime.Now.AddSeconds(6);
			bool flagInitRotation = true;
            while (true)
            {
                while (true)
                {
                    recvPacket = null;
                    InSim.Connect.objPacket objPack = insimConnection.Receive();
                    if (objPack != null)
                    {
                        recvPacket = objPack.recvPacket;
                        datRecvPacket = objPack.dateReceived;
                    }

                    if (TermProg == true)
                        return;
//                    if (insimConnection.TInsimReceive.IsAlive == false)
//                        throw new Exception("Thread TInsimReceive dead.");
                    if (TwebCmd.IsAlive == false)
                        throw new Exception("Thread TwbCmd dead.");
                    if (TPubStatUserPB.IsAlive == false)
                        throw new Exception("Thread TPubStatUserPB dead.");
                    if (gripSqlDbs.isThreadsAlive() == false)
                        throw new Exception("Thread gripDbs upload dead.");
                    if (driftSqlDbs.isThreadsAlive() == false)
                        throw new Exception("Thread driftDbs upload dead.");

                    // Initialize the first rotation on boot
                    if (flagInitRotation && newCfg.varsLapper.EnableRotation && DateTime.Now > initRotation)
                    {
                        flagInitRotation = false;
                        string[] tmpTracks = newCfg.varsLapper.RotateTracks.Split(',');
                        string[] tmpCars = newCfg.varsLapper.RotateCars.Split(',');
                        SendMsg("/end");
                        System.Threading.Thread.Sleep(6000);
                        SendMsg("/qual=0");
                        SendMsg("/clear");
                        if (newCfg.varsLapper.RotateCars != "")
                        {
                            SendMsg("/cars=" + tmpCars[currRace.currRotateCar]);
                            //                    SendMsg(0, "^7New cars!&^2Go to pit!");
                            newCfg.executeFunction("OnRotateCar", null, null);
                            RestartTimer();
                        }
                        if (newCfg.varsLapper.RotateTracks != "")
                        {
                            SendMsg("/track=" + tmpTracks[currRace.currRotateTrack]);
                            currState.ShortTrackName = tmpTracks[currRace.currRotateTrack];
                            newCfg.executeFunction("OnRotateTrack", null, null);

                        }
                    }

#region Race restart Auto and Race Rotation
                    // Race restart Auto and Race Rotation
                    if ( newCfg.varsLapper.AutoRestartRaceSec > 0 && ( DateTime.Now > currRace.nextRestart && currRace.autoRestartOn || ForceRotation > 0 ) )
                    {
                        if ( newCfg.varsLapper.EnableRotation && ( currRace.racesDone >= newCfg.varsLapper.RotateEveryNbRaces || ForceRotation > 0  ) )
                        {
                            ForceRotation--;
                            currRace.racesDone = 0;
                            currRace.currRotateTrack++;
                            currRace.currRotateCar++;
                            string[] tmpTracks = newCfg.varsLapper.RotateTracks.Split(',');
                            string[] tmpCars = newCfg.varsLapper.RotateCars.Split(',');
                            if (currRace.currRotateTrack > tmpTracks.Length - 1)
                                currRace.currRotateTrack = 0;
                            if (currRace.currRotateCar > tmpCars.Length - 1)
                                currRace.currRotateCar = 0;
                            SendMsg("/end");
                            System.Threading.Thread.Sleep(6000);
                            SendMsg("/qual=0");
                            SendMsg("/clear");
                            if (newCfg.varsLapper.RotateCars != "")
                            {
                                SendMsg("/cars=" + tmpCars[currRace.currRotateCar]);
                                newCfg.executeFunction("OnRotateCar", null, null);
                                RestartTimer();
                            }
                            if (newCfg.varsLapper.RotateTracks != "")
                            {
                                SendMsg("/track=" + tmpTracks[currRace.currRotateTrack]);
								currState.ShortTrackName = tmpTracks[currRace.currRotateTrack];
                                newCfg.executeFunction("OnRotateTrack", null, null);
                            }

                        }
                        else
                        {
                            currRace.nextRestart = DateTime.Now.AddDays(365); // Next restart after 365 Day, ie no restart before a player end a race
                            currRace.autoRestartOn = false;
                            SendMsg("/restart");
                        }
                    }
#endregion

                    if (DateTime.Now > nextTime100ms)
                    {
                        try
                        {
                            string cmd = (string)incommingCmdStack.Dequeue(); //Message Commands from the console.
                            byte[] outMsg = myEncoder.MST(cmd);
                            insimConnection.Send(outMsg, outMsg.Length);
                        }
                        catch
                        {
                        }
                    }

                    //Send Discordmessage from queue
                    if (DateTime.Now > nextTime250ms)
                    {
                        try
                        {
                            //Retreive message from queue
                            string value = (string)incomingdiscordmsgstack.Dequeue();
                            //Split value
                            string[] args = value.Split('/');

                            ulong DiscordChannel = DiscordChannel = UInt64.Parse(args[0].ToString());
                            string Message = args[1].ToString();

                            //Send to discord
                            DiscordBot.SendDiscordBotMessage(DiscordChannel, Message);
                        }
                        catch
                        {
                        }
                    }


                    // Do a loop of button, player Task, Global Task every second, External command
                    if (DateTime.Now > nextTime1000ms)
					{
						// Voting System
						if (currVotation.lastVotedRestart != listOfPlayers.voteRestart)
						{
							trVote(newCfg.varsLapper.VoteRestart, listOfPlayers.voteRestart, "OnVoteRestartChange", "OnVoteRestartReach", "/restart");
							currVotation.lastVotedRestart = listOfPlayers.voteRestart;
						}
						if (currVotation.lastVotedQualify != listOfPlayers.voteQualify)
						{
							trVote(newCfg.varsLapper.VoteQualify, listOfPlayers.voteQualify, "OnVoteQualifyChange", "OnVoteQualifyReach", "/qualify");
							currVotation.lastVotedQualify = listOfPlayers.voteQualify;
						}
						if (currVotation.lastVotedEnd != listOfPlayers.voteEnd)
						{
							trVote(newCfg.varsLapper.VoteEnd, listOfPlayers.voteEnd, "OnVoteEndChange", "OnVoteEndReach", "/end");
							currVotation.lastVotedEnd = listOfPlayers.voteEnd;
						}
						nextTime1000ms = DateTime.Now.AddMilliseconds((double)1000);

					}
					// Do a loop of button every 500 ms	
                    if (DateTime.Now > nextTime500ms)
                    {
                        listOfPlayers.loopTask(newCfg);
                        loopTask();
                        listOfPlayers.loopButton(newCfg);
                        nextTime500ms = DateTime.Now.AddMilliseconds((double)500);
                    }
                    // Do a loop of button every 250 ms	
                    if (DateTime.Now > nextTime250ms)
                    {
                        loopplayerdetector();
                        nextTime250ms = DateTime.Now.AddMilliseconds((double)250);
                    }


#region Retreive return from web commands
                    
                    webReturn wr = objWebCmd.getWebReturn();
                    if (wr != null)
                    {
                        currInfoPlayer = listOfPlayers.getPlayerByUCID(wr.UCID);
                        newCfg.addFunction("WebReturnFunction", wr.WebRet, "");
                        newCfg.executeFunction("WebReturnFunction", currInfoPlayer, null);
                    }
                    
#endregion

#region Test if stat player retreived from pubstat by thread and link to player
                    Monitor.Enter(objPubStatUser.QReturneduserPst);
					if (objPubStatUser.QReturneduserPst.Count != 0)
					{
						infoPlayer CI;
						LapperThreads.PubStatUser.PubStatUserPst stat = (LapperThreads.PubStatUser.PubStatUserPst)objPubStatUser.QReturneduserPst.Dequeue();
						CI = listOfPlayers.getPlayerByUserName(stat.userName);
						if (CI != null)
							CI.pubStatUserPst = stat;
					}
					Monitor.Exit(objPubStatUser.QReturneduserPst);
					Monitor.Enter(objPubStatUser.QReturnedUserPb);
					if (objPubStatUser.QReturnedUserPb.Count != 0)
					{
						infoPlayer CI;
						LapperThreads.PubStatUser.PubStatUserPb stat = (LapperThreads.PubStatUser.PubStatUserPb)objPubStatUser.QReturnedUserPb.Dequeue();
						CI = listOfPlayers.getPlayerByUserName(stat.userName);
						if (CI != null)
							CI.pubStatUserPB = stat;
					}
					Monitor.Exit(objPubStatUser.QReturnedUserPb);
#endregion

					if (recvPacket != null)
                        break;
                    else
                    {
                        System.Threading.Thread.Sleep(100);
                        nbCnt++;
                        if (nbCnt > 100)
                        {
                            stiny = myEncoder.TINY_PING();
                            insimConnection.Send(stiny, stiny.Length);
                        }

                    }

                }
                nbCnt = 0;

#region Manage packet and launch correct action
                if (recvPacket.Length > 3)
                {
                    string packetHead = insimConnection.packetHead(recvPacket);
                    uint verifyID = insimConnection.verifyID(recvPacket);

                     //Console.WriteLine(recvPacket.Length + " / " + packetHead);

                    switch (packetHead)
                    {
                        case "TINY"://confirm ack with ack
                            InSim.Decoder.TINY tiny = new InSim.Decoder.TINY(recvPacket);
                            managePacket(tiny);
                            break;
                        case "SMALL":
                            InSim.Decoder.SMALL small = new InSim.Decoder.SMALL(recvPacket);
                            managePacket(small);
                            break;
                        case "AXI": // AutoX Info
                            InSim.Decoder.AXI axi = new InSim.Decoder.AXI(recvPacket);
                            managePacket(axi);
                            break;
                        case "AXM": // object Info
                            InSim.Decoder.AXM axm = new InSim.Decoder.AXM(recvPacket);
                            managePacket(axm);
                            break;
                        case "AXC": // autocross cleared
                            break;
                        case "AXO": //hit an autocross object
                            break;
                        case "CON": //Car Contact
                            InSim.Decoder.CON con = new InSim.Decoder.CON(recvPacket);
                            managePacket(con);
                            break;
                        case "RST": // Race Start
                            InSim.Decoder.RST rst = new InSim.Decoder.RST(recvPacket);
                            managePacket(rst);
                            break;
                        case "REN": // race end (return to game setup screen)
                            break;
                        case "NCN": // New Connection
                            InSim.Decoder.NCN newConnection = new InSim.Decoder.NCN(recvPacket);
                            managePacket(newConnection);
                            break;
                        case "CCH": // NEW CCH : (camera changed)
                            break;
                        case "CNL": // ConN Leave (end connection is moved down into this slot)
                            InSim.Decoder.CNL lostConnection = new InSim.Decoder.CNL(recvPacket);
                            managePacket(lostConnection);
                            break;
                        case "NPL": // New PLayer joining race (if number already exists, then leaving pits)
                            InSim.Decoder.NPL newPlayer = new InSim.Decoder.NPL(recvPacket);
                            managePacket(newPlayer);
                            break;
                        case "PLP": //PLayer Pits (go to settings - stays in player list)
                            InSim.Decoder.PLP plp = new InSim.Decoder.PLP(recvPacket);
                            managePacket(plp);
                            break;
                        case "PLL": // PLayer Leave race (spectate - leaves player list, all are shunted down)
                            InSim.Decoder.PLL pll = new InSim.Decoder.PLL(recvPacket);
                            managePacket(pll);
                            break;
                        case "CPR": // Current Player rename
                            InSim.Decoder.CPR cpr = new InSim.Decoder.CPR(recvPacket);
                            managePacket(cpr);
                            break;
                        case "LAP": // LAP time
                            InSim.Decoder.LAP lapDec = new InSim.Decoder.LAP(recvPacket);
                            managePacket(lapDec);
                            break;
                        case "SP1":
                        case "SP2":
                        case "SP3":
                        case "SPX": // Splitting info
                            InSim.Decoder.SPX splitdec = new InSim.Decoder.SPX(recvPacket);
                            managePacket(splitdec);
                            break;
                        case "RES": // RESult (qualify or finish) temporary
                            InSim.Decoder.RES res = new InSim.Decoder.RES(recvPacket);
                            managePacket(res);
                            break;
                        case "FIN": // Final Result
                            InSim.Decoder.FIN fin = new InSim.Decoder.FIN(recvPacket);
                            managePacket(fin);
                            break;
                        case "REO": // REOrder (when race restarts after qualifying)
                            InSim.Decoder.REO reo = new InSim.Decoder.REO(recvPacket);
                            managePacket(reo);
                            break;
                        case "STA": // State
                            InSim.Decoder.STA state = new InSim.Decoder.STA(recvPacket);
                            managePacket(state);
                            break;
                        case "MAL": //Allowed Mods
                            InSim.Decoder.MAL mal = new InSim.Decoder.MAL(recvPacket);
                            managePacket(mal);
                            break;
                        case "MSO": // Message Output
                            InSim.Decoder.MSO mso = new InSim.Decoder.MSO(recvPacket);
                            managePacket(mso);
                            break;
                        case "MCI":
                            InSim.Decoder.MCI mci = new InSim.Decoder.MCI(recvPacket);
                            managePacket(datRecvPacket, mci);
                            break;
                        case "ISM": //ISM multiplayer notification packet
                            InSim.Decoder.ISM ism = new InSim.Decoder.ISM(recvPacket);
                            managePacket(ism);
                            break;
                        case "PEN": // NEW PEN : (penalty)
                            InSim.Decoder.PEN pen = new InSim.Decoder.PEN(recvPacket);
                            managePacket(pen);
                            break;
                        case "BFN": // NEW BFN : (Button request)
                            InSim.Decoder.BFN bfn = new InSim.Decoder.BFN(recvPacket);
                            managePacket(bfn);
                            break;
                        case "BTC": // NEW BTC : (Button click)
                            InSim.Decoder.BTC btc = new InSim.Decoder.BTC(recvPacket);
                            managePacket(btc);
                            break;
                        case "BTT": // NEW BTC : (Button text)
                            InSim.Decoder.BTT btt = new InSim.Decoder.BTT(recvPacket);
                            managePacket(btt);
                            break;
                        case "TOC": // NEW TOC : (take over car)
                            InSim.Decoder.TOC toc = new InSim.Decoder.TOC(recvPacket);
                            managePacket(toc);
                            break;
                        case "PIT": // NEW PIT : (pit stop)
                            InSim.Decoder.PIT pit = new InSim.Decoder.PIT(recvPacket);
                            managePacket(pit);
                            break;
                        case "PSF": // NEW PSF : (pit stop finished)
                            InSim.Decoder.PSF psf = new InSim.Decoder.PSF(recvPacket);
                            managePacket(psf);
                            break;
                        case "NLP": // Packet P layer info ( Not used )
                            break;
                        case "VTN": // Insim Vote Notify
                            InSim.Decoder.VTN vtn = new InSim.Decoder.VTN(recvPacket);
                            managePacket(vtn);
                            break;
                        case "III": // NEW III : InSimInfo
                            break;
                        case "PLA": // NEW PLA : (Pit LAne)
                            InSim.Decoder.PLA pla = new InSim.Decoder.PLA(recvPacket);
                            managePacket(pla);
                            break;
                        case "FLG": // NEW FLG : (yellow or blue flags)
                            InSim.Decoder.FLG flg = new InSim.Decoder.FLG(recvPacket);
                            managePacket(flg);
                            break;
                        case "PFL": // NEW PFL : (player help flags)
                            InSim.Decoder.PFL pfl = new InSim.Decoder.PFL(recvPacket);
                            managePacket(pfl);
                            break;
                        case "UCO": //Cross a insimchecker (autoX object)
                            InSim.Decoder.UCO uco = new InSim.Decoder.UCO(recvPacket);
                            managePacket(uco);
                            break;
                        case "OBH"://contact car + object (collision report)
                            InSim.Decoder.OBH obh = new InSim.Decoder.OBH(recvPacket);
                            managePacket(obh);
                            break;
                        case "HLV"://Hot Lap Validity - off track / hit wall / speeding in pits / out of bounds
                            InSim.Decoder.HLV hlv = new InSim.Decoder.HLV(recvPacket);
                            managePacket(hlv);
                            break;
                        case "RIP": //replay information packet
                            break;
                        case "SSH": //screenshot
                            break;
                        case "CRS": // Car Reset
                            InSim.Decoder.CRS crs = new InSim.Decoder.CRS(recvPacket);
                            managePacket(crs);
                            break;
                        case "NCI": // New HostInformation
                            InSim.Decoder.NCI nci = new InSim.Decoder.NCI(recvPacket);
                            managePacket(nci);
                            break;
                        case "CSC": // Current State Car
                            InSim.Decoder.CSC csc = new InSim.Decoder.CSC(recvPacket);
                            managePacket(csc);
                            break;
                        case "ACR": // Admin Command report //Added 07-11-2017
                            InSim.Decoder.ACR acr = new InSim.Decoder.ACR(recvPacket);
                            managePacket(acr);
                            break;
                        case "SLC": // Player Select a car //Added 07-11-2017
                            InSim.Decoder.SLC slc = new InSim.Decoder.SLC(recvPacket);
                            managePacket(slc);
                            break;
                        case "CIM": // Conn Interface Mode //Added 12-04-2018
                            InSim.Decoder.CIM cim = new InSim.Decoder.CIM(recvPacket);
                            managePacket(cim);
                            break;
                        default:
                            myDebug.WriteLine("mss","Warning packet not catched (" + packetHead + ")");
                            break;
                    }
                }
#endregion
            }
        }
    }
}