public static bool UpdateDamageInformation(Dictionary <string, Dictionary <string, DamageEntry> > damageDealt, bool healing = false)
        {
            bool newDamage = false;
            Dictionary <string, DamageData> totalResults = healing ? totalHealingResults : totalDamageResults;

            lock (totalResults) {
                foreach (KeyValuePair <string, Dictionary <string, DamageEntry> > kvp in damageDealt)
                {
                    string player = kvp.Key;
                    Dictionary <string, DamageEntry> playerDamage = kvp.Value;
                    if (!totalResults.ContainsKey(player))
                    {
                        totalResults.Add(player, new DamageData {
                            damagePerMinute = new Dictionary <string, DamageEntry>(), totalDamage = 0
                        });
                    }
                    DamageData data = totalResults[player];
                    foreach (KeyValuePair <string, DamageEntry> kvp2 in playerDamage)
                    {
                        string      timestamp = kvp2.Key;
                        DamageEntry entry     = kvp2.Value;
                        int         damage    = entry.damage;
                        // if the damage for the given timestamp does not exist yet, add it
                        if (!data.damagePerMinute.ContainsKey(timestamp))
                        {
                            data.damagePerMinute.Add(timestamp, entry);
                            data.totalDamage += damage;
                            newDamage         = true;
                        }
                        // if it does exist, select the biggest of the two
                        // the reason we select the biggest of the two is:
                        // - if the timestamp is 'the current time', totalDamageResults may hold an old value, so we update it
                        // - if timestamp is old, a part of the log for the time could have already been removed (because the log was full)
                        //    so the 'new' damage is only part of the damage for this timestamp
                        else if (data.damagePerMinute[timestamp].damage < damage)
                        {
                            data.totalDamage += damage - data.damagePerMinute[timestamp].damage;
                            data.damagePerMinute[timestamp] = entry;
                            newDamage = true;
                        }
                    }
                }
            }
            return(newDamage);
        }
        private static void SearchChunk(IEnumerable <string> chunk, ReadMemoryResults res, bool readChatMessages = true, bool readLogMessages = true)
        {
            List <int> stamps = TimestampManager.getLatestStamps(3, ignoreStamp);

            foreach (string it in chunk)
            {
                string logMessage = it;
                string t          = logMessage.Substring(0, 5);
                int    hour       = int.Parse(logMessage.Substring(0, 2));
                int    minute     = int.Parse(logMessage.Substring(3, 2));
                if (!stamps.Contains(TimestampManager.getStamp(hour, minute)))
                {
                    continue;                                                            // the log message is not recent, so we skip parsing it
                }
                if (flashClient)
                {
                    // there is some inconsistency with log messages, certain log messages use "12:00: Message.", others use "12:00 Message"
                    // if there is a : after the timestamp we remove it
                    if (logMessage[5] == ':')
                    {
                        logMessage = logMessage.Remove(5, 1);
                    }
                }

                string message = logMessage.Substring(6); // message without timestamp
                if (readLogMessages)
                {
                    if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " You see ")
                    {
                        // the message contains "you see", so it's a look message
                        if (!res.lookMessages.ContainsKey(t))
                        {
                            res.lookMessages.Add(t, new List <string>());
                        }
                        res.lookMessages[t].Add(logMessage);
                        continue;
                    }
                    else if (message.Contains(':'))
                    {
                        if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " Loot of ")   // loot drop message
                        {
                            if (!res.itemDrops.ContainsKey(t))
                            {
                                res.itemDrops.Add(t, new List <string>());
                            }
                            res.itemDrops[t].Add(logMessage);
                            continue;
                        }
                    }
                    else if (logMessage.Length > 17 && logMessage.Substring(5, 12) == " You gained ")
                    {
                        // the message is an experience string, "You gained x experience."
                        try {
                            int experience = int.Parse(logMessage.Substring(17).Split(' ')[0]);
                            if (!res.exp.ContainsKey(t))
                            {
                                res.exp.Add(t, experience);
                            }
                            else
                            {
                                res.exp[t] = res.exp[t] + experience;
                            }
                        } catch {
                        }
                        continue;
                    }
                    else if (logMessage.Length == 19 && logMessage.Substring(5, 14) == " You are dead.")
                    {
                        if (!res.deaths.ContainsKey(t))
                        {
                            res.deaths.Add(t, true);
                        }
                    }
                    else if (logMessage.Length > 18)
                    {
                        string[] split = message.Split(' ');
                        int      index = split.IndexOf("hitpoints");
                        if (index > 0)
                        {
                            int ind;
                            // damage log message (X loses Y hitpoints due to an attack by Z.)
                            int damage = 0;
                            if (!int.TryParse(split[index - 1], out damage))
                            {
                                continue;
                            }
                            string player;
                            if (logMessage.Substring(logMessage.Length - 12) == "your attack.")
                            {
                                // X lost Y hitpoints because of your attack.
                                // attacker is the player himself
                                player = "You";
                            }
                            else if (split.Contains("by"))
                            {
                                // X lost Y hitpoints because of an attack by Z.
                                // Z is the attacker => after the word "by"
                                player = "";
                                ind    = split.IndexOf("by") + 1;
                                for (int i = ind; i < split.Length; i++)
                                {
                                    player = (player == "" ? player : player + " ") + split[i];
                                }
                            }
                            else
                            {
                                continue;
                            }
                            string splitTerm;
                            if (split.Contains("loses"))
                            {
                                splitTerm = "loses";
                            }
                            else if (split.Contains("lose"))
                            {
                                splitTerm = "lose";
                            }
                            else
                            {
                                continue;
                            }
                            ind = split.IndexOf(splitTerm);
                            string target = "";
                            for (int i = 0; i < ind; i++)
                            {
                                target = (target == "" ? target : target + " ") + split[i];
                            }
                            if (!res.damageDealt.ContainsKey(player))
                            {
                                res.damageDealt.Add(player, new Dictionary <string, DamageEntry>());
                            }
                            DamageEntry damageEntry;
                            if (!res.damageDealt[player].ContainsKey(t))
                            {
                                damageEntry        = new DamageEntry();
                                damageEntry.damage = damage;
                                damageEntry.targetDamage.Add(target, damage);
                                res.damageDealt[player].Add(t, damageEntry);
                            }
                            else
                            {
                                damageEntry         = res.damageDealt[player][t];
                                damageEntry.damage += damage;
                                if (damageEntry.targetDamage.ContainsKey(target))
                                {
                                    damageEntry.targetDamage[target] += damage;
                                }
                                else
                                {
                                    damageEntry.targetDamage.Add(target, damage);
                                }
                            }
                            continue;
                        }
                        else
                        {
                            index = split.IndexOf("hitpoints.");
                            if (index > 0)
                            {
                                // heal log message (X healed Y for Z hitpoints.)
                                int healing = 0;
                                if (!int.TryParse(split[index - 1], out healing))
                                {
                                    continue;
                                }

                                int forIndex = split.IndexOf("for");
                                if (forIndex <= 0)
                                {
                                    continue;
                                }


                                string splitTerm;
                                if (split.Contains("heal"))
                                {
                                    splitTerm = "heal";
                                }
                                else if (split.Contains("healed"))
                                {
                                    splitTerm = "healed";
                                }
                                else
                                {
                                    continue;
                                }
                                int healIndex = split.IndexOf(splitTerm);
                                if (healIndex >= forIndex)
                                {
                                    continue;
                                }

                                string source = "";
                                for (int i = 0; i < healIndex; i++)
                                {
                                    if (split[i] == "was" || split[i] == "were")
                                    {
                                        break;
                                    }
                                    if (split[i] == "by")
                                    {
                                        continue;
                                    }
                                    source = (source == "" ? source : source + " ") + split[i];
                                }
                                string target = "";
                                for (int i = healIndex + 1; i < forIndex; i++)
                                {
                                    if (split[i] == "was" || split[i] == "were")
                                    {
                                        break;
                                    }
                                    if (split[i] == "by")
                                    {
                                        continue;
                                    }
                                    target = (target == "" ? target : target + " ") + split[i];
                                }
                                if (target == "yourself" || target == "itself" || target == "himself" || target == "herself")
                                {
                                    target = source;
                                }

                                if (target.Length == 0 || source.Length == 0)
                                {
                                    continue;
                                }

                                if (split.Contains("by"))
                                {
                                    // X healed Y for Z. => X is the source and Y is the target (default)
                                    // X was healed by Y for Z. => X is the target and Y is the source, so swap source and target
                                    string temp = source;
                                    source = target;
                                    target = temp;
                                }

                                if (!res.healingDone.ContainsKey(source))
                                {
                                    res.healingDone.Add(source, new Dictionary <string, DamageEntry>());
                                }
                                DamageEntry healingEntry;
                                if (!res.healingDone[source].ContainsKey(t))
                                {
                                    healingEntry        = new DamageEntry();
                                    healingEntry.damage = healing;
                                    healingEntry.targetDamage.Add(target, healing);
                                    res.healingDone[source].Add(t, healingEntry);
                                }
                                else
                                {
                                    healingEntry         = res.healingDone[source][t];
                                    healingEntry.damage += healing;
                                    if (healingEntry.targetDamage.ContainsKey(target))
                                    {
                                        healingEntry.targetDamage[target] += healing;
                                    }
                                    else
                                    {
                                        healingEntry.targetDamage.Add(target, healing);
                                    }
                                }
                            }
                            else if (logMessage.Substring(5, 14) == " You advanced " && logMessage.Contains("level", StringComparison.OrdinalIgnoreCase))
                            {
                                // advancement log message (You advanced from level x to level x + 1.)
                                if (logMessage[logMessage.Length - 1] == '.')
                                {
                                    if (GlobalDataManager.AddLevelAdvance(logMessage))
                                    {
                                        res.newAdvances.Add(logMessage);
                                    }
                                    continue;
                                }
                            }
                            else if (logMessage.Substring(5, 7) == " Using " && logMessage.Substring(logMessage.Length - 3, 3) == "...")
                            {
                                // using log message (Using one of X items...)
                                var values = Parser.ParseUsingMessage(logMessage);
                                if (!res.usingMessages.ContainsKey(values.Item1))
                                {
                                    res.usingMessages.Add(values.Item1, new Dictionary <string, HashSet <int> >());
                                }
                                if (!res.usingMessages[values.Item1].ContainsKey(t))
                                {
                                    res.usingMessages[values.Item1].Add(t, new HashSet <int>());
                                }
                                res.usingMessages[values.Item1][t].Add(values.Item2);
                                continue;
                            }
                            else
                            {
                                foreach (Event ev in StorageManager.eventIdMap.Values)
                                {
                                    foreach (string evMessage in ev.eventMessages)
                                    {
                                        if (logMessage.Length == evMessage.Length + 6 && logMessage.Contains(evMessage.Trim(), StringComparison.OrdinalIgnoreCase))
                                        {
                                            res.eventMessages.Add(new Tuple <Event, string>(ev, logMessage));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (readChatMessages)
                {
                    if (message.Contains(':'))
                    {
                        if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " Loot of ")   // loot drop message
                        {
                            continue;
                        }
                        else     // if the message contains the ':' symbol but is not a loot drop message, it is a chat message, i.e. a command or url
                                 // we only split at most once, because the chat message can contain the ':' symbol as well and we don't want to discard that
                        {
                            string[] split   = message.Split(new char[] { ':' }, 2);
                            string   command = split[1];
                            // now get the player name, we have to discard the level that is between brackets
                            // players can also have spaces in their name, so we take that into account
                            string[] playersplit = split[0].Split(' ');
                            string   player      = "";
                            foreach (string str in playersplit)
                            {
                                if (str.Contains('['))
                                {
                                    break;
                                }
                                player = (player == "" ? player : player + " ") + str;
                            }
                            if (player == "http" || player == "https")
                            {
                                continue;                                        // I don't remember why we do this, possible http link in a log message? not sure
                            }
                            if (command.Contains('@'))
                            {
                                // @ symbol symbolizes a command, so if there is an @ symbol, we treat the string as a command
                                if (!res.commands.ContainsKey(t))
                                {
                                    res.commands.Add(t, new List <Tuple <string, string> >());
                                }
                                res.commands[t].Add(new Tuple <string, string>(player, command));
                            }
                            else if (command.Contains("www") || command.Contains("http") || command.Contains(".com") || command.Contains(".net") || command.Contains(".tv") || command.Contains(".br"))
                            {
                                // check if the command is an url, we aren't really smart about this, just check for a couple of common url-like things
                                if (!res.urls.ContainsKey(t))
                                {
                                    res.urls.Add(t, new List <Tuple <string, string> >());
                                }
                                res.urls[t].Add(new Tuple <string, string>(player, command));
                            }
                        }
                    }
                }
            }
        }
        private static void SearchChunk(IEnumerable<string> chunk, ReadMemoryResults res, bool readChatMessages = true, bool readLogMessages = true)
        {
            List<int> stamps = TimestampManager.getLatestStamps(3, ignoreStamp);
            foreach (string it in chunk) {
                string logMessage = it;
                string t = logMessage.Substring(0, 5);
                int hour = int.Parse(logMessage.Substring(0, 2));
                int minute = int.Parse(logMessage.Substring(3, 2));
                if (!stamps.Contains(TimestampManager.getStamp(hour, minute))) continue; // the log message is not recent, so we skip parsing it

                if (FlashClient) {
                    // there is some inconsistency with log messages, certain log messages use "12:00: Message.", others use "12:00 Message"
                    // if there is a : after the timestamp we remove it
                    if (logMessage[5] == ':') {
                        logMessage = logMessage.Remove(5, 1);
                    }
                }

                logMessage = logMessage.Replace(" (active prey bonus)", "");

                string message = logMessage.Substring(6); // message without timestamp
                if (readLogMessages) {
                    if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " You see " && logMessage[logMessage.Length - 1] == '.') {
                        // the message contains "you see", so it's a look message
                        if (!res.lookMessages.ContainsKey(t)) res.lookMessages.Add(t, new List<string>());
                        if (!skipDuplicateCommands || !res.lookMessages[t].Contains(logMessage)) {
                            res.lookMessages[t].Add(logMessage);
                        }
                        continue;
                    } else if (message.Contains(':')) {
                        if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " Loot of ") { // loot drop message
                            if (!res.itemDrops.ContainsKey(t)) res.itemDrops.Add(t, new List<string>());
                            res.itemDrops[t].Add(logMessage);
                            continue;
                        }
                    } else if (logMessage.Length > 17 && logMessage.Substring(5, 12) == " You gained " && logMessage.EndsWith("experience points.")) {
                        // the message is an experience string, "You gained x experience."
                        try {
                            int experience = int.Parse(logMessage.Substring(17).Split(' ')[0]);
                            if (!res.exp.ContainsKey(t)) res.exp.Add(t, experience);
                            else res.exp[t] = res.exp[t] + experience;
                        } catch {
                        }
                        continue;
                    } else if (logMessage.Length == 19 && logMessage.Substring(5, 14) == " You are dead.") {
                        if (!res.deaths.ContainsKey(t))
                            res.deaths.Add(t, true);
                    } else if (logMessage.Length > 18) {
                        string[] split = message.Split(' ');
                        int index = split.IndexOf("hitpoints");
                        if (index > 0) {
                            int ind;
                            // damage log message (X loses Y hitpoints due to an attack by Z.)
                            int damage = 0;
                            if (!int.TryParse(split[index - 1], out damage)) {
                                continue;
                            }
                            string player;
                            if ((logMessage.Substring(logMessage.Length - 12) == "your attack.") || (logMessage.Substring(logMessage.Length - 21) == "your critical attack.")) {
                                // X lost Y hitpoints because of your attack.
                                // attacker is the player himself
                                player = "You";
                            } else if (split.Contains("by")) {
                                // X lost Y hitpoints because of an attack by Z.
                                // Z is the attacker => after the word "by"
                                player = "";
                                ind = split.IndexOf("by") + 1;
                                for (int i = ind; i < split.Length; i++) {
                                    player = (player == "" ? player : player + " ") + split[i];
                                }
                            } else {
                                continue;
                            }
                            string splitTerm;
                            if (split.Contains("loses")) {
                                splitTerm = "loses";
                            } else if (split.Contains("lose")) {
                                splitTerm = "lose";
                            } else {
                                continue;
                            }
                            ind = split.IndexOf(splitTerm);
                            string target = "";
                            for (int i = 0; i < ind; i++) {
                                target = (target == "" ? target : target + " ") + split[i];
                            }
                            if (!res.damageDealt.ContainsKey(player)) {
                                res.damageDealt.Add(player, new Dictionary<string, DamageEntry>());
                            }
                            DamageEntry damageEntry;
                            if (!res.damageDealt[player].ContainsKey(t)) {
                                damageEntry = new DamageEntry();
                                damageEntry.damage = damage;
                                damageEntry.targetDamage.Add(target, damage);
                                res.damageDealt[player].Add(t, damageEntry);
                            } else {
                                damageEntry = res.damageDealt[player][t];
                                damageEntry.damage += damage;
                                if (damageEntry.targetDamage.ContainsKey(target)) {
                                    damageEntry.targetDamage[target] += damage;
                                } else {
                                    damageEntry.targetDamage.Add(target, damage);
                                }
                            }
                            continue;
                        } else {
                            index = split.IndexOf("hitpoints.");
                            if (index > 0) {
                                // heal log message (X healed Y for Z hitpoints.)
                                int healing = 0;
                                if (!int.TryParse(split[index - 1], out healing)) {
                                    continue;
                                }

                                int forIndex = split.IndexOf("for");
                                if (forIndex <= 0) {
                                    continue;
                                }

                                string splitTerm;
                                if (split.Contains("heal")) {
                                    splitTerm = "heal";
                                } else if (split.Contains("healed")) {
                                    splitTerm = "healed";
                                } else {
                                    continue;
                                }
                                int healIndex = split.IndexOf(splitTerm);
                                if (healIndex >= forIndex) {
                                    continue;
                                }

                                string source = "";
                                for (int i = 0; i < healIndex; i++) {
                                    if (split[i] == "was" || split[i] == "were") break;
                                    if (split[i] == "by") continue;
                                    source = (source == "" ? source : source + " ") + split[i];
                                }
                                string target = "";
                                for (int i = healIndex + 1; i < forIndex; i++) {
                                    if (split[i] == "was" || split[i] == "were") break;
                                    if (split[i] == "by") continue;
                                    target = (target == "" ? target : target + " ") + split[i];
                                }
                                if (target == "yourself" || target == "itself" || target == "himself" || target == "herself") {
                                    target = source;
                                }

                                if (target.Length == 0 || source.Length == 0) {
                                    continue;
                                }

                                if (split.Contains("by")) {
                                    // X healed Y for Z. => X is the source and Y is the target (default)
                                    // X was healed by Y for Z. => X is the target and Y is the source, so swap source and target
                                    string temp = source;
                                    source = target;
                                    target = temp;
                                }

                                if (!res.healingDone.ContainsKey(source)) {
                                    res.healingDone.Add(source, new Dictionary<string, DamageEntry>());
                                }
                                DamageEntry healingEntry;
                                if (!res.healingDone[source].ContainsKey(t)) {
                                    healingEntry = new DamageEntry();
                                    healingEntry.damage = healing;
                                    healingEntry.targetDamage.Add(target, healing);
                                    res.healingDone[source].Add(t, healingEntry);
                                } else {
                                    healingEntry = res.healingDone[source][t];
                                    healingEntry.damage += healing;
                                    if (healingEntry.targetDamage.ContainsKey(target)) {
                                        healingEntry.targetDamage[target] += healing;
                                    } else {
                                        healingEntry.targetDamage.Add(target, healing);
                                    }
                                }
                            } else if (logMessage.Substring(5, 14) == " You advanced " && logMessage.Contains("level", StringComparison.OrdinalIgnoreCase)) {
                                // advancement log message (You advanced from level x to level x + 1.)
                                if (logMessage[logMessage.Length - 1] == '.') {
                                    if (GlobalDataManager.AddLevelAdvance(logMessage)) {
                                        res.newAdvances.Add(logMessage);
                                    }
                                    continue;
                                }
                            } else if (logMessage.Substring(5, 7) == " Using " && logMessage.Substring(logMessage.Length - 3, 3) == "...") {
                                // using log message (Using one of X items...)
                                var values = Parser.ParseUsingMessage(logMessage);
                                if (!res.usingMessages.ContainsKey(values.Item1)) res.usingMessages.Add(values.Item1, new Dictionary<string, HashSet<int>>());
                                if (!res.usingMessages[values.Item1].ContainsKey(t)) res.usingMessages[values.Item1].Add(t, new HashSet<int>());
                                res.usingMessages[values.Item1][t].Add(values.Item2);
                                continue;
                            } else if (logMessage.Length > 50 && logMessage.Substring(5, 45) == " Congratulations! You earned the achievement ") {
                                Achievement achievement = StorageManager.getAchievement(Parser.GetAchievement(logMessage));
                                if (achievement != null) {
                                    res.achievements.Add(new Tuple<Achievement, string>(achievement, logMessage));
                                } else {
                                    Console.WriteLine("Unrecognized achievement {0}.", Parser.GetAchievement(logMessage));
                                }
                            } else {
                                foreach (Event ev in StorageManager.eventIdMap.Values) {
                                    foreach (string evMessage in ev.eventMessages) {
                                        if (logMessage.Length == evMessage.Length + 6 && logMessage.Contains(evMessage.Trim(), StringComparison.OrdinalIgnoreCase)) {
                                            res.eventMessages.Add(new Tuple<Event, string>(ev, logMessage));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (readChatMessages) {
                    if (message.Contains(':')) {
                        if (logMessage.Length > 14 && logMessage.Substring(5, 9) == " Loot of ") { // loot drop message
                            continue;
                        } else { // if the message contains the ':' symbol but is not a loot drop message, it is a chat message, i.e. a command or url
                                 // we only split at most once, because the chat message can contain the ':' symbol as well and we don't want to discard that
                            string[] split = message.Split(new char[] { ':' }, 2);
                            string command = split[1];
                            // now get the player name, we have to discard the level that is between brackets
                            // players can also have spaces in their name, so we take that into account
                            string[] playersplit = split[0].Split(' ');
                            string player = "";
                            foreach (string str in playersplit) {
                                if (str.Contains('[')) break;
                                player = (player == "" ? player : player + " ") + str;
                            }
                            if (player == "http" || player == "https") continue; // I don't remember why we do this, possible http link in a log message? not sure
                            if (command.Contains('@')) {
                                // @ symbol symbolizes a command, so if there is an @ symbol, we treat the string as a command
                                if (!res.commands.ContainsKey(t)) res.commands.Add(t, new List<Tuple<string, string>>());
                                var tpl = new Tuple<string, string>(player, command);
                                if (!skipDuplicateCommands || !res.commands[t].Contains(tpl)) {
                                    res.commands[t].Add(tpl);
                                }
                            } else if (command.Contains("www") || command.Contains("http") || command.Contains(".com") || command.Contains(".net") || command.Contains(".tv") || command.Contains(".br")) {
                                // check if the command is an url, we aren't really smart about this, just check for a couple of common url-like things
                                if (!res.urls.ContainsKey(t)) res.urls.Add(t, new List<Tuple<string, string>>());
                                res.urls[t].Add(new Tuple<string, string>(player, command));
                            }
                        }
                    }
                }
            }
        }