corrade-vassal – Rev 4

Subversion Repositories:
Rev:
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using System.Threading;
using System.Web;
using System.Windows.Forms;
using OpenMetaverse;
using Parallel = System.Threading.Tasks.Parallel;
using Timer = System.Timers.Timer;

namespace Vassal
{
    public partial class Vassal : Form
    {
        public static System.Timers.Timer overviewTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        public static System.Timers.Timer topScriptsTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        public static System.Timers.Timer topCollidersTabTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        public static VassalConfiguration vassalConfiguration = new VassalConfiguration();
        public static Vassal vassalForm;
        public static readonly object ClientInstanceTeleportLock = new object();

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>RFC1738 URL Escapes a string</summary>
        /// <param name="data">a string to escape</param>
        /// <returns>an RFC1738 escaped string</returns>
        private static string wasURLEscapeDataString(string data)
        {
            return HttpUtility.UrlEncode(data);
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>RFC1738 URL Unescape a string</summary>
        /// <param name="data">a string to unescape</param>
        /// <returns>an RFC1738 unescaped string</returns>
        private static string wasURLUnescapeDataString(string data)
        {
            return HttpUtility.UrlDecode(data);
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>URI unescapes an RFC3986 URI escaped string</summary>
        /// <param name="data">a string to unescape</param>
        /// <returns>the resulting string</returns>
        private static string wasURIUnescapeDataString(string data)
        {
            // Uri.UnescapeDataString can only handle 32766 characters at a time
            return string.Join("", Enumerable.Range(0, (data.Length + 32765)/32766)
                .Select(o => Uri.UnescapeDataString(data.Substring(o*32766, Math.Min(32766, data.Length - (o*32766)))))
                .ToArray());
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>RFC3986 URI Escapes a string</summary>
        /// <param name="data">a string to escape</param>
        /// <returns>an RFC3986 escaped string</returns>
        private static string wasURIEscapeDataString(string data)
        {
            // Uri.EscapeDataString can only handle 32766 characters at a time
            return string.Join("", Enumerable.Range(0, (data.Length + 32765)/32766)
                .Select(o => Uri.EscapeDataString(data.Substring(o*32766, Math.Min(32766, data.Length - (o*32766)))))
                .ToArray());
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Gets an array element at a given modulo index.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="index">a positive or negative index of the element</param>
        /// <param name="data">the array</param>
        /// <return>an array element</return>
        public static T wasGetElementAt<T>(T[] data, int index)
        {
            switch (index < 0)
            {
                case true:
                    return data[((index%data.Length) + data.Length)%data.Length];
                default:
                    return data[index%data.Length];
            }
        }

        #region KEY-VALUE DATA

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Returns the value of a key from a key-value data string.
        /// </summary>
        /// <param name="key">the key of the value</param>
        /// <param name="data">the key-value data segment</param>
        /// <returns>true if the key was found in data</returns>
        private static string wasKeyValueGet(string key, string data)
        {
            return data.Split('&')
                .AsParallel()
                .Select(o => o.Split('=').ToList())
                .Where(o => o.Count.Equals(2))
                .Select(o => new
                {
                    k = o.First(),
                    v = o.Last()
                })
                .Where(o => o.k.Equals(key))
                .Select(o => o.v)
                .FirstOrDefault();
        }

        #endregion

        #region CRYPTOGRAPHY

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Gets a sub-array from an array.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="data">the array</param>
        /// <param name="start">the start index</param>
        /// <param name="stop">the stop index (-1 denotes the end)</param>
        /// <returns>the array slice between start and stop</returns>
        public static T[] wasGetSubArray<T>(T[] data, int start, int stop)
        {
            if (stop.Equals(-1))
                stop = data.Length - 1;
            T[] result = new T[stop - start + 1];
            Array.Copy(data, start, result, 0, stop - start + 1);
            return result;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Delete a sub-array and return the result.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="data">the array</param>
        /// <param name="start">the start index</param>
        /// <param name="stop">the stop index (-1 denotes the end)</param>
        /// <returns>the array without elements between start and stop</returns>
        public static T[] wasDeleteSubArray<T>(T[] data, int start, int stop)
        {
            if (stop.Equals(-1))
                stop = data.Length - 1;
            T[] result = new T[data.Length - (stop - start) - 1];
            Array.Copy(data, 0, result, 0, start);
            Array.Copy(data, stop + 1, result, start, data.Length - stop - 1);
            return result;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Concatenate multiple arrays.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="arrays">multiple arrays</param>
        /// <returns>a flat array with all arrays concatenated</returns>
        public static T[] wasConcatenateArrays<T>(params T[][] arrays)
        {
            int resultLength = 0;
            foreach (T[] o in arrays)
            {
                resultLength += o.Length;
            }
            T[] result = new T[resultLength];
            int offset = 0;
            for (int x = 0; x < arrays.Length; x++)
            {
                arrays[x].CopyTo(result, offset);
                offset += arrays[x].Length;
            }
            return result;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Permutes an array in reverse a given number of times.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="input">the array</param>
        /// <param name="times">the number of times to permute</param>
        /// <returns>the array with the elements permuted</returns>
        private static T[] wasReversePermuteArrayElements<T>(T[] input, int times)
        {
            if (times.Equals(0)) return input;
            T[] slice = new T[input.Length];
            Array.Copy(input, 1, slice, 0, input.Length - 1);
            Array.Copy(input, 0, slice, input.Length - 1, 1);
            return wasReversePermuteArrayElements(slice, --times);
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Permutes an array forward a given number of times.
        /// </summary>
        /// <typeparam name="T">the array type</typeparam>
        /// <param name="input">the array</param>
        /// <param name="times">the number of times to permute</param>
        /// <returns>the array with the elements permuted</returns>
        private static T[] wasForwardPermuteArrayElements<T>(T[] input, int times)
        {
            if (times.Equals(0)) return input;
            T[] slice = new T[input.Length];
            Array.Copy(input, input.Length - 1, slice, 0, 1);
            Array.Copy(input, 0, slice, 1, input.Length - 1);
            return wasForwardPermuteArrayElements(slice, --times);
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Encrypt or decrypt a message given a set of rotors, plugs and a reflector.
        /// </summary>
        /// <param name="message">the message to encyrpt or decrypt</param>
        /// <param name="rotors">any combination of: 1, 2, 3, 4, 5, 6, 7, 8, b, g</param>
        /// <param name="plugs">the letter representing the start character for the rotor</param>
        /// <param name="reflector">any one of: B, b, C, c</param>
        /// <returns>either a decrypted or encrypted string</returns>
        private static string wasEnigma(string message, char[] rotors, char[] plugs, char reflector)
        {
            Dictionary<char, char[]> def_rotors = new Dictionary<char, char[]>
            {
                {
                    '1', new[]
                    {
                        'e', 'k', 'm', 'f', 'l',
                        'g', 'd', 'q', 'v', 'z',
                        'n', 't', 'o', 'w', 'y',
                        'h', 'x', 'u', 's', 'p',
                        'a', 'i', 'b', 'r', 'c',
                        'j'
                    }
                },
                {
                    '2', new[]
                    {
                        'a', 'j', 'd', 'k', 's',
                        'i', 'r', 'u', 'x', 'b',
                        'l', 'h', 'w', 't', 'm',
                        'c', 'q', 'g', 'z', 'n',
                        'p', 'y', 'f', 'v', 'o',
                        'e'
                    }
                },
                {
                    '3', new[]
                    {
                        'b', 'd', 'f', 'h', 'j',
                        'l', 'c', 'p', 'r', 't',
                        'x', 'v', 'z', 'n', 'y',
                        'e', 'i', 'w', 'g', 'a',
                        'k', 'm', 'u', 's', 'q',
                        'o'
                    }
                },
                {
                    '4', new[]
                    {
                        'e', 's', 'o', 'v', 'p',
                        'z', 'j', 'a', 'y', 'q',
                        'u', 'i', 'r', 'h', 'x',
                        'l', 'n', 'f', 't', 'g',
                        'k', 'd', 'c', 'm', 'w',
                        'b'
                    }
                },
                {
                    '5', new[]
                    {
                        'v', 'z', 'b', 'r', 'g',
                        'i', 't', 'y', 'u', 'p',
                        's', 'd', 'n', 'h', 'l',
                        'x', 'a', 'w', 'm', 'j',
                        'q', 'o', 'f', 'e', 'c',
                        'k'
                    }
                },
                {
                    '6', new[]
                    {
                        'j', 'p', 'g', 'v', 'o',
                        'u', 'm', 'f', 'y', 'q',
                        'b', 'e', 'n', 'h', 'z',
                        'r', 'd', 'k', 'a', 's',
                        'x', 'l', 'i', 'c', 't',
                        'w'
                    }
                },
                {
                    '7', new[]
                    {
                        'n', 'z', 'j', 'h', 'g',
                        'r', 'c', 'x', 'm', 'y',
                        's', 'w', 'b', 'o', 'u',
                        'f', 'a', 'i', 'v', 'l',
                        'p', 'e', 'k', 'q', 'd',
                        't'
                    }
                },
                {
                    '8', new[]
                    {
                        'f', 'k', 'q', 'h', 't',
                        'l', 'x', 'o', 'c', 'b',
                        'j', 's', 'p', 'd', 'z',
                        'r', 'a', 'm', 'e', 'w',
                        'n', 'i', 'u', 'y', 'g',
                        'v'
                    }
                },
                {
                    'b', new[]
                    {
                        'l', 'e', 'y', 'j', 'v',
                        'c', 'n', 'i', 'x', 'w',
                        'p', 'b', 'q', 'm', 'd',
                        'r', 't', 'a', 'k', 'z',
                        'g', 'f', 'u', 'h', 'o',
                        's'
                    }
                },
                {
                    'g', new[]
                    {
                        'f', 's', 'o', 'k', 'a',
                        'n', 'u', 'e', 'r', 'h',
                        'm', 'b', 't', 'i', 'y',
                        'c', 'w', 'l', 'q', 'p',
                        'z', 'x', 'v', 'g', 'j',
                        'd'
                    }
                }
            };

            Dictionary<char, char[]> def_reflectors = new Dictionary<char, char[]>
            {
                {
                    'B', new[]
                    {
                        'a', 'y', 'b', 'r', 'c', 'u', 'd', 'h',
                        'e', 'q', 'f', 's', 'g', 'l', 'i', 'p',
                        'j', 'x', 'k', 'n', 'm', 'o', 't', 'z',
                        'v', 'w'
                    }
                },
                {
                    'b', new[]
                    {
                        'a', 'e', 'b', 'n', 'c', 'k', 'd', 'q',
                        'f', 'u', 'g', 'y', 'h', 'w', 'i', 'j',
                        'l', 'o', 'm', 'p', 'r', 'x', 's', 'z',
                        't', 'v'
                    }
                },
                {
                    'C', new[]
                    {
                        'a', 'f', 'b', 'v', 'c', 'p', 'd', 'j',
                        'e', 'i', 'g', 'o', 'h', 'y', 'k', 'r',
                        'l', 'z', 'm', 'x', 'n', 'w', 't', 'q',
                        's', 'u'
                    }
                },
                {
                    'c', new[]
                    {
                        'a', 'r', 'b', 'd', 'c', 'o', 'e', 'j',
                        'f', 'n', 'g', 't', 'h', 'k', 'i', 'v',
                        'l', 'm', 'p', 'w', 'q', 'z', 's', 'x',
                        'u', 'y'
                    }
                }
            };

            // Setup rotors from plugs.
            foreach (char rotor in rotors)
            {
                char plug = plugs[Array.IndexOf(rotors, rotor)];
                int i = Array.IndexOf(def_rotors[rotor], plug);
                if (i.Equals(0)) continue;
                def_rotors[rotor] = wasConcatenateArrays(new[] {plug},
                    wasGetSubArray(wasDeleteSubArray(def_rotors[rotor], i, i), i, -1),
                    wasGetSubArray(wasDeleteSubArray(def_rotors[rotor], i + 1, -1), 0, i - 1));
            }

            StringBuilder result = new StringBuilder();
            foreach (char c in message)
            {
                if (!char.IsLetter(c))
                {
                    result.Append(c);
                    continue;
                }

                // Normalize to lower.
                char l = char.ToLower(c);

                Action<char[]> rotate = o =>
                {
                    int i = o.Length - 1;
                    do
                    {
                        def_rotors[o[0]] = wasForwardPermuteArrayElements(def_rotors[o[0]], 1);
                        if (i.Equals(0))
                        {
                            rotors = wasReversePermuteArrayElements(o, 1);
                            continue;
                        }
                        l = wasGetElementAt(def_rotors[o[1]], Array.IndexOf(def_rotors[o[0]], l) - 1);
                        o = wasReversePermuteArrayElements(o, 1);
                    } while (--i > -1);
                };

                // Forward pass through the Enigma's rotors.
                rotate.Invoke(rotors);

                // Reflect
                int x = Array.IndexOf(def_reflectors[reflector], l);
                l = (x + 1)%2 == 0 ? def_reflectors[reflector][x - 1] : def_reflectors[reflector][x + 1];

                // Reverse the order of the rotors.
                Array.Reverse(rotors);

                // Reverse pass through the Enigma's rotors.
                rotate.Invoke(rotors);

                if (char.IsUpper(c))
                {
                    l = char.ToUpper(l);
                }
                result.Append(l);
            }

            return result.ToString();
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Expand the VIGENRE key to the length of the input.
        /// </summary>
        /// <param name="input">the input to expand to</param>
        /// <param name="enc_key">the key to expand</param>
        /// <returns>the expanded key</returns>
        private static string wasVigenereExpandKey(string input, string enc_key)
        {
            string exp_key = string.Empty;
            int i = 0, j = 0;
            do
            {
                char p = input[i];
                if (!char.IsLetter(p))
                {
                    exp_key += p;
                    ++i;
                    continue;
                }
                int m = j%enc_key.Length;
                exp_key += enc_key[m];
                ++j;
                ++i;
            } while (i < input.Length);
            return exp_key;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Encrypt using VIGENERE.
        /// </summary>
        /// <param name="input">the input to encrypt</param>
        /// <param name="enc_key">the key to encrypt with</param>
        /// <returns>the encrypted input</returns>
        private static string wasEncryptVIGENERE(string input, string enc_key)
        {
            char[] a =
            {
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
                'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
            };

            enc_key = wasVigenereExpandKey(input, enc_key);
            string result = string.Empty;
            int i = 0;
            do
            {
                char p = input[i];
                if (!char.IsLetter(p))
                {
                    result += p;
                    ++i;
                    continue;
                }
                char q =
                    wasReversePermuteArrayElements(a, Array.IndexOf(a, enc_key[i]))[
                        Array.IndexOf(a, char.ToLowerInvariant(p))];
                if (char.IsUpper(p))
                {
                    q = char.ToUpperInvariant(q);
                }
                result += q;
                ++i;
            } while (i < input.Length);
            return result;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Decrypt using VIGENERE.
        /// </summary>
        /// <param name="input">the input to decrypt</param>
        /// <param name="enc_key">the key to decrypt with</param>
        /// <returns>the decrypted input</returns>
        private static string wasDecryptVIGENERE(string input, string enc_key)
        {
            char[] a =
            {
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
                'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
            };

            enc_key = wasVigenereExpandKey(input, enc_key);
            string result = string.Empty;
            int i = 0;
            do
            {
                char p = input[i];
                if (!char.IsLetter(p))
                {
                    result += p;
                    ++i;
                    continue;
                }
                char q =
                    a[
                        Array.IndexOf(wasReversePermuteArrayElements(a, Array.IndexOf(a, enc_key[i])),
                            char.ToLowerInvariant(p))];
                if (char.IsUpper(p))
                {
                    q = char.ToUpperInvariant(q);
                }
                result += q;
                ++i;
            } while (i < input.Length);
            return result;
        }

        ///////////////////////////////////////////////////////////////////////////
        //  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     An implementation of the ATBASH cypher for latin alphabets.
        /// </summary>
        /// <param name="data">the data to encrypt or decrypt</param>
        /// <returns>the encrypted or decrypted data</returns>
        private static string wasATBASH(string data)
        {
            char[] a =
            {
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
                'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
            };

            char[] input = data.ToArray();

            Parallel.ForEach(Enumerable.Range(0, data.Length), i =>
            {
                char e = input[i];
                if (!char.IsLetter(e)) return;
                int x = 25 - Array.BinarySearch(a, char.ToLowerInvariant(e));
                if (!char.IsUpper(e))
                {
                    input[i] = a[x];
                    return;
                }
                input[i] = char.ToUpperInvariant(a[x]);
            });

            return new string(input);
        }

        #endregion

        /// <summary>
        ///     Corrade's input filter function.
        /// </summary>
        private static readonly Func<string, string> wasInput = o =>
        {
            if (string.IsNullOrEmpty(o)) return string.Empty;

            foreach (Filter filter in vassalConfiguration.InputFilters)
            {
                switch (filter)
                {
                    case Filter.RFC1738:
                        o = wasURLUnescapeDataString(o);
                        break;
                    case Filter.RFC3986:
                        o = wasURIUnescapeDataString(o);
                        break;
                    case Filter.ENIGMA:
                        o = wasEnigma(o, vassalConfiguration.ENIGMA.rotors.ToArray(),
                            vassalConfiguration.ENIGMA.plugs.ToArray(),
                            vassalConfiguration.ENIGMA.reflector);
                        break;
                    case Filter.VIGENERE:
                        o = wasDecryptVIGENERE(o, vassalConfiguration.VIGENERESecret);
                        break;
                    case Filter.ATBASH:
                        o = wasATBASH(o);
                        break;
                    case Filter.BASE64:
                        o = Encoding.UTF8.GetString(Convert.FromBase64String(o));
                        break;
                }
            }
            return o;
        };

        /// <summary>
        ///     Corrade's output filter function.
        /// </summary>
        private static readonly Func<string, string> wasOutput = o =>
        {
            if (string.IsNullOrEmpty(o)) return string.Empty;

            foreach (Filter filter in vassalConfiguration.OutputFilters)
            {
                switch (filter)
                {
                    case Filter.RFC1738:
                        o = wasURLEscapeDataString(o);
                        break;
                    case Filter.RFC3986:
                        o = wasURIEscapeDataString(o);
                        break;
                    case Filter.ENIGMA:
                        o = wasEnigma(o, vassalConfiguration.ENIGMA.rotors.ToArray(),
                            vassalConfiguration.ENIGMA.plugs.ToArray(),
                            vassalConfiguration.ENIGMA.reflector);
                        break;
                    case Filter.VIGENERE:
                        o = wasEncryptVIGENERE(o, vassalConfiguration.VIGENERESecret);
                        break;
                    case Filter.ATBASH:
                        o = wasATBASH(o);
                        break;
                    case Filter.BASE64:
                        o = Convert.ToBase64String(Encoding.UTF8.GetBytes(o));
                        break;
                }
            }
            return o;
        };

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>Escapes a dictionary's keys and values for sending as POST data.</summary>
        /// <param name="data">A dictionary containing keys and values to be escaped</param>
        private static Dictionary<string, string> wasKeyValueEscape(Dictionary<string, string> data)
        {
            return data.AsParallel().ToDictionary(o => wasOutput(o.Key), p => wasOutput(p.Value));
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Converts a list of string to a comma-separated values string.
        /// </summary>
        /// <param name="l">a list of strings</param>
        /// <returns>a commma-separated list of values</returns>
        /// <remarks>compliant with RFC 4180</remarks>
        public static string wasEnumerableToCSV(IEnumerable<string> l)
        {
            string[] csv = l.Select(o => o.Clone() as string).ToArray();
            Parallel.ForEach(csv.Select((v, i) => new {i, v}), o =>
            {
                string cell = o.v.Replace("\"", "\"\"");
                switch (new[] {'"', ' ', ',', '\r', '\n'}.Any(p => cell.Contains(p)))
                {
                    case true:
                        csv[o.i] = "\"" + cell + "\"";
                        break;
                    default:
                        csv[o.i] = cell;
                        break;
                }
            });
            return String.Join(",", csv);
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Converts a comma-separated list of values to a list of strings.
        /// </summary>
        /// <param name="csv">a comma-separated list of values</param>
        /// <returns>a list of strings</returns>
        /// <remarks>compliant with RFC 4180</remarks>
        public static IEnumerable<string> wasCSVToEnumerable(string csv)
        {
            Stack<char> s = new Stack<char>();
            StringBuilder m = new StringBuilder();
            for (int i = 0; i < csv.Length; ++i)
            {
                switch (csv[i])
                {
                    case ',':
                        if (!s.Any() || !s.Peek().Equals('"'))
                        {
                            yield return m.ToString();
                            m = new StringBuilder();
                            continue;
                        }
                        m.Append(csv[i]);
                        continue;
                    case '"':
                        if (i + 1 < csv.Length && csv[i].Equals(csv[i + 1]))
                        {
                            m.Append(csv[i]);
                            ++i;
                            continue;
                        }
                        if (!s.Any() || !s.Peek().Equals(csv[i]))
                        {
                            s.Push(csv[i]);
                            continue;
                        }
                        s.Pop();
                        continue;
                }
                m.Append(csv[i]);
            }

            yield return m.ToString();
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Serialises a dictionary to key-value data.
        /// </summary>
        /// <param name="data">a dictionary</param>
        /// <returns>a key-value data encoded string</returns>
        private static string wasKeyValueEncode(Dictionary<string, string> data)
        {
            return String.Join("&", data.AsParallel().Select(o => String.Join("=", o.Key, o.Value)));
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Sends a post request to an URL with set key-value pairs.
        /// </summary>
        /// <param name="URL">the url to send the message to</param>
        /// <param name="message">key-value pairs to send</param>
        /// <param name="millisecondsTimeout">the time in milliseconds for the request to timeout</param>
        private static string wasPOST(string URL, Dictionary<string, string> message, uint millisecondsTimeout)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest) WebRequest.Create(URL);
                request.UserAgent = VASSAL_CONSTANTS.USER_AGENT;
                request.Proxy = WebRequest.DefaultWebProxy;
                request.Timeout = (int) millisecondsTimeout;
                request.AllowAutoRedirect = true;
                request.AllowWriteStreamBuffering = true;
                request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                request.Method = WebRequestMethods.Http.Post;
                // set the content type based on chosen output filers
                switch (vassalConfiguration.OutputFilters.Last())
                {
                    case Filter.RFC1738:
                        request.ContentType = VASSAL_CONSTANTS.CONTENT_TYPE.WWW_FORM_URLENCODED;
                        break;
                    default:
                        request.ContentType = VASSAL_CONSTANTS.CONTENT_TYPE.TEXT_PLAIN;
                        break;
                }
                // send request
                using (Stream requestStream = request.GetRequestStream())
                {
                    using (StreamWriter dataStream = new StreamWriter(requestStream))
                    {
                        dataStream.Write(wasKeyValueEncode(message));
                    }
                }
                // read response
                using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                {
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        if (responseStream != null)
                        {
                            using (
                                StreamReader streamReader = new StreamReader(responseStream))
                            {
                                return streamReader.ReadToEnd();
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {

            }

            return null;
        }

        /// <summary>
        ///     Constants used by Corrade.
        /// </summary>
        public struct VASSAL_CONSTANTS
        {
            /// <summary>
            ///     Copyright.
            /// </summary>
            public const string COPYRIGHT = @"(c) Copyright 2013 Wizardry and Steamworks";

            public const string WIZARDRY_AND_STEAMWORKS = @"Wizardry and Steamworks";
            public const string VASSAL = @"Vassal";
            public const string WIZARDRY_AND_STEAMWORKS_WEBSITE = @"http://grimore.org";

            /// <summary>
            ///     Vassal version.
            /// </summary>
            public static readonly string VASSAL_VERSION = Assembly.GetEntryAssembly().GetName().Version.ToString();

            /// <summary>
            ///     Corrade user agent.
            /// </summary>
            public static readonly string USER_AGENT =
                $"{VASSAL}/{VASSAL_VERSION} ({WIZARDRY_AND_STEAMWORKS_WEBSITE})";

            /// <summary>
            ///     Vassal compile date.
            /// </summary>
            public static readonly string VASSAL_COMPILE_DATE = new DateTime(2000, 1, 1).Add(new TimeSpan(
                TimeSpan.TicksPerDay*Assembly.GetEntryAssembly().GetName().Version.Build + // days since 1 January 2000
                TimeSpan.TicksPerSecond*2*Assembly.GetEntryAssembly().GetName().Version.Revision)).ToLongDateString();

            /// <summary>
            ///     Vassal configuration file.
            /// </summary>
            public static readonly string VASSAL_CONFIGURATION_FILE = @"Vassal.ini";

            /// <summary>
            ///     Vassal regions file.
            /// </summary>
            public static readonly string VASSAL_REGIONS = @"Regions.csv";

            /// <summary>
            ///     Conten-types that Corrade can send and receive.
            /// </summary>
            public struct CONTENT_TYPE
            {
                public const string TEXT_PLAIN = @"text/plain";
                public const string WWW_FORM_URLENCODED = @"application/x-www-form-urlencoded";
            }
        }

        private static readonly System.Action updateCurrentRegionName = () =>
        {
            try
            {
                string result = wasPOST(vassalConfiguration.HTTPServerURL,
                    wasKeyValueEscape(new Dictionary<string, string>
                    {
                        {"command", "getregiondata"},
                        {"group", vassalConfiguration.Group},
                        {"password", vassalConfiguration.Password},
                        {"data", "Name"}
                    }), 60000);
                bool success;
                if (string.IsNullOrEmpty(result) ||
                    !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                {
                    vassalForm.BeginInvoke((MethodInvoker) (() =>
                    {
                        vassalForm.StatusText.Text = @"Failed to query Corrade for current region.";
                    }));
                    return;
                }
                switch (success)
                {
                    case true:
                        vassalForm.BeginInvoke((MethodInvoker) (() =>
                        {
                            vassalForm.CurrentRegionAt.Visible = true;
                            vassalForm.CurrentRegionName.Visible = true;
                            vassalForm.CurrentRegionName.Text =
                                wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).Last();
                        }));
                        break;
                    default:
                        vassalForm.BeginInvoke((MethodInvoker) (() =>
                        {
                            vassalForm.CurrentRegionAt.Visible = false;
                            vassalForm.CurrentRegionName.Visible = false;
                            vassalForm.StatusText.Text = @"Error getting current region: " +
                                                         wasInput(wasKeyValueGet("error", result));
                        }));
                        break;
                }
            }
            catch (Exception ex)
            {
                vassalForm.BeginInvoke((MethodInvoker) (() =>
                {
                    vassalForm.StatusText.Text =
                        @"Error getting current region: " +
                        ex.Message;
                }));
            }
        };

        public Vassal()
        {
            InitializeComponent();
            vassalForm = this;
        }

        private void RegionSelected(object sender, EventArgs e)
        {
            string selectedRegionName = string.Empty;
            Vector3 selectedRegionPosition = Vector3.Zero;
            bool startTeleport = false;
            vassalForm.Invoke((MethodInvoker) (() =>
            {
                ListViewItem listViewItem = LoadedRegions.SelectedItem as ListViewItem;
                switch (listViewItem != null && LoadedRegions.SelectedIndex != -1)
                {
                    case true:
                        selectedRegionName = listViewItem.Text;
                        selectedRegionPosition = (Vector3)listViewItem.Tag;
                        startTeleport = true;
                        break;
                    default:
                        startTeleport = false;
                        break;
                }
            }));

            if (!startTeleport) return;
            

            // Announce teleport.
            vassalForm.Invoke((MethodInvoker)(() =>
            {
                vassalForm.RegionTeleportGroup.Enabled = false;
                vassalForm.StatusProgress.Value = 0;
                vassalForm.StatusText.Text = @"Teleporting to " + selectedRegionName;
            }));

            new Thread(() =>
            {
                Monitor.Enter(ClientInstanceTeleportLock);
                try
                {
                    int elapsedSeconds = 0;
                    System.Timers.Timer teleportTimer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
                    teleportTimer.Elapsed += (o, p) =>
                    {
                        vassalForm.Invoke((MethodInvoker) (() =>
                        {
                            vassalForm.StatusProgress.Value =
                                Math.Min(
                                    (int)
                                        (100d*
                                         (TimeSpan.FromSeconds(++elapsedSeconds).TotalMilliseconds/
                                          vassalConfiguration.TeleportTimeout)), 100);
                        }));
                    };
                    teleportTimer.Start();
                    string result = null;
                    ManualResetEvent receivedPOST = new ManualResetEvent(false);
                    new Thread(() =>
                    {
                        result = wasInput(wasPOST(vassalConfiguration.HTTPServerURL,
                            wasKeyValueEscape(new Dictionary<string, string>
                            {
                                {"command", "teleport"},
                                {"group", vassalConfiguration.Group},
                                {"password", vassalConfiguration.Password},
                                {"region", selectedRegionName},
                                {"position", selectedRegionPosition.ToString()},
                                {"fly", "True"}
                            }), vassalConfiguration.TeleportTimeout));
                        receivedPOST.Set();
                    }) {IsBackground = true}.Start();
                    receivedPOST.WaitOne((int) vassalConfiguration.TeleportTimeout, false);
                    teleportTimer.Stop();
                    switch (!string.IsNullOrEmpty(result) && wasKeyValueGet("success", result) == "True")
                    {
                        case true:
                            vassalForm.Invoke((MethodInvoker) (() =>
                            {
                                vassalForm.StatusText.Text = @"Now at " + selectedRegionName;
                                vassalForm.CurrentRegionName.Text = selectedRegionName;
                            }));
                            break;
                        default:
                            switch (!string.IsNullOrEmpty(result))
                            {
                                case true:
                                    vassalForm.Invoke((MethodInvoker) (() =>
                                    {
                                        vassalForm.StatusText.Text = @"Failed teleporting to " + selectedRegionName +
                                                                     @": " +
                                                                     wasKeyValueGet("error", result);
                                    }));
                                    break;
                                default:
                                    vassalForm.Invoke((MethodInvoker) (() =>
                                    {
                                        vassalForm.StatusText.Text = @"Failed teleporting to " + selectedRegionName;
                                    }));
                                    break;
                            }
                            break;
                    }
                }
                catch (Exception ex)
                {
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        vassalForm.StatusText.Text = @"Error communicating with Corrade: " + ex.Message;
                    }));
                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        vassalForm.StatusProgress.Value = 100;
                        vassalForm.RegionTeleportGroup.Enabled = true;
                        // Set the map image to the loading spinner.
                        Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                        System.IO.Stream file =
                            thisAssembly.GetManifestResourceStream("Vassal.img.loading.gif");
                        switch (file != null)
                        {
                            case true:
                                vassalForm.Invoke((MethodInvoker) (() =>
                                {
                                    RegionAvatarsMap.SizeMode = PictureBoxSizeMode.CenterImage;
                                    RegionAvatarsMap.Image = Image.FromStream(file);
                                    RegionAvatarsMap.Refresh();
                                }));
                                break;
                        }
                        // Clear the top scripts table.
                        TopScriptsGridView.Rows.Clear();
                        // Clear the top colliders table.
                        TopCollidersGridView.Rows.Clear();
                        // Invalidate data for overview tab.
                        vassalForm.CurrentRegionAt.Visible = false;
                        vassalForm.CurrentRegionName.Visible = false;
                        Agents.Text = string.Empty;
                        Agents.Enabled = false;
                        LastLag.Text = string.Empty;
                        LastLag.Enabled = false;
                        Dilation.Text = string.Empty;
                        Dilation.Enabled = false;
                        FPS.Text = string.Empty;
                        FPS.Enabled = false;
                        PhysicsFPS.Text = string.Empty;
                        PhysicsFPS.Enabled = false;
                        ActiveScripts.Text = string.Empty;
                        ActiveScripts.Enabled = false;
                        ScriptTime.Text = string.Empty;
                        ScriptTime.Enabled = false;
                        Objects.Text = string.Empty;
                        Objects.Enabled = false;
                    }));
                }
            }).Start();

        }

        private void SettingsRequested(object sender, EventArgs e)
        {
            SettingsForm settingsForm = new SettingsForm {TopMost = true};
            settingsForm.Show();
        }

        private void VassalShown(object sender, EventArgs e)
        {
            // Set the version
            vassalForm.Version.Text = @"v" + VASSAL_CONSTANTS.VASSAL_VERSION;

            // Disable estate manager tabs since we will enable these dynamically.
            TopScriptsTab.Enabled = false;
            TopCollidersTab.Enabled = false;

            // Get the configuration file settings if it exists.
            if (File.Exists(VASSAL_CONSTANTS.VASSAL_CONFIGURATION_FILE))
            {
                // Load the configuration.
                VassalConfiguration.Load(VASSAL_CONSTANTS.VASSAL_CONFIGURATION_FILE, ref vassalConfiguration);
            }

            // Get all the regions if they exist.
            if (File.Exists(VASSAL_CONSTANTS.VASSAL_REGIONS))
            {
                Vector3 localPosition;
                List<KeyValuePair<string, Vector3>> ConfiguredRegions = new List<KeyValuePair<string, Vector3>>(
                    File.ReadAllLines(VASSAL_CONSTANTS.VASSAL_REGIONS)
                        .Select(o => new List<string>(wasCSVToEnumerable(o)))
                        .Where(o => o.Count == 2)
                        .ToDictionary(o => o.First(),
                            p =>
                                Vector3.TryParse(p.Last(), out localPosition)
                                    ? localPosition
                                    : Vector3.Zero).OrderBy(o => o.Key).ToDictionary(o => o.Key, o => o.Value));
                // Populate the loaded regions.
                LoadedRegions.Items.Clear();
                LoadedRegions.Items.AddRange(
                    ConfiguredRegions.Select(o => (object) new ListViewItem {Text = o.Key, Tag = o.Value})
                        .ToArray());
                // Populate the batch restart grid view.
                BatchRestartGridView.Rows.Clear();
                foreach (KeyValuePair<string, Vector3> data in ConfiguredRegions)
                {
                    BatchRestartGridView.Rows.Add(data.Key, data.Value.ToString());
                }
            }

            // Set the map image to the loading spinner.
            Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            System.IO.Stream file =
                thisAssembly.GetManifestResourceStream("Vassal.img.loading.gif");
            switch (file != null)
            {
                case true:
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        RegionAvatarsMap.SizeMode = PictureBoxSizeMode.CenterImage;
                        RegionAvatarsMap.Image = Image.FromStream(file);
                        RegionAvatarsMap.Refresh();
                    }));
                    break;
            }

            // Start the overview timer.
            overviewTabTimer.Elapsed += (o, p) =>
            {
                if (!Monitor.TryEnter(ClientInstanceTeleportLock))
                    return;

                try
                {
                    // Do not do anything in case the tab is not selected.
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        if (!Tabs.SelectedTab.Equals(OverviewTab))
                            throw new Exception();
                    }));

                    // Get the statistics.
                    string result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "getregiondata"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {
                                "data", wasEnumerableToCSV(new[]
                                {
                                    "Agents",
                                    "LastLag",
                                    "Dilation",
                                    "FPS",
                                    "PhysicsFPS",
                                    "ActiveScripts",
                                    "ScriptTime",
                                    "Objects",
                                    "Name",
                                    "IsEstateManager"
                                })
                            }
                        }), vassalConfiguration.DataTimeout);

                    bool success;
                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();

                    List<string> data = wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).ToList();
                    if (data.Count.Equals(0))
                        throw new Exception();

                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        // Drop access to features if we are not estate managers.
                        bool isEstateManager;
                        switch (
                            bool.TryParse(data[data.IndexOf("IsEstateManager") + 1], out isEstateManager) &&
                            isEstateManager)
                        {
                            case true: // we are an estate manager
                                TopScriptsTab.Enabled = true;
                                TopCollidersTab.Enabled = true;
                                break;
                            default:
                                TopScriptsTab.Enabled = false;
                                TopCollidersTab.Enabled = false;
                                break;
                        }

                        // Show the region name.
                        vassalForm.CurrentRegionName.Text = data[data.IndexOf("Name") + 1];
                        vassalForm.CurrentRegionAt.Visible = true;
                        vassalForm.CurrentRegionName.Visible = true;

                        // Populate the overview tab.
                        Agents.Text = data[data.IndexOf("Agents") + 1];
                        Agents.Enabled = true;
                        LastLag.Text = data[data.IndexOf("LastLag") + 1];
                        LastLag.Enabled = true;
                        Dilation.Text = data[data.IndexOf("Dilation") + 1];
                        Dilation.Enabled = true;
                        FPS.Text = data[data.IndexOf("FPS") + 1];
                        FPS.Enabled = true;
                        PhysicsFPS.Text = data[data.IndexOf("PhysicsFPS") + 1];
                        PhysicsFPS.Enabled = true;
                        ActiveScripts.Text = data[data.IndexOf("ActiveScripts") + 1];
                        ActiveScripts.Enabled = true;
                        ScriptTime.Text = data[data.IndexOf("ScriptTime") + 1];
                        ScriptTime.Enabled = true;
                        Objects.Text = data[data.IndexOf("Objects") + 1];
                        Objects.Enabled = true;
                    }));

                    // Get the map image.
                    result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "getgridregiondata"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {"data", "MapImageID"}
                        }), vassalConfiguration.DataTimeout);

                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();

                    data = wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result))).ToList();
                    if (!data.Count.Equals(2))
                        throw new Exception();
                    result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "download"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {"item", data.Last()},
                            {"type", "Texture"},
                            {"format", "Jpeg"},
                        }), vassalConfiguration.DataTimeout);
                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();
                    byte[] mapImageBytes = Convert.FromBase64String(wasInput(wasKeyValueGet("data", result)));
                    Image mapImage;
                    using (MemoryStream memoryStream = new MemoryStream(mapImageBytes, 0, mapImageBytes.Length))
                    {
                        mapImage = Image.FromStream(memoryStream);
                    }

                    // Get the avatar positions.
                    result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "getavatarpositions"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {"entity", "region"}
                        }), vassalConfiguration.DataTimeout);

                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();

                    // Every thrid element represents a Vecto3 of the avatar position.
                    data =
                        wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result)))
                            .Skip(2)
                            .Where((x, i) => i%3 == 0)
                            .ToList();

                    // Draw the avatars onto the map.
                    Graphics mapGraphics = Graphics.FromImage(mapImage);
                    Vector3 position;
                    foreach (string vector in data)
                    {
                        switch (Vector3.TryParse(vector, out position))
                        {
                            case true:
                                mapGraphics.FillEllipse(Brushes.Chartreuse,
                                    new Rectangle((int) position.X, (int) position.Y, 4, 4));
                                break;
                        }
                    }
                    mapGraphics.DrawImage(mapImage, new Point(0, 0));

                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        RegionAvatarsMap.SizeMode = PictureBoxSizeMode.StretchImage;
                        RegionAvatarsMap.Image = mapImage;
                        RegionAvatarsMap.Refresh();
                    }));
                }
                catch (Exception)
                {

                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                }

            };
            overviewTabTimer.Start();

            // Start the top scores timer.
            topScriptsTabTimer.Elapsed += (o, p) =>
            {
                if (!Monitor.TryEnter(ClientInstanceTeleportLock))
                    return;

                try
                {
                    // Do not do anything in case the tab is not selected.
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        if (!Tabs.SelectedTab.Equals(TopScriptsTab))
                            throw new Exception();
                    }));

                    // Get the statistics.
                    string result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "getregiontop"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {"type", "scripts"}
                        }), vassalConfiguration.DataTimeout);

                    bool success;
                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();

                    HashSet<List<string>> data =
                        new HashSet<List<string>>(wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result)))
                            .Select((x, i) => new {Index = i, Value = x})
                            .GroupBy(x => x.Index/5)
                            .Select(x => x.Select(v => v.Value).ToList()));
                    if (data.Count.Equals(0))
                        throw new Exception();

                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        // Remove rows that are not in the data update.
                        foreach (
                            int index in
                                TopScriptsGridView.Rows.AsParallel().Cast<DataGridViewRow>()
                                    .Where(
                                        topScriptsRow =>
                                            !data.Any(q => q[2].Equals(topScriptsRow.Cells["TopScriptsUUID"].Value)))
                                    .Select(q => q.Index))
                        {
                            TopScriptsGridView.Rows.RemoveAt(index);
                        }
                        // Now update or add new data.
                        foreach (List<string> dataComponents in data.Where(q => q.Count.Equals(5)))
                        {
                            DataGridViewRow row =
                                TopScriptsGridView.Rows.AsParallel()
                                    .Cast<DataGridViewRow>()
                                    .FirstOrDefault(q => q.Cells["TopScriptsUUID"].Value.Equals(dataComponents[2]));
                            switch (row != null)
                            {
                                case true: // the row exists, so update it.
                                    row.Cells["TopScriptsScore"].Value = dataComponents[0];
                                    row.Cells["TopScriptsTaskName"].Value = dataComponents[1];
                                    row.Cells["TopScriptsUUID"].Value = dataComponents[2];
                                    row.Cells["TopScriptsOwner"].Value = dataComponents[3];
                                    row.Cells["TopScriptsPosition"].Value = dataComponents[4];
                                    break;
                                case false: // the row dosn't exist, so add it.
                                    TopScriptsGridView.Rows.Add(dataComponents[0], dataComponents[1], dataComponents[2],
                                        dataComponents[3], dataComponents[4]);
                                    break;
                            }
                        }
                    }));
                }
                catch (Exception)
                {

                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                }
            };
            topScriptsTabTimer.Start();

            // Start the top colliders timer.
            topCollidersTabTimer.Elapsed += (o, p) =>
            {
                if (!Monitor.TryEnter(ClientInstanceTeleportLock))
                    return;

                try
                {
                    // Do not do anything in case the tab is not selected.
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        if (!Tabs.SelectedTab.Equals(TopCollidersTab))
                            throw new Exception();
                    }));

                    // Get the statistics.
                    string result = wasPOST(vassalConfiguration.HTTPServerURL,
                        wasKeyValueEscape(new Dictionary<string, string>
                        {
                            {"command", "getregiontop"},
                            {"group", vassalConfiguration.Group},
                            {"password", vassalConfiguration.Password},
                            {"type", "colliders"}
                        }), vassalConfiguration.DataTimeout);

                    bool success;
                    if (string.IsNullOrEmpty(result) ||
                        !bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        throw new Exception();

                    HashSet<List<string>> data =
                        new HashSet<List<string>>(wasCSVToEnumerable(wasInput(wasKeyValueGet("data", result)))
                            .Select((x, i) => new {Index = i, Value = x})
                            .GroupBy(x => x.Index/5)
                            .Select(x => x.Select(v => v.Value).ToList()));
                    if (data.Count.Equals(0))
                        throw new Exception();

                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        // Remove rows that are not in the data update.
                        foreach (
                            int index in
                                TopCollidersGridView.Rows.AsParallel().Cast<DataGridViewRow>()
                                    .Where(
                                        topCollidersRow =>
                                            !data.Any(q => q[2].Equals(topCollidersRow.Cells["TopCollidersUUID"].Value)))
                                    .Select(q => q.Index))
                        {
                            TopCollidersGridView.Rows.RemoveAt(index);
                        }
                        // Now update or add new data.
                        foreach (List<string> dataComponents in data.Where(q => q.Count.Equals(5)))
                        {
                            DataGridViewRow row =
                                TopCollidersGridView.Rows.AsParallel()
                                    .Cast<DataGridViewRow>()
                                    .FirstOrDefault(q => q.Cells["TopCollidersUUID"].Value.Equals(dataComponents[2]));
                            switch (row != null)
                            {
                                case true: // the row exists, so update it.
                                    row.Cells["TopCollidersScore"].Value = dataComponents[0];
                                    row.Cells["TopSCollidersTaskName"].Value = dataComponents[1];
                                    row.Cells["TopCollidersUUID"].Value = dataComponents[2];
                                    row.Cells["TopCollidersOwner"].Value = dataComponents[3];
                                    row.Cells["TopCollidersPosition"].Value = dataComponents[4];
                                    break;
                                case false: // the row dosn't exist, so add it.
                                    TopCollidersGridView.Rows.Add(dataComponents[0], dataComponents[1], dataComponents[2],
                                        dataComponents[3], dataComponents[4]);
                                    break;
                            }
                        }

                    }));
                }
                catch (Exception)
                {

                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                }
            };
            topCollidersTabTimer.Start();
        }

        private void RequestedEditRegions(object sender, EventArgs e)
        {
            // Clear any selection.
            LoadedRegions.SelectedIndex = -1;
            RegionEditForm regionEditForm = new RegionEditForm {TopMost = true};
            regionEditForm.Show();
        }

        private void RequestSelecting(object sender, TabControlCancelEventArgs e)
        {
            e.Cancel = !e.TabPage.Enabled;
        }

        private void RequestExportTopScripts(object sender, EventArgs e)
        {
            vassalForm.BeginInvoke((MethodInvoker) (() =>
            {
                switch (vassalForm.ExportCSVDialog.ShowDialog())
                {
                    case DialogResult.OK:
                        string file = vassalForm.ExportCSVDialog.FileName;
                        new Thread(() =>
                        {
                            vassalForm.BeginInvoke((MethodInvoker) (() =>
                            {
                                try
                                {
                                    vassalForm.StatusText.Text = @"exporting...";
                                    vassalForm.StatusProgress.Value = 0;

                                    using (StreamWriter streamWriter = new StreamWriter(file, false, Encoding.UTF8))
                                    {
                                        foreach (DataGridViewRow topScriptsRow in TopScriptsGridView.Rows)
                                        {
                                            streamWriter.WriteLine(wasEnumerableToCSV(new[]
                                            {
                                                topScriptsRow.Cells["TopScriptsScore"].Value.ToString(),
                                                topScriptsRow.Cells["TopScriptsTaskName"].Value.ToString(),
                                                topScriptsRow.Cells["TopScriptsUUID"].Value.ToString(),
                                                topScriptsRow.Cells["TopScriptsOwner"].Value.ToString(),
                                                topScriptsRow.Cells["TopScriptsPosition"].Value.ToString()
                                            }));
                                        }
                                    }

                                    vassalForm.StatusText.Text = @"exported";
                                    vassalForm.StatusProgress.Value = 100;
                                }
                                catch (Exception ex)
                                {
                                    vassalForm.StatusText.Text = ex.Message;
                                }
                            }));
                        })
                        {IsBackground = true, Priority = ThreadPriority.Normal}.Start();
                        break;
                }
            }));
        }

        private void RequestExportTopColliders(object sender, EventArgs e)
        {
            vassalForm.BeginInvoke((MethodInvoker) (() =>
            {
                switch (vassalForm.ExportCSVDialog.ShowDialog())
                {
                    case DialogResult.OK:
                        string file = vassalForm.ExportCSVDialog.FileName;
                        new Thread(() =>
                        {
                            vassalForm.BeginInvoke((MethodInvoker) (() =>
                            {
                                try
                                {
                                    vassalForm.StatusText.Text = @"exporting...";
                                    vassalForm.StatusProgress.Value = 0;

                                    using (StreamWriter streamWriter = new StreamWriter(file, false, Encoding.UTF8))
                                    {
                                        foreach (DataGridViewRow topCollidersRow in TopCollidersGridView.Rows)
                                        {
                                            streamWriter.WriteLine(wasEnumerableToCSV(new[]
                                            {
                                                topCollidersRow.Cells["TopCollidersScore"].Value.ToString(),
                                                topCollidersRow.Cells["TopCollidersTaskName"].Value.ToString(),
                                                topCollidersRow.Cells["TopCollidersUUID"].Value.ToString(),
                                                topCollidersRow.Cells["TopCollidersOwner"].Value.ToString(),
                                                topCollidersRow.Cells["TopCollidersPosition"].Value.ToString()
                                            }));
                                        }
                                    }

                                    vassalForm.StatusText.Text = @"exported";
                                    vassalForm.StatusProgress.Value = 100;
                                }
                                catch (Exception ex)
                                {
                                    vassalForm.StatusText.Text = ex.Message;
                                }
                            }));
                        })
                        {IsBackground = true, Priority = ThreadPriority.Normal}.Start();
                        break;
                }
            }));
        }

        private void RequestFilterTopScripts(object sender, EventArgs e)
        {
            vassalForm.BeginInvoke((MethodInvoker) (() =>
            {
                Regex topScriptsRowRegex;
                switch (!string.IsNullOrEmpty(TopScriptsFilter.Text))
                {
                    case true:
                        topScriptsRowRegex = new Regex(TopScriptsFilter.Text, RegexOptions.Compiled);
                        break;
                    default:
                        topScriptsRowRegex = new Regex(@".+?", RegexOptions.Compiled);
                        break;
                }
                foreach (DataGridViewRow topScriptsRow in TopScriptsGridView.Rows.AsParallel().Cast<DataGridViewRow>())
                {
                    topScriptsRow.Visible =
                        topScriptsRowRegex.IsMatch(topScriptsRow.Cells["TopScriptsScore"].Value.ToString()) ||
                        topScriptsRowRegex.IsMatch(
                            topScriptsRow.Cells["TopScriptsTaskName"].Value.ToString()) ||
                        topScriptsRowRegex.IsMatch(topScriptsRow.Cells["TopScriptsUUID"].Value.ToString()) ||
                        topScriptsRowRegex.IsMatch(topScriptsRow.Cells["TopScriptsOwner"].Value.ToString()) ||
                        topScriptsRowRegex.IsMatch(
                            topScriptsRow.Cells["TopScriptsPosition"].Value.ToString());
                }
            }));
        }

        private void RequestFilterTopColliders(object sender, EventArgs e)
        {
            vassalForm.BeginInvoke((MethodInvoker)(() =>
            {
                Regex topCollidersRowRegex;
                switch (!string.IsNullOrEmpty(TopScriptsFilter.Text))
                {
                    case true:
                        topCollidersRowRegex = new Regex(TopScriptsFilter.Text, RegexOptions.Compiled);
                        break;
                    default:
                        topCollidersRowRegex = new Regex(@".+?", RegexOptions.Compiled);
                        break;
                }
                foreach (DataGridViewRow topCollidersRow in TopCollidersGridView.Rows.AsParallel().Cast<DataGridViewRow>())
                {
                    topCollidersRow.Visible =
                        topCollidersRowRegex.IsMatch(topCollidersRow.Cells["TopCollidersScore"].Value.ToString()) ||
                        topCollidersRowRegex.IsMatch(
                            topCollidersRow.Cells["TopCollidersTaskName"].Value.ToString()) ||
                        topCollidersRowRegex.IsMatch(topCollidersRow.Cells["TopCollidersUUID"].Value.ToString()) ||
                        topCollidersRowRegex.IsMatch(topCollidersRow.Cells["TopCollidersOwner"].Value.ToString()) ||
                        topCollidersRowRegex.IsMatch(
                            topCollidersRow.Cells["TopCollidersPosition"].Value.ToString());
                }
            }));
        }

        private void RequestReturnTopScriptsObjects(object sender, EventArgs e)
        {
            // Block teleports and disable button.
            vassalForm.Invoke((MethodInvoker) (() =>
            {
                vassalForm.ReturnTopScriptsButton.Enabled = false;
                RegionTeleportGroup.Enabled = false;
            }));

            // Enqueue all the UUIDs to return.
            Queue<KeyValuePair<UUID, Vector3>> returnUUIDs = new Queue<KeyValuePair<UUID, Vector3>>();
            vassalForm.Invoke((MethodInvoker) (() =>
            {
                foreach (
                    DataGridViewRow topScriptsRow in
                        TopScriptsGridView.Rows.AsParallel()
                            .Cast<DataGridViewRow>()
                            .Where(o => o.Selected || o.Cells.Cast<DataGridViewCell>().Any(p => p.Selected)))
                {
                    Vector3 objectPosition;
                    UUID returnUUID;
                    if (!UUID.TryParse(topScriptsRow.Cells["TopScriptsUUID"].Value.ToString(), out returnUUID) ||
                        !Vector3.TryParse(topScriptsRow.Cells["TopScriptsPosition"].Value.ToString(), out objectPosition))
                        continue;
                    returnUUIDs.Enqueue(new KeyValuePair<UUID, Vector3>(returnUUID, objectPosition));
                }
            }));

            // If no rows were selected, enable teleports, the return button and return.
            if (returnUUIDs.Count.Equals(0))
            {
                vassalForm.Invoke((MethodInvoker)(() =>
                {
                    vassalForm.ReturnTopScriptsButton.Enabled = true;
                    RegionTeleportGroup.Enabled = true;
                }));
                return;
            }

            new Thread(() =>
            {
                Monitor.Enter(ClientInstanceTeleportLock);

                try
                {
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        vassalForm.StatusProgress.Value = 0;
                    }));
                    int totalObjects = returnUUIDs.Count;
                    do
                    {
                        // Dequeue the first object.
                        KeyValuePair<UUID, Vector3> objectData = returnUUIDs.Dequeue();

                        vassalForm.Invoke((MethodInvoker) (() =>
                        {
                            vassalForm.StatusText.Text = @"Returning object UUID: " + objectData.Key.ToString();
                        }));

                        string currentRegionName = string.Empty;
                        vassalForm.Invoke((MethodInvoker) (() =>
                        {
                            currentRegionName = CurrentRegionName.Text;
                        }));
                       
                        // Teleport to the object.
                        string result = wasPOST(vassalConfiguration.HTTPServerURL,
                            wasKeyValueEscape(new Dictionary<string, string>
                            {
                                {"command", "teleport"},
                                {"group", vassalConfiguration.Group},
                                {"password", vassalConfiguration.Password},
                                {"position", objectData.Value.ToString()},
                                {"region", currentRegionName},
                                {"fly", "True"}
                            }), vassalConfiguration.TeleportTimeout);

                        if (string.IsNullOrEmpty(result))
                        {
                            vassalForm.Invoke((MethodInvoker)(() =>
                            {
                                vassalForm.StatusText.Text = @"Error communicating with Corrade.";
                            }));
                            continue;
                        }

                        // Return the object.
                        result = wasPOST(vassalConfiguration.HTTPServerURL,
                            wasKeyValueEscape(new Dictionary<string, string>
                            {
                                {"command", "derez"},
                                {"group", vassalConfiguration.Group},
                                {"password", vassalConfiguration.Password},
                                {"item", objectData.Key.ToString()},
                                {"range", "32" }, // maximal prim size = 64 - middle bounding box at half
                                {"type", "ReturnToOwner"}
                            }), vassalConfiguration.DataTimeout);

                        if (string.IsNullOrEmpty(result))
                        {
                            vassalForm.Invoke((MethodInvoker) (() =>
                            {
                                vassalForm.StatusText.Text = @"Error communicating with Corrade.";
                            }));
                            continue;
                        }

                        bool success;
                        if (!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        {
                            vassalForm.Invoke((MethodInvoker) (() =>
                            {
                                vassalForm.StatusText.Text = @"No success status could be retrieved. ";
                            }));
                            continue;
                        }

                        switch (success)
                        {
                            case true:
                                vassalForm.Invoke((MethodInvoker) (() =>
                                {
                                    vassalForm.StatusText.Text = @"Returned object: " + objectData.Key.ToString();
                                    // Remove the row from the grid view.
                                    DataGridViewRow row = 
                                    TopScriptsGridView.Rows.AsParallel()
                                        .Cast<DataGridViewRow>()
                                        .FirstOrDefault(
                                            o => o.Cells["TopScriptsUUID"].Value.Equals(objectData.Key.ToString()));
                                    if (row == null) return;
                                    int i = row.Index;
                                    TopScriptsGridView.Rows.RemoveAt(i);
                                }));
                                break;
                            case false:
                                vassalForm.Invoke((MethodInvoker) (() =>
                                {
                                    vassalForm.StatusText.Text = @"Could not return object " + objectData.Key.ToString() +
                                                                 @": " +
                                                                 wasInput(wasKeyValueGet("error", result));
                                }));
                                break;
                        }

                        vassalForm.Invoke((MethodInvoker) (() =>
                        {
                            vassalForm.StatusProgress.Value =
                                Math.Min((int) (Math.Abs(returnUUIDs.Count - totalObjects)/
                                                (double) totalObjects), 100);
                        }));
                    } while (!returnUUIDs.Count.Equals(0));
                    vassalForm.Invoke((MethodInvoker) (() =>
                    {
                        vassalForm.StatusProgress.Value = 100;
                    }));
                }
                catch (Exception ex)
                {
                    vassalForm.Invoke((MethodInvoker)(() =>
                    {
                        vassalForm.StatusText.Text = @"Unexpected error: " + ex.Message;
                    }));
                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                    // Allow teleports and enable button.
                    vassalForm.BeginInvoke((MethodInvoker)(() =>
                    {
                        vassalForm.ReturnTopScriptsButton.Enabled = true;
                        RegionTeleportGroup.Enabled = true;
                    }));
                }
            })
            {IsBackground = true}.Start();
        }

        private void RequestReturnTopCollidersObjects(object sender, EventArgs e)
        {
            // Block teleports and disable button.
            vassalForm.Invoke((MethodInvoker)(() =>
            {
                vassalForm.ReturnTopCollidersButton.Enabled = false;
                RegionTeleportGroup.Enabled = false;
            }));

            // Enqueue all the UUIDs to return.
            Queue<KeyValuePair<UUID, Vector3>> returnUUIDs = new Queue<KeyValuePair<UUID, Vector3>>();
            vassalForm.Invoke((MethodInvoker)(() =>
            {
                foreach (
                    DataGridViewRow topCollidersRow in
                        TopCollidersGridView.Rows.AsParallel()
                            .Cast<DataGridViewRow>()
                            .Where(o => o.Selected || o.Cells.Cast<DataGridViewCell>().Any(p => p.Selected)))
                {
                    Vector3 objectPosition;
                    UUID returnUUID;
                    if (!UUID.TryParse(topCollidersRow.Cells["TopCollidersUUID"].Value.ToString(), out returnUUID) ||
                        !Vector3.TryParse(topCollidersRow.Cells["TopCollidersPosition"].Value.ToString(), out objectPosition))
                        continue;
                    returnUUIDs.Enqueue(new KeyValuePair<UUID, Vector3>(returnUUID, objectPosition));
                }
            }));

            // If no rows were selected, enable teleports, the return button and return.
            if (returnUUIDs.Count.Equals(0))
            {
                vassalForm.Invoke((MethodInvoker)(() =>
                {
                    vassalForm.ReturnTopCollidersButton.Enabled = true;
                    RegionTeleportGroup.Enabled = true;
                }));
                return;
            }

            new Thread(() =>
            {
                Monitor.Enter(ClientInstanceTeleportLock);

                try
                {
                    vassalForm.Invoke((MethodInvoker)(() =>
                    {
                        vassalForm.StatusProgress.Value = 0;
                    }));
                    int totalObjects = returnUUIDs.Count;
                    do
                    {
                        // Dequeue the first object.
                        KeyValuePair<UUID, Vector3> objectData = returnUUIDs.Dequeue();

                        vassalForm.Invoke((MethodInvoker)(() =>
                        {
                            vassalForm.StatusText.Text = @"Returning UUID: " + objectData.Key.ToString();
                        }));

                        string currentRegionName = string.Empty;
                        vassalForm.Invoke((MethodInvoker)(() =>
                        {
                            currentRegionName = CurrentRegionName.Text;
                        }));

                        // Teleport to the object.
                        string result = wasPOST(vassalConfiguration.HTTPServerURL,
                            wasKeyValueEscape(new Dictionary<string, string>
                            {
                                {"command", "teleport"},
                                {"group", vassalConfiguration.Group},
                                {"password", vassalConfiguration.Password},
                                {"position", objectData.Value.ToString()},
                                {"region", currentRegionName},
                                {"fly", "True"}
                            }), vassalConfiguration.DataTimeout);

                        if (string.IsNullOrEmpty(result))
                        {
                            vassalForm.Invoke((MethodInvoker)(() =>
                            {
                                vassalForm.StatusText.Text = @"Error communicating with Corrade.";
                            }));
                            continue;
                        }

                        // Return the object.
                        result = wasPOST(vassalConfiguration.HTTPServerURL,
                            wasKeyValueEscape(new Dictionary<string, string>
                            {
                                {"command", "derez"},
                                {"group", vassalConfiguration.Group},
                                {"password", vassalConfiguration.Password},
                                {"item", objectData.Key.ToString()},
                                {"range", "32" }, // maximal prim size = 64 - middle bounding box at half
                                {"type", "ReturnToOwner"}
                            }), vassalConfiguration.DataTimeout);

                        if (string.IsNullOrEmpty(result))
                        {
                            vassalForm.Invoke((MethodInvoker)(() =>
                            {
                                vassalForm.StatusText.Text = @"Error communicating with Corrade.";
                            }));
                            continue;
                        }

                        bool success;
                        if (!bool.TryParse(wasInput(wasKeyValueGet("success", result)), out success))
                        {
                            vassalForm.Invoke((MethodInvoker)(() =>
                            {
                                vassalForm.StatusText.Text = @"No success status could be retrieved. ";
                            }));
                            continue;
                        }

                        switch (success)
                        {
                            case true:
                                vassalForm.Invoke((MethodInvoker)(() =>
                                {
                                    vassalForm.StatusText.Text = @"Returned object: " + objectData.Key.ToString();
                                    // Remove the row from the grid view.
                                    DataGridViewRow row =
                                    TopCollidersGridView.Rows.AsParallel()
                                        .Cast<DataGridViewRow>()
                                        .FirstOrDefault(
                                            o => o.Cells["TopCollidersUUID"].Value.Equals(objectData.Key.ToString()));
                                    if (row == null) return;
                                    int i = row.Index;
                                    TopCollidersGridView.Rows.RemoveAt(i);
                                }));
                                break;
                            case false:
                                vassalForm.Invoke((MethodInvoker)(() =>
                                {
                                    vassalForm.StatusText.Text = @"Could not return object " + objectData.Key.ToString() +
                                                                 @": " +
                                                                 wasInput(wasKeyValueGet("error", result));
                                }));
                                break;
                        }

                        vassalForm.Invoke((MethodInvoker)(() =>
                        {
                            vassalForm.StatusProgress.Value =
                                Math.Min((int)(Math.Abs(returnUUIDs.Count - totalObjects) /
                                                (double)totalObjects), 100);
                        }));
                    } while (!returnUUIDs.Count.Equals(0));
                    vassalForm.Invoke((MethodInvoker)(() =>
                    {
                        vassalForm.StatusProgress.Value = 100;
                    }));
                }
                catch (Exception ex)
                {
                    vassalForm.Invoke((MethodInvoker)(() =>
                    {
                        vassalForm.StatusText.Text = @"Unexpected error: " + ex.Message;
                    }));
                }
                finally
                {
                    Monitor.Exit(ClientInstanceTeleportLock);
                    // Allow teleports and enable button.
                    vassalForm.BeginInvoke((MethodInvoker)(() =>
                    {
                        vassalForm.ReturnTopScriptsButton.Enabled = true;
                        RegionTeleportGroup.Enabled = true;
                    }));
                }
            })
            { IsBackground = true }.Start();
        }

        private void RequestBatchRestart(object sender, EventArgs e)
        {

        }
    }
}