using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel.Composition;
using System.ComponentModel;
using System.Globalization;
using AirScout.PlaneFeeds.Plugin.MEFContract;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using System.Windows.Forms;
using AirScout.PlaneFeeds.Plugin;
using System.Xml;
using System.Xml.Linq;

namespace AirScout.PlaneFeeds.Plugin.FlexJSON
{

    [Serializable]
    public class FlexJSONSettings
    {
        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for UTC [OPTIONAL]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_UTC { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Hex [OPTIONAL]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Hex { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Call [OPTIONAL]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Call { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Latitude [deg] [MANDATORY]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Lat { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Longitde [deg] [MANDATORY]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Lon { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Altitude [ft, m] [MANDATORY]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Alt { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Units for Altitude [ft, m]")]
        [DefaultValue("ft")]
        public string Units_Alt { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Speed [kts, km/h] [MANDATORY]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Speed { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Units for Speed [kts, km/h]")]
        [DefaultValue("kts")]
        public string Units_Speed { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Track in [deg] [MANDATORY]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Track { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Type [OPTIONAL]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Type { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Index for Registration [OPTIONAL]. Use -1 for detect, 255 for not used.")]
        [DefaultValue(-1)]
        public int Index_Reg { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Minimum Count of Elements per Plane Position Data Set")]
        [DefaultValue(10)]
        public int Min_Elements { get; set; }

        [CategoryAttribute("Data Field Properties")]
        [DescriptionAttribute("Minium Count of Plane Position Data Sets")]
        [DefaultValue(50)]
        public int Min_Planes { get; set; }

        [Browsable(false)]
        [DefaultValue("")]
        [XmlIgnore]
        public string DisclaimerAccepted { get; set; }

        [CategoryAttribute("Web Feed")]
        [DescriptionAttribute("Save downloaded JSON to file")]
        [DefaultValue(false)]
        [XmlIgnore]
        public bool SaveToFile { get; set; }

        [CategoryAttribute("Web Feed")]
        [DescriptionAttribute("Base URL for website.")]
        [DefaultValue("")]
        public string URL { get; set; }

        [CategoryAttribute("Web Feed")]
        [DescriptionAttribute("Timeout for loading the site.")]
        [DefaultValue(30)]
        [XmlIgnore]
        public int Timeout { get; set; }

        public FlexJSONSettings()
        {
            Default();
            Load(true);
        }

        /// <summary>
        /// Sets all properties to their default value according to the [DefaultValue=] attribute
        /// </summary>
        public void Default()
        {
            // set all properties to their default values according to definition in [DeafultValue=]
            foreach (var p in this.GetType().GetProperties())
            {
                try
                {
                    // initialize all properties with default value if set
                    if (Attribute.IsDefined(p, typeof(DefaultValueAttribute)))
                    {
                        p.SetValue(this, ((DefaultValueAttribute)Attribute.GetCustomAttribute(
                            p, typeof(DefaultValueAttribute)))?.Value, null);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("[" + this.GetType().Name + "]: Cannot set default value of: " + p.Name + ", " + ex.Message);
                }
            }
        }

        /// <summary>
        /// Loads settings from a XML-formatted configuration file into settings.
        /// </summary>
        /// <param name="loadall">If true, ignore the [XmlIgnore] attribute, e.g. load all settings available in the file.<br>If false, load only settings without [XmlIgore] attrbute.</br></param>
        /// <param name="filename">The filename of the settings file.</param>
        public void Load(bool loadall, string filename = "")
        {
            // use standard filename if empty
            // be careful because Linux file system is case sensitive
            if (String.IsNullOrEmpty(filename))
                filename = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace(".dll", ".cfg").Replace(".DLL", ".CFG")).LocalPath;
            // do nothing if file not exists
            if (!File.Exists(filename))
                return;
            try
            {
                string xml = "";
                using (StreamReader sr = new StreamReader(File.OpenRead(filename)))
                {
                    xml = sr.ReadToEnd();
                }
                XDocument xdoc = XDocument.Parse(xml);
                PropertyInfo[] properties = this.GetType().GetProperties();
                foreach (PropertyInfo p in properties)
                {
                    if (!loadall)
                    {
                        // check on XmlIgnore attribute, skip if set
                        object[] attr = p.GetCustomAttributes(typeof(XmlIgnoreAttribute), false);
                        if (attr.Length > 0)
                            continue;
                    }
                    try
                    {
                        // get matching element
                        XElement typenode = xdoc.Element(this.GetType().Name);
                        if (typenode != null)
                        {
                            XElement element = typenode.Element(p.Name);
                            if (element != null)
                                p.SetValue(this, Convert.ChangeType(element.Value, p.PropertyType), null);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("[" + this.GetType().Name + "]: Error while loading property[" + p.Name + " from " + filename + ", " + ex.Message);
                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine("[" + this.GetType().Name + "]: Cannot load settings from " + filename + ", " + ex.Message);
            }
        }

        /// <summary>
        /// Saves settings from settings into a XML-formatted configuration file
        /// </summary>
        /// <param name="saveall">If true, ignore the [XmlIgnore] attribute, e.g. save all settings.<br>If false, save only settings without [XmlIgore] attrbute.</param>
        /// <param name="filename">The filename of the settings file.</param>
        public void Save(bool saveall, string filename = "")
        {
            // use standard filename if empty
            // be careful because Linux file system is case sensitive
            if (String.IsNullOrEmpty(filename))
                filename = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace(".dll", ".cfg").Replace(".DLL",".CFG")).LocalPath;
            XmlAttributeOverrides overrides = new XmlAttributeOverrides();
            if (saveall)
            {
                // ovverride the XmlIgnore attributes to get all serialized
                PropertyInfo[] properties = this.GetType().GetProperties();
                foreach (PropertyInfo p in properties)
                {
                    XmlAttributes attribs = new XmlAttributes { XmlIgnore = false };
                    overrides.Add(this.GetType(), p.Name, attribs);
                }
            }
            try
            {
                using (StreamWriter sw = new StreamWriter(File.Create(filename)))
                {
                    XmlSerializer s = new XmlSerializer(this.GetType(), overrides);
                    s.Serialize(sw, this);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot save settings to " + filename + ", " + ex.Message);
            }
        }
    }




    [Export(typeof(IPlaneFeedPlugin))]
    [ExportMetadata("Name", "PlaneFeedPlugin")]
    public class FlexJSONPlugin : IPlaneFeedPlugin
    {

        //********************************** START OF INTERFACE ******************************************

        public string Name
        {
            get
            {
                return "[WebFeed]           Flexible JSON";
            }
        }

        public string Info
        {
            get
            {
                return "Web feed with flexible JSON column assignment. Use it for webfeed with unknown content.\n" +
                       "(c) AirScout(www.airscout.eu)\n" +
                       "This feed tries to read unknown JSON data into an array of plane information.\n" +
                       "If this is successful, a \"forensic\" algorithm tries to find out which information is on which index.\n" +
                       "You can manually enter indices as well.See documentation for further details.";
            }
        }

        public string Version
        {
            get
            {
                return Assembly.GetExecutingAssembly().GetName().Version.ToString();
            }
        }

        public bool HasSettings
        {
            get
            {
                return true;
            }
        }

        public bool CanImport
        {
            get
            {
                return true;
            }
        }

        public bool CanExport
        {
            get
            {
                return true;
            }
        }
        public string Disclaimer
        {
            get
            {
                return "This plane feed might fetch data from an Internet server via Deep Link\n" + 
                        "technology(see http://en.wikipedia.org/wiki/Deep_link)\n." + 
                        "The use is probably not intended by the website owners and could be changed in URL and data format frequently and without further notice.\n" +
                        "Furthermore, it might cause legal issues in some countries.\n" +
                        "By clicking on \"Accept\" you understand that you are\n\n" +
                        "DOING THAT ON YOUR OWN RISK\n\n" +
                        "The auhor of this software will not be responsible in any case.";
            }
        }

        public string DisclaimerAccepted
        {
            get
            {
                return Settings.DisclaimerAccepted;
            }
            set
            {
                Settings.DisclaimerAccepted = value;
            }
        }

        public void ResetSettings()
        {
            Settings.Default();
        }

        public void LoadSettings()
        {
            Settings.Load(true);
        }

        public void SaveSettings()
        {
            Settings.Save(true);
        }

        public object GetSettings()
        {
            return this.Settings;
        }

        public void ImportSettings()
        {
            OpenFileDialog Dlg = new OpenFileDialog();
            Dlg.FileName = "*.feed";
            Dlg.DefaultExt = "feed";
            Dlg.Filter = "Plane Feeds | .feed";
            Dlg.CheckFileExists = true;
            if (Dlg.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    Settings.Load(false, Dlg.FileName);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot import from " + Dlg.FileName + ", " + ex.Message);
                }
            }
        }

        public void ExportSettings()
        {
            SaveFileDialog Dlg = new SaveFileDialog();
            Dlg.DefaultExt = "feed";
            Dlg.Filter = "Plane Feeds | .feed";
            Dlg.OverwritePrompt = true;
            if (Dlg.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    Settings.Save(false, Dlg.FileName);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot export to " + Dlg.FileName + ", " + ex.Message);
                }
            }

        }

        public void Start(PlaneFeedPluginArgs args)
        {
            // keep the TmpDirectory
            TmpDirectory = args.TmpDirectory;
        }

        public PlaneFeedPluginPlaneInfoList GetPlanes(PlaneFeedPluginArgs args)
        {
            // intialize variables
            VarConverter VC = new VarConverter();
            VC.AddVar("APPDIR", args.AppDirectory);
            VC.AddVar("DATADIR", args.AppDataDirectory);
            VC.AddVar("LOGDIR", args.LogDirectory);
            VC.AddVar("DATABASEDIR", args.DatabaseDirectory);
            VC.AddVar("MINLAT", args.MinLat);
            VC.AddVar("MAXLAT", args.MaxLat);
            VC.AddVar("MINLON", args.MinLon);
            VC.AddVar("MAXLON", args.MaxLon);
            VC.AddVar("MINALTM", args.MinAlt);
            VC.AddVar("MAXALTM", args.MaxAlt);
            VC.AddVar("MINALTFT", (int)UnitConverter.m_ft((double)args.MinAlt));
            VC.AddVar("MAXALTFT", (int)UnitConverter.m_ft((double)args.MaxAlt));
            // initialize plane info list
            PlaneFeedPluginPlaneInfoList planes = new PlaneFeedPluginPlaneInfoList();
            string json = "";
            // calculate url and get json
            String url = VC.ReplaceAllVars(Settings.URL);
            // check parameters
            if (String.IsNullOrEmpty(url))
                throw new ArgumentException("The URL is empty.");
            if (Settings.Timeout <= 0)
                throw new ArgumentException("The value for Timout is invalid: " + Settings.Timeout.ToString());
            Console.WriteLine("[" + this.GetType().Name + "]: Creating web request: " + url);
            // check url and choose the according download mode
            if (!url.ToLowerInvariant().StartsWith("https://"))
            {
                HttpWebRequest webrequest = (HttpWebRequest)HttpWebRequest.Create(url);
                webrequest.Referer = "";
                webrequest.Timeout = (int)Settings.Timeout * 1000;
                webrequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0";
                webrequest.Accept = "application/json, text/javascript, */*;q=0.01";
                webrequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip;
                Console.WriteLine("[" + this.GetType().Name + "]: Getting web response");
                HttpWebResponse webresponse = (HttpWebResponse)webrequest.GetResponse();
                Console.WriteLine("[" + this.GetType().Name + "]: Reading stream");
                using (StreamReader sr = new StreamReader(webresponse.GetResponseStream()))
                {
                    json = sr.ReadToEnd();
                }
            }
            else
            {
                json = TlsClient.DownloadFile(url, Settings.Timeout * 1000);
            }
            // save raw data to file if enabled
            if (Settings.SaveToFile)
            {
                using (StreamWriter sw = new StreamWriter(args.TmpDirectory + Path.DirectorySeparatorChar + this.GetType().Name + "_" + DateTime.UtcNow.ToString("yyyy-MM-dd HH_mm_ss") + ".json"))
                {
                    sw.WriteLine(json);
                }
            }
            Console.WriteLine("[" + this.GetType().Name + "]: Analyzing data");
            JavaScriptSerializer js = new JavaScriptSerializer();
            dynamic root = js.Deserialize<dynamic>(json);
            // get all planeinfos in an array
            GetAllPlaneInfos(root);
            // try to find all indices automatically if not set
            // mind the search order!
            if (Settings.Index_Hex < 0)
            {
                FindHexIndex();
                Settings.Save(true);
            }
            if (Settings.Index_UTC < 0)
            { 
                FindUTCIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Type < 0)
            {
                FindTypeIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Lat < 0)
            { 
                FindLatIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Lon < 0)
            { 
                FindLonIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Alt < 0)
            { 
                FindAltIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Track < 0)
            { 
                FindTrackIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Speed < 0)
            {
                FindSpeedIndex();
                Settings.Save(true);
            }
            if (Settings.Index_Reg < 0)
            { 
                FindRegIndex();
                Settings.Save(true);
            }
            //  all must produce/have some mandatory data
            if (Settings.Index_Lat < 0)
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot get planes, Lat index not found/not set!");
            if (Settings.Index_Lon < 0)
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot get planes, Lon index not found/not set!");
            if (Settings.Index_Alt < 0)
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot get planes, Alt index not found/not set!");
            if (Settings.Index_Track < 0)
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot get planes, Track index not found/not set!");
            if (Settings.Index_Speed < 0)
                throw new InvalidOperationException("[" + this.GetType().Name + "]: Cannot get planes, Speed index not found/not set!");
            // fill plane info
            foreach (string[] info in PlaneList)
            {
                PlaneFeedPluginPlaneInfo plane = new PlaneFeedPluginPlaneInfo();
                if ((Settings.Index_Hex > 0) && (Settings.Index_Hex < info.Length))
                    plane.Hex = To_Hex(info[Settings.Index_Hex]);
                else
                    plane.Hex = "";
                if ((Settings.Index_UTC > 0) && (Settings.Index_UTC < info.Length))
                    plane.Time = To_UTC(info[Settings.Index_UTC]);
                else
                    plane.Time = DateTime.UtcNow;
                if ((Settings.Index_Lat > 0) && (Settings.Index_Lat < info.Length))
                    plane.Lat = To_Lat(info[Settings.Index_Lat]);
                else
                    plane.Lat = double.MinValue;
                if ((Settings.Index_Lon > 0) && (Settings.Index_Lon < info.Length))
                    plane.Lon = To_Lon(info[Settings.Index_Lon]);
                else
                    plane.Lon = double.MinValue;
                if ((Settings.Index_Alt > 0) && (Settings.Index_Alt < info.Length))
                    plane.Alt = To_Alt(info[Settings.Index_Alt]);
                else
                    plane.Alt = double.MinValue;
                if ((Settings.Index_Speed > 0) && (Settings.Index_Speed < info.Length))
                    plane.Speed = To_Speed(info[Settings.Index_Speed]);
                else
                    plane.Speed = int.MinValue;
                if ((Settings.Index_Track > 0) && (Settings.Index_Track < info.Length))
                    plane.Track = To_Track(info[Settings.Index_Track]);
                else
                    plane.Track = int.MinValue;
                if ((Settings.Index_Call > 0) && (Settings.Index_Call < info.Length))
                    plane.Call = To_Call(info[Settings.Index_Call]);
                else
                    plane.Call = "";
                if ((Settings.Index_Type > 0) && (Settings.Index_Type < info.Length))
                    plane.Type = To_Type(info[Settings.Index_Type]);
                else
                    plane.Type = "";
                if ((Settings.Index_Reg > 0) && (Settings.Index_Reg < info.Length))
                    plane.Reg = To_Reg(info[Settings.Index_Reg]);
                else
                    plane.Reg = "";
                planes.Add(plane);
            }
            Console.WriteLine("[" + this.GetType().Name + "]: Returning planes");
            return planes;
        }

        public void Stop(PlaneFeedPluginArgs args)
        {
            // save settings
            Settings.Save(true);
        }

        // **************************** END OF INTERFACE *********************************

        private string TmpDirectory;
        private FlexJSONSettings Settings = new FlexJSONSettings(); 

        public FlexJSONPlugin()
        {
        }


        // *********************** Plane info converters **********************************

        #region PlaneInfoCheckers


        // checks for int and long values
        private bool IsInt(string s)
        {
            s = s.Trim();
            // int should contain only these chars
            string allowed = "-01234567890";
            int i;
            for (i = 0; i < s.Length; i++)
            {
                if (!allowed.Contains(s[i]))
                    return false;
            }
            long l;
            // try to convert to int
            if (!long.TryParse(s, out l))
                return false;
            // all checks paased
            return true;
        }

        private bool IsDouble(string s)
        {
            s = s.Trim();
            // double must contain at least a decimal separator
            if (!s.Contains(".") && !s.Contains(","))
                return false;
            // try to convert to double
            double d;
            // try to convert with regional settings
            if (!double.TryParse(s, out d))
            {
                // try invariant
                if (!double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
                    return false;
            }
            // all checks passed
            return true;
        }

        private bool IsHex(string s)
        {
            bool b;
            s = s.Trim();
            if (s.Length != 6)
                return false;
            long l;
            if (!long.TryParse(s, System.Globalization.NumberStyles.HexNumber, null, out l))
                return false;
            // all checks passed
            return true;
        }

        private bool IsUTC(string s)
        {
            // get current time  in all three formats
            DateTime now = DateTime.UtcNow;
            int now_int = (Int32)(now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
            long now_long = (long)now_int * 1000;

            s = s.Trim();
            // to short for a UTC value
            if (s.Length < 10)
                return false;
            if (!IsInt(s))
            {
                // maybe DateTime formatted
                if (!s.ToCharArray().Any(c => "-_:".Contains(c)))
                    return false;
            }
            // assuming int
            long l = 0;
            // assuming integer values
            if (s.Length < 13)
            {
                // assuming UNIX time in sec
                if (!long.TryParse(s, out l))
                    return false;
                if (Math.Abs(now_int - l) > 1000)
                    return false;
            }
            // assuming UNIX time in msec
            if (!long.TryParse(s, out l))
                return false;
            if (Math.Abs(now_long - l) > 1000000)
                return false;
            // all checks passed
            return true;
        }

        private bool IsLat(string s)
        {
            bool b;
            s = s.Trim();
            if (!IsDouble(s))
                return false;
            double d = To_Double(s);
            if (d < -90)
                return false;
            if (d > 90)
                return false;
            // all checks passed
            return true;
        }

        private bool IsLon(string s)
        {
            bool b;
            s = s.Trim();
            if (!IsDouble(s))
                return false;
            double d = To_Double(s);
            if (d < -180)
                return false;
            if (d > 180)
                return false;
            // all checks passed
            return true;
        }

        private bool IsAlt(string s)
        {
            bool b;
            s = s.Trim();
            if (!IsInt(s) && !IsDouble(s))
                return false;
            double d = To_Double(s);
            // don't accept negative values though they might occur
            if (d < 0)
                return false;
            if ((Settings.Units_Alt.ToLower() == "m ") && (d > 30000))
                return false;
            // assuming altitude in feet
            if (d > 100000)
                return false;
            // all checks passed
            return true;
        }

        private bool IsTrack(string s)
        {
            bool b;
            s = s.Trim();
            if (!IsInt(s) && !IsDouble(s))
                return false;
            double d = To_Double(s);
            if (d < 0)
                return false;
            if (d > 360)
                return false;
            // all checks passed
            return true;
        }

        private bool IsSpeed(string s)
        {
            bool b;
            s = s.Trim();
            if (!IsInt(s) && !IsDouble(s))
                return false;
            double d = To_Double(s);
            if (d < 0)
                return false;
            // don't accept higher values though they might occur
            if ((Settings.Units_Speed.ToLower() == "km/h ") && (d > 1000))
                return false;
            // assuming speed in kts
            if (d > 550)
                return false;
            // all checks passed
            return true;
        }

        private bool IsReg(string s)
        {
            bool b;
            s = s.Trim();
            // Registration should contain a "-" or start with "N"
            if (!s.Contains("-"))
            {
                if (!s.StartsWith("N"))
                    return false;
            }
            return true;
        }

        private bool IsType(string s)
        {
            bool b;
            s = s.Trim();
            if (s.Length != 4)
                return false;
            // type should contain at least on number
            for (int i = 0; i < s.Length; i++)
            {
                if (char.IsNumber(s[i]))
                    return true;
            }
            return false; ;
        }

        #endregion

        #region PlaneInfoConverters

        public int To_Int(string s)
        {
            int i;
            if (int.TryParse(s, out i))
                return i;
            return int.MinValue;
        }

        public long To_Long(string s)
        {
            long l;
            if (long.TryParse(s, out l))
                return l;
            return long.MinValue;
        }

        public double To_Double(string s)
        {
            double d;
            if (double.TryParse(s, out d))
                return d;
            if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d))
                return d;
            return double.MinValue;
        }

        public DateTime To_UTC(string s)
        {
            if (String.IsNullOrEmpty(s))
                return DateTime.MinValue;
            // remove qoutes and spaces, if any
            s = s.Replace("\"", String.Empty).Trim();
            // UTC could be either:
            // a 32bit integer containing seconds since 1970-01-01  
            // a 64bit ticks containing ticks[ms] since 1970-01-01
            // a valid DateTime string, like "yyyy-MM-dd HH:mm:ss"
            // try to convert UNIX times first
            if ((s.Length >= 10) && IsInt(s))
            {
                try
                {
                    if (s.Length > 10)
                    {
                        // try to convert to milliseconds
                        // try to convert to seconds
                        long l = System.Convert.ToInt64(s);
                        DateTime timestamp = new System.DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
                        timestamp = timestamp.AddMilliseconds(l);
                        return timestamp;
                    }
                    else
                    {
                        // try to convert to seconds
                        long l = System.Convert.ToInt64(s);
                        DateTime timestamp = new System.DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
                        timestamp = timestamp.AddSeconds(l);
                        return timestamp;
                    }
                }
                catch (Exception ex)
                {
                    //                    Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
                }
            }
            // check for Standard DateTime notation
            try
            {
                // try to convert to UTC timestamp
                DateTime timestamp;
                if (DateTime.TryParse(s, out timestamp))
                    return timestamp;
            }
            catch (Exception ex)
            {
                //               Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return DateTime.MinValue;
        }

        public string To_Hex(string s)
        {
            if (String.IsNullOrEmpty(s))
                return null;
            // remove qoutes and spaces, if any
            s = s.Replace("\"", String.Empty).ToUpper().Trim();
            // check if Hex
            if (!IsHex(s))
                return null;
            try
            {
                // try to convert to Hex value
                long hex = System.Convert.ToInt64(s, 16);
                // check boundaries
                if ((hex < 0) || (hex > 16777215))
                    return null;
                return s;
            }
            catch (Exception ex)
            {
                //                Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return null;
        }

        public double To_Lat(string s)
        {
            s = s.Replace("\"", String.Empty).Trim();
            if (s.Length < 3)
                return double.MinValue;
            // double Lon must contain a decimal separator
            if (s.IndexOf(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) < 0)
                return double.MinValue;
            try
            {
                // try to convert to double
                double d = System.Convert.ToDouble(s);
                // check bounds
                if ((d >= -90.0) && (d <= 90.0))
                    return d;
            }
            catch (Exception ex)
            {
                //                Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return double.MinValue;
        }


        public double To_Lon(string s)
        {
            s = s.Replace("\"", String.Empty).Trim();
            if (s.Length < 3)
                return double.MinValue;
            // double Lon must contain a decimal separator
            if (s.IndexOf(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) < 0)
                return double.MinValue;
            try
            {
                // try to convert to double
                double d = System.Convert.ToDouble(s);
                // check bounds
                if ((d >= -180.0) && (d <= 180.0))
                    return d;
            }
            catch (Exception ex)
            {
                //                Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return double.MinValue;
        }


        public int To_Alt(string s)
        {
            s = s.Replace("\"", String.Empty).Trim();
            try
            {
                // try to convert to integer
                long alt = System.Convert.ToInt64(s);
                // convert m to ft
                if (Settings.Units_Alt.ToLower() == "m")
                    alt = (int)((double)alt * 3.28084);
                // check bounds
                if ((alt < 0) || (alt > 100000))
                    return int.MinValue;
                return (int)alt;
            }
            catch (Exception ex)
            {
                //               Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return int.MinValue;
        }

        public int To_Track(string s)
        {
            s = s.Replace("\"", String.Empty).Trim();
            try
            {
                // try to convert to integer
                long track = System.Convert.ToInt64(s);
                // check bounds
                if ((track < 0) || (track > 360))
                    return int.MinValue;
                return (int)track;
            }
            catch (Exception ex)
            {
                //                Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return int.MinValue;
        }

        public int To_Speed(string s)
        {
            s = s.Replace("\"", String.Empty).Trim();
            try
            {
                // try to convert to integer
                long speed = System.Convert.ToInt64(s);
                // convert km/h to kts
                if (Settings.Units_Speed.ToLower() == "km/h")
                    return (int)((double)speed / 1.852);
                if ((speed < 0) || (speed > 550))
                    return int.MinValue;
                return (int)speed;
            }
            catch (Exception ex)
            {
                //                Console.WriteLine("[" + System.Reflection.MethodBase.GetCurrentMethod().Name + "]" + ex.Message + ": " + s);
            }
            return int.MinValue;
        }

        public string To_Call(string s)
        {
            if (String.IsNullOrEmpty(s))
                return null;
            // remove qoutes and spaces, if any
            s = s.Replace("\"", String.Empty).ToUpper().Trim();
            return s;
        }

        public string To_Type(string s)
        {
            if (String.IsNullOrEmpty(s))
                return null;
            // remove qoutes and spaces, if any
            s = s.Replace("\"", String.Empty).ToUpper().Trim();
            return s;
        }

        public string To_Reg(string s)
        {
            if (String.IsNullOrEmpty(s))
                return null;
            // remove qoutes and spaces, if any
            s = s.Replace("\"", String.Empty).ToUpper().Trim();
            return s;
        }

        #endregion

        #region IndexFinders

        private void FindHexIndex()
        {
            if (PlaneList.Count <= 0)
                return;
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsHexColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsHexColumns[i] > maxcount)
                    {
                        maxcount = IsHexColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Hex = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindUTCIndex()
        {
            if (PlaneList.Count <= 0)
                return;
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsUTCColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsUTCColumns[i] > maxcount)
                    {
                        maxcount = IsUTCColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_UTC = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindLatIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsLatColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsLatColumns[i] > maxcount)
                    {
                        maxcount = IsLatColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Lat = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindLonIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsLonColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsLonColumns[i] > maxcount)
                    {
                        maxcount = IsLonColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Lon = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindTrackIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsTrackColumns.Length; i++)
            {
                if (!DetectedColumns[i] && (MaxColumns[i] <= 360))
                {
                    if (IsTrackColumns[i] > maxcount)
                    {
                        maxcount = IsTrackColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Track = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindSpeedIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsSpeedColumns.Length; i++)
            {
                if (!DetectedColumns[i] && (MaxColumns[i] > 360))
                {
                    if (IsSpeedColumns[i] > maxcount)
                    {
                        maxcount = IsSpeedColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Speed = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindAltIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsAltColumns.Length; i++)
            {
                if (!DetectedColumns[i] && (MeanColumns[i] > 5000))
                {
                    if (IsAltColumns[i] > maxcount)
                    {
                        maxcount = IsAltColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Alt = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindTypeIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsTypeColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsTypeColumns[i] > maxcount)
                    {
                        maxcount = IsTypeColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Type = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        private void FindRegIndex()
        {
            int maxindex = -1;
            int maxcount = 0;
            for (int i = 0; i < IsRegColumns.Length; i++)
            {
                if (!DetectedColumns[i])
                {
                    if (IsRegColumns[i] > maxcount)
                    {
                        maxcount = IsRegColumns[i];
                        maxindex = i;
                    }
                }
            }
            if (maxcount > (int)(0.9 * (double)PlaneList.Count))
            {
                Settings.Index_Reg = maxindex;
                DetectedColumns[maxindex] = true;
            }
        }

        #endregion

        // *********************** Parsing elements ************************************

        private class ElementInfo
        {
            public string Key;
            public string TypeName;
            public int Length;
            public dynamic Element;

            public ElementInfo(string key, string typename, int length, dynamic element)
            {
                Key = key;
                TypeName = typename;
                Length = length;
                Element = element;
            }
        }

        private List<ElementInfo> Elements = new List<ElementInfo>();
        private Dictionary<int, int> ElementLengths = new Dictionary<int, int>();
        private List<string[]> PlaneList = new List<string[]>();
        private double[] MaxColumns;
        private double[] MinColumns;
        private double[] MeanColumns;
        private int[] IsDoubleColumns;
        private int[] IsIntColumns;
        private int[] IsHexColumns;
        private int[] IsUTCColumns;
        private int[] IsLatColumns;
        private int[] IsLonColumns;
        private int[] IsAltColumns;
        private int[] IsTrackColumns;
        private int[] IsSpeedColumns;
        private int[] IsTypeColumns;
        private int[] IsRegColumns;
        private bool[] DetectedColumns;

        private void AddElement(string key, dynamic el)
        {
            string typename = el.GetType().Name;
            if (typename.ToLower().Contains("dictionary"))
            {
                // dictionary found
                // count the length and maintain element lengths
                int i = 0;
                int c = el.Values.Count;
                if (ElementLengths.TryGetValue(c, out i))
                {
                    ElementLengths[c]++;
                }
                else
                {
                    ElementLengths.Add(c, 1);
                }
                // add element to elements list
                ElementInfo info = new ElementInfo(key, typename, c, el);
                Elements.Add(info);
                // recurse element, key ist dictionary key
                foreach (dynamic e in el.Keys)
                {
                    AddElement(e, el[e]);
                }
            }
            else if (typename.ToLower().Contains("[]"))
            {
                // array found
                // add element to elements list
                // count the length and maintain element lengths
                int i = 0;
                int c = el.Length;
                if (ElementLengths.TryGetValue(c, out i))
                {
                    ElementLengths[c]++;
                }
                else
                {
                    ElementLengths.Add(c, 1);
                }
                ElementInfo info = new ElementInfo(key, typename, c, el);
                Elements.Add(info);
                // recurse elements, key is a unique ID 
                foreach (dynamic e in el)
                {
                    AddElement(Guid.NewGuid().ToString(), e);
                }
            }
            else if (typename.ToLower().Contains("arraylist"))
            {
                // ArrayList found
                // add element to elements list
                // count the length and maintain element lengths
                int i = 0;
                int c = el.Count;
                if (ElementLengths.TryGetValue(c, out i))
                {
                    ElementLengths[c]++;
                }
                else
                {
                    ElementLengths.Add(c, 1);
                }
                ElementInfo info = new ElementInfo(key, typename, c, el);
                Elements.Add(info);
                Console.WriteLine("Element found: " + info.Key + "; " + info.TypeName + "; " + c.ToString() + "; ", el.ToString());
                // recurse elements, key is a unique ID 
                foreach (dynamic e in el)
                {
                    AddElement(Guid.NewGuid().ToString(), e);
                }
            }
        }

        private string[] GetPlaneInfo(ElementInfo el)
        {
            string[] info = new string[el.Length + 1];
            // set key
            info[0] = el.Key;
            // grab info
            if (el.TypeName.ToLower().Contains("dictionary"))
            {
                // dictionary found
                int count = 1;
                foreach (dynamic e in el.Element.Values)
                {
                    if (e.GetType().IsPrimitive || (e.GetType().Name.ToLower() == "string"))
                    {
                        info[count] = e.ToString();
                    }
                    else
                    {
                        info[count] = "";
                    }
                    count++;
                }
            }
            else if (el.TypeName.ToLower().Contains("[]"))
            {
                // array found
                int count = 1;
                foreach (dynamic e in el.Element)
                {
                    string typename = e.GetType().Name;
                    if (e.GetType().IsPrimitive || (typename.ToLower() == "string") || (typename.ToLower() == "decimal"))
                    {
                        info[count] = e.ToString();
                    }
                    else
                    {
                        info[count] = "";
                    }
                    count++;
                }
            }
            else if (el.TypeName.ToLower().Contains("arraylist"))
            {
                // ArrayList found
                int count = 1;
                foreach (dynamic e in el.Element)
                {
                    string typename = e.GetType().Name;
                    if (e.GetType().IsPrimitive || (typename.ToLower() == "string") || (typename.ToLower() == "decimal"))
                    {
                        info[count] = e.ToString();
                    }
                    else
                    {
                        info[count] = "";
                    }
                    count++;
                }
            }
            return info;
        }

        private void GetColumnInfo()
        {
            double[] sum = new double[MinColumns.Length];
            for (int i = 0; i < sum.Length; i++)
                sum[i] = 0;
            foreach (string[] info in PlaneList)
            {
                if (info == null)
                    continue;
                for (int i = 0; i < info.Length; i++)
                {
                    if (IsDouble(info[i]))
                    {
                        IsDoubleColumns[i]++;
                        double d = To_Double(info[i]);
                        if (d != double.MinValue)
                        {
                            if (d < MinColumns[i])
                                MinColumns[i] = d;
                            else if (d > MaxColumns[i])
                                MaxColumns[i] = d;
                        }
                        sum[i] = sum[i] + d;
                    }
                    if (IsInt(info[i]))
                    {
                        IsIntColumns[i]++;
                        long l = To_Long(info[i]);
                        if (l != long.MinValue)
                        {
                            if (l < MinColumns[i])
                                MinColumns[i] = l;
                            else if (l > MaxColumns[i])
                                MaxColumns[i] = l;
                        }
                        sum[i] = sum[i] + l;
                    }
                    if (IsHex(info[i]))
                        IsHexColumns[i]++;
                    if (IsUTC(info[i]))
                        IsUTCColumns[i]++;
                    if (IsLat(info[i]))
                        IsLatColumns[i]++;
                    if (IsLon(info[i]))
                        IsLonColumns[i]++;
                    if (IsAlt(info[i]))
                        IsAltColumns[i]++;
                    if (IsTrack(info[i]))
                        IsTrackColumns[i]++;
                    if (IsSpeed(info[i]))
                        IsSpeedColumns[i]++;
                    if (IsType(info[i]))
                        IsTypeColumns[i]++;
                    if (IsReg(info[i]))
                        IsRegColumns[i]++;
                }
            }
            for (int i = 0; i < sum.Length; i++)
                MeanColumns[i] = sum[i] / PlaneList.Count();
        }

        private void GetAllPlaneInfos(dynamic obj)
        {
            // get all elements in a linear list and count the lengths
            Elements.Clear();
            ElementLengths.Clear();
            // start at the root element
            AddElement("", obj);
            // find the majority of element lengths --> assuming that these are the plane elements
            int maxcount = 0;
            int maxkey = 0;
            foreach (int key in ElementLengths.Keys)
            {
                if (ElementLengths[key] > maxcount)
                {
                    maxcount = ElementLengths[key];
                    maxkey = key;
                }
            }
            // get all plane infos in a clean list
            PlaneList.Clear();
            foreach (ElementInfo el in Elements)
            {
                // add matching elements to planes list
                if (el.Length == maxkey)
                {
                    string[] info = GetPlaneInfo(el);
                    PlaneList.Add(info);
                }
            }
            // store the plane i nfo table as CSV, so that a user can determine which columns at which indices are needed
            // catch the exception here to continue grabbing planes if user left thge file open :-)
            string csvfilename = Path.Combine(TmpDirectory, "FlexJSON.csv");
            try
            {
                using (StreamWriter sw = new StreamWriter(csvfilename))
                {
                    foreach (string[] infos in PlaneList)
                    {
                        foreach (string info in infos)
                            sw.Write("\"" + info + "\"" + ";");
                        sw.WriteLine();
                    }
                }
            }
            catch (Exception ex)
            {
                // do nothing
                Console.WriteLine("[" + this.GetType().Name + "]: Cannot write CSV-table " + csvfilename + ", " + ex.ToString());
            }
            // clear all info arrays
            MaxColumns = new double[maxkey + 1];
            MinColumns = new double[maxkey + 1];
            MeanColumns = new double[maxkey + 1];
            IsIntColumns = new int[maxkey + 1];
            IsDoubleColumns = new int[maxkey + 1];
            IsHexColumns = new int[maxkey + 1];
            IsUTCColumns = new int[maxkey + 1];
            IsLatColumns = new int[maxkey + 1];
            IsLonColumns = new int[maxkey + 1];
            IsAltColumns = new int[maxkey + 1];
            IsTrackColumns = new int[maxkey + 1];
            IsSpeedColumns = new int[maxkey + 1];
            IsTypeColumns = new int[maxkey + 1];
            IsRegColumns = new int[maxkey + 1];
            DetectedColumns = new bool[maxkey + 1];


            for (int i = 0; i < maxkey + 1; i++)
            {
                MaxColumns[i] = 0;
                MinColumns[i] = 0;
                MeanColumns[i] = 0;
                IsIntColumns[i] = 0;
                IsDoubleColumns[i] = 0;
                IsHexColumns[i] = 0;
                IsUTCColumns[i] = 0;
                IsLatColumns[i] = 0;
                IsAltColumns[i] = 0;
                IsTrackColumns[i] = 0;
                IsSpeedColumns[i] = 0;
                IsTypeColumns[i] = 0;
                IsRegColumns[i] = 0;
                DetectedColumns[i] = false;
            }
            // initially set all indices already found
            PropertyInfo[] properties = typeof(PlaneFeedPluginSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo p in properties)
            {
                // find all index properties
                if (p.Name.StartsWith("Index_") && (p.PropertyType == typeof(int)))
                {
                    int index = (int)p.GetValue(Settings, null);
                    // mark detected column as true
                    if ((index >= 0) && (index < DetectedColumns.Length))
                        DetectedColumns[index] = true;
                }
            }
            // get min/max info per column
            GetColumnInfo();
        }

        [System.Diagnostics.DebuggerNonUserCode]
        private string ReadPropertyString(dynamic o, string propertyname)
        {
            string s = null;
            try
            {
                s = o[propertyname];
            }
            catch
            {

            }
            return s;
        }



        [System.Diagnostics.DebuggerNonUserCode]
        private int ReadPropertyInt(dynamic o, string propertyname)
        {
            int i = int.MinValue;
            double d = ReadPropertyDouble(o, propertyname);
            if ((d != double.MinValue) && (d >= int.MinValue) && (d <= int.MaxValue))
                i = (int)d;
            return i;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        private double ReadPropertyDouble(dynamic o, string propertyname)
        {
            double d = double.MinValue;
            try
            {
                string s = o[propertyname].ToString(CultureInfo.InvariantCulture);
                d = double.Parse(s, CultureInfo.InvariantCulture);
            }
            catch
            {
                // do nothing if something went wrong
            }
            return d;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        private long ReadPropertyLong(dynamic o, string propertyname)
        {
            long l = long.MinValue;
            try
            {
                l = o[propertyname];
            }
            catch
            {
                // do nothing if something went wrong
            }
            return l;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        private bool ReadPropertyBool(dynamic o, string propertyname)
        {
            bool b = false;
            try
            {
                string s = o[propertyname].ToString();
                b = s.ToLower() == "true";
            }
            catch
            {
                // do nothing if something went wrong
            }
            return b;
        }

    }


    /// <summary>
    /// //////////////////////////////////////////// Helpers ////////////////////////////////////////////
    /// </summary>

    public static class UnitConverter
    {
        public static double ft_m(double feet)
        {
            return feet / 3.28084;
        }

        public static double m_ft(double m)
        {
            return m * 3.28084;
        }

        public static double kts_kmh(double kts)
        {
            return kts * 1.852;
        }

        public static double kmh_kts(double kmh)
        {
            return kmh / 1.852;
        }

        public static double km_mi(double km)
        {
            return km * 1.609;
        }

        public static double mi_km(double mi)
        {
            return mi / 1.609;
        }
    }

    public class VarConverter : Dictionary<string, object>
    {
        public readonly char VarSeparator = '%';

        public void AddVar(string var, object value)
        {
            // adds a new var<>value pair to dictionary
            object o;
            if (this.TryGetValue(var, out o))
            {
                // item found --> update value
                o = value;
            }
            else
            {
                // item not found --> add new
                this.Add(var, value);
            }
        }

        public object GetValue(string var)
        {
            // finds a var in dictionary and returns its value
            object o;
            if (this.TryGetValue(var, out o))
            {
                // item found --> return value
                return o;
            }
            // item not found --> return null
            return null;
        }

        public string ReplaceAllVars(string s)
        {
            // check for var separotors first
            if (s.Contains(VarSeparator))
            {
                // OK, string is containing vars --> crack the string first and replace vars
                try
                {
                    string[] a = s.Split(VarSeparator);
                    // as we are always using a pair of separators the length of a[] must be odd
                    if (a.Length % 2 == 0)
                        throw new ArgumentException("Number of separators is not an even number.");
                    // create new string and replace all vars (on odd indices)
                    s = "";
                    for (int i = 0; i < a.Length; i++)
                    {
                        if (i % 2 == 0)
                        {
                            // cannot be not a var on that position
                            s = s + a[i];
                        }
                        else
                        {
                            // var identifier: upper the string and try to convert
                            a[i] = a[i].ToUpper();
                            object o;
                            if (this.TryGetValue(a[i], out o))
                            {
                                // convert floating points with invariant culture info
                                if (o.GetType() == typeof(double))
                                    s = s + ((double)o).ToString(CultureInfo.InvariantCulture);
                                else if (o.GetType() == typeof(float))
                                    s = s + ((float)o).ToString(CultureInfo.InvariantCulture);
                                else
                                    s = s + o.ToString();
                            }
                            else
                            {
                                throw new ArgumentException("Var identifier not found: " + a[i]);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    // throw an excecption 
                    throw new ArgumentException("Error while parsing string for variables [" + ex.Message + "]: " + s);
                }
            }
            return s;
        }

    }

}

