private void ListenerCallback(IAsyncResult result)
        {
            // Short circuits the callback if we're disposing. Saves us a lot of hassle with error handling.
            if (Disposed) return;

            try
            {
                var myListener = (HttpListener) result.AsyncState;
                HttpListenerContext context = myListener.EndGetContext(result);
                HttpListenerRequest request = context.Request;
                string content;
                using (var sr = new StreamReader(request.InputStream))
                    content = sr.ReadToEnd();
                string originalUrl = request.RawUrl;
                string url = originalUrl;
                if (url.Contains("?"))
                {
                    content = url.Substring(url.IndexOf("?") + 1);
                    url = url.Substring(0, url.IndexOf("?"));
                }
                if (request.RemoteEndPoint == null) return;
                var remoteIP = request.RemoteEndPoint.Address.ToString();
                var isAuthenticated = Config.Settings.WebServerPassword.Length > 0 &&
                                       _authenticated.ContainsKey(remoteIP);

                var parameters = new Dictionary<string, string>();
                if (content.Contains("="))
                {
                    string[] values = content.Split(new[] {"&"}, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string value in values)
                    {
                        if (value.Contains("="))
                        {
                            string key = value.Substring(0, value.IndexOf("="));
                            string innerValue = "";
                            if (key.Length + 1 != value.Length)
                                innerValue = value.Substring(value.IndexOf("=") + 1);
                            if (!parameters.ContainsKey(key)) parameters.Add(key, innerValue);
                        }
                        else if (!parameters.ContainsKey(value)) parameters.Add(value, "");
                    }
                }
                HttpListenerResponse response = context.Response;

                switch (url.ToLower())
                {
                    case "/Style.css":
                        {
                            string html = Resources.Style;
                            byte[] buffer = Encoding.UTF8.GetBytes(html);
                            response.ContentLength64 = buffer.Length;
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;
                    case "/login.htm":
                        {
                            string html;
                            if (isAuthenticated)
                            {
                                html = Resources.Authenticated;
                                html = html.Replace("<!-- url -->", parameters.ContainsKey("target") ? 
                                    HttpUtility.UrlDecode(parameters["target"]) : "/");
                            }
                            else if (parameters.ContainsKey("target"))
                            {
                                html = GetAuthenticationPage(parameters["target"]);
                            }
                            else if (parameters.ContainsKey("Password") &&
                                     parameters["Password"] == Config.Settings.WebServerPassword)
                            {
                                _authenticated.Add(remoteIP, DateTime.Now.AddDays(1));
                                html = Resources.Authenticated;
                                html = html.Replace("<!-- url -->", parameters.ContainsKey("Target") ? 
                                    HttpUtility.UrlDecode(parameters["Target"]) : "/");
                            }
                            else
                            {
                                html = Resources.RetryAuthenticate;
                                html = html.Replace("<!-- url -->", parameters.ContainsKey("Target") ? 
                                    parameters["Target"] : "/");
                            }
                            byte[] buffer = Encoding.UTF8.GetBytes(html);
                            response.ContentLength64 = buffer.Length;
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;

                    case "/favicon.ico":
                        {
                            response.Headers.Add("Content-Type", "image/x-icon");
                            Icon favIcon = Resources.LuciusIcon;
                            var ms = new MemoryStream();
                            favIcon.Save(ms);
                            response.ContentLength64 = ms.Length;
                            ms.Seek(0, SeekOrigin.Begin);
                            var buffer = new byte[ms.Length];
                            ms.Read(buffer, 0, buffer.Length);
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;

                    case "/apple-touch-icon.png":
                    case "/apple-touch-icon-precomposed.png":
                        {
                            response.Headers.Add("Content-Type", "image/png");
                            Image appleIcon = Resources.AppleTouchIcon;
                            var ms = new MemoryStream();
                            appleIcon.Save(ms, ImageFormat.Png);
                            response.ContentLength64 = ms.Length;
                            ms.Seek(0, SeekOrigin.Begin);
                            var buffer = new byte[ms.Length];
                            ms.Read(buffer, 0, buffer.Length);
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;

                    case "/tournament.htm":
                        {
                            string name = "";
                            if (parameters.ContainsKey("name")) name = HttpUtility.UrlDecode(parameters["name"]);
                            if (string.IsNullOrEmpty(name))
                            {
                                url = "/";
                                break;
                            }

                            name = name.Replace("%20", " ");
                            var tournament = Config.Settings.GetTournament(name);
                            if (tournament == null)
                            {
                                url = "/";
                                break;
                            }

                            var html = Resources.ChooseRound;
                            html = html.Replace("<!-- name -->", tournament.Name + " -- Round " +
                                                                 tournament.Rounds.Count + " of " +
                                                                 tournament.TotalRounds);
                            var message = isAuthenticated ? 
                                    "\t\tChoose the Round to manage from the list below:<br />\r\n" +
                                    "\t\t(Unscored rounds are listed first, followed by rounds with scores.)<br />" :
                                    "\t\tBelow is a list of the Matches this round and their end scores, if recorded.<br />";
                            html = html.Replace("<!-- message -->", message);

                            var roundsScored = "";
                            var roundsOpen = "";

                            var id = 0;
                            foreach (var match in tournament.Rounds[tournament.Rounds.Count - 1].Matches)
                            {
                                var player1 = Config.Settings.GetPlayer(match.Players[0]);
                                if (match.Players.Count == 2)
                                {
                                    var player2 = Config.Settings.GetPlayer(match.Players[1]);
                                    if (isAuthenticated)
                                    {
                                        var tag = "\t\t<a href=\"Round.htm?name=" +
                                                     HttpUtility.UrlEncode(tournament.Name) + "&id=" +
                                                     id + "\"><p class=\"roundbordered\"><br />Match #" +
                                                     (id + 1) +
                                                     "<br />" + player1.Name + " VS " + player2.Name + "<br />";
                                        if (match.Results.Count > 0)
                                            roundsScored += tag + "SCORED -> " +
                                                            match.Results[player1.ID].VictoryPoints +
                                                            " VPs VS " +
                                                            match.Results[player2.ID].VictoryPoints +
                                                            " VPs <- SCORED<br /><br /></p></a>\r\n";
                                        else
                                            roundsOpen += tag + "0 VPs VS 0 VPs<br /><br /></p></a>\r\n";
                                    }
                                    else
                                    {
                                        roundsScored += "\t\t<p class=\"round\"><br />Match #" + (id + 1) +
                                                        "<br />" + player1.Name + " VS " + player2.Name + "<br />";
                                        if (match.Results.Count > 0)
                                            roundsScored += "SCORED -> " +
                                                            match.Results[player1.ID].VictoryPoints +
                                                            " VPs VS " +
                                                            match.Results[player2.ID].VictoryPoints +
                                                            " VPs <- SCORED<br /></p>\r\n";
                                        else
                                            roundsScored += "0 VPs VS 0 VPs<br /></p>\r\n";
                                    }
                                }
                                else
                                    roundsScored += "\t\t<p class=\"round\"><br />" + player1.Name +
                                                    " -- Bye Round<br /><br /></p>\r\n";
                                id++;
                            }

                            if (isAuthenticated)
                            {
                                string scores = "<p class=\"round\">Rounds with Scores<br /></p><br />" + roundsScored;
                                if (roundsOpen.Length > 0)
                                    scores = "<p class=\"round\">Open Rounds<br /></p><br />" + roundsOpen +
                                             "<br /><br />\r\n" + scores;
                                html = html.Replace("<!-- entries -->", scores);
                            }
                            else
                            {
                                roundsScored += "\r\n\t\t<a href=\"/Login.htm?target=" + originalUrl +
                                                "\"><p class=\"name\"><br />Log In to Edit Rounds<br /><br /></p></a>\r\n";
                                html = html.Replace("<!-- entries -->", roundsScored);
                            }

                            byte[] buffer = Encoding.UTF8.GetBytes(html);
                            response.ContentLength64 = buffer.Length;
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;

                    case "/round.htm":
                        {
                            string html;
                            if (!isAuthenticated)
                                html = GetAuthenticationPage(originalUrl);
                            else
                            {
                                var name = "";
                                if (parameters.ContainsKey("name")) name = HttpUtility.UrlDecode(parameters["name"]);
                                var id = -1;
                                if (parameters.ContainsKey("id")) int.TryParse(parameters["id"], out id);

                                if (string.IsNullOrEmpty(name) || id <= -1)
                                {
                                    url = "/";
                                    break;
                                }
                                var tournament = Config.Settings.GetTournament(name);
                                if (tournament == null)
                                {
                                    url = "/";
                                    break;
                                }
                                if (id >= tournament.Rounds[tournament.Rounds.Count - 1].Matches.Count)
                                {
                                    url = "/";
                                    break;
                                }

                                var match = tournament.Rounds[tournament.Rounds.Count - 1].Matches[id];
                                var player1 = Config.Settings.GetPlayer(match.Players[0]);
                                var player2 = Config.Settings.GetPlayer(match.Players[1]);

                                html = Resources.EditRound;
                                html = html.Replace("<!-- error -->", "");
                                html = html.Replace("<!-- player1 -->", player1.Name);
                                html = html.Replace("<!-- player1VP -->", match.Results.ContainsKey(player1.ID) ? 
                                    match.Results[player1.ID].VictoryPoints.ToString() : "0");
                                html = html.Replace("<!-- player2 -->", player2.Name);
                                html = html.Replace("<!-- player2VP -->", match.Results.ContainsKey(player2.ID) ? 
                                    match.Results[player2.ID].VictoryPoints.ToString() : "0");
                                html = html.Replace("<!-- name -->", HttpUtility.UrlEncode(tournament.Name));
                                html = html.Replace("<!-- player1ID -->", player1.ID);
                                html = html.Replace("<!-- player2ID -->", player2.ID);
                                html = html.Replace("<!-- ID -->", id.ToString());
                            }
                            var buffer = Encoding.UTF8.GetBytes(html);
                            response.ContentLength64 = buffer.Length;
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        break;

                    case "/submit.htm":
                        {
                            string mode = "";
                            if (parameters.ContainsKey("Mode")) mode = parameters["Mode"];
                            if (mode == "UpdateScores")
                            {
                                string html;
                                if (!isAuthenticated)
                                    html = GetAuthenticationPage(originalUrl);
                                else
                                {
                                    var name =  HttpUtility.UrlDecode(parameters["Name"]);
                                    name = name.Replace("+", " ");
                                    var player1ID = parameters["Player1ID"];
                                    var player2ID = parameters["Player2ID"];
                                    var player1Vp = parameters["Player1VP"];
                                    var player2Vp = parameters["Player2VP"];

                                    var e = new MatchUpdateFromWebEventArgs
                                        {
                                            TournamentName = name,
                                            Player1ID = player1ID,
                                            Player2ID = player2ID
                                        };

                                    if (player1Vp.ToUpper() != "F")
                                    {
                                        int vp;
                                        if (!int.TryParse(player1Vp, out vp))
                                        {
                                            url = "/";
                                            break;
                                        }
                                        e.Player1Vp = vp;
                                    }
                                    else
                                        e.Player1Forfeit = true;

                                    if (player2Vp.ToUpper() != "F")
                                    {
                                        int vp;
                                        if (!int.TryParse(player2Vp, out vp))
                                        {
                                            url = "/";
                                            break;
                                        }
                                        e.Player2Vp = vp;
                                    }
                                    else
                                        e.Player2Forfeit = true;

                                    if (MatchUpdateFromWeb != null)
                                        MatchUpdateFromWeb(this, e);

                                    html = Resources.Submitted;
                                    html = html.Replace("<!-- url -->", "Tournament.htm?name=" + parameters["Name"]);
                                    const string message = "Scores have been successfully submitted. Redirecting back to " +
                                                           "the tournament page in a few seconds...";
                                    html = html.Replace("<!-- message -->", message);
                                }
                                var buffer = Encoding.UTF8.GetBytes(html);
                                response.ContentLength64 = buffer.Length;
                                response.OutputStream.Write(buffer, 0, buffer.Length);
                            }
                            else
                                url = "/";
                        }
                        break;

                    default:
                        url = "/";
                        break;
                }

                // We may fall through to here if we got a bad URL.
                if (url.ToLower() == "/")
                {
                    var html = Resources.ChooseTournament;
                    var tournamentHTML = "";
                    foreach (var tournament in Config.Settings.Tournaments)
                        if (!tournament.Completed && tournament.Rounds.Count > 0)
                        {
                            var currentRound = tournament.Rounds.Count;
                            if (tournament.Rounds[currentRound - 1].Completed)
                            {
                                tournamentHTML += "\t\t<a href=\"Tournament.htm?name=" +
                                                  HttpUtility.UrlEncode(tournament.Name) + "\">" + "<p><br />" +
                                                  tournament.Name + " - Round " + currentRound +
                                                  " Completed<br /><br /></p>\r\n";
                            }
                            else
                            {
                                tournamentHTML += "\t\t<a href=\"Tournament.htm?name=" +
                                                  HttpUtility.UrlEncode(tournament.Name) + "\">" + "<p><br />" +
                                                  tournament.Name + " - Round " + currentRound +
                                                  " Active<br /><br /></p>\r\n";
                            }
                        }

                    if (tournamentHTML.Length == 0)
                        tournamentHTML = "\t\tThere are no active tournaments to record scores for!<br />";
                    html = html.Replace("<!-- entries -->", tournamentHTML);

                    var buffer = Encoding.UTF8.GetBytes(html);
                    response.ContentLength64 = buffer.Length;
                    response.OutputStream.Write(buffer, 0, buffer.Length);
                }

                response.OutputStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                // Code 995 occurs when the HttpListener is stopped. We can ignore this error.
                if (ex is HttpListenerException && ((HttpListenerException) ex).ErrorCode == 995)
                    return;

                if (WebStatusChanged == null) return;
                var e = new WebStatusChangedEventArgs
                    {
                        Active = true,
                        Error = true,
                        Message = ex.Message
                    };
                WebStatusChanged(this, e);
            }
        }
        private void Server_MatchUpdateFromWeb(object sender, MatchUpdateFromWebEventArgs e)
        {
            Tournament tournament = Config.Settings.GetTournament(e.TournamentName);
            foreach (TournamentMatch match in tournament.Rounds[tournament.Rounds.Count - 1].Matches)
            {
                if (match.Players.Contains(e.Player1ID) && match.Players.Contains(e.Player2ID))
                {
                    PlayerRecord record1 = Config.Settings.GetPlayer(e.Player1ID);
                    PlayerRecord record2 = Config.Settings.GetPlayer(e.Player2ID);
                    var result1 = new MatchResult(e.Player1ID);
                    var result2 = new MatchResult(e.Player2ID);
                    if (e.Player1Forfeit)
                    {
                        result1.Forfeited = true;
                        result2.VictoryPoints = 8;
                    }
                    else if (e.Player2Forfeit)
                    {
                        result1.VictoryPoints = 8;
                        result2.Forfeited = true;
                    }
                    else
                    {
                        result1.VictoryPoints = e.Player1Vp;
                        result2.VictoryPoints = e.Player2Vp;
                    }
                    match.Results.Clear();
                    match.Results.Add(e.Player1ID, result1);
                    match.Results.Add(e.Player2ID, result2);
                    match.CalculateResults();

                    Config.Settings.SaveEvents();

                    if (LogWindow != null)
                        LogWindow.WriteLog("[Web Server] Recorded a match score in " + e.TournamentName + ": " +
                                           record1.Name + " = " +
                                           (e.Player1Forfeit ? "Forfeited" : e.Player1Vp.ToString()) +
                                           " VP" + (e.Player1Vp == 1 ? "" : "s") + ", " +
                                           record2.Name + " = " +
                                           (e.Player2Forfeit ? "Forfeited" : e.Player2Vp.ToString()) +
                                           " VP" + (e.Player2Vp == 1 ? "" : "s") + ".");

                    foreach (Form form in MdiChildren)
                        if (form is frmTournamentRound)
                        {
                            var roundForm = (frmTournamentRound) form;
                            if (roundForm.TournamentName == e.TournamentName)
                            {
                                foreach (ctlTournamentMatch matchCtl in roundForm.pnlMatches.Controls)
                                {
                                    if (matchCtl.Player1ID == e.Player1ID && matchCtl.Player2ID == e.Player2ID)
                                    {
                                        roundForm.Invoke(new MethodInvoker(delegate
                                            {
                                                roundForm.UpdateMatch(matchCtl,
                                                                      e.Player1Forfeit ? "F" : e.Player1Vp.ToString(),
                                                                      e.Player2Forfeit ? "F" : e.Player2Vp.ToString());
                                            }));
                                        break;
                                    }
                                }
                                break;
                            }
                        }
                }
            }
        }