public StatTracker() {
            m_eventLog = new List<EventLog>();
            m_playerCache = new Dictionary<string, Player>();
            m_itemCache = new Dictionary<string, string>();
            m_currentEvent = new EventLog();
            m_player = new Player();
            m_sessionStats = new SessionStats();
            m_userID = "";
            m_sessionStarted = false;
            m_lastEventFound = false;
            m_initialized = false;
            m_initializing = false;
            m_countEvents = false;
            m_preparingSession = false;
            m_activeSeconds = 0;

            m_currentEvent.Initialize();
        }
        void UpdateEventLog(bool getName = false)
        {
            // Update the username displayed.
            if (getName)
            {
                GeckoElement title = (GeckoElement)m_browser.Document.GetElementById("title");
                System.IO.TextReader nameReader = new System.IO.StringReader(title.InnerHtml);
                // Parse HTML by loading the text stream.
                HtmlAgilityPack.HtmlDocument html = new HtmlAgilityPack.HtmlDocument();
                html.Load(nameReader);
                HtmlNode node = html.DocumentNode.SelectSingleNode(".//a");
                this.playerNameLabel.Text = node.InnerText;
                this.playerNameLabel.Visible = true;
                m_username = node.InnerText;

                // Determine faction. Faction is stored as class name under logo.
                GeckoElement faction = (GeckoElement)m_browser.Document.GetElementById("factionLogo");
                if (faction.ClassName == "faction_nc")
                    m_userFaction = "NC";
                else if (faction.ClassName == "faction_tr")
                    m_userFaction = "TR";
                else if (faction.ClassName == "faction_vs")
                    m_userFaction = "VS";
                else
                    m_userFaction = "Unknown";

                SaveUserName();
            }

            // Get killboard table.
            GeckoElement collection = (GeckoElement)m_browser.Document.GetElementById("timelineTable");

            if (collection == null) return;

            // Load formatted html with javascript results into reader stream.
            System.IO.TextReader reader = new System.IO.StringReader(collection.InnerHtml);

            // Parse HTML by loading the text stream.
            HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
            htmlDoc.Load(reader);

            // Rows
            HtmlNodeCollection rows = htmlDoc.DocumentNode.SelectNodes(".//tr");
            for (int i = 0; i < rows.Count; i++)
            {
                // Initialize new event.
                EventLog newEvent = new EventLog();

                newEvent.Initialize();

                // Columns
                HtmlNodeCollection cols = rows[i].SelectNodes(".//td");

                // Timestamp, event, location, method.
                newEvent.timeStamp = cols[0].InnerText;
                newEvent.userName = cols[1].InnerText;
                newEvent.location = cols[2].InnerText;
                newEvent.method = cols[3].InnerText;

                // Determine faction from cols[1] inner html.
                if (cols[1].InnerHtml.Contains("faction nc"))
                    newEvent.faction = "NC";
                else if (cols[1].InnerHtml.Contains("faction tr"))
                    newEvent.faction = "TR";
                else if (cols[1].InnerHtml.Contains("faction vs"))
                    newEvent.faction = "VS";
                else
                    newEvent.faction = "Unknown";

                // Extract additional information.

                // Check for death.
                int indexStart = newEvent.userName.IndexOf("KILLED BY");
                if (indexStart != -1)
                {
                    // Remove KILLED BY and all white space.
                    newEvent.userName = newEvent.userName.Remove(indexStart);
                    newEvent.death = true;
                }
                newEvent.userName = RemoveWhiteSurroundingSpace(newEvent.userName);

                // Check for headshot.
                indexStart = newEvent.method.IndexOf("Headshot");
                if (indexStart != -1)
                {
                    // Remove KILLED BY and all white space.
                    newEvent.method = newEvent.method.Remove(indexStart);
                    newEvent.headshot = true;
                }
                newEvent.method = RemoveWhiteSurroundingSpace(newEvent.method);

                // Check for suicide.
                if (newEvent.userName == m_username || newEvent.method == "Suicide")
                {
                    newEvent.death = true;
                    newEvent.suicide = true;
                }

                // Check if the new event being added is the latest event. A full check needs to be done
                // if the current event doesn't match. Rarely the site may first report the items in the wrong order.
                if (newEvent == m_currentEvent || m_eventLog.Contains(newEvent))
                    break;

                // Determine the order in which to add the event.
                if (m_sessionStarted)
                {
                    if (i < m_eventLog.Count)
                        m_eventLog.Insert(i, newEvent);
                    else
                        m_eventLog.Add(newEvent);
                }
                else
                    m_eventLog.Add(newEvent);

               // Add session weapon stats unless this event was a death or team kill.
               AddSessionWeapon(newEvent);
            }

            if (m_eventLog.Count > 0)
            {
                m_currentEvent = m_eventLog[0];
                // Update killboard.
                this.eventLogGridView.Rows.Clear();
                this.eventLogGridView.Rows.Add(m_eventLog.Count);
                int i = 0;
                foreach (EventLog eventlog in m_eventLog)
                {
                    string eventName = eventlog.userName;

                    this.eventLogGridView.Rows[i].Cells[0].Value = eventName;
                    this.eventLogGridView.Rows[i].Cells[1].Value = eventlog.method;
                    this.eventLogGridView.Rows[i].Cells[1].Style.ForeColor = Color.Beige;
                    //this.eventLogGridView.Rows[i].Cells[2].Value = eventlog.headshot;
                    if (eventlog.headshot)
                        ((DataGridViewImageCell)eventLogGridView.Rows[i].Cells[2]).Value = Properties.Resources.hsImage;

                    // Set row color depending on kill or death.
                    for (int j = 0; j < this.eventLogGridView.Rows[i].Cells.Count; j++)
                    {
                        if (eventlog.death || eventlog.suicide) // Death.
                            this.eventLogGridView.Rows[i].Cells[j].Style.BackColor = Color.Red;
                        else if(eventlog.faction == m_userFaction) // Friendly kill.
                            this.eventLogGridView.Rows[i].Cells[j].Style.BackColor = Color.Orange;
                        else // Enemy kill.
                            this.eventLogGridView.Rows[i].Cells[j].Style.BackColor = Color.Green;
                    }

                    i++;
                }
                this.eventLogGridView.ClearSelection();
                UpdateEventTextFields();
                UpdateWeaponTextFields(m_sessionWeapons, this.sessionWeaponsGridView);
            }

            m_lastEventFound = true;

            m_updatingEvents = false;
        }
        void AddSessionWeapon(EventLog newEvent)
        {
            Weapon newWeapon = new Weapon();
            Weapon oldWeapon = new Weapon();
            oldWeapon.Initialize();
            newWeapon.Initialize();
            newWeapon.name = newEvent.method;
            newWeapon.kills += newEvent.IsKill() ? 1 : 0;
            newWeapon.headShots += newEvent.headshot ? 1 : 0;

            // Add to total deaths.
            if (m_sessionStarted)
            {
                if (!newEvent.IsKill())
                {
                    UpdateOverallStats(0, 0, 1);
                }
                // Update overall stats. Should only be called once overall stats have been set initially.
                // *Additionally it will not update a weapon not found in the All Weapons section.
                else
                {
                    // * Testing with always updating stats regardless of if weapon was used previously.
                    //if (m_weaponsUpdated && IsWeaponInAllWeapons(newWeapon.name))
                    //{
                    // Do not give kills or headshots for team kills.
                    if(newEvent.faction != m_userFaction)
                        UpdateOverallStats(newWeapon.kills, newWeapon.headShots, 0);
                    //}
                }
            }
            // Add session weapon stats unless this event was a death or team kill.
            if(!newEvent.death && newEvent.faction != m_userFaction)
                AddSessionWeapon(newWeapon, oldWeapon);
        }
        public GUIMain()
        {
            InitializeComponent();
            // Load version.
            this.versionLabel.Text = PROGRAM_TITLE + " V " + VERSION_NUM;
            m_highColor = Color.FromArgb(0, 192, 0);
            m_lowColor = Color.Red;
            m_eventLog = new List<EventLog>();
            m_weapons = new Dictionary<string, Weapon>();
            m_sessionWeapons = new Dictionary<string, Weapon>();
            m_sesStartWeapons = new Dictionary<string, Weapon>();
            m_browser = new GeckoWebBrowser();
            m_currentEvent = new EventLog();
            m_sessionStarted = false;
            m_lastEventFound = false;
            m_initialized = false;
            m_pageLoaded = false;
            m_weaponsUpdated = false;
            m_dragging = false;
            m_resizing = false;
            m_totalHS = 0;
            m_totalKills = 0.0f;
            m_totalDeaths = 0.0f;
            m_totalDeathsWithRevives = 0.0f;
            m_activeSeconds = 0;
            m_timeout = 0;
            m_sessionStartHSR = 0.0f;
            m_sessionStartKDR = 0.0f;
            m_username = "";
            m_userID = "";
            m_userFaction = "";

            // Prevent X images showing up.
            ((DataGridViewImageColumn)this.eventLogGridView.Columns[2]).DefaultCellStyle.NullValue = null;

            // Handle mouse movement and resizing on borderless window.
            this.resizePanelLR.MouseDown += OnMouseDownResize;
            this.resizePanelLR.MouseMove += OnMouseMove;
            this.resizePanelLR.MouseUp += OnMouseUp;

            MouseMove += OnMouseMove;
            MouseDown += OnMouseDown;
            MouseUp += OnMouseUp;
            foreach (Control ctrl in this.Controls)
            {
                if (ctrl.GetType() == typeof(Label))
                {
                    ctrl.MouseMove += OnMouseMove;
                    ctrl.MouseDown += OnMouseDown;
                    ctrl.MouseUp += OnMouseUp;
                }
            }

            this.menuStrip1.MouseMove += OnMouseMove;
            this.menuStrip1.MouseDown += OnMouseDown;
            this.menuStrip1.MouseUp += OnMouseUp;

            this.panel1.MouseMove += OnMouseMove;
            this.panel1.MouseDown += OnMouseDown;
            this.panel1.MouseUp += OnMouseUp;

            foreach (Control ctrl in this.panel1.Controls)
            {
                if (ctrl.GetType() == typeof(Label))
                {
                    ctrl.MouseMove += OnMouseMove;
                    ctrl.MouseDown += OnMouseDown;
                    ctrl.MouseUp += OnMouseUp;
                }
            }
            this.panel2.MouseMove += OnMouseMove;
            this.panel2.MouseDown += OnMouseDown;
            this.panel2.MouseUp += OnMouseUp;

            foreach (Control ctrl in this.panel2.Controls)
            {
                if (ctrl.GetType() == typeof(Label))
                {
                    ctrl.MouseMove += OnMouseMove;
                    ctrl.MouseDown += OnMouseDown;
                    ctrl.MouseUp += OnMouseUp;
                }
            }

            m_currentEvent.Initialize();
            this.Controls.Add(m_browser);
            // Handler for when page load completes.
            m_browser.DocumentCompleted += new EventHandler(browser_DocumentCompleted);
        }
        async Task AddSessionWeapon(EventLog newEvent) {
            Weapon newWeapon = new Weapon();
            Weapon oldWeapon = new Weapon();
            oldWeapon.Initialize();
            newWeapon.Initialize();
            if (newEvent.isVehicle) {
                newWeapon.id = "0";
                newWeapon.vehicleId = newEvent.methodID;
            } else
                newWeapon.id = newEvent.methodID;

            newWeapon.name = await GetItemName(GetBestWeaponID(newWeapon));
            newWeapon.kills += newEvent.IsKill() ? 1 : 0;
            newWeapon.headShots += newEvent.headshot ? 1 : 0;

            // Add to total deaths.
            if (m_sessionStarted || m_countEvents) {
                if (!newEvent.IsKill()) {
                    UpdateOverallStats(0, 0, 1);
                }
                    // Update overall stats. Should only be called once overall stats have been set initially.
                else {
                    // Do not give kills or headshots for team kills.
                    // TODO: If you kill someone that is so brand new that even their ID does not come through,
                    // the defender will be null. So if the defender is null then a kill is currently counted. However,
                    // the defender might be a team mate. The only fix I know of is to keep track of this and check it
                    // occasionally and reverse the kill once the name is resolved and same faction is discovered.
                    if (newEvent.defender == null || (newEvent.defender != null && newEvent.defender.faction != m_player.faction))
                        UpdateOverallStats(newWeapon.kills, newWeapon.headShots, 0);
                }
            }
            // Add session weapon stats unless this event was a death or team kill.
            if (!newEvent.death && (newEvent.defender == null || (newEvent.defender != null && newEvent.defender.faction != m_player.faction)))
                await AddSessionWeapon(newWeapon, oldWeapon);
        }
        public async Task<bool> GetEventStats(int numEvents = 50) {
            string result = await GetAsyncRequest("characters_event/?character_id=" + m_userID + "&c:limit=" + numEvents + "&type=KILL,DEATH");

            Newtonsoft.Json.Linq.JObject jObject = Newtonsoft.Json.Linq.JObject.Parse(result);

            if (jObject == null || !jObject.HasValues)
                return false;

            Newtonsoft.Json.Linq.JToken jToken = jObject["characters_event_list"];

            if (jToken == null || !jToken.HasValues)
                return false;

            // Create a list of each json object.
            List<eventJson> jsonEvents = new List<eventJson>();
            jsonEvents = Newtonsoft.Json.JsonConvert.DeserializeObject<List<eventJson>>(jToken.ToString());

            int i = 0;
            // Convert the json object into an event with IDs resolved.
            foreach (eventJson jsonEvent in jsonEvents) {
                // Initialize new event.
                EventLog newEvent = new EventLog();
                newEvent.Initialize();

                Player attacker = await CreatePlayer(jsonEvent.attacker_character_id);
                Player defender = await CreatePlayer(jsonEvent.character_id);

                // Weapon IDs take priority over vehicle IDs. Weapon IDs such as breaker rocket pods
                // also show up with vehicle IDs like Reavers. A Reaver should only count if no
                // other weapon was used.
                if (jsonEvent.attacker_vehicle_id != "0" && jsonEvent.attacker_weapon_id == "0") {
                    newEvent.methodID = jsonEvent.attacker_vehicle_id;
                    newEvent.method = await GetVehicleName(jsonEvent.attacker_vehicle_id);
                    newEvent.isVehicle = true;
                } else {
                    newEvent.methodID = jsonEvent.attacker_weapon_id;
                    newEvent.method = await GetItemName(jsonEvent.attacker_weapon_id);
                }
                newEvent.headshot = Int32.Parse(jsonEvent.is_headshot) == 1 ? true : false;
                newEvent.timeStamp = jsonEvent.timestamp;
                newEvent.attacker = attacker;
                newEvent.defender = defender;
                newEvent.death = defender == m_player;

                // Check for suicide.
                if ((attacker == m_player && defender == m_player)) {
                    newEvent.death = true;
                    newEvent.suicide = true;
                    newEvent.method = "Suicide";
                    newEvent.opponent = m_player;
                }

                // Determine the best option for new information.
                if (attacker != null && attacker != m_player)
                    newEvent.opponent = attacker;

                if (defender != null && defender != m_player)
                    newEvent.opponent = defender;

                // Check if the new event being added is the latest event. A full check needs to be done
                // if the current event doesn't match. Rarely the site may first report the items in the wrong order.
                if (newEvent == m_currentEvent)
                    break;

                // Don't add the same event. The API can sometimes report it twice.
                if (m_eventLog.Contains(newEvent))
                    continue;

                // If an iteration has gone through then new information has been gathered.
                m_hasEventUpdated = true;

                // Determine the order in which to add the event.
                if (m_sessionStarted) {
                    if (i < m_eventLog.Count)
                        m_eventLog.Insert(i, newEvent);
                    else
                        m_eventLog.Add(newEvent);
                } else
                    m_eventLog.Add(newEvent);

                if (!m_preparingSession) {
                    // Add session weapon stats unless this event was a death or team kill.
                    if(i < numEvents - 1)
                        await AddSessionWeapon(newEvent);
                }
                i++;
            }

            // Display the killboard.
            // Only update the fields if a change in events occurred.
            if (i > 0) {
                m_currentEvent = m_eventLog[0];
                m_lastEventFound = true;
            }
            // Update loaded players' online status.
            await CheckOnlineStatus();

            return true;
        }
 private static bool MyEquals(EventLog e1, EventLog e2) {
     return (e1.headshot == e2.headshot && e1.location == e2.location && e1.method == e2.method &&
         e1.suicide == e2.suicide && e1.timeStamp == e2.timeStamp &&
         ((e1.attacker == null && e2.attacker == null) || (e1.attacker != null && e2.attacker != null && e1.attacker.fullName == e2.attacker.fullName)) &&
         ((e1.defender == null && e2.defender == null) || (e1.defender != null && e2.defender != null && e1.defender.fullName == e2.defender.fullName)));
 }