A class to provide common tasks against the Stats Database
Inheritance: BF2Statistics.Database.DatabaseDriver, IDisposable
        /// <summary>
        /// Creates a new Gamespy Account
        /// </summary>
        /// <remarks>Used by the login server when a create account request is made</remarks>
        /// <param name="Nick">The Account Name</param>
        /// <param name="Pass">The Account Password</param>
        /// <param name="Email">The Account Email Address</param>
        /// <param name="Country">The Country Code for this Account</param>
        /// <returns>A bool indicating whether the account was created sucessfully</returns>
        public bool CreateUser(string Nick, string Pass, string Email, string Country)
        {
            int Pid = 0;

            // Attempt to connect to stats database, and get a PID from there
            try
            {
                // try see if the player ID exists in the stats database
                using (StatsDatabase Db = new StatsDatabase())
                {
                    // NOTE: online account names in the stats DB start with a single space!
                    var Row = Db.Query("SELECT id FROM player WHERE upper(name) = upper(@P0)", " " + Nick);
                    Pid = (Row.Count == 0) ? GenerateAccountId() : Int32.Parse(Row[0]["id"].ToString());
                }
            }
            catch
            {
                Pid = GenerateAccountId();
            }

            // Create the user in the database
            int Rows = base.Execute("INSERT INTO accounts(id, name, password, email, country) VALUES(@P0, @P1, @P2, @P3, @P4)",
                Pid, Nick, Pass, Email, Country
            );

            return (Rows != 0);
        }
        /// <summary>
        /// Returns a bool stating whether the criteria for this award is met for a givin player
        /// </summary>
        /// <param name="Pid">The player ID</param>
        /// <param name="Level">The award level if the criteria is met</param>
        /// <returns></returns>
        public bool CriteriaMet(int Pid, StatsDatabase Driver, ref int Level)
        {
            // Get the award count (or level for badges) for this award
            string Query = "SELECT COALESCE(max(level), 0) FROM awards WHERE id=@P0 AND awd=@P1";
            int AwardCount = Convert.ToInt32(Driver.ExecuteScalar(Query, Pid, AwardId));
            bool IsRibbon = (AwardId > 3000000);

            // Can only recieve ribbons once in a lifetime, so return false if we have it already
            if (IsRibbon && AwardCount > 0)
                return false;

            // Medals and Badges can receive multiple times (Badges are award level, not count)
            if (!IsRibbon)
                Level = AwardCount + 1;

            // Loop through each criteria and see if we have met the criteria
            foreach (AwardCriteria Criteria in Criterias)
            {
                // Build the query. We always use a count() or sum() to return a sortof bool.
                string Where = Criteria.Where.Replace("###", Level.ToString());
                Query = String.Format("SELECT {0} FROM {1} WHERE id={2} AND {3}", Criteria.Field, Criteria.Table, Pid, Where);

                // If we dont meet the expected result, the criteria is unmet, no use continuing
                if (Convert.ToInt32(Driver.ExecuteScalar(Query)) < Criteria.ExpectedResult)
                    return false;
            }

            return true;
        }
        /// <summary>
        /// This request provides details on a particular players rank, and
        /// whether or not to show the user a promotion/demotion announcement
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetRankInfo(HttpClient Client, StatsDatabase Driver)
        {
            int Pid = 0;
            List<Dictionary<string, object>> Rows;

            // Setup Params
            if (Client.Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Client.Request.QueryString["pid"], out Pid);

            // Fetch Player
            Rows = Driver.Query("SELECT rank, chng, decr FROM player WHERE id=@P0", Pid);
            if (Rows.Count == 0)
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Player Doesnt Exist");
                Client.Response.Send();
                return;
            }

            // Output status... chng set to 1 shows the Promotion Announcement, whereas decr shows the Demotion Announcement
            Client.Response.WriteResponseStart();
            Client.Response.WriteHeaderLine("rank", "chng", "decr");
            Client.Response.WriteDataLine(Rows[0]["rank"], Rows[0]["chng"], Rows[0]["decr"]);
            Client.Response.Send();

            // Reset
            Driver.Execute("UPDATE player SET chng=0, decr=0 WHERE id=@P0", Pid);
        }
        /// <summary>
        /// This request provides a list of awards for a particular player
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetAwardsInfo(HttpClient Client, StatsDatabase Driver)
        {
            int Pid;

            // make sure we have a valid player ID
            if (!Client.Request.QueryString.ContainsKey("pid") || !Int32.TryParse(Client.Request.QueryString["pid"], out Pid))
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Client.Response.Send();
                return;
            }

            // Output header data
            Client.Response.WriteResponseStart();
            Client.Response.WriteHeaderLine("pid", "asof");
            Client.Response.WriteDataLine(Pid, DateTime.UtcNow.ToUnixTimestamp());
            Client.Response.WriteHeaderLine("award", "level", "when", "first");

            try
            {
                // Fetch Player Awards
                List<Dictionary<string, object>> Awards = Driver.GetPlayerAwards(Pid);

                // Write each award as a new data line
                foreach (Dictionary<string, object> Award in Awards)
                    Client.Response.WriteDataLine(Award["awd"], Award["level"], Award["earned"], Award["first"]);
            }
            catch { }

            // Send Response
            Client.Response.Send();
        }
        public override void HandleRequest()
        {
            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                int Pid = 0;

                // Setup Params
                if (Client.Request.QueryString.ContainsKey("pid"))
                    Int32.TryParse(Client.Request.QueryString["pid"], out Pid);

                // Fetch Player
                var Rows = Database.Query("SELECT rank, chng, decr FROM player WHERE id=@P0", Pid);
                if (Rows.Count == 0)
                {
                    Response.WriteResponseStart(false);
                    Response.WriteHeaderLine("asof", "err");
                    Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Player Doesnt Exist");
                    Response.Send();
                    return;
                }

                // Output status... chng set to 1 shows the Promotion Announcement, whereas decr shows the Demotion Announcement
                Response.WriteResponseStart();
                Response.WriteHeaderLine("rank", "chng", "decr");
                Response.WriteDataLine(Rows[0]["rank"], Rows[0]["chng"], Rows[0]["decr"]);
                Response.Send();

                // Reset
                Database.Execute("UPDATE player SET chng=0, decr=0 WHERE id=@P0", Pid);
            }
        }
        /// <summary>
        /// Creates a new Gamespy Account
        /// </summary>
        /// <remarks>Used by the login server when a create account request is made</remarks>
        /// <param name="Nick">The Account Name</param>
        /// <param name="Pass">The UN-HASHED Account Password</param>
        /// <param name="Email">The Account Email Address</param>
        /// <param name="Country">The Country Code for this Account</param>
        /// <returns>Returns the Player ID if sucessful, 0 otherwise</returns>
        public int CreateUser(string Nick, string Pass, string Email, string Country)
        {
            int Pid = 0;

            // Attempt to connect to stats database, and get a PID from there
            try
            {
                // try see if the player ID exists in the stats database
                using (StatsDatabase Db = new StatsDatabase())
                {
                    // NOTE: online account names in the stats DB start with a single space!
                    var Row = Db.Query("SELECT id FROM player WHERE upper(name) = upper(@P0)", " " + Nick);
                    Pid = (Row.Count == 0) ? StatsManager.GenerateNewPlayerPid() : Int32.Parse(Row[0]["id"].ToString());
                }
            }
            catch
            {
                Pid = StatsManager.GenerateNewPlayerPid();
            }

            // Create the user in the database
            int Rows = base.Execute("INSERT INTO accounts(id, name, password, email, country) VALUES(@P0, @P1, @P2, @P3, @P4)",
                                    Pid, Nick, Pass.GetMD5Hash(false), Email.ToLowerInvariant(), Country
                                    );

            return((Rows != 0) ? Pid : 0);
        }
        public SearchForPlayers(HttpClient Client, StatsDatabase Driver)
        {
            string Nick;
            List<Dictionary<string, object>> Rows;

            // Setup Params
            if (!Client.Request.QueryString.ContainsKey("nick"))
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Client.Response.Send();
                return;
            }
            else
                Nick = Client.Request.QueryString["nick"];

            // Timestamp Header
            Client.Response.WriteResponseStart();
            Client.Response.WriteHeaderLine("asof");
            Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp());

            // Output status
            int i = 0;
            Client.Response.WriteHeaderLine("n", "pid", "nick", "score");
            Rows = Driver.Query("SELECT id, name, score FROM player WHERE name LIKE @P0 LIMIT 20", "%" + Nick + "%");
            foreach (Dictionary<string, object> Player in Rows)
            {
                Client.Response.WriteDataLine(i + 1, Rows[i]["id"], Rows[i]["name"].ToString().Trim(), Rows[i]["score"]);
                i++;
            }

            // Send Response
            Client.Response.Send();
        }
        public override void HandleRequest()
        {
            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                int Pid = 0;
                List<Dictionary<string, object>> Rows;

                // Setup Params
                if (Client.Request.QueryString.ContainsKey("pid"))
                    Int32.TryParse(Client.Request.QueryString["pid"], out Pid);

                // Fetch Player
                Rows = Database.Query("SELECT rank FROM player WHERE id=@P0", Pid);
                if (Rows.Count == 0)
                {
                    Response.WriteResponseStart(false);
                    Response.WriteFreeformLine("Player Doesnt Exist!");
                    Client.Response.Send();
                    return;
                }

                // Reset
                Database.Execute("UPDATE player SET chng=0, decr=0 WHERE id=@P0", Pid);
                Response.WriteResponseStart();
                Response.WriteFreeformLine(String.Format("Cleared rank notification {0}", Pid));
                Response.Send();
            }
        }
        public override void HandleRequest()
        {
            int Pid;

            // make sure we have a valid player ID
            if (!Request.QueryString.ContainsKey("pid") || !Int32.TryParse(Request.QueryString["pid"], out Pid))
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // Output header data
            Response.WriteResponseStart();
            Response.WriteHeaderLine("pid", "asof");
            Response.WriteDataLine(Pid, DateTime.UtcNow.ToUnixTimestamp());
            Response.WriteHeaderLine("award", "level", "when", "first");

            // Fetch Player Awards
            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                List<Dictionary<string, object>> Awards = Database.GetPlayerAwards(Pid);

                // Write each award as a new data line
                foreach (Dictionary<string, object> Award in Awards)
                    Response.WriteDataLine(Award["awd"], Award["level"], Award["earned"], Award["first"]);

                // Send Response
                Response.Send();
            }
        }
        /// <summary>
        /// This request clears all rank announcements for a specific player
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public RankNotification(HttpClient Client, StatsDatabase Driver)
        {
            int Pid = 0;
            List<Dictionary<string, object>> Rows;

            // Setup Params
            if (Client.Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Client.Request.QueryString["pid"], out Pid);

            // Fetch Player
            Rows = Driver.Query("SELECT rank FROM player WHERE id=@P0", Pid);
            if (Rows.Count == 0)
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteFreeformLine("Player Doesnt Exist!");
                Client.Response.Send();
                return;
            }

            // Reset
            Driver.Execute("UPDATE player SET chng=0, decr=0 WHERE id=@P0", Pid);
            Client.Response.WriteResponseStart();
            Client.Response.WriteFreeformLine(String.Format("Cleared rank notification {0}", Pid));
            Client.Response.Send();
        }
        public override void HandleRequest(MvcRoute Route)
        {
            // Try and Fetch Player ID
            Int32.TryParse(Route.Action, out Pid);

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Fetch Player
                Rows = Database.Query("SELECT * FROM player WHERE id=@P0", Pid);
                if (Rows.Count == 0)
                {
                    Client.Response.Redirect("/bf2stats/search");
                    return;
                }

                // Load our page based on the param passed
                if (Route.Params.Length > 0)
                {
                    if (Route.Params[0] == "rankings")
                        ShowRankings();
                    else if (Route.Params[0] == "history")
                        ShowHistory();
                    else
                        Client.Response.StatusCode = 404;
                }
                else
                {
                    ShowStats();
                }
            }
        }
 /// <summary>
 /// Clears the stats database of all data
 /// </summary>
 private void ClearStatsBtn_Click(object sender, EventArgs e)
 {
     if (MessageBox.Show(
         "Are you sure you want to clear the stats database? This will ERASE ALL stats data, and cannot be recovered!",
         "Confirm",
         MessageBoxButtons.OKCancel,
         MessageBoxIcon.Warning) == DialogResult.OK)
     {
         try
         {
             using (StatsDatabase Database = new StatsDatabase())
             {
                 Database.Truncate();
             }
             Notify.Show("Database Successfully Cleared!", "All stats have successfully been cleared.", AlertType.Success);
         }
         catch (Exception E)
         {
             MessageBox.Show(
                 "An error occured while clearing the stats database!\r\n\r\nMessage: " + E.Message,
                 "Error",
                 MessageBoxButtons.OK,
                 MessageBoxIcon.Error
             );
         }
     }
 }
        /// <summary>
        /// This request provides details on a particular players map info
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <queryParam name="mapid" type="int">The unique map ID</queryParam>
        /// <queryParam name="customonly" type="int">Defines whether to only display custom maps</queryParam>
        /// <queryParam name="mapname" type="string">The unique map's name</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetMapInfo(HttpClient Client, StatsDatabase Driver)
        {
            // Setup Variables
            int Pid = 0, MapId = 0, CustomOnly = 0;
            string MapName = "";
            SelectQueryBuilder Query = new SelectQueryBuilder(Driver);

            // Setup QueryString Params
            if (Client.Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Client.Request.QueryString["pid"], out Pid);
            if (Client.Request.QueryString.ContainsKey("mapid"))
                Int32.TryParse(Client.Request.QueryString["mapid"], out MapId);
            if (Client.Request.QueryString.ContainsKey("customonly"))
                Int32.TryParse(Client.Request.QueryString["customonly"], out CustomOnly);
            if (Client.Request.QueryString.ContainsKey("mapname"))
                MapName = Client.Request.QueryString["mapname"].Trim();

            // Prepare Response
            Client.Response.WriteResponseStart();

            // Is this a Player Map Request?
            if (Pid != 0)
            {
                // Build our query statement
                Query.SelectFromTable("maps");
                Query.SelectColumns("maps.*", "mapinfo.name AS mapname");
                Query.AddJoin(JoinType.InnerJoin, "mapinfo", "id", Comparison.Equals, "maps", "mapid");
                Query.AddWhere("maps.id", Comparison.Equals, Pid);
                Query.AddOrderBy("mapid", Sorting.Ascending);

                // Execute the reader, and add each map to the output
                Client.Response.WriteHeaderLine("mapid", "mapname", "time", "win", "loss", "best", "worst");
                foreach (Dictionary<string, object> Map in Driver.QueryReader(Query.BuildCommand()))
                    Client.Response.WriteDataLine(Map["mapid"], Map["mapname"], Map["time"], Map["win"], Map["loss"], Map["best"], Map["worst"]);
            }
            else
            {
                // Build our query statement
                Query.SelectFromTable("mapinfo");
                Query.SelectColumns("id", "name", "score", "time", "times", "kills", "deaths");
                Query.AddOrderBy("id", Sorting.Ascending);

                // Select our where statement
                if (MapId > 0)
                    Query.AddWhere("id", Comparison.Equals, MapId);
                else if (!String.IsNullOrEmpty(MapName))
                    Query.AddWhere("name", Comparison.Equals, MapName);
                else if (CustomOnly == 1)
                    Query.AddWhere("id", Comparison.GreaterOrEquals, 700);

                // Execute the reader, and add each map to the output
                Client.Response.WriteHeaderLine("mapid", "name", "score", "time", "times", "kills", "deaths");
                foreach (Dictionary<string, object> Map in Driver.QueryReader(Query.BuildCommand()))
                    Client.Response.WriteDataLine(Map["id"], Map["name"], Map["score"], Map["time"], Map["times"], Map["kills"], Map["deaths"]);
            }

            // Send Response
            Client.Response.Send();
        }
        /// <summary>
        /// This request provides details of the leaderboard
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <queryParam name ="nick" type="string">Unique player nickname</queryParam>
        /// <queryParam name="type" type="string">"score", "kit", "vehicle", "weapon"</queryParam>
        /// <queryParam name="id" type="int|string (score)">the various fetch variables (kit ids, vehicle id's etc etc)</queryParam>
        /// <queryParam name="before" type="int">The number of players before</queryParam>
        /// <queryParam name="after" type="int">The number of players after</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetLeaderBoard(HttpClient Client, StatsDatabase Database)
        {
            // Set internal variables
            this.Response = Client.Response;
            this.QueryString = Client.Request.QueryString;
            this.Driver = Database;

            // We need a type!
            if (!QueryString.ContainsKey("type"))
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // Setup Params
            if(QueryString.ContainsKey("pid"))
                Int32.TryParse(QueryString["pid"], out Pid);
            if (QueryString.ContainsKey("id"))
                Id = QueryString["id"];
            if (QueryString.ContainsKey("before"))
                Int32.TryParse(QueryString["before"], out Before);
            if(QueryString.ContainsKey("after"))
                Int32.TryParse(QueryString["after"], out After);
            if (QueryString.ContainsKey("pos"))
                Int32.TryParse(QueryString["pos"], out Pos);

            Min = (Pos - 1) - Before;
            Max = After + 1;

            // Do our requested Task
            switch (QueryString["type"])
            {
                case "score":
                    DoScore();
                    break;
                case "risingstar":
                    DoRisingStar();
                    break;
                case "kit":
                    DoKit();
                    break;
                case "vehicle":
                    DoVehicles();
                    break;
                case "weapon":
                    DoWeapons();
                    break;
                default:
                    //Response.HTTPStatusCode = ASPResponse.HTTPStatus.BadRequest;
                    Response.Send();
                    break;
            }
        }
        public override void HandleRequest()
        {
            // We need a type!
            if (!Request.QueryString.ContainsKey("type"))
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // Setup Params
            if (Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Request.QueryString["pid"], out Pid);
            if (Request.QueryString.ContainsKey("id"))
                Id = Request.QueryString["id"];
            if (Request.QueryString.ContainsKey("before"))
                Int32.TryParse(Request.QueryString["before"], out Before);
            if (Request.QueryString.ContainsKey("after"))
                Int32.TryParse(Request.QueryString["after"], out After);
            if (Request.QueryString.ContainsKey("pos"))
                Int32.TryParse(Request.QueryString["pos"], out Pos);

            Min = (Pos - 1) - Before;
            Max = After + 1;

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Do our requested Task
                switch (Request.QueryString["type"])
                {
                    case "score":
                        DoScore();
                        break;
                    case "risingstar":
                        DoRisingStar();
                        break;
                    case "kit":
                        DoKit();
                        break;
                    case "vehicle":
                        DoVehicles();
                        break;
                    case "weapon":
                        DoWeapons();
                        break;
                    default:
                        //Response.HTTPStatusCode = ASPResponse.HTTPStatus.BadRequest;
                        Response.Send();
                        break;
                }
            }
        }
 /// <summary>
 /// Grab lowest PID on startup
 /// </summary>
 static GetPlayerID()
 {
     // Get the lowest Offline PID from the database
     using (StatsDatabase Driver = new StatsDatabase())
     {
         int DefaultPid = MainForm.Config.ASP_DefaultPID;
         var Rows = Driver.Query(String.Format("SELECT COALESCE(MIN(id), {0}) AS min FROM player", DefaultPid));
         int Lowest = Int32.Parse(Rows[0]["min"].ToString());
         LowestPid = (Lowest > DefaultPid) ? DefaultPid : Lowest -1;
     }
 }
        public override void HandleRequest()
        {
            int Pid = 0;
            int Unlock = 0;
            List<Dictionary<string, object>> Rows;

            // Setup Params
            if (Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Request.QueryString["pid"], out Pid);
            if (Request.QueryString.ContainsKey("id"))
                Int32.TryParse(Request.QueryString["id"], out Unlock);

            // Make sure we have valid parameters
            if (Pid == 0 || Unlock == 0)
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Fetch Player
                Rows = Database.Query("SELECT availunlocks, usedunlocks FROM player WHERE id=@P0", Pid);
                if (Rows.Count == 0)
                {
                    Response.WriteResponseStart(false);
                    Response.WriteHeaderLine("asof", "err");
                    Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Player Doesnt Exist");
                    Response.Send();
                    return;
                }

                // Update Unlock, setting its state to 's' (unlocked)
                Database.Execute("UPDATE unlocks SET state = 's' WHERE id = @P0 AND kit = @P1", Pid, Unlock);

                // Update players used and avail unlock counts
                Database.Execute("UPDATE player SET availunlocks = @P0, usedunlocks = @P1 WHERE id = @P2",
                    int.Parse(Rows[0]["availunlocks"].ToString()) - 1,
                    int.Parse(Rows[0]["usedunlocks"].ToString()) + 1,
                    Pid
                );

                // Send Response
                Response.WriteResponseStart();
                Response.WriteHeaderLine("response");
                Response.WriteDataLine("OK");
                Response.Send();
            }
        }
        //&info=per*,cmb*,twsc,cpcp,cacp,dfcp,kila,heal,rviv,rsup,rpar,tgte,dkas,dsab,cdsc,rank,cmsc,kick,kill,deth,suic,ospm,klpm,klpr,dtpr,bksk,wdsk,bbrs,tcdr,ban,dtpm,lbtl,osaa,vrk,tsql,tsqm,tlwf,mvks,vmks,mvn*,vmr*,fkit,fmap,fveh,fwea,wtm-,wkl-,wdt-,wac-,wkd-,vtm-,vkl-,vdt-,vkd-,vkr-,atm-,awn-,alo-,abr-,ktm-,kkl-,kdt-,kkd-
        /// <summary>
        /// This request provides details on a particular player
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <queryParam name="info" type="string">The requested player data keys, seperated by a comma</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetPlayerInfo(HttpClient Client, StatsDatabase Database)
        {
            // Load class Variables
            this.Response = Client.Response;
            this.QueryString = Client.Request.QueryString;
            this.Driver = Database;

            // Setup Params
            if (QueryString.ContainsKey("pid"))
                Int32.TryParse(QueryString["pid"], out Pid);
            if (QueryString.ContainsKey("transpose"))
                Int32.TryParse(QueryString["transpose"], out Transpose);
            if (QueryString.ContainsKey("info"))
                Info = QueryString["info"].Split(',').ToList<string>();

            // Make sure our required params are indeed passed
            if (Pid == 0 || Info.Count == 0)
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Client.Response.Send();
                return;
            }

            // Get Missing keys for a standard request
            List<string> ReqKeys = RequiredKeys.Split(',').ToList<string>();
            IEnumerable<string> Diff = from item in ReqKeys where !Info.Contains(item) select item;
            List<string> MissingKeys = new List<string>(Diff);

            // Standard BF2HQ Request
            if (MissingKeys.Count == 0)
                DoFullRequest();
            // Time Info
            else if (QueryString["info"] == "ktm-,vtm-,wtm-,mtm-")
                DoTimeRequest();
            // Map Info
            else if (QueryString["info"].StartsWith("mtm-,mwn-,mls-"))
                DoMapRequest();
            else if (QueryString["info"].StartsWith("rank") && QueryString["info"].EndsWith("vac-"))
                DoServerRequest();
            else
                Response.Send();
        }
        public override void HandleRequest()
        {
            int Pid;

            // make sure we have a valid player ID
            if (!Request.QueryString.ContainsKey("pid")
                || !Int32.TryParse(Request.QueryString["pid"], out Pid)
                || !Request.QueryString.ContainsKey("nick"))
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Fetch Player
                string PlayerNick = Request.QueryString["nick"].Replace("%20", " ");
                string CC = (Request.QueryString.ContainsKey("cid")) ? Client.Request.QueryString["cid"] : "";
                if (Database.ExecuteScalar<int>("SELECT COUNT(*) FROM player WHERE id=@P0 OR name=@P1", Pid, PlayerNick) > 0)
                {
                    Response.WriteResponseStart(false);
                    Response.WriteFreeformLine("Player already Exists!");
                    Response.Send();
                    return;
                }

                // Create Player
                Database.Execute(
                    "INSERT INTO player(id, name, country, joined, isbot) VALUES(@P0, @P1, @P2, @P3, 0)",
                    Pid, PlayerNick, CC, DateTime.UtcNow.ToUnixTimestamp()
                );

                // Confirm
                Response.WriteResponseStart();
                Response.WriteFreeformLine("Player Created Successfully!");
                Response.Send();
            }
        }
        /// <summary>
        /// This request creates a player with the specified Pid when called
        /// </summary>
        /// <queryParam name="pid" type="int">The unique player ID</queryParam>
        /// <queryParam name ="nick" type="string">Unique player nickname</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public CreatePlayer(HttpClient Client, StatsDatabase Driver)
        {
            int Pid;

            // make sure we have a valid player ID
            if (!Client.Request.QueryString.ContainsKey("pid")
                || !Int32.TryParse(Client.Request.QueryString["pid"], out Pid)
                || !Client.Request.QueryString.ContainsKey("nick"))
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Client.Response.Send();
                return;
            }

            // Fetch Player
            string PlayerNick = Client.Request.QueryString["nick"].Replace("%20", " ");
            string CC = (Client.Request.QueryString.ContainsKey("cid")) ? Client.Request.QueryString["cid"] : "";
            var Rows = Driver.Query("SELECT name FROM player WHERE id=@P0 OR name=@P1", Pid, PlayerNick);
            if (Rows.Count > 0)
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteFreeformLine("Player already Exists!");
                Client.Response.Send();
                return;
            }

            // Create Player
            Driver.Execute(
                "INSERT INTO player(id, name, country, joined, isbot) VALUES(@P0, @P1, @P2, @P3, 0)",
                Pid, PlayerNick, CC, DateTime.UtcNow.ToUnixTimestamp()
            );

            // Confirm
            Client.Response.WriteResponseStart();
            Client.Response.WriteFreeformLine("Player Created Successfully!");
            Client.Response.Send();
        }
        /// <summary>
        /// Constructor
        /// </summary>
        public PlayerSearchForm()
        {
            InitializeComponent();

            // Establish DB connection
            try
            {
                Driver = new StatsDatabase();
            }
            catch (DbConnectException Ex)
            {
                HttpServer.Stop();
                ExceptionForm.ShowDbConnectError(Ex);
                Load += (s, e) => Close(); // Close form
                return;
            }

            // Initialize sorting
            SortedCol = DataTable.Columns[1];
            SortedCol.HeaderCell.SortGlyphDirection = SortOrder.Ascending;
            LimitSelect.SelectedIndex = 2;
        }
        /// <summary>
        /// This request provides claninfo to the bf2server, to set the filter rules
        /// for players joining the server.
        /// </summary>
        /// <queryParam name="type" type="int">The Listype (whitelist, blacklist)</queryParam>
        /// <queryParam name ="clantag" type="string">Specified the required clantag</queryParam>
        /// <queryParam name="score" type="int">The minimum required score</queryParam>
        /// <queryParam name="rank" type="int">The minimum required ranked</queryParam>
        /// <queryParam name="time" type="int">The minimum required global time</queryParam>
        /// <queryParam name="kdratio" type="float">The minimum required kill/death ratio</queryParam>
        /// <queryParam name="country" type="string">The country code (Ex: us, br, no) required, seperated by comma, that is required</queryParam>
        /// <queryParam name="banned" type="int">Specified the maximum ban count to be accepted into the list</queryParam>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public GetClanInfo(HttpClient Client, StatsDatabase Database)
        {
            int Type = 0;
            Dictionary<string, string> QueryString = Client.Request.QueryString;
            Driver = Database;

            // make sure we have a valid player ID
            if (!QueryString.ContainsKey("type") || !Int32.TryParse(QueryString["type"], out Type))
            {
                Client.Response.WriteResponseStart(false);
                Client.Response.WriteHeaderLine("asof", "err");
                Client.Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Client.Response.Send();
                return;
            }

            // Filler Variables
            int I = 0;
            float F;
            string S;
            List<DbParameter> Params = new List<DbParameter>();

            // Prepare Query
            SelectQueryBuilder Query = new SelectQueryBuilder(Driver);
            Query.SelectColumns("id", "name");
            Query.SelectFromTable("player");
            Query.SetWhereOperator(LogicOperator.And);
            Query.AddWhere("ip", Comparison.NotEqualTo, "0.0.0.0");
            Query.AddOrderBy("id", Sorting.Ascending);
            WhereClause Where = null;

            switch (Type)
            {
                // Blacklist
                case 0:
                    int BanLimit = (QueryString.ContainsKey("banned") && Int32.TryParse(QueryString["banned"], out I)) ? I : 100;
                    Where = new WhereClause("banned", Comparison.GreaterOrEquals, BanLimit);
                    Where.AddClause(LogicOperator.Or, "permban", Comparison.Equals, 1);
                    break;
                // Whitelist
                case 1:
                    if (QueryString.ContainsKey("clantag"))
                    {
                        Where = new WhereClause("clantag", Comparison.Equals, QueryString["clantag"]);
                        Where.AddClause(LogicOperator.And, "permban", Comparison.Equals, 0);
                    }
                    break;
                // Greylist
                case 2:
                    // List of possible query's
                    string[] Queries = new string[] { "score", "rank", "time", "kdratio", "country", "banned" };
                    foreach (string Param in Queries)
                    {
                        if (QueryString.ContainsKey(Param))
                        {
                            switch (Param)
                            {
                                case "score":
                                case "time":
                                case "rank":
                                    if (Int32.TryParse(QueryString[Param], out I))
                                    {
                                        if (Where == null)
                                            Where = new WhereClause(Param, Comparison.GreaterOrEquals, I);
                                        else
                                            Where.AddClause(LogicOperator.And, Param, Comparison.GreaterOrEquals, I);
                                    }
                                    break;
                                case "kdratio":
                                    if (float.TryParse(QueryString["kdratio"], out F))
                                    {
                                        if (Where == null)
                                            Where = new WhereClause("(kills / deaths)", Comparison.GreaterOrEquals, F);
                                        else
                                            Where.AddClause(LogicOperator.And, "(kills / deaths)", Comparison.GreaterOrEquals, F);
                                    }
                                    break;
                                case "country":
                                    S = QueryString["country"].Replace(",", "','");
                                    if (Where == null)
                                        Where = new WhereClause(Param, Comparison.In, S.Split(','));
                                    else
                                        Where.AddClause(LogicOperator.And, Param, Comparison.In, S.Split(','));
                                    break;
                                case "banned":
                                    if (Int32.TryParse(QueryString["banned"], out I))
                                    {
                                        if(Where == null)
                                            Where = new WhereClause("banned", Comparison.LessThan, I);
                                        else
                                            Where.AddClause(LogicOperator.And,"banned", Comparison.LessThan, I);

                                        Where.AddClause(LogicOperator.And, "permban", Comparison.Equals, 0);
                                    }
                                    break;
                            }
                        }
                    }
                    break;
            }

            // Pepare 2 output headers
            int size = 0;
            FormattedOutput Output1 = new FormattedOutput("size", "asof");
            FormattedOutput Output2 = new FormattedOutput("pid", "nick");

            // Query the database, add each player to Output 2
            if(Where != null) Query.AddWhere(Where);
            List<Dictionary<string, object>> Players = Driver.ExecuteReader(Query.BuildCommand());
            foreach (Dictionary<string, object> P in Players)
            {
                size++;
                Output2.AddRow(P["id"].ToString(), P["name"].ToString());
            }

            // Send Response
            Output1.AddRow(size, DateTime.UtcNow.ToUnixTimestamp());
            Client.Response.AddData(Output1);
            Client.Response.AddData(Output2);
            Client.Response.Send();
        }
        /// <summary>
        /// This request takes snapshot data, and processes it into the stats database
        /// </summary>
        /// <param name="Client">The HttpClient who made the request</param>
        /// <param name="Driver">The Stats Database Driver. Connection errors are handled in the calling object</param>
        public SnapshotPost(HttpClient Client, StatsDatabase Driver)
        {
            // First and foremost. Make sure that we are authorized to be here!
            IPEndPoint RemoteIP = Client.RemoteEndPoint;
            if (!Client.Request.IsLocal)
            {
                // Setup local vars
                bool IsValid = false;
                IPAddress Ip;

                // Loop through all Config allowed game hosts, and determine if the remote host is allowed
                // to post snapshots here
                if (!String.IsNullOrWhiteSpace(MainForm.Config.ASP_GameHosts))
                {
                    string[] Hosts = MainForm.Config.ASP_GameHosts.Split(',');
                    foreach (string Host in Hosts)
                    {
                        if (IPAddress.TryParse(Host, out Ip) && Ip.Equals(RemoteIP.Address))
                        {
                            IsValid = true;
                            break;
                        }
                    }
                }

                // If we are not on the GameHost list, too bad sucka!
                if (!IsValid)
                {
                    // Notify User
                    Notify.Show("Snapshot Denied!", "Invalid Server IP: " + RemoteIP.Address.ToString(), AlertType.Warning);
                    if (Client.Request.UserAgent == "GameSpyHTTP/1.0")
                    {
                        Client.Response.WriteResponseStart(false);
                        Client.Response.WriteHeaderLine("response");
                        Client.Response.WriteDataLine("Unauthorised Gameserver");
                    }
                    else
                        Client.Response.StatusCode = (int)HttpStatusCode.Forbidden;

                    Client.Response.Send();
                    return;
                }
            }

            // Make sure we have post data
            if (!Client.Request.HasEntityBody)
            {
                // No Post Data
                if (Client.Request.UserAgent == "GameSpyHTTP/1.0")
                {
                    Client.Response.WriteResponseStart(false);
                    Client.Response.WriteHeaderLine("response");
                    Client.Response.WriteDataLine("SNAPSHOT Data NOT found!");
                }
                else
                    Client.Response.StatusCode = (int)HttpStatusCode.BadRequest;

                Client.Response.Send();
                return;
            }

            // Create our snapshot object and filename
            string SnapshotData;
            Snapshot Snapshot;
            string FileName = String.Empty;
            bool BackupCreated = false;

            // Create snapshot backup file if the snapshot is valid
            try
            {
                // Read Snapshot
                using (StreamReader Reader = new StreamReader(Client.Request.InputStream))
                    SnapshotData = Reader.ReadToEnd();

                // Create the Snapshot Object
                Snapshot = new Snapshot(SnapshotData, DateTime.UtcNow, Driver);

                // Make sure data is valid!
                if (!Snapshot.IsValid)
                {
                    Notify.Show("Error Processing Snapshot!", "Snapshot Data NOT Complete or Invalid!", AlertType.Warning);
                    Client.Response.WriteResponseStart(false);
                    Client.Response.WriteHeaderLine("response");
                    Client.Response.WriteDataLine("SNAPSHOT Data NOT complete or invalid!");
                    Client.Response.Send();
                    return;
                }
            }
            catch (Exception E)
            {
                ASPServer.Log("ERROR: [SnapshotPreProcess] " + E.Message + " @ " + E.TargetSite);
                Client.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
                Client.Response.Send();
                return;
            }

            // Create backup of snapshot
            try
            {
                // Backup the snapshot
                FileName = Snapshot.ServerPrefix + "-" + Snapshot.MapName + "_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".txt";
                File.AppendAllText(Path.Combine(Paths.SnapshotTempPath, FileName), SnapshotData);
                BackupCreated = true;
            }
            catch (Exception E)
            {
                ASPServer.Log("WARNING: Unable to create Snapshot Backup File: " + E.Message);
            }

            // Tell the server we are good to go
            Client.Response.WriteResponseStart();
            Client.Response.WriteHeaderLine("response");
            Client.Response.WriteDataLine("OK");
            Client.Response.Send();

            // Have the snapshot class handle the rest :)
            try
            {
                // Do the snapshot
                Snapshot.Process();

                // Move the Temp snapshot to the Processed folder
                if (BackupCreated)
                {
                    try {
                        File.Move(Path.Combine(Paths.SnapshotTempPath, FileName), Path.Combine(Paths.SnapshotProcPath, FileName));
                    }
                    catch (Exception e) {
                        ASPServer.Log("WARNING: [SnapshotFileOperations] " + e.Message);
                        File.Delete(Path.Combine(Paths.SnapshotTempPath, FileName));
                    }
                }

                // Notify User
                Notify.Show("Snapshot Processed Successfully!", "From Server IP: " + RemoteIP.Address.ToString(), AlertType.Success);

                // Fire Event
                SnapshotReceived(true);
            }
            catch (Exception E)
            {
                // Notify user
                Notify.Show("Error Processing Snapshot!", E.Message, AlertType.Warning);
                ASPServer.Log("ERROR: [SnapshotProcessing] " + E.Message);

                // Fire event
                SnapshotReceived(false);
            }
        }
        /// <summary>
        /// Backs up the asp database
        /// </summary>
        private void ExportAsASPBtn_Click(object sender, EventArgs e)
        {
            // Define backup folder for this backup, and create it if it doesnt exist
            string Folder = Path.Combine(Paths.DocumentsFolder, "Backups", "bak_" + DateTime.Now.ToString("yyyyMMdd_HHmm"));
            if (!Directory.Exists(Folder))
                Directory.CreateDirectory(Folder);

            // Abortion indicator
            bool Aborted = false;

            // Open the database connection
            StatsDatabase Database;
            try
            {
                Database = new StatsDatabase();
            }
            catch (Exception Ex)
            {
                MessageBox.Show(
                    "Unable to connect to database\r\n\r\nMessage: " + Ex.Message,
                    "Database Connection Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error
                );

                // Stop the ASP server, and close this form
                ASP.ASPServer.Stop();
                this.Close();
                return;
            }

            // Show loading screen
            LoadingForm.ShowScreen(this);

            // Backup each table into its own bak file
            foreach (string Table in StatsDatabase.StatsTables)
            {
                // Create file path
                string BakFile = Path.Combine(Folder, Table + ".bak");

                // Backup tables
                try
                {
                    using (Stream Str = File.Open(BakFile, FileMode.Create))
                    using (StreamWriter Wtr = new StreamWriter(Str))
                    {
                        // Use a memory efficient way to export this stuff
                        foreach (Dictionary<string, object> Row in Database.QueryReader("SELECT * FROM " + Table))
                            Wtr.WriteLine(String.Join("\t", Row.Values));

                        Wtr.Flush();
                    }
                }
                catch (Exception Ex)
                {
                    // Close loading form
                    LoadingForm.CloseForm();

                    // Display the Exception Form
                    ExceptionForm Form = new ExceptionForm(Ex, false);
                    Form.Message = "An error occured while trying to backup the \"" + Table + "\" table. "
                        + "The backup operation will now be cancelled.";
                    DialogResult Result = Form.ShowDialog();
                    Aborted = true;

                    // Try and remove backup folder
                    try
                    {
                        DirectoryInfo Dir = new DirectoryInfo(Folder);
                        Dir.Delete(true);
                    }
                    catch { }
                }

                if (Aborted) break;
            }

            // Only display success message if we didnt abort
            if (!Aborted)
            {
                // Close loading form
                LoadingForm.CloseForm();

                string NL = Environment.NewLine;
                MessageBox.Show(
                    String.Concat("Backup has been completed successfully!", NL, NL, "Backup files have been saved to:", NL, Folder),
                    "Backup Success",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information
                );
            }

            // Close the connection
            Database.Dispose();
        }
        public override void HandleRequest()
        {
            // Setup Variables
            List<Dictionary<string, object>> Rows;
            Dictionary<string, string> QueryString = Request.QueryString;

            // Querystring vars
            int IsAI = 0;
            int ListPlayers = 0;
            string PlayerNick = "";

            // Setup Params
            if (QueryString.ContainsKey("nick"))
                PlayerNick = Uri.UnescapeDataString(QueryString["nick"].Replace("%20", " "));
            if (QueryString.ContainsKey("ai"))
                Int32.TryParse(QueryString["ai"], out IsAI);
            if (QueryString.ContainsKey("playerlist"))
                Int32.TryParse(QueryString["playerlist"], out ListPlayers);

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Handle Request
                if (!String.IsNullOrWhiteSpace(PlayerNick))
                {
                    int Pid;

                    // Create player if they donot exist
                    Rows = Database.Query("SELECT id FROM player WHERE name = @P0 LIMIT 1", PlayerNick);
                    if (Rows.Count == 0)
                    {
                        // Grab new Player ID using thread safe methods
                        Pid = (IsAI > 0) ? StatsManager.GenerateNewAIPid() : StatsManager.GenerateNewPlayerPid();

                        // Create Player
                        Database.Execute(
                            "INSERT INTO player(id, name, joined, isbot) VALUES(@P0, @P1, @P2, @P3)",
                            Pid, PlayerNick, DateTime.UtcNow.ToUnixTimestamp(), IsAI
                        );
                    }
                    else
                        Pid = Int32.Parse(Rows[0]["id"].ToString());

                    // Send Response
                    Response.WriteResponseStart();
                    Response.WriteHeaderLine("pid");
                    Response.WriteDataLine(Pid);
                }
                else if (ListPlayers != 0)
                {
                    // Prepare Response
                    Response.WriteResponseStart();
                    Response.WriteHeaderLine("pid");

                    // Fetch Players
                    Rows = Database.Query("SELECT id FROM player WHERE isbot=0 LIMIT 1000");
                    foreach (Dictionary<string, object> Player in Rows)
                        Response.WriteDataLine(Player["id"]);
                }
                else
                {
                    Response.WriteResponseStart(false);
                    Response.WriteHeaderLine("asof", "err");
                    Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                }

                // Send Response
                Response.Send();
            }
        }
        /// <summary>
        /// Imports ASP created BAK files (Mysql Out FILE)
        /// </summary>
        private void ImportASPBtn_Click(object sender, EventArgs e)
        {
            // Open File Select Dialog
            FolderSelectDialog Dialog = new FolderSelectDialog();
            Dialog.Title = "Select ASP Database Backup Folder";
            Dialog.InitialDirectory = Path.Combine(Paths.DocumentsFolder, "Backups");
            if (Dialog.ShowDialog())
            {
                // Get files list from path
                string path = Dialog.SelectedPath;
                string[] BakFiles = Directory.GetFiles(path, "*.bak");
                if (BakFiles.Length > 0)
                {
                    // Open the database connection
                    StatsDatabase Database;
                    try
                    {
                        Database = new StatsDatabase();
                    }
                    catch (Exception Ex)
                    {
                        MessageBox.Show(
                            "Unable to connect to database\r\n\r\nMessage: " + Ex.Message,
                            "Database Connection Error",
                            MessageBoxButtons.OK, MessageBoxIcon.Error
                        );

                        // Stop the ASP server, and close this form
                        ASP.ASPServer.Stop();
                        this.Close();
                        return;
                    }

                    // Show task dialog
                    TaskForm.Show(this, "Importing Stats", "Importing ASP Stats Bak Files...", false);
                    TaskForm.UpdateStatus("Removing old stats data");

                    // Clear old database records
                    Database.Truncate();
                    Thread.Sleep(500);

                    // Begin transaction
                    DbTransaction Transaction = Database.BeginTransaction();

                    // import each table
                    foreach (string file in BakFiles)
                    {
                        // Get table name
                        string table = Path.GetFileNameWithoutExtension(file);

                        // Update progress
                        TaskForm.UpdateStatus("Processing stats table: " + table);

                        // Import table data
                        try
                        {
                            // Sqlite kinda sucks... no import methods
                            if (Database.DatabaseEngine == DatabaseEngine.Sqlite)
                            {
                                string[] Lines = File.ReadAllLines(file);
                                foreach (string line in Lines)
                                {
                                    string[] Values = line.Split('\t');
                                    Database.Execute(
                                        String.Format("INSERT INTO {0} VALUES({1})", table, "\"" + String.Join("\", \"", Values) + "\"")
                                    );
                                }
                            }
                            else
                                Database.Execute(String.Format("LOAD DATA LOCAL INFILE '{0}' INTO TABLE {1};", file.Replace('\\', '/'), table));
                        }
                        catch (Exception Ex)
                        {
                            // Show exception error
                            ExceptionForm Form = new ExceptionForm(Ex, false);
                            Form.Message = String.Format("Failed to import data into table {0}!{2}{2}Error: {1}", table, Ex.Message, Environment.NewLine);
                            DialogResult Result = Form.ShowDialog();

                            // Rollback!
                            TaskForm.UpdateStatus("Rolling back stats data");
                            Transaction.Rollback();

                            // Update message
                            TaskForm.CloseForm();
                            return;
                        }
                    }

                    // Commit the transaction, and alert the user
                    Transaction.Commit();
                    TaskForm.CloseForm();
                    Notify.Show("Stats imported successfully!", "Operation Successful", AlertType.Success);

                    // Displose Connection
                    Database.Dispose();
                }
                else
                {
                    // Alert the user and tell them they failed
                    MessageBox.Show(
                        "Unable to locate any .bak files in this folder. Please select an ASP created database backup folder that contains backup files.",
                        "Backup Error",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error
                    );
                }
            }
        }
        /// <summary>
        /// Adds a server's posted snapshot into the Snapshot Processing Queue, which
        /// will process the snapshot as soon as possible. This method is Non-Blocking.
        /// </summary>
        /// <remarks>
        /// Any errors that occur during the actual import of the data will be
        /// logged inside the StatsDebug log
        /// </remarks>
        /// <param name="Data">The snapshot data provided by the server.</param>
        /// <param name="ServerAddress">The IP address of the server.</param>
        /// <exception cref="UnauthorizedAccessException">
        ///     Thrown if the Server IP is not authorized to post game data to this server
        /// </exception>
        /// <exception cref="InvalidDataException">
        ///     Thrown if the provided Snapshot data is not valid, and cannot be processed
        /// </exception>
        public static void QueueServerSnapshot(string Data, IPAddress ServerAddress)
        {
            // Make sure the server is authorized
            if (!IsAuthorizedGameServer(ServerAddress))
                throw new UnauthorizedAccessException("Un-Authorised Gameserver (Ip: " + ServerAddress + ")");

            // Create the Snapshot Object
            Snapshot Snap = new Snapshot(Data, ServerAddress);

            // Update this server in the Database
            using (StatsDatabase Database = new StatsDatabase())
            {
                // Try and grab the ID of this server
                int id = Database.ExecuteScalar<int>(
                    "SELECT COALESCE(id, -1), COUNT(id) FROM servers WHERE ip=@P0 AND port=@P1",
                    ServerAddress, Snap.ServerPort
                );

                // New server?
                if (id < 0)
                {
                    InsertQueryBuilder builder = new InsertQueryBuilder(Database);
                    builder.SetTable("servers");
                    builder.SetField("ip", ServerAddress);
                    builder.SetField("port", Snap.ServerPort);
                    builder.SetField("prefix", Snap.ServerPrefix);
                    builder.SetField("name", Snap.ServerName);
                    builder.SetField("queryport", Snap.QueryPort);
                    builder.SetField("lastupdate", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
                    builder.Execute();
                }
                else // existing
                {
                    UpdateQueryBuilder builder = new UpdateQueryBuilder(Database);
                    builder.SetTable("servers");
                    builder.SetField("prefix", Snap.ServerPrefix);
                    builder.SetField("name", Snap.ServerName);
                    builder.SetField("queryport", Snap.QueryPort);
                    builder.SetField("lastupdate", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
                    builder.AddWhere("id", Comparison.Equals, id);
                    builder.Execute();
                }
            }

            // Add snapshot to Queue
            SnapshotQueue.Enqueue(Snap);
        }
        /// <summary>
        /// Method to be called everytime the HttpStatsServer is started
        /// </summary>
        public static void Load(StatsDatabase Driver)
        {
            // Get the lowest Offline PID from the database
            var Rows = Driver.Query(
                String.Format(
                    "SELECT COALESCE(MIN(id), {0}) AS min, COALESCE(MAX(id), {0}) AS max FROM player WHERE id < {1}",
                    DEFAULT_PID, MAX_PID
                )
            );

            int Lowest = Int32.Parse(Rows[0]["min"].ToString());
            int Highest = Int32.Parse(Rows[0]["max"].ToString());
            AiPid = (Lowest > DEFAULT_PID) ? DEFAULT_PID : Lowest;
            PlayerPid = (Highest < DEFAULT_PID) ? DEFAULT_PID : Highest;
        }
        /// <summary>
        /// Imports a Player XML Sheet from the specified path
        /// </summary>
        /// <param name="XmlPath">The full path to the XML file</param>
        public static void ImportPlayerXml(string XmlPath)
        {
            // Connect to database first!
            using (StatsDatabase Driver = new StatsDatabase())
            {
                // Load elements
                XDocument Doc = XDocument.Load(new FileStream(XmlPath, FileMode.Open, FileAccess.Read));
                XElement Info = Doc.Root.Element("Info");
                XElement TableData = Doc.Root.Element("TableData");

                // Make sure player doesnt already exist
                int Pid = Int32.Parse(Info.Element("Pid").Value);
                if (Driver.PlayerExists(Pid))
                    throw new Exception(String.Format("Player with PID {0} already exists!", Pid));

                // Begin Transaction
                using (DbTransaction Transaction = Driver.BeginTransaction())
                {
                    try
                    {
                        // Loop through tables
                        foreach (XElement Table in TableData.Elements())
                        {
                            // Loop through Rows
                            foreach (XElement Row in Table.Elements())
                            {
                                InsertQueryBuilder QueryBuilder = new InsertQueryBuilder(Table.Name.LocalName, Driver);
                                foreach (XElement Col in Row.Elements())
                                {
                                    if (Col.Name.LocalName == "name")
                                        QueryBuilder.SetField(Col.Name.LocalName, Col.Value.UnescapeXML());
                                    else
                                        QueryBuilder.SetField(Col.Name.LocalName, Col.Value);
                                }

                                QueryBuilder.Execute();
                            }
                        }

                        // Commit Transaction
                        Transaction.Commit();
                    }
                    catch
                    {
                        Transaction.Rollback();
                        throw;
                    }
                }
            }
        }
        /// <summary>
        /// Exports a players stats and history into an Xml file
        /// </summary>
        /// <param name="XmlPath">The folder path to where the XML will be saved</param>
        /// <param name="Pid">Player ID</param>
        /// <param name="Name">Player Name</param>
        public static void ExportPlayerXml(string XmlPath, int Pid, string Name)
        {
            //  Create full path
            string sPath = Path.Combine(
                XmlPath,
                String.Format("{0}_{1}_{2}.xml", Name.Trim().MakeFileNameSafe(), Pid, DateTime.Now.ToString("yyyyMMdd_HHmm"))
            );

            // Delete file if it exists already
            if (File.Exists(sPath))
                File.Delete(sPath);

            // Create XML Settings
            XmlWriterSettings Settings = new XmlWriterSettings();
            Settings.Indent = true;
            Settings.IndentChars = "\t";
            Settings.NewLineChars = Environment.NewLine;
            Settings.NewLineHandling = NewLineHandling.Replace;

            // Write XML data
            using (StatsDatabase Driver = new StatsDatabase())
            using (XmlWriter Writer = XmlWriter.Create(sPath, Settings))
            {
                // Player Element
                Writer.WriteStartDocument();
                Writer.WriteStartElement("Player");

                // Manifest
                Writer.WriteStartElement("Info");
                Writer.WriteElementString("Pid", Pid.ToString());
                Writer.WriteElementString("Name", Name.EscapeXML());
                Writer.WriteElementString("BackupDate", DateTime.Now.ToString());
                Writer.WriteEndElement();

                // Start Tables Element
                Writer.WriteStartElement("TableData");

                // Add each tables data
                foreach (string Table in StatsDatabase.PlayerTables)
                {
                    // Open table tag
                    Writer.WriteStartElement(Table);

                    // Fetch row
                    List<Dictionary<string, object>> Rows;
                    if (Table == "kills")
                        Rows = Driver.Query(String.Format("SELECT * FROM {0} WHERE attacker={1} OR victim={1}", Table, Pid));
                    else
                        Rows = Driver.Query(String.Format("SELECT * FROM {0} WHERE id={1}", Table, Pid));

                    // Write each row's columns with its value to the xml file
                    foreach (Dictionary<string, object> Row in Rows)
                    {
                        // Open Row tag
                        Writer.WriteStartElement("Row");
                        foreach (KeyValuePair<string, object> Column in Row)
                        {
                            if (Column.Key == "name")
                                Writer.WriteElementString(Column.Key, Column.Value.ToString().EscapeXML());
                            else
                                Writer.WriteElementString(Column.Key, Column.Value.ToString());
                        }

                        // Close Row tag
                        Writer.WriteEndElement();
                    }

                    // Close table tag
                    Writer.WriteEndElement();
                }

                // Close Tags and File
                Writer.WriteEndElement();  // Close Tables Element
                Writer.WriteEndElement();  // Close Player Element
                Writer.WriteEndDocument(); // End and Save file
            }
        }
        public override void HandleRequest(MvcRoute Route)
        {
            // Get our POST variables
            HttpRequest Request = Client.Request;
            Dictionary<string, string> postParams = Request.GetFormUrlEncodedPostVars();
            int[] pids = new int[0];

            // Fetch our cookie, which contains our PiD's
            Cookie C = Request.Request.Cookies["leaderboard"] ?? new Cookie("leaderboard", "");

            // Convert cookie format into a readable one, and make sure we end with a comma!
            Model.CookieValue = C.Value.Trim().Replace('|', ',');
            if (!Model.CookieValue.EndsWith(","))
                Model.CookieValue += ",";

            // Save Leaderboard
            if (postParams.ContainsKey("set") && postParams.ContainsKey("leaderboard")) // Save cookie
            {
                Model.CookieValue = postParams["leaderboard"];
            }
            else if (Route.Action != "index" && Route.Params.Length != 0)
            {
                switch (Route.Action)
                {
                    case "add":
                        if (Validator.IsValidPID(Route.Params[0]))
                            Model.CookieValue += $"{Route.Params[0]},";
                        break;
                    case "remove":
                        if (Validator.IsValidPID(Route.Params[0]))
                            Model.CookieValue = Model.CookieValue.Replace($"{Route.Params[0]},", "");
                        break;
                    case "list":
                        Model.CookieValue = Route.Params[0];
                        break;
                }
            }

            // Read pids from the cookie
            try
            {
                // Pids are stored as xxxx,yyyyy,zzzz in the cookie
                if (Model.CookieValue.Length > 0)
                {
                    string[] players = Model.CookieValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    if (players.Length > 0)
                    {
                        pids = Array.ConvertAll(players, Int32.Parse).Distinct().ToArray();
                    }
                }
            }
            catch
            {
                // Bad Cookie value, so flush it!
                Model.CookieValue = "";
                C.Value = String.Empty;
                Client.Response.SetCookie(C);
            }

            // if "get" is POSTED, that means we are generated a URL instead of a cookie
            if (postParams.ContainsKey("get"))
            {
                Client.Response.Redirect(Model.Root + "/myleaderboard/list/" + String.Join(",", pids));
                return;
            }

            // If we have some player ID's, then the leaderboard is not empty
            if (pids.Length > 0)
            {
                // NOTE: The HttpServer will handle the DbConnectException
                using (StatsDatabase Database = new StatsDatabase())
                {
                    var Rows = Database.Query(
                        String.Format("SELECT id, name, score, time, country, rank, lastonline, kills, deaths FROM player WHERE id IN ({0})", String.Join(",", pids)
                    ));

                    // Loop through each result, and process
                    foreach (Dictionary<string, object> Row in Rows)
                    {
                        // DO Kill Death Ratio
                        double Kills = Int32.Parse(Row["kills"].ToString());
                        double Deaths = Int32.Parse(Row["deaths"].ToString());
                        double Kdr = (Deaths > 0) ? Math.Round(Kills / Deaths, 3) : Kills;

                        // Get Score Per Min
                        double Score = Int32.Parse(Row["score"].ToString());
                        double Mins = Int32.Parse(Row["time"].ToString()) / 60;
                        double SPM = (Mins > 0) ? Math.Round(Score / Mins, 4) : Score;

                        int Pid = Int32.Parse(Row["id"].ToString());

                        // Add Result
                        Model.Players.Add(new PlayerResult
                        {
                            Pid = Pid,
                            Name = Row["name"].ToString(),
                            Score = (int)Score,
                            Rank = Int32.Parse(Row["rank"].ToString()),
                            TimePlayed = FormatTime(Int32.Parse(Row["time"].ToString())),
                            LastOnline = FormatDate(Int32.Parse(Row["lastonline"].ToString())),
                            Country = Row["country"].ToString().ToUpperInvariant(),
                            Kdr = Kdr,
                            Spm = SPM,
                            Status = GetOnlineStatus(Pid)
                        });
                    }
                }
            }

            // Finally, set the cookie if we arent viewing from a List
            if (Route.Action != "list")
            {
                Model.CookieValue = String.Join(",", pids);
                C.Value = String.Join("|", pids);
                C.Expires = DateTime.Now.AddYears(1);
                C.Path = "/";
                Client.Response.SetCookie(C);
            }

            // TO prevent null expception in the template
            if (Model.CookieValue == null)
                Model.CookieValue = String.Empty;

            // Set content type
            base.SendTemplateResponse("myleaderboard", typeof(LeaderboardModel), Model);
        }