
//    AirScout Aircraft Scatter Prediction
//    Copyright (C) DL2ALF
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using GMap.NET;
using GMap.NET.MapProviders;
using GMap.NET.WindowsForms;
using GMap.NET.WindowsForms.Markers;
using GMap.NET.WindowsForms.ToolTips;
using System.IO;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Zeptomoby.OrbitTools;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Configuration;
using WinTest;
using Renci.SshNet;
using System.Diagnostics;
using AquaControls;
using AirScout.Core;
using AirScout.PlaneFeeds.Generic;
using AirScout.Aircrafts;
using NDde;
using NDde.Server;
using NDde.Client;
using ScoutBase;
using ScoutBase.Core;
using ScoutBase.Elevation;
using ScoutBase.Stations;
using ScoutBase.Propagation;
using SerializableGenerics;
using Ionic.Zip;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SQLiteDatabase;
using System.Xml;
using System.Xml.Serialization;
using System.Security.Cryptography;
using OxyPlot;
using OxyPlot.WindowsForms;
using OxyPlot.Series;
using OxyPlot.Axes;
using System.Data.SQLite;
using AirScout.AircraftPositions;
using AirScout.Signals;

namespace AirScout
{


    public partial class MapDlg : Form
    {

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Application Directory")]
        public string AppDirectory
        {
            get
            {
                return Application.StartupPath.TrimEnd(Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Local Application Data Directory")]
        public string AppDataDirectory
        {
            get
            {
                return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Application.CompanyName, Application.ProductName).TrimEnd(Path.DirectorySeparatorChar);
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Logfile Directory")]
        public string LogDirectory
        {
            get
            {
                // get Property
                string logdir = Properties.Settings.Default.Log_Directory;
                // replace Windows/Linux directory spearator chars
                logdir = logdir.Replace('\\', Path.DirectorySeparatorChar);
                logdir = logdir.Replace('/', Path.DirectorySeparatorChar);
                // set to default value if empty
                if (String.IsNullOrEmpty(logdir))
                    logdir = "Log";
                // replace variables, if any
                logdir = VC.ReplaceAllVars(logdir);
                // remove directory separator chars at begin and end
                logdir = logdir.TrimStart(Path.DirectorySeparatorChar);
                logdir = logdir.TrimEnd(Path.DirectorySeparatorChar);
                // fully qualify path
                if (!logdir.Contains(Path.VolumeSeparatorChar))
                    logdir = Path.Combine(AppDataDirectory, logdir);
                return logdir;
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Tempfile Directory")]
        public string TmpDirectory
        {
            get
            {
                // get Property
                string tmpdir = Properties.Settings.Default.Tmp_Directory;
                // replace Windows/Linux directory spearator chars
                tmpdir = tmpdir.Replace('\\', Path.DirectorySeparatorChar);
                tmpdir = tmpdir.Replace('/', Path.DirectorySeparatorChar);
                // set to default value if empty
                if (String.IsNullOrEmpty(tmpdir))
                    tmpdir = "Tmp";
                // replace variables, if any
                tmpdir = VC.ReplaceAllVars(tmpdir);
                // remove directory separator chars at begin and end
                tmpdir = tmpdir.TrimStart(Path.DirectorySeparatorChar);
                tmpdir = tmpdir.TrimEnd(Path.DirectorySeparatorChar);
                // fully qualify path
                if (!tmpdir.Contains(Path.VolumeSeparatorChar))
                    tmpdir = Path.Combine(AppDataDirectory, tmpdir);
                return tmpdir;
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Database Directory")]
        public string DatabaseDirectory
        {
            get
            {
                // get Property
                string databasedir = Properties.Settings.Default.Database_Directory;
                // replace Windows/Linux directory spearator chars
                databasedir = databasedir.Replace('\\', Path.DirectorySeparatorChar);
                databasedir = databasedir.Replace('/', Path.DirectorySeparatorChar);
                // set to default value if empty
                if (String.IsNullOrEmpty(databasedir))
                    databasedir = "Database";
                // replace variables, if any
                databasedir = VC.ReplaceAllVars(databasedir);
                // remove directory separator chars at begin and end
                databasedir = databasedir.TrimStart(Path.DirectorySeparatorChar);
                databasedir = databasedir.TrimEnd(Path.DirectorySeparatorChar);
                // fully qualify path
                if (!databasedir.Contains(Path.VolumeSeparatorChar))
                    databasedir = Path.Combine(AppDataDirectory, databasedir);
                return databasedir;
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Icon Directory")]
        public string IconDirectory
        {
            get
            {
                // get Property
                string icondir = Properties.Settings.Default.Icon_Directory;
                // replace Windows/Linux directory spearator chars
                icondir = icondir.Replace('\\', Path.DirectorySeparatorChar);
                icondir = icondir.Replace('/', Path.DirectorySeparatorChar);
                // set to default value if empty
                if (String.IsNullOrEmpty(icondir))
                    icondir = "Icon";
                // replace variables, if any
                icondir = VC.ReplaceAllVars(icondir);
                // remove directory separator chars at begin and end
                icondir = icondir.TrimStart(Path.DirectorySeparatorChar);
                icondir = icondir.TrimEnd(Path.DirectorySeparatorChar);
                // fully qualify path
                if (!icondir.Contains(Path.VolumeSeparatorChar))
                    icondir = Path.Combine(AppDataDirectory, icondir);
                return icondir;
            }
        }

        [CategoryAttribute("Directories")]
        [DescriptionAttribute("Elevation Directory")]
        public string ElevationDirectory
        {
            get
            {
                // get Property
                string elevationdir = Properties.Settings.Default.Elevation_Directory;
                // replace Windows/Linux directory spearator chars
                elevationdir = elevationdir.Replace('\\', Path.DirectorySeparatorChar);
                elevationdir = elevationdir.Replace('/', Path.DirectorySeparatorChar);
                // set to default value if empty
                if (String.IsNullOrEmpty(elevationdir))
                    elevationdir = "elevation";
                // replace variables, if any
                elevationdir = VC.ReplaceAllVars(elevationdir);
                // remove directory separator chars at begin and end
                elevationdir = elevationdir.TrimStart(Path.DirectorySeparatorChar);
                elevationdir = elevationdir.TrimEnd(Path.DirectorySeparatorChar);
                // fully qualify path
                if (!elevationdir.Contains(Path.VolumeSeparatorChar))
                    elevationdir = Path.Combine(AppDataDirectory, elevationdir);
                return elevationdir;
            }
        }

        public static LogWriter Log;

        public VarConverter VC = new VarConverter();

        private int cntdn = 0;

        DateTime CurrentTime = DateTime.UtcNow;

        public bool ForceClose = false;


        // FlightRadar
        PlaneInfoCache Planes = new PlaneInfoCache();
        SortedList<string, PlaneInfo> ActivePlanes = new SortedList<string, PlaneInfo>();
        private DateTime History_OldestEntry = DateTime.MinValue;
        private DateTime History_YoungestEntry = DateTime.MinValue;
        private List<string> SelectedPlanes = new List<string>();

        // Path
        public List<ElevationPathDesignator> ElevationPaths = new List<ElevationPathDesignator>();
        public List<PropagationPathDesignator> PropagationPaths = new List<PropagationPathDesignator>();

        // Map
        // Overlays
        GMapOverlay gmo_Routes = new GMapOverlay("Routes");
        GMapOverlay gmo_PropagationPaths = new GMapOverlay("PropagationPaths");
        GMapOverlay gmo_Objects = new GMapOverlay("Objects");
        GMapOverlay gmo_Planes = new GMapOverlay("Planes");
        GMapOverlay gmo_Airports = new GMapOverlay("Airports");
        GMapOverlay gmo_Callsigns = new GMapOverlay("Callsigns");
        GMapOverlay gmo_NearestPaths = new GMapOverlay("PropagationPaths");
        GMapOverlay gmo_NearestPlanes = new GMapOverlay("Planes");

        // Routes
        GMapRoute gmr_FullPath;
        GMapRoute gmr_VisiblePpath;
        GMapRoute gmr_NearestFull;
        GMapRoute gmr_NearestVisible;

        // Markers
        GMapMarker gmm_MyLoc;
        GMapMarker gmm_DXLoc;
        GMapMarker gmm_CurrentMarker;
        bool isDraggingMarker;

        // Bitmap indices
        private int bmindex_darkorange;
        private int bmindex_lightgreen;
        private int bmindex_red;
        private int bmindex_gray;
        private int bmindex_magenta;

        // Tooltip font
        public Font ToolTipFont;

        // right control boxes
        int gb_Map_Info_Width = 0;

        int gb_Map_Info_MaximizedHeight = 0;
        int gb_Map_Zoom_MaximizedHeight = 0;
        int gb_Map_Filter_MaximizedHeigth = 0;
        int gb_Map_Alarms_MaximizedHeight = 0;
        int gb_Map_Info_MinimizedHeight = 0;
        int gb_Map_Zoom_MinimizedHeight = 0;
        int gb_Map_Filter_MinimizedHeigth = 0;
        int gb_Map_Alarms_MinimizedHeight = 0;

        int CurrentMapSplitterDistance = 0;

        // Plane feeds
        public ArrayList PlaneFeeds;
        public PlaneFeed bw_PlaneFeed1;
        public PlaneFeed bw_PlaneFeed2;
        public PlaneFeed bw_PlaneFeed3;

        // Startup
        private bool FirstRun = true;
        private bool CleanRun = false;

        private Splash SplashDlg = new Splash();

        // Background workers
        public ElevationDatabaseUpdater bw_GLOBEUpdater = new ElevationDatabaseUpdater();
        public ElevationDatabaseUpdater bw_SRTM3Updater = new ElevationDatabaseUpdater();
        public ElevationDatabaseUpdater bw_SRTM1Updater = new ElevationDatabaseUpdater();

        public StationDatabaseUpdater bw_StationDatabaseUpdater = new StationDatabaseUpdater();
        public AircraftDatabaseUpdater bw_AircraftDatabaseUpdater = new AircraftDatabaseUpdater();

        public AircraftPositionDatabaseMaintainer bw_AircraftDatabaseMaintainer = new AircraftPositionDatabaseMaintainer();

        public PathCalculator bw_GLOBEPathCalculator = new PathCalculator(ELEVATIONMODEL.GLOBE);
        public PathCalculator bw_SRTM3PathCalculator = new PathCalculator(ELEVATIONMODEL.SRTM3);
        public PathCalculator bw_SRTM1PathCalculator = new PathCalculator(ELEVATIONMODEL.SRTM1);

        // Modes
        AIRSCOUTPATHMODE PathMode = AIRSCOUTPATHMODE.NONE;
        AIRSCOUTLIFEMODE LifeMode = AIRSCOUTLIFEMODE.NONE;
        AIRSCOUTPLAYMODE PlayMode = AIRSCOUTPLAYMODE.NONE;

        private int Time_Offline_Interval = 0;

        // charting

        // path chart
        PlotModel pm_Path = new PlotModel();
        PlotView pv_Path = new PlotView();
        LinearAxis Path_X = new LinearAxis();
        LinearAxis Path_Y = new LinearAxis();
        AreaSeries Path_Elevation = new AreaSeries();
        LineSeries Min_H1 = new LineSeries();
        LineSeries Min_H2 = new LineSeries();
        LineSeries Max_H = new LineSeries();
        AreaSeries Min_H = new AreaSeries();
        LineSeries Planes_Hi = new LineSeries();
        LineSeries Planes_Lo = new LineSeries();

        // elevation chart
        PlotModel pm_Elevation = new PlotModel();
        PlotView pv_Elevation = new PlotView();
        LinearAxis Elevation_X = new LinearAxis();
        LinearAxis Elevation_Y = new LinearAxis();
        AreaSeries Elevation = new AreaSeries();
        LineSeries LOS = new LineSeries();

        // spectrum chart
        PlotModel pm_Spectrum = new PlotModel();
        PlotView pv_Spectrum = new PlotView();
        LinearAxis Spectrum_X = new LinearAxis();
        LinearAxis Spectrum_Y = new LinearAxis();
        LineSeries Spectrum = new LineSeries();
        AreaSeries SpectrumRecord = new AreaSeries();
        int SpectrumPointsCount = 0;
        int SpectrumMaxPoints = 600;

        // webbrowser
        private System.Windows.Forms.WebBrowser wb_News = null;

        CultureInfo LocalCulture;

        // Watchlist control
        private bool WatchlistUpdating = false;
        private bool WatchlistAllCheckedChanging = false;
        bool WatchlistAllChecked = false;
        CheckBoxState WatchlistAllCheckedState = CheckBoxState.UncheckedNormal;
        System.Drawing.Point WatchlistOldMousePos = new System.Drawing.Point(0, 0);

        // Analysis
        private List<DateTime> AllLastUpdated = new List<DateTime>();
        private List<AircraftPositionDesignator> AllPositions = new List<AircraftPositionDesignator>();
        private long AircraftPositionsCount = 0;

        // nearest plane
        private PlaneInfo NearestPlane = null;

        // Airports
        private List<AirportDesignator> Airports = new List<AirportDesignator>();

        #region Startup & Initialization

        public MapDlg()
        {
            // save current local LocalCulture
            LocalCulture = Application.CurrentCulture;

            // force culture invariant language for GUI
            Application.CurrentCulture = CultureInfo.InvariantCulture;

            InitializeComponent();

            // do basic initialization
            this.Text = "AirScout - Aircraft Scatter Prediction V" + Application.ProductVersion + " (c) 2013-2018 DL2ALF";

            // create a new renderer wich is clipping the status text on overflow
            ss_Main.Renderer = new ClippingToolStripRenderer();

            // initialize settings
            InitializeSettings();
            // initialize charting
            InitializeCharts();
            // Initilialize Webbrowser
            InitializeWebbrowser();

            // check for command line args
            string[] args = Environment.GetCommandLineArgs();
            if ((args != null) && (args.Count() > 1))
            {
                // try to clean installation & database
                if ((args[1].ToUpper() == "/CLEAN") ||
                    (args[1].ToUpper() == "-CLEAN"))
                {
                    CleanupDlg Dlg = new CleanupDlg(this);
                    DialogResult result = Dlg.ShowDialog();
                    // exit immediately if user closes the form without pressing a button
                    if (result == DialogResult.Abort)
                        System.Environment.Exit(-1);
                    if (result == DialogResult.OK)
                        CleanRun = true;
                }
            }
            // set elevation database update event handler
            bw_GLOBEUpdater.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationDatabaseUpdater_ProgressChanged);
            bw_SRTM3Updater.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationDatabaseUpdater_ProgressChanged);
            bw_SRTM1Updater.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationDatabaseUpdater_ProgressChanged);

            // set station database updater event handler
            bw_StationDatabaseUpdater.ProgressChanged += new ProgressChangedEventHandler(bw_StationDatabaseUpdater_ProgressChanged);
            bw_StationDatabaseUpdater.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_StationDatabaseUpdater_RunWorkerCompleted);

            // set aircraft database updater event handler
            bw_AircraftDatabaseUpdater.ProgressChanged += new ProgressChangedEventHandler(bw_AircraftDatabaseUpdater_ProgressChanged);
            bw_AircraftDatabaseUpdater.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_AircraftDatabaseUpdater_RunWorkerCompleted);

            // set aircraft database maintainer event handler
            bw_AircraftDatabaseMaintainer.ProgressChanged += new ProgressChangedEventHandler(bw_AircraftDatabaseMaintainer_ProgressChanged);
            bw_AircraftDatabaseMaintainer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_AircraftDatabaseMaintainer_RunWorkerCompleted);

            // set elevation path calculator event handler
            bw_GLOBEPathCalculator.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationPathCalculator_ProgressChanged);
            bw_SRTM3PathCalculator.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationPathCalculator_ProgressChanged);
            bw_SRTM1PathCalculator.ProgressChanged += new ProgressChangedEventHandler(bw_ElevationPathCalculator_ProgressChanged);
            // save FirstRun property before trying to upgrade user settings
            FirstRun = Properties.Settings.Default.FirstRun;
        }

        public void CheckDirectories()
        {
            // check if directories exist
            if (!Directory.Exists(TmpDirectory))
                Directory.CreateDirectory(TmpDirectory);
            if (!Directory.Exists(LogDirectory))
                Directory.CreateDirectory(LogDirectory);
            if (!Directory.Exists(IconDirectory))
                Directory.CreateDirectory(IconDirectory);
            if (!Directory.Exists(DatabaseDirectory))
                Directory.CreateDirectory(DatabaseDirectory);
            if (!Directory.Exists(ElevationDirectory))
                Directory.CreateDirectory(ElevationDirectory);
        }

        /// <summary>
        /// Returns the default value of a property
        /// </summary>
        /// <param name="propertyname">The property name.</param>
        /// <returns>The property value.</returns>
        public static dynamic GetPropertyDefaultValue(string propertyname)
        {
            string p = (string)Properties.Settings.Default.Properties[propertyname].DefaultValue;
            Type t = Properties.Settings.Default.Properties[propertyname].PropertyType;
            return Convert.ChangeType(p, t);
        }

        private void InitializeSettings()
        {
            // check for invalid settings
            // check if band is BNONE --> set to 1.2G
            if (Properties.Settings.Default.Band == BAND.BNONE)
                Properties.Settings.Default.Band = BAND.B1_2G;
            // chekc if band settings are NULL --> set to default
            if (Properties.Settings.Default.Path_Band_Settings == null)
                Properties.Settings.Default.Path_Band_Settings = new BandSettings(true);
            // check if watchlist in null --> create new
            if (Properties.Settings.Default.Watchlist == null)
                Properties.Settings.Default.Watchlist = new Watchlist();
            string location = StationData.Database.GetDBLocation();
            // check calls
            if (String.IsNullOrEmpty(Properties.Settings.Default.MyCall))
                Properties.Settings.Default.MyCall = GetPropertyDefaultValue(nameof(Properties.Settings.Default.MyCall));
            if (String.IsNullOrEmpty(Properties.Settings.Default.DXCall))
                Properties.Settings.Default.DXCall = GetPropertyDefaultValue(nameof(Properties.Settings.Default.DXCall));
            // check lat/lon
            if (!GeographicalPoint.Check(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon))
            {
                Properties.Settings.Default.MyLat = GetPropertyDefaultValue(nameof(Properties.Settings.Default.MyLat));
                Properties.Settings.Default.MyLon = GetPropertyDefaultValue(nameof(Properties.Settings.Default.MyLon));
            }
            if (!GeographicalPoint.Check(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon))
            {
                Properties.Settings.Default.DXLat = GetPropertyDefaultValue(nameof(Properties.Settings.Default.DXLat));
                Properties.Settings.Default.DXLon = GetPropertyDefaultValue(nameof(Properties.Settings.Default.DXLon));
            }
            // set current elevation model
            SetElevationModel();
            // set antenna height to 10m be default
            Properties.Settings.Default.MyHeight = 10;
            Properties.Settings.Default.DXHeight = 10;
        }

        private void CheckSettings()
        {
            Log.WriteMessage("Checking properties...");
            // check for empty MyCalls list
            if (Properties.Settings.Default.MyCalls == null)
            {
                Properties.Settings.Default.MyCalls = new List<string>();
                Properties.Settings.Default.MyCalls.Add("DL2ALF");
            }
            if (Properties.Settings.Default.MyCalls.Count == 0)
                Properties.Settings.Default.MyCalls.Add("DL2ALF");
            // list all properties in log
            foreach (SettingsPropertyValue p in Properties.Settings.Default.PropertyValues)
            {
                if ((p != null) && (p.Name != null))
                {
                    string value = p.PropertyValue != null ? p.PropertyValue.ToString() : "[null]";
                    Log.WriteMessage("Checking property " + p.Name + " = " + value);
                }
            }
            /*
            // check URLs for trailing separator
            if (!Properties.Settings.Default.Elevation_GLOBE_URL.EndsWith("/"))
                Properties.Settings.Default.Elevation_GLOBE_URL = Properties.Settings.Default.Elevation_GLOBE_URL + "/";
            if (!Properties.Settings.Default.Elevation_SRTM3_URL.EndsWith("/"))
                Properties.Settings.Default.Elevation_SRTM3_URL = Properties.Settings.Default.Elevation_SRTM3_URL + "/";
            if (!Properties.Settings.Default.Elevation_SRTM1_URL.EndsWith("/"))
                Properties.Settings.Default.Elevation_SRTM1_URL = Properties.Settings.Default.Elevation_SRTM1_URL + "/";
            if (!Properties.Settings.Default.StationDatabase_Update_URL.EndsWith("/"))
                Properties.Settings.Default.StationDatabase_Update_URL = Properties.Settings.Default.StationDatabase_Update_URL + "/";
            */
            Log.WriteMessage("Checking properties finished...");
        }

        private void CheckInternet()
        {
            // check internet connectivity
            try
            {
                using (var client = new WebClient())
                {
                    string url = Properties.Settings.Default.Connectivity_URL;
                    using (var stream = client.OpenRead(url))
                    {
                    }
                }
            }
            catch
            {
                MessageBox.Show("Could not find an internet connection.\nThis is required on first run.\nAirScout will close.", "Internet connectivity");
                this.Close();
            }
        }

        private void InitializeLogfile()
        {
            // set directories and formats for logfile
            ScoutBase.Core.Properties.Settings.Default.LogWriter_Directory = LogDirectory;
            ScoutBase.Core.Properties.Settings.Default.LogWriter_FileFormat = "AirScout_{0:yyyy_MM_dd}.log";
            ScoutBase.Core.Properties.Settings.Default.LogWriter_MessageFormat = "{0:u} {1}";
            // gets an instance of a LogWriter
            Log = LogWriter.Instance;
            Log.WriteMessage("-------------------------------------------------------------------------------------");
            Log.WriteMessage(Application.ProductName + " is starting up.", 0, false);
            Log.WriteMessage("-------------------------------------------------------------------------------------");
            Log.WriteMessage("Operating system          : " + Environment.OSVersion.Platform);
            Log.WriteMessage("Application directory     : " + AppDirectory);
            Log.WriteMessage("Application data directory: " + AppDataDirectory);
            Log.WriteMessage("Log directory             : " + LogDirectory);
            Log.WriteMessage("Temp directory            : " + TmpDirectory);
            Log.WriteMessage("Database directory        : " + DatabaseDirectory);
            Log.WriteMessage("Elevation directory       : " + ElevationDirectory);
            Log.WriteMessage("Application settings file : " + AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            Log.WriteMessage("User settings file        : " + GetUserSettingsPath());
            Log.WriteMessage("Disk space available      : " + SupportFunctions.GetDriveAvailableFreeSpace(Path.GetPathRoot(AppDataDirectory)) / 1024 / 1024 + " MB");
            Log.WriteMessage("-------------------------------------------------------------------------------------");
        }

        private void InitializeDatabase()
        {
            Log.WriteMessage("Started.");
            SetElevationModel();
            // reset database status
            Properties.Settings.Default.Elevation_GLOBE_DatabaseStatus = DATABASESTATUS.UNDEFINED;
            Properties.Settings.Default.Elevation_SRTM3_DatabaseStatus = DATABASESTATUS.UNDEFINED;
            Properties.Settings.Default.Elevation_SRTM1_DatabaseStatus = DATABASESTATUS.UNDEFINED;
            // set nearfield suppression
            PropagationData.Database.NearFieldSuppression = Properties.Settings.Default.Path_NearFieldSuppression;
            Log.WriteMessage("Finished.");
        }

        private void MapDlg_Load(object sender, EventArgs e)
        {
            try
            {
                // Show splash screen
                SplashDlg.Show();
                // bring window to front
                SplashDlg.BringToFront();
                Application.DoEvents();
                // Check directories, complete it and create, if not exist
                SplashDlg.Status("Checking directories...");
                CheckDirectories();
                // start a log, specify format of logfile and entries
                SplashDlg.Status("Initializing logfile...");
                InitializeLogfile();
                // Check properties
                SplashDlg.Status("Checking settings...");
                CheckSettings();
                // get AirScout password phrase
                InitializePassword();
                // Initialize database
                SplashDlg.Status("Initializing database...");
                InitializeDatabase();
                // initialize icons
                SplashDlg.Status("Creating icons...");
                InitializeIcons();
                ToolTipFont = CreateFontFromString(Properties.Settings.Default.Map_ToolTipFont);
                SplashDlg.Status("Loading Map...");

                // initialize map
                // setting User Agent to fix Open Street Map issue 2016-09-20
                GMap.NET.MapProviders.GMapProvider.UserAgent = "AirScout";
                // set initial settings for main map
                gm_Main.MapProvider = GMapProviders.Find(Properties.Settings.Default.Map_Provider);
                gm_Main.IgnoreMarkerOnMouseWheel = true;
                gm_Main.MinZoom = 0;
                gm_Main.MaxZoom = 20;
                gm_Main.Zoom = 6;
                gm_Main.DragButton = System.Windows.Forms.MouseButtons.Left;
                gm_Main.CanDragMap = true;
                gm_Main.ScalePen = new Pen(Color.Black, 3);
                gm_Main.MapScaleInfoEnabled = true;
                gm_Main.Overlays.Add(gmo_Airports);
                gm_Main.Overlays.Add(gmo_Callsigns);
                gm_Main.Overlays.Add(gmo_PropagationPaths);
                gm_Main.Overlays.Add(gmo_Routes);
                gm_Main.Overlays.Add(gmo_Objects);
                gm_Main.Overlays.Add(gmo_Planes);

                // setting User Agent to fix Open Street Map issue 2016-09-20
                GMap.NET.MapProviders.GMapProvider.UserAgent = "AirScout";
                // set initial settings for main map
                gm_Nearest.MapProvider = GMapProviders.Find(Properties.Settings.Default.Map_Provider);
                gm_Nearest.IgnoreMarkerOnMouseWheel = true;
                gm_Nearest.MinZoom = 0;
                gm_Nearest.MaxZoom = 20;
                gm_Nearest.Zoom = 6;
                gm_Nearest.DragButton = System.Windows.Forms.MouseButtons.Left;
                gm_Nearest.CanDragMap = true;
                gm_Nearest.ScalePen = new Pen(Color.Black, 3);
                gm_Nearest.MapScaleInfoEnabled = true;
                gm_Nearest.Overlays.Add(gmo_NearestPaths);
                gm_Nearest.Overlays.Add(gmo_NearestPlanes);
                gm_Nearest.Position = new PointLatLng(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);

                cntdn = Properties.Settings.Default.Planes_Update;
                btn_Map_PlayPause.Select();

                // intially fill dialog box elements and set band
                string[] bands = Bands.GetStringValuesExceptNoneAndAll();
                foreach (string b in bands)
                    cb_Band.Items.Add(b);
                BAND band = Properties.Settings.Default.Band;
                cb_Band.SelectedItem = Bands.GetStringValue(band);
                string[] cats = PlaneCategories.GetStringValues();
                foreach (string cat in cats)
                    cb_Planes_Filter_Min_Cat.Items.Add(cat);
                // initialize gauge controls
                ag_Azimuth.fromAngle = -90;
                ag_Azimuth.toAngle = 270;

                ag_Elevation.fromAngle = 180;
                ag_Elevation.toAngle = 270;

                // install additional mouse events
                gb_Map_Info.MouseClick += new MouseEventHandler(this.gb_Map_Info_MouseClick);
                gb_Map_Zoom.MouseClick += new MouseEventHandler(this.gb_Map_Zoom_MouseClick);
                gb_Map_Filter.MouseClick += new MouseEventHandler(this.gb_Map_Filter_MouseClick);
                gb_Map_Alarms.MouseClick += new MouseEventHandler(this.gb_Map_Alarms_MouseClick);

                // get installed plane feeds
                PlaneFeeds = new PlaneFeedEnumeration().EnumFeeds();

                // set planefeed event handler
                foreach (PlaneFeed feed in PlaneFeeds)
                {
                    feed.ProgressChanged += new ProgressChangedEventHandler(bw_PlaneFeed_ProgressChanged);
                }

                // select feeds
                foreach (PlaneFeed feed in PlaneFeeds)
                {
                    if (Properties.Settings.Default.Planes_PlaneFeed1 == feed.Name)
                        bw_PlaneFeed1 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed2 == feed.Name)
                        bw_PlaneFeed2 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed3 == feed.Name)
                        bw_PlaneFeed3 = feed;
                }

                // update image list sizes
                il_Planes_L.ImageSize = new System.Drawing.Size(Properties.Settings.Default.Planes_IconSize_L, Properties.Settings.Default.Planes_IconSize_L);
                il_Planes_M.ImageSize = new System.Drawing.Size(Properties.Settings.Default.Planes_IconSize_M, Properties.Settings.Default.Planes_IconSize_M);
                il_Planes_H.ImageSize = new System.Drawing.Size(Properties.Settings.Default.Planes_IconSize_H, Properties.Settings.Default.Planes_IconSize_H);
                il_Planes_S.ImageSize = new System.Drawing.Size(Properties.Settings.Default.Planes_IconSize_S, Properties.Settings.Default.Planes_IconSize_S);

                // try to upgrade user settings from prevoius version on first run
                try
                {
                    if (FirstRun)
                    {
                        Log.WriteMessage("Preparing for first run.");
                        // try to ugrade settings when not started with /CLEAN option
                        if (!CleanRun)
                        {
                            // Mono hack to assure that default values were initilaized
                            if (SupportFunctions.IsMono)
                            {
                                Properties.Settings.Default.Reset();
                                SaveUserSettings();
                                Properties.Settings.Default.Reload();
                            }
                            Log.WriteMessage("Upgrading settings.");
                            Properties.Settings.Default.Upgrade();
                            // handle skip to version 1.3.0.0
                            if (String.IsNullOrEmpty(Properties.Settings.Default.Version) || (String.Compare(Properties.Settings.Default.Version, "1.3.0.0") < 0))
                            {
                                /*
                                // reset elevation data url to new default values
                                Properties.Settings.Default.Elevation_GLOBE_URL = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_GLOBE_URL));
                                Properties.Settings.Default.Elevation_SRTM3_URL = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_SRTM3_URL));
                                Properties.Settings.Default.Elevation_SRTM1_URL = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_SRTM1_URL));
                                // reset elevation data path to default values
                                Properties.Settings.Default.Elevation_GLOBE_DataPath = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_GLOBE_DataPath));
                                Properties.Settings.Default.Elevation_SRTM3_DataPath = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_SRTM3_DataPath));
                                Properties.Settings.Default.Elevation_SRTM1_DataPath = GetPropertyDefaultValue(nameof(Properties.Settings.Default.Elevation_SRTM1_DataPath));
                                // reset stations data url to its default value
                                Properties.Settings.Default.StationDatabase_Update_URL = GetPropertyDefaultValue(nameof(Properties.Settings.Default.StationDatabase_Update_URL));
                                */

                                Properties.Settings.Default.Version = Application.ProductVersion;

                                SaveUserSettings();
                            }
                            AirScout.PlaneFeeds.Properties.Settings.Default.Upgrade();
                        }
                        CheckDirectories();
                        CheckSettings();
                        // get passphrase again
                        InitializePassword();
                        // reset topmost state
                        SplashDlg.TopMost = false;
                        // must have internet connection on FirstRun
                        CheckInternet();
                        /*
                        // run database updater once for basic information
                        bw_DatabaseUpdater.RunWorkerAsync(UPDATERSTARTOPTIONS.FIRSTRUN);
                        // wait till finished
                        while (bw_DatabaseUpdater.IsBusy)
                            Application.DoEvents();
                        */
                        SplashDlg.Close();
                        // show FirstRunWizard
                        try
                        {
                            FirstRunWizard Dlg = new FirstRunWizard();
                            if (Dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
                            {
                                Log.WriteMessage("Aborting FirstRunWizard.");
                                // flush the log and exit immediately
                                Log.FlushLog();
                                System.Environment.Exit(-1);
                            }
                            else
                            {
                                // reset FirstRun property
                                Properties.Settings.Default.FirstRun = false;
                                Properties.Settings.Default.FirstRun = Properties.Settings.Default.FirstRun;
                                // set privacy statements (for legacy)
                                Properties.Settings.Default.First_Agree = true;
                                Properties.Settings.Default.First_Disagree = false;
                                Properties.Settings.Default.First_Privacy = false;

                                // save settings
                                SaveUserSettings();
                            }
                        }
                        catch (Exception ex)
                        {
                            Log.WriteMessage(ex.Message);
                            // flush the log and exit immediately
                            Log.FlushLog();
                            System.Environment.Exit(-1);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                }
                // refresh the Layout
                OnSizeChanged(null);
                // get initial width of boxes
                gb_Map_Info_Width = gb_Map_Info.Width;

                // get initial height of boxes
                gb_Map_Info_MinimizedHeight = gb_Map_Info.Height - gb_Map_Info.DisplayRectangle.Height;
                gb_Map_Zoom_MinimizedHeight = gb_Map_Zoom.Height - gb_Map_Zoom.DisplayRectangle.Height;
                gb_Map_Filter_MinimizedHeigth = gb_Map_Filter.Height - gb_Map_Filter.DisplayRectangle.Height;
                gb_Map_Alarms_MinimizedHeight = gb_Map_Alarms.Height - gb_Map_Alarms.DisplayRectangle.Height;
                gb_Map_Info_MaximizedHeight = gb_Map_Info.Height;
                gb_Map_Zoom_MaximizedHeight = gb_Map_Zoom.Height;
                gb_Map_Filter_MaximizedHeigth = gb_Map_Filter.Height;
                gb_Map_Alarms_MaximizedHeight = gb_Map_Alarms.Height;

                // correct splitter values if default
                if (Properties.Settings.Default.MainSplitter_Distance <= 0)
                    Properties.Settings.Default.MainSplitter_Distance = this.Width - gb_Map_Info_Width;
                if (Properties.Settings.Default.MapSplitter_Distance <= 0)
                    Properties.Settings.Default.MapSplitter_Distance = this.Height - 300;

                // check directories and settings for missing values
                CheckDirectories();
                CheckSettings();

                // select plane feeds again
                foreach (PlaneFeed feed in PlaneFeeds)
                {
                    if (Properties.Settings.Default.Planes_PlaneFeed1 == feed.Name)
                        bw_PlaneFeed1 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed2 == feed.Name)
                        bw_PlaneFeed2 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed3 == feed.Name)
                        bw_PlaneFeed3 = feed;
                }

                // start permanent background workers
                StartAllBackgroundWorkers();

                // start main timer
                ti_Progress.Start();

                FirstRun = false;

                // check if a vaild feed is on
                if ((bw_PlaneFeed1 == null) && (bw_PlaneFeed2 == null) && (bw_PlaneFeed3 == null))
                    MessageBox.Show("Plane Feeds are disabled. \n\nYou can use the software anyway, but you will never see a plane on the map. \nIn order to get valid plane positions do the following:\n\n1. Go to the \"Options/Plane\" tab and activate at least one plane feed\n2. Go to the \"Options/General\" tab and adjust \"Planes Positions Range\" to a suitable area around your location", "Plane Feeds Disabled");

                // invalidate tracking values
                Properties.Settings.Default.Track_SetAz = double.NaN;
                Properties.Settings.Default.Track_SetEl = double.NaN;

                // set online mode by default
                Properties.Settings.Default.Time_Mode_Online = true;

                // install OnIdle event handler
                // must be here at first!
                Application.Idle += new EventHandler(OnIdle);
                // move map to MyLoc
                if (!double.IsNaN(Properties.Settings.Default.MyLat) && !double.IsNaN(Properties.Settings.Default.MyLon))
                    gm_Main.Position = new PointLatLng(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
                // update status
                UpdateStatus();
                // show airports on map
                UpdateAirports();
                // show watchlist on map
                UpdateWatchlistInMap();
                // set special event handlers on locator combo boxes
                // set callsign history and locs
                cb_MyCall_TextChanged(this, null);
                // set MyLoc and DXLoc combobox properties
                cb_MyLoc.DisplayMember = nameof(LocatorDropDownItem.Locator);
                cb_MyLoc.ValueMember = nameof(LocatorDropDownItem.GeoLocation);
                cb_MyLoc.Precision = (int)Properties.Settings.Default.Locator_MaxLength / 2;
                cb_MyLoc.SmallLettersForSubsquares = Properties.Settings.Default.Locator_SmallLettersForSubsquares;
                cb_MyLoc.AutoLength = Properties.Settings.Default.Locator_AutoLength;
                cb_DXLoc.DisplayMember = nameof(LocatorDropDownItem.Locator);
                cb_DXLoc.ValueMember = nameof(LocatorDropDownItem.GeoLocation);
                cb_DXLoc.Precision = (int)Properties.Settings.Default.Locator_MaxLength / 2;
                cb_DXLoc.SmallLettersForSubsquares = Properties.Settings.Default.Locator_SmallLettersForSubsquares;
                cb_DXLoc.AutoLength = Properties.Settings.Default.Locator_AutoLength;
                // populate watchlist
                RefreshWatchlistView();
                // set players bounds
                sb_Analysis_Play.Minimum = 0;
                sb_Analysis_Play.Maximum = int.MaxValue;
                // set path mode to single
                PathMode = AIRSCOUTPATHMODE.SINGLE;
                // set life mode to life
                LifeMode = AIRSCOUTLIFEMODE.LIFE;
                // set play mode to pause
                PlayMode = AIRSCOUTPLAYMODE.PAUSE;

                // maintain background calculations thread wait
                Properties.Settings.Default.Background_Calculations_ThreadWait = 0;
                Log.WriteMessage("Finished.");
                // start timer to finish startup
                ti_Startup.Start();
            }
            catch (Exception ex)
            {
                // close the application in case of any exception
                Log.WriteMessage(ex.ToString());
                // close the splash window
                if (SplashDlg != null)
                    SplashDlg.Close();
                MessageBox.Show("An error occured during startup: " + ex.ToString() + "\n\nPress >OK< to close the application.", "AirScout", MessageBoxButtons.OK);
                this.Close();
            }
        }

        private void FinishStartup()
        {
            // finish startup
            // close splash window
            // set window layout
            if (SplashDlg != null)
                SplashDlg.Close();
            // restore window size, state and location
            try
            {
                if (!SupportFunctions.IsMono)
                {
                    this.Size = Properties.Settings.Default.General_WindowSize;
                    this.Location = Properties.Settings.Default.General_WindowLocation;
                    this.WindowState = Properties.Settings.Default.General_WindowState;
                    // set splitter positions
                    sc_Map.SplitterDistance = Properties.Settings.Default.MapSplitter_Distance;
                    sc_Main.SplitterDistance = Properties.Settings.Default.MainSplitter_Distance;
                    CurrentMapSplitterDistance = Properties.Settings.Default.MapSplitter_Distance;
                    // hide latest news tab
                    tp_News.Hide();
                }
                else
                {
                    // ignore window settings under Linux/Mono
                    // start always maximized
                    this.WindowState = FormWindowState.Maximized;
                    sc_Map.SplitterDistance = this.Height - 220;
                    sc_Main.SplitterDistance = this.Width - gb_Map_Info_Width;

                }
            }
            catch (Exception ex)
            {
                // do nothing if failed
                Log.WriteMessage(ex.Message);
            }
            // make main window visible
            this.Visible = true;
            // set focus to map
            this.btn_Map_Save.Focus();
            // Linux/Mono compatibility
            // simulate splitter click
            this.sc_Map_SplitterMoved(this, null);
            this.sc_Main_SplitterMoved(this, null);
        }

        private void StartAllBackgroundWorkers()
        {
            // start all background workers
            // check if the thread is not NULL and not activated
            Say("Starting background threads...");
            if ((bw_AirportMapper != null) && !bw_AirportMapper.IsBusy)
                bw_AirportMapper.RunWorkerAsync();
            if ((bw_JSONWriter != null) && !bw_JSONWriter.IsBusy)
                bw_JSONWriter.RunWorkerAsync();
            if ((bw_NewsFeed != null) && !bw_NewsFeed.IsBusy)
                bw_NewsFeed.RunWorkerAsync();
            if ((bw_AircraftDatabaseMaintainer != null) && (!bw_AircraftDatabaseMaintainer.IsBusy))
            {
                AircraftPositionDatabaseMaintainerStartOptions startoptions = new AircraftPositionDatabaseMaintainerStartOptions();
                startoptions.Name = "Aircrafts";
                startoptions.Database_MaxCount = (long)Properties.Settings.Default.AircraftDatabase_MaxCount;
                startoptions.Database_MaxSize = (double)Properties.Settings.Default.AircraftDatabase_MaxSize;
                startoptions.Database_MaxDaysLifetime = (int)Properties.Settings.Default.AircraftDatabase_MaxDaysLifetime;
                bw_AircraftDatabaseMaintainer.RunWorkerAsync(startoptions);
            }
            if (Properties.Settings.Default.Background_Update_OnStartup)
            {
                if ((bw_StationDatabaseUpdater != null) && !bw_StationDatabaseUpdater.IsBusy)
                {
                    StationDatabaseUpdaterStartOptions startoptions = new StationDatabaseUpdaterStartOptions();
                    startoptions.Name = "Stations";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE;
                    bw_StationDatabaseUpdater.RunWorkerAsync(startoptions);
                }
                if ((bw_AircraftDatabaseUpdater != null) && !bw_AircraftDatabaseUpdater.IsBusy)
                {
                    AircraftDatabaseUpdaterStartOptions startoptions = new AircraftDatabaseUpdaterStartOptions();
                    startoptions.Name = "Aircrafts";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE;
                    bw_AircraftDatabaseUpdater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (bw_GLOBEUpdater != null) && !bw_GLOBEUpdater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "GLOBE";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE;
                    startoptions.Model = ELEVATIONMODEL.GLOBE;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_GLOBE_EnableCache;
                    bw_GLOBEUpdater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (bw_SRTM3Updater != null) && !bw_SRTM3Updater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "SRTM3";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE;
                    startoptions.Model = ELEVATIONMODEL.SRTM3;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_SRTM3_EnableCache;
                    bw_SRTM3Updater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (bw_SRTM1Updater != null) && !bw_SRTM1Updater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "SRTM1";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE;
                    startoptions.Model = ELEVATIONMODEL.SRTM1;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_SRTM1_EnableCache;
                    bw_SRTM1Updater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (bw_GLOBEPathCalculator != null) && !bw_GLOBEPathCalculator.IsBusy)
                    bw_GLOBEPathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE);
                if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (bw_SRTM3PathCalculator != null) && !bw_SRTM3PathCalculator.IsBusy)
                    bw_SRTM3PathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE);
                if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (bw_SRTM1PathCalculator != null) && !bw_SRTM1PathCalculator.IsBusy)
                    bw_SRTM1PathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNONCE);
            }
            else if (Properties.Settings.Default.Background_Update_Periodically)
            {
                if ((bw_StationDatabaseUpdater != null) && !bw_StationDatabaseUpdater.IsBusy)
                {
                    StationDatabaseUpdaterStartOptions startoptions = new StationDatabaseUpdaterStartOptions();
                    startoptions.Name = "Stations";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY;
                    bw_StationDatabaseUpdater.RunWorkerAsync(startoptions);
                }
                if ((bw_AircraftDatabaseUpdater != null) && !bw_AircraftDatabaseUpdater.IsBusy)
                {
                    AircraftDatabaseUpdaterStartOptions startoptions = new AircraftDatabaseUpdaterStartOptions();
                    startoptions.Name = "Aircrafts";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY;
                    bw_AircraftDatabaseUpdater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (bw_GLOBEUpdater != null) && !bw_GLOBEUpdater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "GLOBE";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY;
                    startoptions.Model = ELEVATIONMODEL.GLOBE;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_GLOBE_EnableCache;
                    bw_GLOBEUpdater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (bw_SRTM3Updater != null) && !bw_SRTM3Updater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "SRTM3";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY;
                    startoptions.Model = ELEVATIONMODEL.SRTM3;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_SRTM3_EnableCache;
                    bw_SRTM3Updater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (bw_SRTM1Updater != null) && !bw_SRTM1Updater.IsBusy)
                {
                    ElevationDatabaseUpdaterStartOptions startoptions = new ElevationDatabaseUpdaterStartOptions();
                    startoptions.Name = "SRTM1";
                    startoptions.Options = BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY;
                    startoptions.Model = ELEVATIONMODEL.SRTM1;
                    startoptions.MinLat = Properties.Settings.Default.MinLat;
                    startoptions.MinLon = Properties.Settings.Default.MinLon;
                    startoptions.MaxLat = Properties.Settings.Default.MaxLat;
                    startoptions.MaxLon = Properties.Settings.Default.MaxLon;
                    startoptions.FileCacheEnabled = Properties.Settings.Default.Elevation_SRTM1_EnableCache;
                    bw_SRTM1Updater.RunWorkerAsync(startoptions);
                }
                if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (bw_GLOBEPathCalculator != null) && !bw_GLOBEPathCalculator.IsBusy)
                    bw_GLOBEPathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY);
                if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (bw_SRTM3PathCalculator != null) && !bw_SRTM3PathCalculator.IsBusy)
                    bw_SRTM3PathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY);
                if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (bw_SRTM1PathCalculator != null) && !bw_SRTM1PathCalculator.IsBusy)
                    bw_SRTM1PathCalculator.RunWorkerAsync(BACKGROUNDUPDATERSTARTOPTIONS.RUNPERIODICALLY);
            }
            if ((bw_PlaneFeed1 != null) && (!bw_PlaneFeed1.IsBusy))
            {
                PlaneFeedWorkEventArgs feedargs = new PlaneFeedWorkEventArgs();
                feedargs.AppDirectory = AppDirectory;
                feedargs.AppDataDirectory = AppDataDirectory;
                feedargs.LogDirectory = LogDirectory;
                feedargs.TmpDirectory = TmpDirectory;
                feedargs.DatabaseDirectory = DatabaseDirectory;
                feedargs.MaxLat = Properties.Settings.Default.MaxLat;
                feedargs.MinLon = Properties.Settings.Default.MinLon;
                feedargs.MinLat = Properties.Settings.Default.MinLat;
                feedargs.MaxLon = Properties.Settings.Default.MaxLon;
                feedargs.MinAlt = Properties.Settings.Default.Planes_MinAlt;
                feedargs.MaxAlt = Properties.Settings.Default.Planes_MaxAlt;
                feedargs.MyLat = Properties.Settings.Default.MyLat;
                feedargs.MyLon = Properties.Settings.Default.MyLon;
                feedargs.DXLat = Properties.Settings.Default.DXLat;
                feedargs.DXLon = Properties.Settings.Default.DXLon;
                feedargs.KeepHistory = Properties.Settings.Default.Planes_KeepHistory;
                bw_PlaneFeed1.RunWorkerAsync(feedargs);
            }
            if ((bw_PlaneFeed2 != null) && (!bw_PlaneFeed2.IsBusy))
            {
                PlaneFeedWorkEventArgs feedargs = new PlaneFeedWorkEventArgs();
                feedargs.AppDirectory = AppDirectory;
                feedargs.AppDataDirectory = AppDataDirectory;
                feedargs.LogDirectory = LogDirectory;
                feedargs.TmpDirectory = TmpDirectory;
                feedargs.DatabaseDirectory = DatabaseDirectory;
                feedargs.MaxLat = Properties.Settings.Default.MaxLat;
                feedargs.MinLon = Properties.Settings.Default.MinLon;
                feedargs.MinLat = Properties.Settings.Default.MinLat;
                feedargs.MaxLon = Properties.Settings.Default.MaxLon;
                feedargs.MinAlt = Properties.Settings.Default.Planes_MinAlt;
                feedargs.MaxAlt = Properties.Settings.Default.Planes_MaxAlt;
                feedargs.MyLat = Properties.Settings.Default.MyLat;
                feedargs.MyLon = Properties.Settings.Default.MyLon;
                feedargs.DXLat = Properties.Settings.Default.DXLat;
                feedargs.DXLon = Properties.Settings.Default.DXLon;
                feedargs.KeepHistory = Properties.Settings.Default.Planes_KeepHistory;
                bw_PlaneFeed2.RunWorkerAsync(feedargs);
            }
            if ((bw_PlaneFeed3 != null) && (!bw_PlaneFeed3.IsBusy))
            {
                PlaneFeedWorkEventArgs feedargs = new PlaneFeedWorkEventArgs();
                feedargs.AppDirectory = AppDirectory;
                feedargs.AppDataDirectory = AppDataDirectory;
                feedargs.LogDirectory = LogDirectory;
                feedargs.TmpDirectory = TmpDirectory;
                feedargs.DatabaseDirectory = DatabaseDirectory;
                feedargs.MaxLat = Properties.Settings.Default.MaxLat;
                feedargs.MinLon = Properties.Settings.Default.MinLon;
                feedargs.MinLat = Properties.Settings.Default.MinLat;
                feedargs.MaxLon = Properties.Settings.Default.MaxLon;
                feedargs.MinAlt = Properties.Settings.Default.Planes_MinAlt;
                feedargs.MaxAlt = Properties.Settings.Default.Planes_MaxAlt;
                feedargs.MyLat = Properties.Settings.Default.MyLat;
                feedargs.MyLon = Properties.Settings.Default.MyLon;
                feedargs.DXLat = Properties.Settings.Default.DXLat;
                feedargs.DXLon = Properties.Settings.Default.DXLon;
                feedargs.KeepHistory = Properties.Settings.Default.Planes_KeepHistory;
                bw_PlaneFeed3.RunWorkerAsync(feedargs);
            }
            if (Properties.Settings.Default.Server_Activate)
            {
                if ((bw_WinTestReceive != null) && (!bw_WinTestReceive.IsBusy))
                    bw_WinTestReceive.RunWorkerAsync();
                if ((bw_Webserver != null) && (!bw_Webserver.IsBusy))
                    bw_Webserver.RunWorkerAsync();
            }
            if (Properties.Settings.Default.SpecLab_Enabled)
            {
                if ((bw_SpecLab_Receive != null) && (!bw_SpecLab_Receive.IsBusy))
                    bw_SpecLab_Receive.RunWorkerAsync();
            }
            if (Properties.Settings.Default.Track_Activate)
            {
                if ((bw_Track != null) && (!bw_Track.IsBusy))
                    bw_Track.RunWorkerAsync();
            }
            Say("Background threads started.");
        }

        private void StopBackgroundworker(BackgroundWorker worker, string name, int count, int total)
        {
            if (worker == null)
                return;
            if (!worker.IsBusy)
                return;
            worker.CancelAsync();
            // waiting for background threads to finish
            Stopwatch st = new Stopwatch();
            st.Start();
            Say("Stopping background thread " + count.ToString() + " of " + total.ToString() + " [" + name + "]...");
            while ((worker != null) && worker.IsBusy)
                Application.DoEvents();
            st.Stop();
            Log.WriteMessage("Stopping " + name + ", " + st.ElapsedMilliseconds.ToString() + " ms.");
        }

        private void StopAllBackgroundWorkers()
        {
            Say("Stopping background threads...");
            // cancel permanent background workers, wait for finish
            int bcount = 13;
            int i = 1;
            // cancel all threads
            StopBackgroundworker(bw_WinTestReceive, nameof(bw_WinTestReceive), i, bcount); i++;
            StopBackgroundworker(bw_SpecLab_Receive, nameof(bw_SpecLab_Receive), i, bcount); i++;
            StopBackgroundworker(bw_Track, nameof(bw_Track), i, bcount); i++;
            StopBackgroundworker(bw_JSONWriter, nameof(bw_JSONWriter), i, bcount); i++;
            StopBackgroundworker(bw_NewsFeed, nameof(bw_NewsFeed), i, bcount); i++;
            StopBackgroundworker(bw_PlaneFeed1, nameof(bw_PlaneFeed1), i, bcount); i++;
            StopBackgroundworker(bw_PlaneFeed2, nameof(bw_PlaneFeed2), i, bcount); i++;
            StopBackgroundworker(bw_PlaneFeed3, nameof(bw_PlaneFeed3), i, bcount); i++;
            StopBackgroundworker(bw_GLOBEPathCalculator, nameof(bw_GLOBEPathCalculator), i, bcount); i++;
            StopBackgroundworker(bw_SRTM3PathCalculator, nameof(bw_SRTM3PathCalculator), i, bcount); i++;
            StopBackgroundworker(bw_SRTM1PathCalculator, nameof(bw_SRTM1PathCalculator), i, bcount); i++;
            StopBackgroundworker(bw_AircraftDatabaseUpdater, nameof(bw_AircraftDatabaseUpdater), i, bcount); i++;
            StopBackgroundworker(bw_StationDatabaseUpdater, nameof(bw_StationDatabaseUpdater), i, bcount); i++;
            Say("Background threads stopped.");
        }

        private void CancelAllBackgroundWorkers()
        {
            // cancel all background workers, don't wait for finish
            if (bw_AirportMapper != null)
                bw_AirportMapper.CancelAsync();
            if (bw_PlaneFeed1 != null)
                bw_PlaneFeed1.CancelAsync();
            if (bw_PlaneFeed2 != null)
                bw_PlaneFeed2.CancelAsync();
            if (bw_PlaneFeed3 != null)
                bw_PlaneFeed3.CancelAsync();
            if (bw_WinTestReceive != null)
                bw_WinTestReceive.CancelAsync();
            if (bw_SpecLab_Receive != null)
                bw_SpecLab_Receive.CancelAsync();
            if (bw_Track != null)
                bw_Track.CancelAsync();
            if (bw_JSONWriter != null)
                bw_JSONWriter.CancelAsync();
            if (bw_Webserver != null)
                bw_Webserver.CancelAsync();
            if (bw_NewsFeed != null)
                bw_NewsFeed.CancelAsync();
            if (bw_HistoryDownloader != null)
                bw_HistoryDownloader.CancelAsync();
            if (bw_StationDatabaseUpdater != null)
                bw_StationDatabaseUpdater.CancelAsync();
            if (bw_AircraftDatabaseMaintainer != null)
                bw_AircraftDatabaseMaintainer.CancelAsync();
            if (bw_AircraftDatabaseUpdater != null)
                bw_AircraftDatabaseUpdater.CancelAsync();
            if (bw_GLOBEPathCalculator != null)
                bw_GLOBEUpdater.CancelAsync();
            if (bw_SRTM3Updater != null)
                bw_SRTM3Updater.CancelAsync();
            if (bw_SRTM1Updater != null)
                bw_SRTM1Updater.CancelAsync();
            if (bw_GLOBEPathCalculator != null)
                bw_GLOBEPathCalculator.CancelAsync();
            if (bw_SRTM3PathCalculator != null)
                bw_SRTM3PathCalculator.CancelAsync();
            if (bw_SRTM1PathCalculator != null)
                bw_SRTM1PathCalculator.CancelAsync();
        }

        private Bitmap CreatePlaneIcon(Color color)
        {
            // get the basic icon
            Bitmap bm = new Bitmap(AppDirectory + Path.DirectorySeparatorChar + Properties.Settings.Default.Planes_IconFileName);
            // read the content and change color of each pixel
            for (int j = 0; j < bm.Width; j++)
            {
                for (int k = 0; k < bm.Height; k++)
                {
                    // get the color of each pixel
                    Color c = bm.GetPixel(j, k);
                    // check if not transparent
                    if (c.A > 0)
                    {
                        // change color
                        bm.SetPixel(j, k, Color.FromArgb(c.A, color.R, color.G, color.B));
                    }
                }
            }
            return bm;
        }

        public static Color ColorFromHSV(double hue, double saturation, double value)
        {
            int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
            double f = hue / 60 - Math.Floor(hue / 60);

            value = value * 255;
            int v = Convert.ToInt32(value);
            int p = Convert.ToInt32(value * (1 - saturation));
            int q = Convert.ToInt32(value * (1 - f * saturation));
            int t = Convert.ToInt32(value * (1 - (1 - f) * saturation));

            if (hi == 0)
                return Color.FromArgb(255, v, t, p);
            else if (hi == 1)
                return Color.FromArgb(255, q, v, p);
            else if (hi == 2)
                return Color.FromArgb(255, p, v, t);
            else if (hi == 3)
                return Color.FromArgb(255, p, q, v);
            else if (hi == 4)
                return Color.FromArgb(255, t, p, v);
            else
                return Color.FromArgb(255, v, p, q);
        }

        public Color GetColor(double power)
        {
            double H = power * 0.3; // Hue (note 0.4 = Green, see huge chart below)
            double S = 0.95; // Saturation
            double B = 0.95; // Brightness

            return ColorFromHSV((float)H * 360, (float)S, (float)B);
        }

        private Bitmap CreateAirportIcon(int alpha)
        {
            // get the basic icon
            Bitmap bm = new Bitmap(AppDirectory + Path.DirectorySeparatorChar + Properties.Settings.Default.Airports_IconFileName);
            // read the content and change opacity of each pixel
            for (int j = 0; j < bm.Width; j++)
            {
                for (int k = 0; k < bm.Height; k++)
                {
                    // get the color of each pixel
                    Color c = bm.GetPixel(j, k);
                    // check if not transparent
                    if (c.A > 0)
                    {
                        // change color
                        bm.SetPixel(j, k, Color.FromArgb(alpha, c.R, c.G, c.B));
                    }
                }
            }
            return bm;
        }

        private void InitializeIcons()
        {
            // create extra icons regular size
            Log.WriteMessage("Started.");
            try
            {
                // now generate 0% - 100% colored planes
                for (int i = 0; i <= 100; i++)
                {
                    Bitmap bm = CreatePlaneIcon(GetColor(1.0f - (float)i / 100.0f));
                    il_Planes_L.Images.Add(bm);
                    il_Planes_M.Images.Add(bm);
                    il_Planes_H.Images.Add(bm);
                    il_Planes_S.Images.Add(bm);
                }
                il_Planes_L.Images.Add(CreatePlaneIcon(Color.Gray));
                il_Planes_M.Images.Add(CreatePlaneIcon(Color.Gray));
                il_Planes_H.Images.Add(CreatePlaneIcon(Color.Gray));
                il_Planes_S.Images.Add(CreatePlaneIcon(Color.Gray));
                bmindex_gray = il_Planes_M.Images.Count - 1;
                il_Planes_L.Images.Add(CreatePlaneIcon(Color.LightGreen));
                il_Planes_M.Images.Add(CreatePlaneIcon(Color.LightGreen));
                il_Planes_H.Images.Add(CreatePlaneIcon(Color.LightGreen));
                il_Planes_S.Images.Add(CreatePlaneIcon(Color.LightGreen));
                bmindex_lightgreen = il_Planes_M.Images.Count - 1;
                il_Planes_L.Images.Add(CreatePlaneIcon(Color.DarkOrange));
                il_Planes_M.Images.Add(CreatePlaneIcon(Color.DarkOrange));
                il_Planes_H.Images.Add(CreatePlaneIcon(Color.DarkOrange));
                il_Planes_S.Images.Add(CreatePlaneIcon(Color.DarkOrange));
                bmindex_darkorange = il_Planes_M.Images.Count - 1;
                il_Planes_L.Images.Add(CreatePlaneIcon(Color.Red));
                il_Planes_M.Images.Add(CreatePlaneIcon(Color.Red));
                il_Planes_H.Images.Add(CreatePlaneIcon(Color.Red));
                il_Planes_S.Images.Add(CreatePlaneIcon(Color.Red));
                bmindex_red = il_Planes_M.Images.Count - 1;
                il_Planes_L.Images.Add(CreatePlaneIcon(Color.Magenta));
                il_Planes_M.Images.Add(CreatePlaneIcon(Color.Magenta));
                il_Planes_H.Images.Add(CreatePlaneIcon(Color.Magenta));
                il_Planes_S.Images.Add(CreatePlaneIcon(Color.Magenta));
                bmindex_magenta = il_Planes_M.Images.Count - 1;
                il_Airports.Images.Add(CreateAirportIcon(255));
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
            Log.WriteMessage("Finished.");
        }

        private void InitializeCharts()
        {
            // propagation path chart
            pm_Path.Title = String.Empty;
            pm_Path.DefaultFontSize = 6F;
            pm_Path.IsLegendVisible = false;
            pv_Path.BackColor = Color.White;
            pv_Path.Model = pm_Path;
            // add axes
            pm_Path.Axes.Clear();
            // add X-axis
            Path_X.IsZoomEnabled = false;
            Path_X.Maximum = 1000;
            Path_X.Minimum = 0;
            Path_X.MajorGridlineStyle = LineStyle.Solid;
            Path_X.MinorGridlineStyle = LineStyle.Dot;
            Path_X.Position = AxisPosition.Bottom;
            this.pm_Path.Axes.Add(Path_X);
            // add Y-axis
            Path_Y.IsZoomEnabled = false;
            Path_Y.Maximum = 20000;
            Path_Y.Minimum = 0;
            Path_Y.MajorGridlineStyle = LineStyle.Solid;
            Path_Y.MinorGridlineStyle = LineStyle.Dot;
            Path_Y.Position = AxisPosition.Left;
            this.pm_Path.Axes.Add(Path_Y);
            // add series
            pm_Path.Series.Clear();
            Path_Elevation.Title = "Elevation";
            Min_H1.Title = "Min_H1";
            Min_H1.StrokeThickness = 2;
            Min_H1.LineStyle = LineStyle.Solid;
            Min_H1.Color = OxyColors.Red;
            Min_H2.Title = "Min_H2";
            Min_H2.StrokeThickness = 2;
            Min_H2.LineStyle = LineStyle.Solid;
            Min_H2.Color = OxyColors.Gold;
            Max_H.Title = "Max_H";
            Max_H.StrokeThickness = 2;
            Max_H.LineStyle = LineStyle.Dot;
            Max_H.Color = OxyColors.DarkBlue;
            Min_H.Title = "Min_H";
            Min_H.StrokeThickness = 0;
            Min_H.LineStyle = LineStyle.Solid;
            Min_H.Color = OxyColors.Magenta.ChangeSaturation(0.4);
            Planes_Hi.Title = "Planes_Hi";
            Planes_Hi.Color = OxyColors.Transparent;
            Planes_Hi.MarkerType = MarkerType.Square;
            Planes_Hi.MarkerFill = OxyColors.Magenta;
            Planes_Lo.Title = "Planes_Lo";
            Planes_Lo.Color = OxyColors.Transparent;
            Planes_Lo.MarkerType = MarkerType.Square;
            Planes_Lo.MarkerFill = OxyColors.Gray;
            pm_Path.Series.Add(Path_Elevation);
            pm_Path.Series.Add(Min_H1);
            pm_Path.Series.Add(Min_H2);
            pm_Path.Series.Add(Max_H);
            pm_Path.Series.Add(Min_H);
            pm_Path.Series.Add(Planes_Hi);
            pm_Path.Series.Add(Planes_Lo);
            // add legend
            pm_Path.LegendTitle = "";
            pm_Path.LegendPosition = LegendPosition.TopRight;
            pm_Path.LegendBackground = OxyColors.White;
            pm_Path.LegendBorder = OxyColors.Black;
            pm_Path.LegendBorderThickness = 1;
            // add control
            this.tp_Elevation.Controls.Add(pv_Path);
            pv_Path.Paint += new PaintEventHandler(pv_Path_Paint);

            // zoomed elevation chart
            pm_Elevation.Title = String.Empty;
            pm_Elevation.DefaultFontSize = 6F;
            pm_Elevation.IsLegendVisible = false;
            pv_Elevation.BackColor = Color.White;
            pv_Elevation.Model = pm_Elevation;
            // add series
            pm_Elevation.Series.Clear();
            Elevation.Title = "Elevation";
            LOS.Title = "LOS";
            LOS.StrokeThickness = 2;
            LOS.Color = OxyColors.Black;
            pm_Elevation.Series.Add(Elevation);
            pm_Elevation.Series.Add(LOS);
            // create axes
            pm_Elevation.Axes.Clear();
            // add X-axis
            Elevation_X.IsZoomEnabled = false;
            Elevation_X.Maximum = 1000;
            Elevation_X.Minimum = 0;
            Elevation_X.MajorGridlineStyle = LineStyle.Solid;
            Elevation_X.MinorGridlineStyle = LineStyle.Dot;
            Elevation_X.Position = AxisPosition.Bottom;
            this.pm_Elevation.Axes.Add(Elevation_X);
            // add Y-axis
            Elevation_Y.IsZoomEnabled = false;
            // auto size maximum
            //                    Elevation_Y.Maximum = maxelv,
            Elevation_Y.Minimum = 0;
            Elevation_Y.MajorGridlineStyle = LineStyle.Solid;
            Elevation_Y.MinorGridlineStyle = LineStyle.Dot;
            Elevation_Y.Position = AxisPosition.Left;
            this.pm_Elevation.Axes.Add(Elevation_Y);
            // add legend
            pm_Elevation.LegendTitle = "";
            pm_Elevation.LegendPosition = LegendPosition.TopRight;
            pm_Elevation.LegendBackground = OxyColors.White;
            pm_Elevation.LegendBorder = OxyColors.Black;
            pm_Elevation.LegendBorderThickness = 1;
            // add control
            this.tp_Elevation.Controls.Add(pv_Elevation);

            // spectrum chart
            pm_Spectrum.Title = String.Empty;
            pm_Spectrum.DefaultFontSize = 6F;
            pv_Spectrum.BackColor = Color.White;
            pv_Spectrum.Model = pm_Spectrum;
            // add Spectrum series
            pm_Spectrum.Series.Clear();
            // create axes
            pm_Spectrum.Axes.Clear();
            // add X-axis
            Spectrum_X.IsZoomEnabled = false;
            Spectrum_X.Maximum = SpectrumMaxPoints;
            Spectrum_X.Minimum = 0;
            Spectrum_X.MajorGridlineStyle = LineStyle.Solid;
            Spectrum_X.MinorGridlineStyle = LineStyle.Dot;
            Spectrum_X.Position = AxisPosition.Bottom;
            this.pm_Spectrum.Axes.Add(Spectrum_X);
            // add Y-axis
            Spectrum_Y.IsZoomEnabled = false;
            Spectrum_Y.Maximum = 0;
            Spectrum_Y.Minimum = -120;
            Spectrum_Y.MajorGridlineStyle = LineStyle.Solid;
            Spectrum_Y.MinorGridlineStyle = LineStyle.Dot;
            Spectrum_Y.Position = AxisPosition.Left;
            this.pm_Spectrum.Axes.Add(Spectrum_Y);
            // add series
            SpectrumRecord.Color = OxyColors.Magenta;
            pm_Spectrum.Series.Add(SpectrumRecord);
            Spectrum.InterpolationAlgorithm = OxyPlot.InterpolationAlgorithms.CanonicalSpline;
            Spectrum.StrokeThickness = 3;
            Spectrum.Color = OxyColors.Goldenrod;
            pm_Spectrum.Series.Add(Spectrum);
            // add control
            this.pv_Spectrum.Dock = DockStyle.Fill;
            this.gb_Spectrum.Controls.Add(pv_Spectrum);
            return;

        }

        private void InitializeWebbrowser()
        {
            // do not initialize webbrowser --> not working on all Linux systems
            if (SupportFunctions.IsMono)
                return;
            // iniitialize webbrowser on Windows
            this.wb_News = new System.Windows.Forms.WebBrowser();
            // 
            // wb_News
            // 
            this.wb_News.DataBindings.Add(new System.Windows.Forms.Binding("Url", global::AirScout.Properties.Settings.Default, "News_URL", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
            this.wb_News.Dock = System.Windows.Forms.DockStyle.Fill;
            this.wb_News.Location = new System.Drawing.Point(0, 0);
            this.wb_News.MinimumSize = new System.Drawing.Size(20, 20);
            this.wb_News.Name = "wb_News";
            this.wb_News.Size = new System.Drawing.Size(844, 197);
            this.wb_News.TabIndex = 0;
            this.wb_News.Url = global::AirScout.Properties.Settings.Default.News_URL;
            this.tp_News.Controls.Add(this.wb_News);
        }

        private void InitializePassword()
        {
            // get current AirScout password phrase from website and store it in settings
            try
            {
                // get upload info
                WebRequest myWebRequest = WebRequest.Create(Properties.Settings.Default.SFTP_PwdURL);
                WebResponse myWebResponse = myWebRequest.GetResponse();
                Stream ReceiveStream = myWebResponse.GetResponseStream();
                Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
                StreamReader readStream = new StreamReader(ReceiveStream, encode);
                string s = readStream.ReadToEnd();
                Properties.Settings.Default.Password = s;
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void UpdateAirports()
        {
            if (!Properties.Settings.Default.Airports_Activate)
                return;
            if ((Airports == null) || (Airports.Count == 0))
                return;
            foreach (AirportDesignator airport in Airports)
            {
                try
                {
                    GMarkerGoogle gm = new GMarkerGoogle(new PointLatLng(airport.Lat, airport.Lon), ToolTipFont, RotateImageByAngle(il_Airports.Images[0], 0));
                    gm.ToolTipText = airport.Airport + "\n" +
                        airport.IATA + "/" + airport.ICAO;
                    gm.ToolTipMode = MarkerTooltipMode.OnMouseOver;
                    gm.Tag = airport.IATA + "," + airport.ICAO;
                    gmo_Airports.Markers.Add(gm);
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                }
            }
        }

        private void ti_Startup_Tick(object sender, EventArgs e)
        {
            FinishStartup();
            ti_Startup.Stop();
        }


        #endregion

        #region User Settings

        private string GetUserSettingsPath()
        {
            if (!SupportFunctions.IsMono)
                return ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
            // try to build a path to user specific settings under Linux/Mono
            string usersettingspath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            usersettingspath = Path.Combine(usersettingspath, Application.CompanyName, AppDomain.CurrentDomain.FriendlyName);
            usersettingspath += "_Url_";
            Assembly assembly = Assembly.GetEntryAssembly();
            if (assembly == null)
            {
                assembly = Assembly.GetCallingAssembly();
            }
            byte[] pkt = assembly.GetName().GetPublicKeyToken();
            byte[] hash = SHA1.Create().ComputeHash((pkt != null && pkt.Length > 0) ? pkt : Encoding.UTF8.GetBytes(assembly.EscapedCodeBase));
            StringBuilder evidence_string = new StringBuilder();
            byte[] array = hash;
            for (int i = 0; i < array.Length; i++)
            {
                byte b = array[i];
                evidence_string.AppendFormat("{0:x2}", b);
            }
            usersettingspath += evidence_string.ToString();
            if (!Directory.Exists(usersettingspath))
            {
                Directory.CreateDirectory(usersettingspath);
            }
            usersettingspath = Path.Combine(usersettingspath, "user.config");
            return usersettingspath;
        }

        private void SaveUserSettings()
        {
            Log.WriteMessage("Saving configuration...");
            Properties.Settings.Default.Save();
            if (!SupportFunctions.IsMono)
                return;
            Console.WriteLine("Creating XML document...");
            XmlDocument doc = new XmlDocument();
            XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
            XmlElement root = doc.DocumentElement;
            doc.InsertBefore(xmlDeclaration, root);
            XmlElement configuration = doc.CreateElement(string.Empty, "configuration", string.Empty);
            doc.AppendChild(configuration);
            XmlElement configsections = doc.CreateElement(string.Empty, "configSections", string.Empty);
            configuration.AppendChild(configsections);
            XmlElement usersettingsgroup = doc.CreateElement(string.Empty, "sectionGroup", string.Empty);
            XmlAttribute usersettingsname = doc.CreateAttribute(string.Empty, "name", string.Empty);
            usersettingsname.Value = "userSettings";
            usersettingsgroup.Attributes.Append(usersettingsname);
            XmlElement usersection = doc.CreateElement(string.Empty, "section", string.Empty);
            XmlAttribute sectionname = doc.CreateAttribute(string.Empty, "name", string.Empty);
            sectionname.Value = "AirScout.PlaneFeeds.Properties.Settings";
            usersection.Attributes.Append(sectionname);
            XmlAttribute sectiontype = doc.CreateAttribute(string.Empty, "type", string.Empty);
            sectiontype.Value = "System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
            usersection.Attributes.Append(sectiontype);
            XmlAttribute sectionallowexedefinition = doc.CreateAttribute(string.Empty, "allowExeDefinition", string.Empty);
            sectionallowexedefinition.Value = "MachineToLocalUser";
            usersection.Attributes.Append(sectionallowexedefinition);
            XmlAttribute sectionrequirepermission = doc.CreateAttribute(string.Empty, "requirePermission", string.Empty);
            sectionrequirepermission.Value = "false";
            usersection.Attributes.Append(sectionrequirepermission);
            usersettingsgroup.AppendChild(usersection);
            configsections.AppendChild(usersettingsgroup);
            XmlElement usersettings = doc.CreateElement(string.Empty, "userSettings", string.Empty);
            configuration.AppendChild(usersettings);
            Console.WriteLine("Writing user settings...");
            // append AirScout.PlaneFeeds properties
            Console.WriteLine("Appending AirScout.PlaneFeeds.Properties.Settings.Default node...");
            XmlElement planefeedproperties = doc.CreateElement(string.Empty, AirScout.PlaneFeeds.Properties.Settings.Default.ToString(), string.Empty);
            usersettings.AppendChild(planefeedproperties);
            foreach (SettingsPropertyValue p in AirScout.PlaneFeeds.Properties.Settings.Default.PropertyValues)
            {
                if ((p != null) && (p.Name != null) && (p.PropertyValue != null) && !p.UsingDefaultValue)
                {
                    //                    Console.WriteLine("Appending " + p.Name + " = " + p.PropertyValue.ToString());
                    XmlElement setting = doc.CreateElement(string.Empty, "setting", string.Empty);
                    XmlAttribute name = doc.CreateAttribute(string.Empty, "name", string.Empty);
                    name.Value = p.Name.ToString();
                    setting.Attributes.Append(name);
                    XmlAttribute serializeas = doc.CreateAttribute(string.Empty, "serializeAs", string.Empty);
                    serializeas.Value = p.Property.SerializeAs.ToString();
                    setting.Attributes.Append(serializeas);
                    XmlElement value = doc.CreateElement(string.Empty, "value", string.Empty);
                    if (p.PropertyValue != null && p.Property.SerializeAs == SettingsSerializeAs.String)
                    {
                        XmlText text = doc.CreateTextNode(p.SerializedValue.ToString());
                        value.AppendChild(text);
                    }
                    else
                    {
                        if (p.PropertyValue != null && p.Property.SerializeAs == SettingsSerializeAs.Xml)
                        {
                            MemoryStream ms = new MemoryStream();
                            XmlWriter writer = XmlWriter.Create(ms, new XmlWriterSettings
                            {
                                NewLineOnAttributes = true,
                                OmitXmlDeclaration = true
                            });
                            XmlSerializer serializer = new XmlSerializer(p.PropertyValue.GetType());
                            serializer.Serialize(writer, p.PropertyValue);
                            byte[] text2 = new byte[ms.ToArray().Length - 3];
                            Array.Copy(ms.ToArray(), 3, text2, 0, text2.Length);
                            XmlText xml = doc.CreateTextNode(Encoding.UTF8.GetString(text2.ToArray<byte>()));
                            value.AppendChild(xml);
                            value.InnerXml = WebUtility.HtmlDecode(value.InnerXml);
                        }
                    }
                    setting.AppendChild(value);
                    planefeedproperties.AppendChild(setting);
                }
            }
            // append AirScout properties
            Console.WriteLine("Appending AirScout.Properties.Settings.Default node...");
            XmlElement properties = doc.CreateElement(string.Empty, Properties.Settings.Default.ToString(), string.Empty);
            usersettings.AppendChild(properties);
            foreach (SettingsPropertyValue p in Properties.Settings.Default.PropertyValues)
            {
                if ((p != null) && (p.Name != null) && (p.PropertyValue != null) && !p.UsingDefaultValue)
                {
                    //                   Console.WriteLine("Appending " + p.Name + " = " + p.PropertyValue.ToString();
                    XmlElement setting = doc.CreateElement(string.Empty, "setting", string.Empty);
                    XmlAttribute name = doc.CreateAttribute(string.Empty, "name", string.Empty);
                    name.Value = p.Name.ToString();
                    setting.Attributes.Append(name);
                    XmlAttribute serializeas = doc.CreateAttribute(string.Empty, "serializeAs", string.Empty);
                    serializeas.Value = p.Property.SerializeAs.ToString();
                    setting.Attributes.Append(serializeas);
                    XmlElement value = doc.CreateElement(string.Empty, "value", string.Empty);
                    if (p.PropertyValue != null && p.Property.SerializeAs == SettingsSerializeAs.String)
                    {
                        XmlText text = doc.CreateTextNode(p.SerializedValue.ToString());
                        value.AppendChild(text);
                    }
                    else
                    {
                        if (p.PropertyValue != null && p.Property.SerializeAs == SettingsSerializeAs.Xml)
                        {
                            MemoryStream ms = new MemoryStream();
                            XmlWriter writer = XmlWriter.Create(ms, new XmlWriterSettings
                            {
                                NewLineOnAttributes = true,
                                OmitXmlDeclaration = true
                            });
                            XmlSerializer serializer = new XmlSerializer(p.PropertyValue.GetType());
                            serializer.Serialize(writer, p.PropertyValue);
                            byte[] text2 = new byte[ms.ToArray().Length - 3];
                            Array.Copy(ms.ToArray(), 3, text2, 0, text2.Length);
                            XmlText xml = doc.CreateTextNode(Encoding.UTF8.GetString(text2.ToArray<byte>()));
                            value.AppendChild(xml);
                            value.InnerXml = WebUtility.HtmlDecode(value.InnerXml);
                        }
                    }
                    setting.AppendChild(value);
                    properties.AppendChild(setting);
                }
            }
            doc.Save(GetUserSettingsPath());
        }

        #endregion

        #region Idle

        private void OnIdle(object sender, EventArgs args)
        {
            // close window if disagreed
            if (ForceClose)
            {
                Application.Exit();
            }
            /*
            // show/hide group boxes
            if (Properties.Settings.Default.Map_ShowInfoBox)
            {
                gb_Map_Info.ClientSize = new System.Drawing.Size(gb_Map_Info.ClientSize.Width, gb_Map_Info_MaximizedHeight);
                if (gb_Map_Info.Text != "˄ Info")
                    gb_Map_Info.Text = "˄ Info";
            }
            else
            {
                gb_Map_Info.ClientSize = new System.Drawing.Size(gb_Map_Info.ClientSize.Width, gb_Map_Info_MinimizedHeight);
                if (gb_Map_Info.Text != "˅ Info")
                    gb_Map_Info.Text = "˅ Info";
            }
            if (Properties.Settings.Default.Map_ShowZoomBox)
            {
                gb_Map_Zoom.ClientSize = new System.Drawing.Size(gb_Map_Zoom.ClientSize.Width, gb_Map_Zoom_MaximizedHeight);
                if (gb_Map_Zoom.Text != "˄ Map Zoom")
                    gb_Map_Zoom.Text = "˄ Map Zoom";
            }
            else
            {
                gb_Map_Zoom.ClientSize = new System.Drawing.Size(gb_Map_Zoom.ClientSize.Width, gb_Map_Zoom_MinimizedHeight);
                if (gb_Map_Zoom.Text != "˅ Map Zoom")
                    gb_Map_Zoom.Text = "˅ Map Zoom";
            }
            if (Properties.Settings.Default.Map_ShowFilterBox)
            {
                gb_Map_Filter.ClientSize = new System.Drawing.Size(gb_Map_Filter.ClientSize.Width, gb_Map_Filter_MaximizedHeigth);
                if (gb_Map_Filter.Text != "˄ Planes Filter")
                    gb_Map_Filter.Text = "˄ Planes Filter";
            }
            else
            {
                gb_Map_Filter.ClientSize = new System.Drawing.Size(gb_Map_Filter.ClientSize.Width, gb_Map_Filter_MinimizedHeigth);
                if (gb_Map_Filter.Text != "˅ Planes Filter")
                    gb_Map_Filter.Text = "˅ Planes Filter";
            }
            if (Properties.Settings.Default.Map_ShowAlarmBox)
            {
                gb_Map_Alarms.ClientSize = new System.Drawing.Size(gb_Map_Alarms.ClientSize.Width, gb_Map_Alarms_MaximizedHeight);
                if (gb_Map_Alarms.Text != "˄ Alarms")
                    gb_Map_Alarms.Text = "˄ Alarms";
            }
            else
            {
                gb_Map_Alarms.ClientSize = new System.Drawing.Size(gb_Map_Alarms.ClientSize.Width, gb_Map_Alarms_MinimizedHeight);
                if (gb_Map_Alarms.Text != "˅ Alarms")
                    gb_Map_Alarms.Text = "˅ Alarms";
            }
            // show/hide panes
            gmo_Airports.IsVisibile = Properties.Settings.Default.Airports_Activate;
            gmo_Callsigns.IsVisibile = Properties.Settings.Default.Watchlist_Activated;

            pa_Map_Boxes.ClientSize = new System.Drawing.Size(pa_Map_Boxes.ClientSize.Width, ClientSize.Height - ss_Main.Height);
            */
            // enable/disable watchlist button
            if (btn_Control_Manage_Watchlist.Enabled == Properties.Settings.Default.Watchlist_SyncWithKST)
                btn_Control_Manage_Watchlist.Enabled = !Properties.Settings.Default.Watchlist_SyncWithKST;
        }

        #endregion

        #region Closing Down

        private void MapDlg_FormClosing(object sender, FormClosingEventArgs e)
        {
            Log.WriteMessage(Application.ProductName + " is closing.");
            // flush the Log for the first time to save all messages
            Log.FlushLog();
            //save window size, state and location
            Properties.Settings.Default.General_WindowLocation = this.Location;
            if (this.WindowState == FormWindowState.Normal)
                Properties.Settings.Default.General_WindowSize = this.Size;
            else
                Properties.Settings.Default.General_WindowSize = this.RestoreBounds.Size;
            Properties.Settings.Default.General_WindowState = this.WindowState;
            Say("Waiting for background threads to close...");
            // close background threads, save database and settings
            try
            {
                // cancel background workers
                CancelAllBackgroundWorkers();
                // save splitter positions
                Properties.Settings.Default.MainSplitter_Distance = sc_Main.SplitterDistance;
                Properties.Settings.Default.MapSplitter_Distance = CurrentMapSplitterDistance;
                // invalidate tracking values
                Properties.Settings.Default.Track_SetAz = double.NaN;
                Properties.Settings.Default.Track_SetEl = double.NaN;
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
            finally
            {
                // save InMemory databases if any
                if (StationData.Database.IsInMemory())
                {
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    SayDatabase("Saving station database...");
                    StationData.Database.BackupDatabase();
                    st.Stop();
                    Log.WriteMessage("Station database saved, " + st.ElapsedMilliseconds.ToString() + " ms.");
                }
                if (PropagationData.Database.IsInMemory(ELEVATIONMODEL.GLOBE))
                {
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    SayDatabase("Saving GLOBE database...");
                    PropagationData.Database.BackupDatabase(ELEVATIONMODEL.GLOBE);
                    st.Stop();
                    Log.WriteMessage("Propagation database GLOBE saved, " + st.ElapsedMilliseconds.ToString() + " ms.");
                }
                if (PropagationData.Database.IsInMemory(ELEVATIONMODEL.SRTM3))
                {
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    SayDatabase("Saving SRTM3 database...");
                    PropagationData.Database.BackupDatabase(ELEVATIONMODEL.SRTM3);
                    st.Stop();
                    Log.WriteMessage("Propagation database SRTM3 saved, " + st.ElapsedMilliseconds.ToString() + " ms.");
                }
                if (PropagationData.Database.IsInMemory(ELEVATIONMODEL.SRTM1))
                {
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    SayDatabase("Saving SRTM1 database...");
                    PropagationData.Database.BackupDatabase(ELEVATIONMODEL.SRTM1);
                    st.Stop();
                    Log.WriteMessage("Propagation database SRTM1 saved, " + st.ElapsedMilliseconds.ToString() + " ms.");
                }
                if (AircraftData.Database.IsInMemory())
                {
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    SayDatabase("Saving aircraft database...");
                    AircraftData.Database.BackupDatabase();
                    st.Stop();
                    Log.WriteMessage("Aircraft database saved, " + st.ElapsedMilliseconds.ToString() + " ms.");
                }
                // save properties to file
                SaveUserSettings();
                // flush the Log again in case of any exception to save all messages
                Log.FlushLog();
            }
        }

        private void MapDlg_FormClosed(object sender, FormClosedEventArgs e)
        {
            Log.WriteMessage(Application.ProductName + " is closed.");
            // flush the Log for the first time to save all messages
            Log.FlushLog();
        }

        #endregion


        #region Service Functions

        private void Say(string text)
        {
            if (String.Compare(tsl_Status.Text, text) == 0)
                return;
            tsl_Status.Text = text;
        }

        private void SayDatabase(string text)
        {
            if (String.Compare(tsl_Database.Text, text) == 0)
                return;
            tsl_Database.Text = text;
        }

        private void SayCalculations(string text)
        {
            if (String.Compare(tsl_Calculations.Text, text) == 0)
                return;
            tsl_Calculations.Text = text;
        }

        private void SayAnalysis(string text)
        {
            if (String.Compare(tb_Analysis_Status.Text, text) == 0)
                return;
            tb_Analysis_Status.Text = text;
            tb_Analysis_Status.Refresh();
        }

        private void UpdateStatus()
        {
            // upddate TextBoxes
            tb_UTC.Text = CurrentTime.ToString("yyyy-MM-dd HH:mm:ss");
            if (Properties.Settings.Default.Time_Mode_Online)
                tb_UTC.BackColor = Color.LightSalmon;
            else
                tb_UTC.BackColor = Color.Plum;
            string call = Properties.Settings.Default.MyCall;
            cb_MyCall.SilentText = Properties.Settings.Default.MyCall;
            cb_MyLoc.SilentText = MaidenheadLocator.LocFromLatLon(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon,
                Properties.Settings.Default.Locator_SmallLettersForSubsquares,
                (int)Properties.Settings.Default.Locator_MaxLength / 2,
                Properties.Settings.Default.Locator_AutoLength);
            cb_DXCall.Text = Properties.Settings.Default.DXCall;
            cb_DXLoc.SilentText = MaidenheadLocator.LocFromLatLon(Properties.Settings.Default.DXLat,
                Properties.Settings.Default.DXLon,
                Properties.Settings.Default.Locator_SmallLettersForSubsquares,
                (int)Properties.Settings.Default.Locator_MaxLength / 2,
                Properties.Settings.Default.Locator_AutoLength);
            if (MaidenheadLocator.Check(cb_MyLoc.Text) && MaidenheadLocator.Check(cb_DXLoc.Text))
            {
                tb_QTF.Text = Math.Round(LatLon.Bearing(Properties.Settings.Default.MyLat,
                    Properties.Settings.Default.MyLon,
                    Properties.Settings.Default.DXLat,
                    Properties.Settings.Default.DXLon)).ToString("F0");
                tb_QRB.Text = Math.Round(LatLon.Distance(Properties.Settings.Default.MyLat,
                    Properties.Settings.Default.MyLon,
                    Properties.Settings.Default.DXLat,
                    Properties.Settings.Default.DXLon)).ToString("F0");
            }
            else
            {
                tb_QRB.Text = "0";
                tb_QTF.Text = "0";
            }
            // colour Textbox if more precise lat/lon information is available
            if (MaidenheadLocator.IsPrecise(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, 3))
            {
                cb_MyLoc.BackColor = Color.PaleGreen;
            }
            else
            {
                cb_MyLoc.BackColor = Color.FloralWhite;
            }
            // colour Textbox if more precise lat/lon information is available
            if (MaidenheadLocator.IsPrecise(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon, 3))
            {
                cb_DXLoc.BackColor = Color.PaleGreen;
            }
            else
            {
                cb_DXLoc.BackColor = Color.FloralWhite;
            }
            cb_Band.SelectedItem = Bands.GetStringValue(Properties.Settings.Default.Band);
        }

        private void Alarm(string msg)
        {
            if (Properties.Settings.Default.Alarm_Activate)
            {
                gb_Map_Alarms.BackColor = Color.Plum;
                if (Properties.Settings.Default.Alarm_BringWindowToFront)
                {
                    // try different methods to bring the window to front under WinXP and Win7
                    this.TopMost = true;
                    SetForegroundWindow(this.Handle);
                    // restore window size, state and location
                    try
                    {
                        this.WindowState = Properties.Settings.Default.General_WindowState;
                        this.Size = Properties.Settings.Default.General_WindowSize;
                        this.Location = Properties.Settings.Default.General_WindowLocation;
                    }
                    catch (Exception ex)
                    {
                        // do nothing if failed
                        Log.WriteMessage(ex.Message);
                    }
                    this.BringToFront();
                    this.Activate();
                    this.TopMost = false;
                }
                if (Properties.Settings.Default.Alarm_PlaySound)
                    System.Media.SystemSounds.Beep.Play();
            }
        }

        private void MapSave()
        {
            Log.WriteMessage("Started.");
            try
            {
                Bitmap bmp = new Bitmap(this.Width, this.Height);
                this.DrawToBitmap(bmp, new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), this.Size));
                EncoderParameters encoderParameters = new EncoderParameters(1);
                encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
                ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
                foreach (ImageCodecInfo codec in codecs)
                {
                    if (codec.FormatID == System.Drawing.Imaging.ImageFormat.Jpeg.Guid)
                    {
                        bmp.Save(TmpDirectory + Path.DirectorySeparatorChar + Properties.Settings.Default.Band + "_" + Properties.Settings.Default.MyCall.Replace("/", "_") + "_" + Properties.Settings.Default.DXCall.Replace("/", "_") + "_" + CurrentTime.ToString("yyyyMMdd") + "_" + CurrentTime.ToString("HHmmss") + ".jpg", codec, encoderParameters);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
            Log.WriteMessage("Finished.");
        }


        private void cb_Alarms_Activate_CheckedChanged(object sender, EventArgs e)
        {
            if (!cb_Alarms_Activate.Checked)
                gb_Map_Alarms.BackColor = SystemColors.Control;
        }

        private void tb_UTC_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (PlayMode != AIRSCOUTPLAYMODE.PAUSE)
                return;
            SetTimeDlg Dlg = new SetTimeDlg();
            Dlg.cb_Time_Online.Checked = Properties.Settings.Default.Time_Mode_Online;
            Dlg.dtp_SetTimeDlg_Start.Value = Properties.Settings.Default.Time_Offline;
            if (Dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                Properties.Settings.Default.Time_Offline = Dlg.dtp_SetTimeDlg_Start.Value;
                Properties.Settings.Default.Time_Mode_Online = Dlg.cb_Time_Online.Checked;
                UpdateStatus();
            }
        }

        private void UpdateLocation(string call, double lat, double lon, GEOSOURCE source)
        {
            // update callsign database with new lat/lon info
            if (Callsign.Check(call))
                StationData.Database.LocationInsertOrUpdateIfNewer(new LocationDesignator(call, lat, lon, source));
        }

        public LocationDesignator LocationFindOrCreate(string call, string loc)
        {
            // check all parameters
            if (!Callsign.Check(call))
                return null;
            if (!MaidenheadLocator.Check(loc))
                return null;
            // get location info
            LocationDesignator ld = StationData.Database.LocationFindOrCreate(call, loc);
            // get elevation
            ld.Elevation = GetElevation(ld.Lat, ld.Lon);
            ld.BestCaseElevation = false;
            // modify location in case of best case elevation is selected --> but do not store in database or settings!
            if (Properties.Settings.Default.Path_BestCaseElevation)
            {
                if (!MaidenheadLocator.IsPrecise(ld.Lat, ld.Lon, 3))
                {
                    ElvMinMaxInfo maxinfo = GetMinMaxElevationLoc(ld.Loc);
                    if (maxinfo != null)
                    {
                        ld.Lat = maxinfo.MaxLat;
                        ld.Lon = maxinfo.MaxLon;
                        ld.Elevation = maxinfo.MaxElv;
                        ld.BestCaseElevation = true;
                    }
                }
            }
            return ld;
        }

        public LocationDesignator LocationFindOrUpdateOrCreate(string call, double lat, double lon)
        {
            // check all parameters
            if (!Callsign.Check(call))
                return null;
            if (!GeographicalPoint.Check(lat, lon))
                return null;
            // get location info
            LocationDesignator ld = StationData.Database.LocationFindOrUpdateOrCreate(call, lat, lon);
            // get elevation
            ld.Elevation = GetElevation(ld.Lat, ld.Lon);
            ld.BestCaseElevation = false;
            // modify location in case of best case elevation is selected --> but do not store in database or settings!
            if (Properties.Settings.Default.Path_BestCaseElevation)
            {
                if (!MaidenheadLocator.IsPrecise(ld.Lat, ld.Lon, 3))
                {
                    ElvMinMaxInfo maxinfo = GetMinMaxElevationLoc(ld.Loc);
                    if (maxinfo != null)
                    {
                        ld.Lat = maxinfo.MaxLat;
                        ld.Lon = maxinfo.MaxLon;
                        ld.Elevation = maxinfo.MaxElv;
                        ld.BestCaseElevation = true;
                    }
                }
            }
            return ld;
        }

        public LocationDesignator LocationFind(string call, string loc = "")
        {
            // check all parameters
            if (!Callsign.Check(call))
                return null;
            if (!String.IsNullOrEmpty(loc) && !MaidenheadLocator.Check(loc))
                return null;
            // get location info
            LocationDesignator ld = (String.IsNullOrEmpty(loc)) ? StationData.Database.LocationFind(call) : StationData.Database.LocationFind(call, loc);
            // return null if not found
            if (ld == null)
                return null;
            // get elevation
            ld.Elevation = GetElevation(ld.Lat, ld.Lon);
            ld.BestCaseElevation = false;
            // modify location in case of best case elevation is selected --> but do not store in database or settings!
            if (Properties.Settings.Default.Path_BestCaseElevation)
            {
                if (!MaidenheadLocator.IsPrecise(ld.Lat, ld.Lon, 3))
                {
                    ElvMinMaxInfo maxinfo = GetMinMaxElevationLoc(ld.Loc);
                    if (maxinfo != null)
                    {
                        ld.Lat = maxinfo.MaxLat;
                        ld.Lon = maxinfo.MaxLon;
                        ld.Elevation = maxinfo.MaxElv;
                        ld.BestCaseElevation = true;
                    }
                }
            }
            return ld;
        }

        public short GetElevation(string loc)
        {
            return GetElevation(MaidenheadLocator.LatFromLoc(loc), MaidenheadLocator.LonFromLoc(loc));
        }

        public short GetElevation(double lat, double lon)
        {
            if (!GeographicalPoint.Check(lat, lon))
                return 0;
            short elv = ElevationData.Database.ElvMissingFlag;
            // try to get elevation data from distinct elevation model
            // start with detailed one
            if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (elv == ElevationData.Database.ElvMissingFlag))
                elv = ElevationData.Database[lat, lon, ELEVATIONMODEL.SRTM1, false];
            if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (elv == ElevationData.Database.ElvMissingFlag))
                elv = ElevationData.Database[lat, lon, ELEVATIONMODEL.SRTM3, false];
            if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (elv == ElevationData.Database.ElvMissingFlag))
                elv = ElevationData.Database[lat, lon, ELEVATIONMODEL.GLOBE, false];
            // set it to zero if still invalid
            if (elv <= ElevationData.Database.TileMissingFlag)
                elv = 0;
            return elv;
        }

        public ElvMinMaxInfo GetMinMaxElevationLoc(string loc)
        {
            ElvMinMaxInfo elv = new ElvMinMaxInfo();
            // try to get elevation data from distinct elevation model
            // start with detailed one
            if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (elv.MaxElv == ElevationData.Database.ElvMissingFlag))
            {
                ElvMinMaxInfo info = ElevationData.Database.GetMaxElvLoc(loc, ELEVATIONMODEL.SRTM1, false);
                if (info != null)
                {
                    elv.MaxLat = info.MaxLat;
                    elv.MaxLon = info.MaxLon;
                    elv.MaxElv = info.MaxElv;
                    elv.MinLat = info.MinLat;
                    elv.MinLon = info.MinLon;
                    elv.MinElv = info.MinElv;
                }
            }
            if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (elv.MaxElv == ElevationData.Database.ElvMissingFlag))
            {
                ElvMinMaxInfo info = ElevationData.Database.GetMaxElvLoc(loc, ELEVATIONMODEL.SRTM3, false);
                if (info != null)
                {
                    elv.MaxLat = info.MaxLat;
                    elv.MaxLon = info.MaxLon;
                    elv.MaxElv = info.MaxElv;
                    elv.MinLat = info.MinLat;
                    elv.MinLon = info.MinLon;
                    elv.MinElv = info.MinElv;
                }
            }
            if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (elv.MaxElv == ElevationData.Database.ElvMissingFlag))
            {
                ElvMinMaxInfo info = ElevationData.Database.GetMaxElvLoc(loc, ELEVATIONMODEL.GLOBE, false);
                if (info != null)
                {
                    elv.MaxLat = info.MaxLat;
                    elv.MaxLon = info.MaxLon;
                    elv.MaxElv = info.MaxElv;
                    elv.MinLat = info.MinLat;
                    elv.MinLon = info.MinLon;
                    elv.MinElv = info.MinElv;
                }
            }
            // set it to zero if still invalid
            if (elv.MaxElv == ElevationData.Database.ElvMissingFlag)
                elv.MaxElv = 0;
            if (elv.MinElv == ElevationData.Database.ElvMissingFlag)
                elv.MinElv = 0;

            return elv;
        }

        public void SetElevationModel()
        {
            if (Properties.Settings.Default.Elevation_SRTM1_Enabled)
                Properties.Settings.Default.ElevationModel = ELEVATIONMODEL.SRTM1;
            else if (Properties.Settings.Default.Elevation_SRTM3_Enabled)
                Properties.Settings.Default.ElevationModel = ELEVATIONMODEL.SRTM3;
            else if (Properties.Settings.Default.Elevation_GLOBE_Enabled)
                Properties.Settings.Default.ElevationModel = ELEVATIONMODEL.GLOBE;
            else
                Properties.Settings.Default.ElevationModel = ELEVATIONMODEL.NONE;
        }

        public static Font CreateFontFromString(string font)
        {
            try
            {
                string[] a = Properties.Settings.Default.Map_ToolTipFont.Split(';');
                string fontfamily = a[0].Trim();
                float emsize = 0;
                float.TryParse(a[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out emsize);
                FontStyle fontstyle = 0;
                // check if any additional font style is given
                if (a.Length > 2)
                {
                    if (a[2].ToUpper().IndexOf("BOLD") >= 0)
                        fontstyle = fontstyle | FontStyle.Bold;
                    if (a[2].ToUpper().IndexOf("ITALIC") >= 0)
                        fontstyle = fontstyle | FontStyle.Italic;
                    if (a[2].ToUpper().IndexOf("UNDERLINE") >= 0)
                        fontstyle = fontstyle | FontStyle.Underline;
                    if (a[2].ToUpper().IndexOf("STRIKEOUT") >= 0)
                        fontstyle = fontstyle | FontStyle.Strikeout;
                }
                else
                {
                    fontstyle = FontStyle.Regular;
                }
                return new Font(fontfamily, emsize, fontstyle, GraphicsUnit.Point);
            }
            catch
            {

            }
            return null;
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        enum ShowWindowCommands : int
        {
            /// <summary>
            /// Hides the window and activates another window.
            /// </summary>
            Hide = 0,
            /// <summary>
            /// Activates and displays a window. If the window is minimized or
            /// maximized, the system restores it to its original size and position.
            /// An application should specify this flag when displaying the window
            /// for the first time.
            /// </summary>
            Normal = 1,
            /// <summary>
            /// Activates the window and displays it as a minimized window.
            /// </summary>
            ShowMinimized = 2,
            /// <summary>
            /// Maximizes the specified window.
            /// </summary>
            Maximize = 3, // is this the right value?
            /// <summary>
            /// Activates the window and displays it as a maximized window.
            /// </summary>      
            ShowMaximized = 3,
            /// <summary>
            /// Displays a window in its most recent size and position. This value
            /// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except
            /// the window is not activated.
            /// </summary>
            ShowNoActivate = 4,
            /// <summary>
            /// Activates the window and displays it in its current size and position.
            /// </summary>
            Show = 5,
            /// <summary>
            /// Minimizes the specified window and activates the next top-level
            /// window in the Z order.
            /// </summary>
            Minimize = 6,
            /// <summary>
            /// Displays the window as a minimized window. This value is similar to
            /// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the
            /// window is not activated.
            /// </summary>
            ShowMinNoActive = 7,
            /// <summary>
            /// Displays the window in its current size and position. This value is
            /// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the
            /// window is not activated.
            /// </summary>
            ShowNA = 8,
            /// <summary>
            /// Activates and displays the window. If the window is minimized or
            /// maximized, the system restores it to its original size and position.
            /// An application should specify this flag when restoring a minimized window.
            /// </summary>
            Restore = 9,
            /// <summary>
            /// Sets the show state based on the SW_* value specified in the
            /// STARTUPINFO structure passed to the CreateProcess function by the
            /// program that started the application.
            /// </summary>
            ShowDefault = 10,
            /// <summary>
            ///  <b>Windows 2000/XP:</b> Minimizes a window, even if the thread
            /// that owns the window is not responding. This flag should only be
            /// used when minimizing windows from a different thread.
            /// </summary>
            ForceMinimize = 11
        }

        private void ShowOptionsDlg()
        {
            // stop background threads
            Say("Waiting for background threads to close....");
            StopAllBackgroundWorkers();
            // save current settings
            SaveUserSettings();
            // show options dialog
            OptionsDlg Dlg = new OptionsDlg(this);
            Say("Options");
            if (Dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                // clear paths cache assuming that new options were set
                ElevationPaths.Clear();
                PropagationPaths.Clear();
                // update station infos
                UpdateLocation(Properties.Settings.Default.MyCall,
                    Properties.Settings.Default.MyLat,
                    Properties.Settings.Default.MyLon,
                    MaidenheadLocator.IsPrecise(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, 3) ? GEOSOURCE.FROMUSER : GEOSOURCE.FROMLOC);
                UpdateLocation(Properties.Settings.Default.DXCall,
                    Properties.Settings.Default.DXLat,
                    Properties.Settings.Default.DXLon,
                    MaidenheadLocator.IsPrecise(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon, 3) ? GEOSOURCE.FROMUSER : GEOSOURCE.FROMLOC);

                // update map provider
                gm_Main.MapProvider = GMapProviders.Find(Properties.Settings.Default.Map_Provider);

                // update ToolTipFont
                ToolTipFont = CreateFontFromString(Properties.Settings.Default.Map_ToolTipFont);

                // update planefeeds
                bw_PlaneFeed1 = null;
                bw_PlaneFeed2 = null;
                bw_PlaneFeed3 = null;
                foreach (PlaneFeed feed in PlaneFeeds)
                {
                    if (Properties.Settings.Default.Planes_PlaneFeed1 == feed.Name)
                        bw_PlaneFeed1 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed2 == feed.Name)
                        bw_PlaneFeed2 = feed;
                    if (Properties.Settings.Default.Planes_PlaneFeed3 == feed.Name)
                        bw_PlaneFeed3 = feed;
                }
                // update timer interval
                ti_Progress.Interval = Properties.Settings.Default.Map_Update * 1000;
                // update background update intervals
                ScoutBase.Elevation.Properties.Settings.Default.Datatbase_BackgroundUpdate_Period = (int)Properties.Settings.Default.Background_Update_Period;
                ScoutBase.Stations.Properties.Settings.Default.Database_BackgroundUpdate_Period = (int)Properties.Settings.Default.Background_Update_Period;
                AirScout.Aircrafts.Properties.Settings.Default.Database_BackgroundUpdate_Period = (int)Properties.Settings.Default.Background_Update_Period;
                // update database path path and elevation model
                InitializeDatabase();
            }
            else
            {
                // nothing was changed
                Properties.Settings.Default.Reload();
            }
            // start permanent background workers
            StartAllBackgroundWorkers();
            // update status window
            UpdateStatus();
            UpdateAirports();
            UpdateWatchlistInMap();
            RefreshWatchlistView();

        }

        #endregion

        #region Play & Pause

        private void Play()
        {
            PlayMode = AIRSCOUTPLAYMODE.FORWARD;
            // switch tab control according to path mode
            if (PathMode == AIRSCOUTPATHMODE.SINGLE)
                tc_Control.SelectedTab = tp_Control_Single;
            else if (PathMode == AIRSCOUTPATHMODE.MULTI)
                tc_Control.SelectedTab = tp_Control_Multi;
            // update tab control
            tc_Control.Refresh();
            // update all current paths
            UpdatePaths();
            // clear spectrum
            try
            {
                Spectrum.Points.Clear();
                SpectrumPointsCount = 0;
                Spectrum_X.Reset();
                SpectrumRecord.Points.Clear();
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
            // change button image
            btn_Map_PlayPause.Image = il_Main.Images[0];
            // disable controls
            cb_Band.Enabled = false;
            btn_Options.Enabled = false;
            cb_MyCall.Enabled = false;
            cb_MyLoc.Enabled = false;
            cb_DXCall.Enabled = false;
            cb_DXLoc.Enabled = false;
            tc_Control.Enabled = false;
            pa_Planes_Filter.Enabled = false;
            gb_Analysis_Controls.Enabled = false;
            gb_Analysis_Database.Enabled = false;
            gb_Analysis_Player.Enabled = false;
            tc_Main.Enabled = false;
            //referesh main window
            this.Refresh();
        }

        private void Pause()
        {
            PlayMode = AIRSCOUTPLAYMODE.PAUSE;
            // change button image
            btn_Map_PlayPause.Image = il_Main.Images[1];
            // update tab control
            tc_Control.Refresh();
            // enable controls
            cb_Band.Enabled = true;
            btn_Options.Enabled = true;
            cb_MyCall.Enabled = true;
            cb_MyLoc.Enabled = true;
            cb_DXCall.Enabled = true;
            cb_DXLoc.Enabled = true;
            tc_Control.Enabled = true;
            pa_Planes_Filter.Enabled = true;
            gb_Analysis_Controls.Enabled = true;
            gb_Analysis_Database.Enabled = true;
            gb_Analysis_Player.Enabled = true;
            tc_Main.Enabled = true;
            // stop tracking
            Properties.Settings.Default.Track_SetAz = double.NaN;
            Properties.Settings.Default.Track_SetEl = double.NaN;
            //referesh main window
            this.Refresh();
        }

        #endregion

        #region Paths

        private double GetMinH(double max_alt, double H1, double H2)
        {
            double max = Math.Max(H1, H2);
            if (max <= max_alt)
                return max;
            return max_alt;
        }

        private void ClearAllPathsInMap()
        {
            gmo_PropagationPaths.Clear();
        }

        private void DrawPath(PropagationPathDesignator ppath)
        {
            // draws a propagation path to map
            PropagationPoint[] ppoints = new PropagationPoint[0];
            try
            {
                // get infopoints for map
                ppoints = ppath.GetInfoPoints();
                // calculate midpoint
                ScoutBase.Core.LatLon.GPoint midpoint = LatLon.MidPoint(ppath.Lat1, ppath.Lon1, ppath.Lat2, ppath.Lon2);
                GMapMarker gmmid = new GMarkerGoogle(new PointLatLng(midpoint.Lat, midpoint.Lon), ToolTipFont, Properties.Settings.Default.Map_SmallMarkers ? GMarkerGoogleType.blue_small : GMarkerGoogleType.blue_dot);
                gmmid.ToolTipText = ppath.Location1.Call + " <> " + ppath.Location2.Call;
                gmmid.ToolTipMode = MarkerTooltipMode.OnMouseOver;
                gmo_Objects.Markers.Add(gmmid);
                // calculate dx end
                gmm_DXLoc = new GMarkerGoogle(new PointLatLng(ppath.Lat2, ppath.Lon2), ToolTipFont, Properties.Settings.Default.Map_SmallMarkers ? GMarkerGoogleType.yellow_small : GMarkerGoogleType.yellow_dot);
                gmm_DXLoc.ToolTipText = ppath.Location2.Call + "\n" +
                    ppath.Location2.Lat.ToString("F8", CultureInfo.InvariantCulture) + "\n" +
                    ppath.Location2.Lon.ToString("F8", CultureInfo.InvariantCulture) + "\n" +
                    ppath.Location2.Loc + "\n" +
                    GetElevation(ppath.Location2.Lat, ppath.Location2.Lon).ToString("F0") + "m";
                if (Properties.Settings.Default.Track_Activate)
                    gmm_DXLoc.ToolTipText += "\nRight+Click to Turn Antenna";
                gmm_DXLoc.ToolTipMode = MarkerTooltipMode.OnMouseOver;
                gmm_DXLoc.Tag = ppath.Location2.Call;
                gmo_Objects.Markers.Add(gmm_DXLoc);
                // set three small points for hot path, if one
                if (!Properties.Settings.Default.Map_SmallMarkers)
                {
                    int i1 = -1;
                    int i3 = -1;
                    for (int i = 0; i < ppoints.Length; i++)
                    {
                        if (Math.Max(ppoints[i].H1, ppoints[i].H2) < Properties.Settings.Default.Planes_MaxAlt)
                        {
                            if (i1 == -1)
                                i1 = i;
                            else i3 = i;

                        }
                    }
                    if ((i1 >= 0) && (i3 >= 0))
                    {
                        GMapMarker gmi1 = new GMarkerGoogle(new PointLatLng(ppoints[i1].Lat, ppoints[i1].Lon), GMarkerGoogleType.red_small);
                        gmo_Objects.Markers.Add(gmi1);
                        LatLon.GPoint gp = LatLon.MidPoint(ppoints[i1].Lat, ppoints[i1].Lon, ppoints[i3].Lat, ppoints[i3].Lon);
                        GMapMarker gmi2 = new GMarkerGoogle(new PointLatLng(gp.Lat, gp.Lon), GMarkerGoogleType.blue_small);
                        gmo_Objects.Markers.Add(gmi2);
                        GMapMarker gmi3 = new GMarkerGoogle(new PointLatLng(ppoints[i3].Lat, ppoints[i3].Lon), GMarkerGoogleType.yellow_small);
                        gmo_Objects.Markers.Add(gmi3);
                    }
                }
                // draw propagation path according to path status
                // valid: black
                // invalid: red
                gmr_FullPath = new GMapRoute("fullpath");
                gmr_FullPath.Stroke = (ppath.Valid) ? new Pen(Color.Black, 3) : new Pen(Color.Red, 3);
                gmo_PropagationPaths.Routes.Add(gmr_FullPath);
                gmr_NearestFull = new GMapRoute("fullpath");
                gmr_NearestFull.Stroke = (ppath.Valid) ? new Pen(Color.Black, 3) : new Pen(Color.Red, 3);
                gmo_NearestPaths.Routes.Add(gmr_NearestFull);
                foreach (PropagationPoint ppoint in ppoints)
                {
                    gmr_FullPath.Points.Add(new PointLatLng(ppoint.Lat, ppoint.Lon));
                    gmr_NearestFull.Points.Add(new PointLatLng(ppoint.Lat, ppoint.Lon));
                }
                // draw mutual visible path
                gmr_VisiblePpath = new GMapRoute("visiblepath");
                gmr_VisiblePpath.Stroke = new Pen(Color.Magenta, 3);
                gmr_NearestVisible = new GMapRoute("visiblepath");
                gmr_NearestVisible.Stroke = new Pen(Color.Magenta, 3);
                for (int i = 0; i < ppoints.Length; i++)
                {
                    if ((Math.Max(ppoints[i].H1, ppoints[i].H2) > 0) && (Math.Max(ppoints[i].H1, ppoints[i].H2) < Properties.Settings.Default.Planes_MaxAlt))
                    {
                        PointLatLng p = new PointLatLng(ppoints[i].Lat, ppoints[i].Lon);
                        gmr_VisiblePpath.Points.Add(p);
                        gmr_NearestVisible.Points.Add(p);
                    }
                }
                gmo_PropagationPaths.Routes.Add(gmr_VisiblePpath);
                gmo_NearestPaths.Routes.Add(gmr_NearestVisible);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void UpdatePaths()
        {
            // updates all current path to calculate

            Log.WriteMessage("UpdatePath started.");
            Stopwatch st = new Stopwatch();
            st.Start();

            // check if there are a valid home settings
            if (!Callsign.Check(Properties.Settings.Default.MyCall) ||
                !GeographicalPoint.Check(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon))
                return;

            // OK valid, lets continue

            // slow down background calculations
            Properties.Settings.Default.Background_Calculations_ThreadWait = 1000;

            //clear map overlays
            gmo_PropagationPaths.Clear();
            gmo_NearestPaths.Clear();
            gmo_Objects.Clear();

            // clear all planes and tooltips
            gmo_Planes.Clear();

            // clear paths
            ElevationPaths.Clear();
            PropagationPaths.Clear();

            // clear charts
            ClearCharts();

            // put call on MyCalls last recent collection if not already in
            if (Properties.Settings.Default.MyCalls.IndexOf(Properties.Settings.Default.MyCall) < 0)
            {
                Properties.Settings.Default.MyCalls.Insert(0, Properties.Settings.Default.MyCall);
            }

            // keep the MyCalls list small
            while (Properties.Settings.Default.MyCalls.Count > 10)
            {
                Properties.Settings.Default.MyCalls.RemoveAt(Properties.Settings.Default.MyCalls.Count - 1);
            }


            // check and update station database
            LocationDesignator myloc = LocationFindOrUpdateOrCreate(Properties.Settings.Default.MyCall, Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
            Properties.Settings.Default.MyElevation = myloc.Elevation;

            // get qrv info or create default
            QRVDesignator myqrv = StationData.Database.QRVFindOrCreateDefault(myloc.Call, myloc.Loc, Properties.Settings.Default.Band);
            // set qrv defaults if zero
            if (myqrv.AntennaHeight == 0)
                myqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
            if (myqrv.AntennaGain == 0)
                myqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
            if (myqrv.Power == 0)
                myqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);
            // draw my end on the map
            gmm_MyLoc = new GMarkerGoogle(new PointLatLng(myloc.Lat, myloc.Lon), ToolTipFont, Properties.Settings.Default.Map_SmallMarkers ? GMarkerGoogleType.red_small : GMarkerGoogleType.red_dot);
            gmm_MyLoc.ToolTipText = myloc.Call + "\n" +
                myloc.Lat.ToString("F8", CultureInfo.InvariantCulture) + "\n" +
                myloc.Lon.ToString("F8", CultureInfo.InvariantCulture) + "\n" +
                myloc.Loc + "\n" +
                GetElevation(myloc.Lat, myloc.Lon).ToString("F0") + "m";
            gmm_MyLoc.ToolTipMode = MarkerTooltipMode.OnMouseOver;
            gmm_MyLoc.Tag = myloc.Call;
            gmo_Objects.Markers.Add(gmm_MyLoc);

            // do single path mode
            if (PathMode == AIRSCOUTPATHMODE.SINGLE)
            {
                // check if there are a valid DX settings
                if (!Callsign.Check(Properties.Settings.Default.DXCall) ||
                    !GeographicalPoint.Check(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon))
                    return;

                // OK valid, lets continue
                // check and update station database
                LocationDesignator dxloc = LocationFindOrUpdateOrCreate(Properties.Settings.Default.DXCall, Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon);
                Properties.Settings.Default.DXElevation = dxloc.Elevation;

                // get qrv info or create default
                QRVDesignator dxqrv = StationData.Database.QRVFindOrCreateDefault(dxloc.Call, dxloc.Loc, Properties.Settings.Default.Band);
                // set qrv defaults if zero
                if (dxqrv.AntennaHeight == 0)
                    dxqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                if (dxqrv.AntennaGain == 0)
                    dxqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                if (dxqrv.Power == 0)
                    dxqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);

                // find local obstruction, if any
                LocalObstructionDesignator o = ElevationData.Database.LocalObstructionFind(myloc.Lat, myloc.Lon, Properties.Settings.Default.ElevationModel);
                double mybearing = LatLon.Bearing(myloc.Lat, myloc.Lon, dxloc.Lat, dxloc.Lon);
                double myobstr = (o != null) ? o.GetObstruction(myqrv.AntennaHeight, mybearing) : double.MinValue;

                // try to find elevation path in database or create new one and store
                ElevationPathDesignator epath = ElevationData.Database.ElevationPathFindOrCreateFromLatLon(
                    null,
                    myloc.Lat,
                    myloc.Lon,
                    dxloc.Lat,
                    dxloc.Lon,
                    ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                    Properties.Settings.Default.ElevationModel);
                // add additional info to ppath
                epath.Location1 = myloc;
                epath.Location2 = dxloc;
                epath.QRV1 = myqrv;
                epath.QRV2 = dxqrv;

                // try to find propagation path in database or create new one and store
                PropagationPathDesignator ppath = PropagationData.Database.PropagationPathFindOrCreateFromLatLon(
                    null,
                    myloc.Lat,
                    myloc.Lon,
                    GetElevation(myloc.Lat, myloc.Lon) + myqrv.AntennaHeight,
                    dxloc.Lat,
                    dxloc.Lon,
                    GetElevation(dxloc.Lat, dxloc.Lon) + dxqrv.AntennaHeight,
                    Bands.ToGHz(Properties.Settings.Default.Band),
                    LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor,
                    Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance,
                    ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                    Properties.Settings.Default.ElevationModel,
                    myobstr);

                // add additional info to ppath
                ppath.Location1 = myloc;
                ppath.Location2 = dxloc;
                ppath.QRV1 = myqrv;
                ppath.QRV2 = dxqrv;

                // add single path to paths list
                ElevationPaths.Add(epath);
                PropagationPaths.Add(ppath);
                // put DXCall on the watchlist if not already in
                if (Properties.Settings.Default.Watchlist.IndexOf(Properties.Settings.Default.DXCall, MaidenheadLocator.LocFromLatLon(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon, false, 3)) < 0)
                {
                    Properties.Settings.Default.Watchlist.Insert(0, new WatchlistItem(Properties.Settings.Default.DXCall, MaidenheadLocator.LocFromLatLon(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon, false, 3), ppath.Distance > Properties.Settings.Default.Path_MaxLength));
                }
                // keep the watchlist small
                while (Properties.Settings.Default.Watchlist.Count() > Properties.Settings.Default.Watchlist_MaxCount)
                {
                    Properties.Settings.Default.Watchlist.RemoveAt(Properties.Settings.Default.Watchlist.Count() - 1);
                }

            }
            else if (PathMode == AIRSCOUTPATHMODE.MULTI)
            {
                // iterate through watchlist and add selected
                foreach (ListViewItem item in lv_Control_Watchlist.Items)
                {
                    // use only selected items
                    if (!item.Checked)
                        continue;
                    string call = item.Text;
                    string loc = item.SubItems[1].Text;

                    // check if call & loc are valid
                    if (!Callsign.Check(call) || !MaidenheadLocator.Check(loc))
                        continue;

                    // check and update station database
                    LocationDesignator dxloc = LocationFindOrCreate(call, loc);

                    // get qrv info or create default
                    QRVDesignator dxqrv = StationData.Database.QRVFindOrCreateDefault(dxloc.Call, dxloc.Loc, Properties.Settings.Default.Band);
                    // set qrv defaults if zero
                    if (dxqrv.AntennaHeight == 0)
                        dxqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                    if (dxqrv.AntennaGain == 0)
                        dxqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                    if (dxqrv.Power == 0)
                        dxqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);

                    // find local obstruction, if any
                    LocalObstructionDesignator o = ElevationData.Database.LocalObstructionFind(myloc.Lat, myloc.Lon, Properties.Settings.Default.ElevationModel);
                    double mybearing = LatLon.Bearing(myloc.Lat, myloc.Lon, dxloc.Lat, dxloc.Lon);
                    double myobstr = (o != null) ? o.GetObstruction(myqrv.AntennaHeight, mybearing) : double.MinValue;

                    // try to find elevation path in database or create new one and store
                    ElevationPathDesignator epath = ElevationData.Database.ElevationPathFindOrCreateFromLatLon(
                        null,
                        myloc.Lat,
                        myloc.Lon,
                        dxloc.Lat,
                        dxloc.Lon,
                        ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                        Properties.Settings.Default.ElevationModel);
                    // try to find propagation path in database or create new one and store
                    PropagationPathDesignator ppath = PropagationData.Database.PropagationPathFindOrCreateFromLatLon(
                        null,
                        myloc.Lat,
                        myloc.Lon,
                        GetElevation(myloc.Lat, myloc.Lon) + myqrv.AntennaHeight,
                        dxloc.Lat,
                        dxloc.Lon,
                        GetElevation(dxloc.Lat, dxloc.Lon) + dxqrv.AntennaHeight,
                        Bands.ToGHz(Properties.Settings.Default.Band),
                        LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor,
                        Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance,
                        ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                        Properties.Settings.Default.ElevationModel,
                        myobstr);
                    // add additional info to ppath
                    ppath.Location1 = myloc;
                    ppath.Location2 = dxloc;
                    ppath.QRV1 = myqrv;
                    ppath.QRV2 = dxqrv;
                    // add path to paths list

                    ElevationPaths.Add(epath);
                    PropagationPaths.Add(ppath);
                }
            }

            // calculate the area to show in map
            // initially set to my location
            double minlat = myloc.Lat;
            double minlon = myloc.Lon;
            double maxlat = myloc.Lat;
            double maxlon = myloc.Lon;

            double centerlat = myloc.Lat;
            double centerlon = myloc.Lon;

            // now do the drawing
            foreach (PropagationPathDesignator ppath in PropagationPaths)
            {
                DrawPath(ppath);

                // maintain Min/Max values
                minlat = Math.Min(minlat, ppath.Lat2);
                minlon = Math.Min(minlon, ppath.Lon2);
                maxlat = Math.Max(maxlat, ppath.Lat2);
                maxlon = Math.Max(maxlon, ppath.Lon2);
            }

            // show diagram when in SINGLE mode
            if (PathMode == AIRSCOUTPATHMODE.SINGLE)
            {
                // both Elevationpaths & PropagationPaths should contain only one entry
                if ((ElevationPaths.Count > 0) && (PropagationPaths.Count > 0))
                    UpdateCharts(ElevationPaths[ElevationPaths.Count - 1], PropagationPaths[PropagationPaths.Count - 1]);
            }

            // calculate center
            centerlat = LatLon.MidPoint(minlat, minlon, maxlat, maxlon).Lat;
            centerlon = LatLon.MidPoint(minlat, minlon, maxlat, maxlon).Lon;

            // ensure that whole path is visible and optionally centered
            gm_Main.SetZoomToFitRect(RectLatLng.FromLTRB(minlon, maxlat, maxlon, minlat));
            if (Properties.Settings.Default.Map_AutoCenter)
                gm_Main.Position = new PointLatLng(centerlat, centerlon);

            // clear all selections
            SelectedPlanes.Clear();

            // update watchlist locations in map
            UpdateWatchlistInMap();

            // update status window
            UpdateStatus();

            // invalidate tracking
            Properties.Settings.Default.Track_SetAz = double.NaN;
            Properties.Settings.Default.Track_SetEl = double.NaN;

            // speed up background calculations
            Properties.Settings.Default.Background_Calculations_ThreadWait = 0;

            st.Stop();
            Log.WriteMessage("UpdatePath finished, " + st.ElapsedMilliseconds.ToString() + "ms.");
        }

        #endregion

        #region Charts

        private void UpdateCharts(ElevationPathDesignator epath, PropagationPathDesignator ppath)
        {
            // updates the diagram area
            short[] epoints = new short[0];
            PropagationPoint[] ppoints = new PropagationPoint[0];
            try
            {
                ClearCharts();
                // adjust diagram axes
                Path_X.Maximum = ppath.Distance;
                Elevation_X.Maximum = epath.Distance;
                // get infopoints for charting
                epoints = epath.GetInfoPoints();
                ppoints = ppath.GetInfoPoints();
                // calculate epsilon for LOS
                double eps_los = Propagation.EpsilonFromHeights(GetElevation(ppath.Lat1, ppath.Lon1) + ppath.QRV1.AntennaHeight, ppath.Distance, GetElevation(ppath.Lat2, ppath.Lon2) + ppath.QRV2.AntennaHeight, LatLon.Earth.Radius);
                // fill chart
                short maxelv = short.MinValue;
                double myelev = GetElevation(ppath.Lat1, ppath.Lon1);
                for (int i = 0; i < epoints.Length; i++)
                {
                    Path_Elevation.Points.Add(new OxyPlot.DataPoint(i, epoints[i]));
                    Min_H1.Points.Add(new OxyPlot.DataPoint(i, ppoints[i].H1));
                    Min_H2.Points.Add(new OxyPlot.DataPoint(i, ppoints[i].H2));
                    Max_H.Points.Add(new OxyPlot.DataPoint(i, Properties.Settings.Default.Planes_MaxAlt));
                    Min_H.Points.Add(new OxyPlot.DataPoint(i, Properties.Settings.Default.Planes_MaxAlt));
                    Min_H.Points2.Add(new OxyPlot.DataPoint(i, GetMinH(Properties.Settings.Default.Planes_MaxAlt, Min_H1.Points[i].Y, Min_H2.Points[i].Y)));
                    LOS.Points.Add(new OxyPlot.DataPoint(i, Propagation.HeightFromEpsilon(myelev + ppath.QRV1.AntennaHeight, i, eps_los, LatLon.Earth.Radius)));
                    Elevation.Points.Add(new OxyPlot.DataPoint(i, epoints[i]));
                    if (maxelv < epoints[i])
                        maxelv = epoints[i];
                }
                // adjust Y-axis --> max elv + 10%
                Elevation_Y.Maximum = maxelv + maxelv * 0.1;
                // invalidate plots
                pm_Path.InvalidatePlot(true);
                pm_Elevation.InvalidatePlot(true);
                // show path legends
                Charts_ShowLegends(5000);
                // refresh path info
                tp_Elevation.Text = "Pathinfo ";
                if (Properties.Settings.Default.ElevationModel == ELEVATIONMODEL.SRTM1)
                    tp_Elevation.Text = tp_Elevation.Text + "[SRTM1]";
                else if (Properties.Settings.Default.ElevationModel == ELEVATIONMODEL.SRTM3)
                    tp_Elevation.Text = tp_Elevation.Text + "[SRTM3]";
                else if (Properties.Settings.Default.ElevationModel == ELEVATIONMODEL.GLOBE)
                    tp_Elevation.Text = tp_Elevation.Text + "[GLOBE]";
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void ClearCharts()
        {
            try
            {
                // clear all points
                Path_Elevation.Points.Clear();
                Min_H1.Points.Clear();
                Min_H2.Points.Clear();
                Max_H.Points.Clear();
                Min_H.Points.Clear();
                Min_H.Points2.Clear();
                Planes_Hi.Points.Clear();
                Planes_Lo.Points.Clear();
                Elevation.Points.Clear();
                LOS.Points.Clear();
                // update view
                pm_Path.InvalidatePlot(true);
                pm_Elevation.InvalidatePlot(true);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void Charts_ShowLegends(int ms)
        {
            // enable path legends for some seconds
            pm_Path.IsLegendVisible = true;
            pm_Path.InvalidatePlot(true);
            pm_Elevation.IsLegendVisible = true;
            pm_Elevation.InvalidatePlot(true);
            ti_ShowLegends.Interval = ms;
            ti_ShowLegends.Start();
        }

        private void ti_ShowLegends_Tick(object sender, EventArgs e)
        {
            pm_Path.IsLegendVisible = false;
            pm_Path.InvalidatePlot(true);
            pm_Elevation.IsLegendVisible = false;
            pm_Elevation.InvalidatePlot(true);
            ti_ShowLegends.Enabled = false;
        }

        private void pv_Path_Paint(object sender, PaintEventArgs e)
        {
            // draw calsign s on chart
            string mycall = Properties.Settings.Default.MyCall;
            string dxcall = Properties.Settings.Default.DXCall;
            int top = pv_Path.Bottom - pv_Path.Height / 2;
            int left = pv_Path.Left + 50;
            int right = pv_Path.Width - 38;
            Font font = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Bold);
            Graphics g = e.Graphics;
            using (StringFormat format = new StringFormat(StringFormatFlags.DirectionVertical))
            {
                // measure text to emulate TextAlign.Middle
                int mywidth = (int)g.MeasureString(mycall, font).Width;
                int dxwidth = (int)g.MeasureString(dxcall, font).Width;
                using (SolidBrush brush = new SolidBrush(Color.Black))
                {
                    if (pv_Path.Height - 50 > mywidth)
                        g.DrawString(mycall, font, brush, left, top - mywidth / 2, format);
                    if (pv_Path.Height - 50 > dxwidth)
                        g.DrawString(dxcall, font, brush, right, top - dxwidth / 2, format);
                    /*
                    if (pv_Path.Height > 2 * mywidth + 50)
                        g.DrawString(mycall, font, brush, left, top, format);
                    if (pv_Path.Height > 2 * dxwdith + 50)
                        g.DrawString(dxcall, font, brush, right, top, format);
                    */
                }
            }
        }

        #endregion

        #region Planes

        private static Bitmap RotateImageByAngle(System.Drawing.Image oldBitmap, float angle)
        {
            var newBitmap = new Bitmap(oldBitmap.Width, oldBitmap.Height);
            var graphics = Graphics.FromImage(newBitmap);
            graphics.TranslateTransform((float)oldBitmap.Width / 2, (float)oldBitmap.Height / 2);
            graphics.RotateTransform(angle);
            graphics.TranslateTransform(-(float)oldBitmap.Width / 2, -(float)oldBitmap.Height / 2);
            graphics.DrawImage(oldBitmap, new System.Drawing.Point(0, 0));
            return newBitmap;
        }

        private GMarkerGoogle CreatePlaneSimple(PlaneInfo info, bool selected)
        {
            // return on empty info
            if (info == null)
                return null;
            // show flight info only
            // get bitmap according to category
            Bitmap bm;
            int bmindex = bmindex_gray;
            Brush brush = new SolidBrush(Color.FromArgb(180, Color.White));
            if (info.Potential == 100)
            {
                bmindex = bmindex_magenta;
                brush = new SolidBrush(Color.FromArgb(150, Color.Plum));
            }
            else if (info.Potential == 75)
            {
                bmindex = bmindex_red;
                brush = new SolidBrush(Color.FromArgb(150, Color.Red));
            }
            else if (info.Potential == 50)
            {
                bmindex = bmindex_darkorange;
                brush = new SolidBrush(Color.FromArgb(150, Color.DarkOrange));
            }
            if (info.Category == PLANECATEGORY.SUPERHEAVY)
                bm = new Bitmap(il_Planes_S.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.HEAVY)
                bm = new Bitmap(il_Planes_H.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.MEDIUM)
                bm = new Bitmap(il_Planes_M.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.LIGHT)
                bm = new Bitmap(il_Planes_L.Images[bmindex]);
            else
                bm = new Bitmap(il_Planes_M.Images[bmindex]);
            GMarkerGoogle m = new GMarkerGoogle(new PointLatLng(info.Lat, info.Lon), ToolTipFont, RotateImageByAngle(bm, (float)info.Track));
            m.Tag = info.Hex;
            string lat = "";
            if (info.Lat >= 0)
                lat = Math.Abs(info.Lat).ToString("00.00") + "°N";
            else
                lat = Math.Abs(info.Lat).ToString("00.00") + "°S";
            string lon = "";
            if (info.Lon >= 0)
                lon = Math.Abs(info.Lon).ToString("000.00") + "°E";
            else
                lon = Math.Abs(info.Lon).ToString("000.00") + "°W";
            m.ToolTipText = info.Call + "\n--------------------";
            if (Properties.Settings.Default.InfoWin_Position)
                m.ToolTipText += "\nPos: " + lat + " , " + lon;
            if (Properties.Settings.Default.InfoWin_Alt)
            {
                if (Properties.Settings.Default.InfoWin_Metric)
                    m.ToolTipText += "\nAlt: " + (int)info.Alt_m + "m";
                else
                    m.ToolTipText += "\nAlt: " + (int)info.Alt + "ft";
            }
            if (Properties.Settings.Default.InfoWin_Track)
                m.ToolTipText += "\nTrack: " + (int)info.Track + "°";
            if (Properties.Settings.Default.InfoWin_Type)
                m.ToolTipText += "\nType: " + info.Manufacturer + " " + info.Model + " [" + PlaneCategories.GetShortStringValue(info.Category) + "]";
            // set tooltip on if hot
            if (selected)
                m.ToolTipMode = MarkerTooltipMode.Always;
            else
                m.ToolTipMode = MarkerTooltipMode.OnMouseOver;
            if (m.ToolTip != null)
                m.ToolTip.Fill = brush;
            return m;
        }

        private GMarkerGoogle CreatePlaneDetailed(PlaneInfo info, bool selected)
        {
            // return on empty info
            if (info == null)
                return null;
            // get bitmap according to category
            Bitmap bm;
            int bmindex = bmindex_gray;
            Brush brush = new SolidBrush(Color.FromArgb(180, Color.White));
            if (info.Potential == 100)
            {
                bmindex = bmindex_magenta;
                brush = new SolidBrush(Color.FromArgb(150, Color.Plum));
            }
            else if (info.Potential == 75)
            {
                bmindex = bmindex_red;
                brush = new SolidBrush(Color.FromArgb(150, Color.Red));
            }
            else if (info.Potential == 50)
            {
                bmindex = bmindex_darkorange;
                brush = new SolidBrush(Color.FromArgb(150, Color.DarkOrange));
            }
            if (info.Category == PLANECATEGORY.SUPERHEAVY)
                bm = new Bitmap(il_Planes_S.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.HEAVY)
                bm = new Bitmap(il_Planes_H.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.MEDIUM)
                bm = new Bitmap(il_Planes_M.Images[bmindex]);
            else if (info.Category == PLANECATEGORY.LIGHT)
                bm = new Bitmap(il_Planes_L.Images[bmindex]);
            else
                bm = new Bitmap(il_Planes_M.Images[bmindex]);
            GMarkerGoogle m = new GMarkerGoogle(new PointLatLng(info.Lat, info.Lon), ToolTipFont, RotateImageByAngle(bm, (float)info.Track));
            m.Tag = info.Hex;
            string lat = "";
            if (info.Lat >= 0)
                lat = Math.Abs(info.Lat).ToString("00.00") + "°N";
            else
                lat = Math.Abs(info.Lat).ToString("00.00") + "°S";
            string lon = "";
            if (info.Lon >= 0)
                lon = Math.Abs(info.Lon).ToString("000.00") + "°E";
            else
                lon = Math.Abs(info.Lon).ToString("000.00") + "°W";
            int mins = 0;
            if (info.Speed > 0)
                mins = (int)(info.IntQRB / UnitConverter.kts_kmh(info.Speed) * 60.0);
            // fill tooltip texts
            m.ToolTipText = info.Call + "\n--------------------";
            if (Properties.Settings.Default.InfoWin_Position)
                m.ToolTipText += "\nPos: " + lat + " , " + lon;
            if (Properties.Settings.Default.InfoWin_Alt)
            {
                if (Properties.Settings.Default.InfoWin_Metric)
                    m.ToolTipText += "\nAlt: " + (int)info.Alt_m + "m [" + info.AltDiff.ToString("+#;-#;0") + "m]";
                else
                    m.ToolTipText += "\nAlt: " + (int)info.Alt + "ft [" + UnitConverter.m_ft(info.AltDiff).ToString("+#;-#;0") + "ft]";
            }
            if (Properties.Settings.Default.InfoWin_Track)
                m.ToolTipText += "\nTrack: " + info.Track + "°";
            if (Properties.Settings.Default.InfoWin_Speed)
            {
                if (Properties.Settings.Default.InfoWin_Metric)
                    m.ToolTipText += "\nSpeed: " + info.Speed_kmh.ToString("F0") + "km/h";
                else
                    m.ToolTipText += "\nSpeed: " + info.Speed.ToString("F0") + "kts";
            }
            if (Properties.Settings.Default.InfoWin_Type)
                m.ToolTipText += "\nType: " + info.Manufacturer + " " + info.Model + " [" + PlaneCategories.GetShortStringValue(info.Category) + "]";
            if (Properties.Settings.Default.InfoWin_Dist)
            {
                if (Properties.Settings.Default.InfoWin_Metric)
                    m.ToolTipText += "\nDist: " + info.IntQRB.ToString("F0") + "km";
                else
                    m.ToolTipText += "\nDist: " + UnitConverter.km_mi(info.IntQRB).ToString("F0") + "mi";
            }
            if (Properties.Settings.Default.InfoWin_Time)
                m.ToolTipText += "\nTime: " + (CurrentTime + new TimeSpan(0, mins, 0)).ToString("HH:mm") + " [ " + mins.ToString("") + "min]";
            if (Properties.Settings.Default.InfoWin_Angle)
                m.ToolTipText += "\nAngle: " + (info.Angle / Math.PI * 180.0).ToString("F0") + "°";
            if (Properties.Settings.Default.InfoWin_Epsilon)
                m.ToolTipText += "\nEps: " + (info.Eps1 / Math.PI * 180.0).ToString("00.00") + "° <> " + (info.Eps2 / Math.PI * 180.0).ToString("00.00") + "°";
            if (Properties.Settings.Default.InfoWin_Squint)
                m.ToolTipText += "\nSquint: " + (info.Squint / Math.PI * 180).ToString("00.00") + "°";
            if (selected)
            {
                m.ToolTipMode = MarkerTooltipMode.Always;
            }
            else
            {
                m.ToolTipMode = MarkerTooltipMode.OnMouseOver;
            }
            if (m.ToolTip != null)
                m.ToolTip.Fill = brush;
            return m;
        }

        private void DrawPlanes()
        {
            bool alarm = false;
            bool isselected = false;
            string alarm_msg = "";

            List<PlaneInfo> pathplanes = new List<PlaneInfo>();

            // check if any plane is on list --> return empty list
            if ((ActivePlanes == null) || (ActivePlanes.Count == 0))
                return;
            // draw all planes
            foreach (PlaneInfo plane in ActivePlanes.Values)
            {
                try
                {
                    // show planes if it meets filter criteria
                    if ((plane.Alt_m >= Properties.Settings.Default.Planes_MinAlt) && (plane.Category >= Properties.Settings.Default.Planes_Filter_Min_Category))
                    {
                        // check selected state
                        isselected = SelectedPlanes.IndexOf(plane.Hex) >= 0;
                        // now, show plane according to potential
                        switch (plane.Potential)
                        {
                            case 100:
                                gmo_Planes.Markers.Add(CreatePlaneDetailed(plane, isselected));
                                // set alarm
                                if (plane.IntQRB < Properties.Settings.Default.Alarm_Distance)
                                {
                                    alarm = true;
                                    alarm_msg = plane.Call;
                                }
                                break;
                            case 75:
                                gmo_Planes.Markers.Add(CreatePlaneDetailed(plane, isselected));
                                // set alarm
                                if (plane.IntQRB < Properties.Settings.Default.Alarm_Distance)
                                {
                                    alarm = true;
                                    alarm_msg = plane.Call;
                                }
                                break;
                            case 50:
                                gmo_Planes.Markers.Add(CreatePlaneDetailed(plane, isselected));
                                break;
                            default:
                                gmo_Planes.Markers.Add(CreatePlaneSimple(plane, isselected));
                                break;
                        }

                        // count the planes drawed and update caption
                        gb_Map.Text = "Map [" + gmo_Planes.Markers.Count.ToString() + " plane(s)]";
                        // if selected: draw the thin path to crossing point if one
                        if (isselected)
                        {
                            if (plane.IntPoint != null)
                            {
                                GMapRoute intpath = new GMapRoute(plane.Call);
                                intpath.Stroke = new Pen(Color.Black, 1);
                                intpath.Points.Add(new PointLatLng(plane.Lat, plane.Lon));
                                intpath.Points.Add(new PointLatLng(plane.IntPoint.Lat, plane.IntPoint.Lon));
                                gmo_Routes.Routes.Add(intpath);
                                //                                Console.WriteLine(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + ";" + info.IntQRB.ToString("F3"));
                            }
                            // track plane, if enabled
                            if (Properties.Settings.Default.Track_Activate)
                            {
                                double az = LatLon.Bearing(Properties.Settings.Default.MyLat,
                                    Properties.Settings.Default.MyLon,
                                    plane.Lat,
                                    plane.Lon);
                                // calculate elevation
                                // TODO: adjust K-Factor to settings
                                double h = (GetElevation(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon) + Properties.Settings.Default.MyHeight) / 1000;
                                double H = plane.Alt_m / 1000.0;
                                double dist = LatLon.Distance(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, plane.Lat, plane.Lon);
                                double el = Propagation.EpsilonFromHeights(h, dist, H, (LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor) / Math.PI * 180);
                                // set tracking values
                                Properties.Settings.Default.Track_SetAz = az;
                                Properties.Settings.Default.Track_SetEl = el;
                            }
                        }

                        // show planes on chart if in sigle path mode
                        if (PathMode == AIRSCOUTPATHMODE.SINGLE)
                        {
                            if ((plane.IntPoint != null) && (plane.IntQRB <= Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].MaxDistance))
                            {
                                // calculate distance from mylat/mylon
                                double dist = LatLon.Distance(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, plane.IntPoint.Lat, plane.IntPoint.Lon);
                                // add new data points
                                if (plane.AltDiff > 0)
                                {
                                    Planes_Hi.Points.Add(new DataPoint(dist, plane.Alt_m));
                                }
                                else
                                {
                                    Planes_Lo.Points.Add(new DataPoint(dist, plane.Alt_m));
                                }
                            }
                        }
                    }

                    // invalidate chart
                    pm_Path.InvalidatePlot(true);

                    // set alarm if one
                    if (alarm)
                        Alarm(alarm_msg);
                    // stop tracking if selected object is lost for any reason
                    if (!isselected)
                    {
                        Properties.Settings.Default.Track_SetAz = double.NaN;
                        Properties.Settings.Default.Track_SetEl = double.NaN;
                    }
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                }
            }

        }

        private void UpdatePlanes()
        {
            // get current time

            // update status
            UpdateStatus();

            // check for filter settings
            // and color filter box
            int planes_filter_minalt = 0;
            planes_filter_minalt = Properties.Settings.Default.Planes_Filter_Min_Alt;
            if (planes_filter_minalt < 0)
                planes_filter_minalt = 0;
            if ((planes_filter_minalt != 0) || (Properties.Settings.Default.Planes_Filter_Min_Category > PLANECATEGORY.LIGHT))
            {
                pa_Planes_Filter.BackColor = Color.Plum;
            }
            else
            {
                pa_Planes_Filter.BackColor = SystemColors.Control;
            }

            Stopwatch st = new Stopwatch();
            st.Start();
            List<PlaneInfo> allplanes = Planes.GetAll(CurrentTime, Properties.Settings.Default.Planes_Position_TTL);
            // TODO: maintain selected status
            st.Stop();
            // Log.WriteMessage("Getting plane positions from database: " + allplanes.Count().ToString() + " plane(s), " + st.ElapsedMilliseconds.ToString() + " ms.");
            // clear active planes
            ActivePlanes.Clear();

            foreach (PropagationPathDesignator ppath in PropagationPaths)
            {
                st.Reset();
                st.Start();
                NearestPlane = null;
                // get nearest planes per path
                List<PlaneInfo> nearestplanes = AircraftData.Database.GetNearestPlanes(DateTime.UtcNow, ppath, allplanes, Properties.Settings.Default.Planes_Filter_Max_Circumcircle, Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].MaxDistance, Properties.Settings.Default.Planes_MaxAlt);

                foreach (PlaneInfo plane in nearestplanes)
                {
                    // add or update plane in active planes list
                    PlaneInfo activeplane;
                    if (ActivePlanes.TryGetValue(plane.Hex, out activeplane))
                    {
                        // plane found --> update if necessary
                        bool update = false;
                        // plane has higher potential
                        if (plane.Potential > activeplane.Potential)
                            update = true;
                        // plane has same potetial but is nearer to path
                        else if ((plane.Potential == activeplane.Potential) && (plane.IntQRB < activeplane.IntQRB))
                            update = true;
                        // update if necessary
                        if (update)
                        {
                            ActivePlanes.Remove(activeplane.Hex);
                            ActivePlanes.Add(plane.Hex, plane);
                        }
                    }
                    else
                    {
                        // add plane to list if not foound
                        ActivePlanes.Add(plane.Hex, plane);
                    }
                    // maintain nearest plane info
                    // check if plane is within MaxDistance
                    if (plane.IntQRB < Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].MaxDistance)
                    {
                        if (NearestPlane == null)
                        {
                            // set first nearest plane info anyway
                            NearestPlane = plane;
                        }
                        // use higher potential anyway
                        if (plane.Potential > NearestPlane.Potential)
                        {
                            NearestPlane = plane;
                        }
                        else if (plane.Potential == NearestPlane.Potential)
                        {
                            // use nearer plane if same potential
                            if (plane.IntQRB < NearestPlane.IntQRB)
                                NearestPlane = plane;
                        }
                    }
                }
                st.Stop();
                // Log.WriteMessage("Get nearest planes: " + ActivePlanes.Count.ToString() + " plane(s), " + st.ElapsedMilliseconds.ToString() + " ms.");
            }
            st.Start();
            // clear planes overlay in map
            gmo_Planes.Clear();
            // clear all routes except paths
            gmo_Routes.Clear();

            // clear data points in chart
            Planes_Hi.Points.Clear();
            Planes_Lo.Points.Clear();

            // draw planes
            DrawPlanes();
            st.Stop();
            // Log.WriteMessage("Drawing planes: " + ActivePlanes.Count.ToString() + " plane(s), " + st.ElapsedMilliseconds.ToString() + " ms.");

        }

        #endregion

        #region Watchlist

        private void UpdateWatchlistInMap()
        {
            // show callsigns from watchlist
            gmo_Callsigns.Clear();
            if (!Properties.Settings.Default.Watchlist_Activated)
                return;
            // create new watchlist if null
            if (Properties.Settings.Default.Watchlist == null)
                Properties.Settings.Default.Watchlist = new Watchlist();
            foreach (WatchlistItem item in Properties.Settings.Default.Watchlist)
            {
                // nasty!! Should never be null!
                if (item == null)
                    continue;
                LocationDesignator dxloc = LocationFindOrCreate(item.Call, item.Loc);
                GMarkerGoogle gm = new GMarkerGoogle(new PointLatLng(dxloc.Lat, dxloc.Lon), ToolTipFont, (dxloc.Source == GEOSOURCE.FROMUSER) ? GMarkerGoogleType.green_small : GMarkerGoogleType.white_small);
                gm.ToolTipText = dxloc.Call;
                gm.ToolTipMode = MarkerTooltipMode.OnMouseOver;
                gm.Tag = dxloc.Call;
                gmo_Callsigns.Markers.Add(gm);
            }
        }

        private void RefreshWatchlistView()
        {
            // set watchlistupdating flag
            WatchlistUpdating = true;
            // update listview
            lv_Control_Watchlist.BeginUpdate();
            lv_Control_Watchlist.Items.Clear();
            foreach (WatchlistItem item in Properties.Settings.Default.Watchlist)
            {
                // nasty!! Should never be null!
                if (item == null)
                    continue;
                LocationDesignator dxcall = StationData.Database.LocationFindOrCreate(item.Call, item.Loc);
                ListViewItem lvi = new ListViewItem(item.Call);
                lvi.Name = "Call";
                ListViewItem.ListViewSubItem lsi = new ListViewItem.ListViewSubItem(lvi, item.Loc);
                lsi.Name = "Loc";
                lvi.SubItems.Add(lsi);
                lv_Control_Watchlist.Items.Add(lvi);
                if (item.Checked)
                    lvi.Checked = true;
                if (item.Selected)
                    lvi.Selected = true;
                // tag item as "Out of Range"
                if (item.OutOfRange)
                {
                    lvi.Tag = "OOR";
                    lvi.ForeColor = Color.LightGray;
                }
                else
                {
                    lvi.BackColor = (dxcall.Source == GEOSOURCE.FROMUSER) ? Color.PaleGreen : Color.White;
                }
            }
            lv_Control_Watchlist.Sort();
            lv_Control_Watchlist.EndUpdate();
            // reset watchlistupdating flag
            WatchlistUpdating = false;
        }

        #endregion

        #region User Interface

        private void MapDlg_SizeChanged(object sender, EventArgs e)
        {
            try
            {
                // ensure that the Info panel is fully visible
                // set the splitter according to Info panel width
                int dist = sc_Main.Width - gb_Map_Info_Width - 5;
                if ((dist > 0) && (dist < sc_Main.Width))
                    sc_Main.SplitterDistance = dist;
                // set the infowin height according to window size
//                int height = sc_Main.Height - gb_CommonInfo.Height - gb_Map_Buttons.Height - 5;
//                tc_ControlPanel.Height = height;
            }
            catch
            {
                // do nothing if window is minimized or otherwise invisible
            }
        }

        private void MapDlg_Resize(object sender, EventArgs e)
        {
        }

        private void ti_Progress_Tick(object sender, EventArgs e)
        {
            if (LifeMode == AIRSCOUTLIFEMODE.LIFE)
            {
                if (PlayMode == AIRSCOUTPLAYMODE.FORWARD)
                {
                    // update current time
                    if (Properties.Settings.Default.Time_Mode_Online)
                    {
                        CurrentTime = DateTime.UtcNow;
                        UpdatePlanes();
                    }
                }
            }
            else if (LifeMode == AIRSCOUTLIFEMODE.HISTORY)
            {
                if (PlayMode != AIRSCOUTPLAYMODE.PAUSE)
                {
                    Properties.Settings.Default.Time_Offline = Properties.Settings.Default.Time_Offline.AddSeconds(Time_Offline_Interval);
                    if (Properties.Settings.Default.Time_Offline < dtp_Analysis_MinValue.Value)
                        Properties.Settings.Default.Time_Offline = dtp_Analysis_MinValue.Value;
                    if (Properties.Settings.Default.Time_Offline > dtp_Analysis_MaxValue.Value)
                        Properties.Settings.Default.Time_Offline = dtp_Analysis_MaxValue.Value;
                    CurrentTime = Properties.Settings.Default.Time_Offline;
                    tb_Analysis_Time.Text = CurrentTime.ToString("yyyy-MM-hh HH:mm:ss");
                    double span = (CurrentTime - dtp_Analysis_MinValue.Value).TotalSeconds;
                    if ((span > sb_Analysis_Play.Minimum) && (span < sb_Analysis_Play.Maximum))
                        sb_Analysis_Play.Value = (int)span;
                    UpdatePlanes();
                }
            }
        }

        private void sc_Main_SplitterMoved(object sender, SplitterEventArgs e)
        {
        }

        private void sc_Map_SplitterMoved(object sender, SplitterEventArgs e)
        {
        }



        #region Map

        private void gm_Main_OnMapZoomChanged()
        {
            double midlat = LatLon.MidPoint(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon).Lat;
            double midlon = LatLon.MidPoint(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon).Lon;
            if ((PlayMode == AIRSCOUTPLAYMODE.FORWARD) && Properties.Settings.Default.Map_AutoCenter)
                gm_Main.Position = new PointLatLng(midlat, midlon);
            tb_Zoom.Text = gm_Main.Zoom.ToString();
        }

        private void gm_Main_OnMarkerClick(GMapMarker item, MouseEventArgs e)
        {
            try
            {
                if (e.Button == System.Windows.Forms.MouseButtons.Left)
                {
                    // check if callsign clicked
                    if (item.Overlay == gmo_Callsigns)
                    {
                        string call = (string)item.Tag;
                        if (Callsign.Check(call) && (call != Properties.Settings.Default.MyCall))
                        {
                            if (PathMode == AIRSCOUTPATHMODE.MULTI)
                            {
                                // search call on watchlist
                                int index = Properties.Settings.Default.Watchlist.IndexOf(call);
                                if (index >= 0)
                                {
                                    // toggle checked state
                                    Properties.Settings.Default.Watchlist.ElementAt(index).Checked = !Properties.Settings.Default.Watchlist.ElementAt(index).Checked;
                                    // refresh watchlist view
                                    RefreshWatchlistView();
                                    // update paths if running
                                    if (PlayMode == AIRSCOUTPLAYMODE.FORWARD)
                                        UpdatePaths();
                                }
                            }
                        }
                    }
                    // check if plane clicked
                    else if (item.Overlay == gmo_Planes)
                    {
                        // keep the selected state
                        int selectedindex = SelectedPlanes.IndexOf((string)item.Tag);
                        // clear all other selections if tracking is enabled
                        if (Properties.Settings.Default.Track_Activate)
                        {
                            SelectedPlanes.Clear();
                        }
                        // toogle selection of the selected plane
                        if (selectedindex >= 0)
                        {
                            // remove item from selected planes list
                            SelectedPlanes.RemoveAt(selectedindex);
                            // invalidate tracking
                            Properties.Settings.Default.Track_SetAz = double.NaN;
                            Properties.Settings.Default.Track_SetEl = double.NaN;
                        }
                        else
                        {
                            SelectedPlanes.Add((string)item.Tag);
                        }
                    }
                    // check if call clicked
                    else if (item.Overlay == gmo_Callsigns)
                    {
                        LocationDesignator ld = StationData.Database.LocationFind(item.Tag.ToString());
                        if (ld != null)
                        {
                            if (PlayMode != AIRSCOUTPLAYMODE.PAUSE)
                                this.Pause();
                            Properties.Settings.Default.DXCall = item.Tag.ToString();
                            Properties.Settings.Default.DXLat = ld.Lat;
                            Properties.Settings.Default.DXLon = ld.Lon;
                            UpdateStatus();
                            this.Play();
                        }
                    }
                }
                if (e.Button == System.Windows.Forms.MouseButtons.Right)
                {
                    // new for tracking antenna
                    if ((string)item.Tag == Properties.Settings.Default.DXCall)
                    {
                        // Right click on DXCall needle --> set antenna position
                        // get antenna direction
                        double qtf = LatLon.Bearing(Properties.Settings.Default.MyLat,
                            Properties.Settings.Default.MyLon,
                            Properties.Settings.Default.DXLat,
                            Properties.Settings.Default.DXLon);
                        // store new QTF in properties
                        lock (Properties.Settings.Default)
                        {
                            Properties.Settings.Default.Track_SetAz = qtf;
                            Properties.Settings.Default.Track_SetEl = 0;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // do nothing if item not found in planes list
                Log.WriteMessage(ex.Message);
            }
        }

        private void gm_Main_OnMarkerEnter(GMapMarker item)
        {
            if (((item == gmm_MyLoc) || (item == gmm_DXLoc)) && !isDraggingMarker)
                gmm_CurrentMarker = item;
        }

        private void gm_Main_OnMarkerLeave(GMapMarker item)
        {

        }

        private void gm_Main_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && gmm_CurrentMarker != null && gmm_CurrentMarker.IsMouseOver)
                isDraggingMarker = true;
        }

        private void gm_Main_MouseMove(object sender, MouseEventArgs e)
        {
        }

        private void gm_Main_MouseUp(object sender, MouseEventArgs e)
        {
            if (isDraggingMarker)
            {
                isDraggingMarker = false;
            }
        }

        private void gm_Main_Paint(object sender, PaintEventArgs e)
        {
            // paint gauges on top of the map if enabled
            if (Properties.Settings.Default.Track_Activate &&
                (Properties.Settings.Default.Track_SetAz >= 0) &&
                (Properties.Settings.Default.Track_SetAz <= 360) &&
                (Properties.Settings.Default.Track_SetEl >= 0) &&
                (Properties.Settings.Default.Track_SetEl <= 90))
            {
                // update gauges
                ag_Azimuth.Value = (float)Properties.Settings.Default.Track_SetAz;
                ag_Elevation.Value = (float)Properties.Settings.Default.Track_SetEl;
                // draw elements
                int ofsX = ag_Azimuth.Left;
                int ofsY = ag_Azimuth.Top;
                ag_Azimuth.DrawDialText(e.Graphics, ofsX, ofsY);
                ag_Azimuth.DisplayNumber(e.Graphics, ofsX, ofsY);
                ag_Azimuth.DrawCalibration(e.Graphics, ofsX, ofsY);
                ag_Azimuth.DrawCenterPoint(e.Graphics, ofsX, ofsY);
                ag_Azimuth.DrawPointer(e.Graphics, ofsX, ofsY);

                ofsX = ag_Elevation.Left;
                ofsY = ag_Elevation.Top;
                ag_Elevation.DrawDialText(e.Graphics, ofsX, ofsY);
                ag_Elevation.DisplayNumber(e.Graphics, ofsX, ofsY);
                ag_Elevation.DrawCalibration(e.Graphics, ofsX, ofsY);
                ag_Elevation.DrawCenterPoint(e.Graphics, ofsX, ofsY);
                ag_Elevation.DrawPointer(e.Graphics, ofsX, ofsY);
            }
        }

        private void gm_Main_SizeChanged(object sender, EventArgs e)
        {
            // adjust position of gauges
            ag_Azimuth.Left = gm_Main.Right - ag_Azimuth.Width - ag_Elevation.Width - 40;
            ag_Azimuth.Top = gm_Main.Bottom - ag_Azimuth.Height - 20;

            ag_Elevation.Left = gm_Main.Right - ag_Elevation.Width - 20;
            ag_Elevation.Top = gm_Main.Bottom - ag_Elevation.Height - 20;
        }

        #endregion

        #region Right Controls

        private void cb_Band_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cb_Band.SelectedItem != null)
                Properties.Settings.Default.Band = Bands.ParseStringValue((string)cb_Band.SelectedItem);
            else Properties.Settings.Default.Band = BAND.BNONE;
            SaveUserSettings();
            Properties.Settings.Default.Reload();
        }

        #region Tab Control Control Panel

        private void tc_Control_DrawItem(object sender, DrawItemEventArgs e)
        {
            // This event is called once for each tab button in your tab control
            // First paint the background with a color based on the current tab
            // e.Index is the index of the tab in the TabPages collection.
            switch (e.Index)
            {
                case 0:
                    e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.Bounds);
                    break;
                case 1:
                    e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.Bounds);
                    break;
                case 2:
                    e.Graphics.FillRectangle(new SolidBrush((Properties.Settings.Default.Planes_Filter_Min_Category > PLANECATEGORY.LIGHT) ? Color.Plum : SystemColors.Control), e.Bounds);
                    break;
                default:
                    break;
            }

            // Then draw the current tab button text 
            Rectangle paddedBounds = e.Bounds;
            paddedBounds.Inflate(-2, -2);
            e.Graphics.DrawString(tc_Control.TabPages[e.Index].Text, tc_Control.Font, (tc_Control.Enabled) ? SystemBrushes.ControlText : SystemBrushes.GrayText, paddedBounds);
        }


        #region Single Tab

        private void tp_Control_Single_Enter(object sender, EventArgs e)
        {
            if (PathMode != AIRSCOUTPATHMODE.SINGLE)
            {
                PathMode = AIRSCOUTPATHMODE.SINGLE;
                // restore splitter distance
                sc_Map.SplitterDistance = CurrentMapSplitterDistance;
            }
        }

        private void gb_Map_Info_MouseClick(object sender, MouseEventArgs e)
        {
            // get font size in pixels
            double pixels;
            using (Graphics g = this.CreateGraphics())
            {
                pixels = gb_Map_Info.Font.SizeInPoints * g.DpiX / 72;
            }
            if ((e.Y <= pixels) && (e.Button == MouseButtons.Left))
            {
                Properties.Settings.Default.Map_ShowInfoBox = !Properties.Settings.Default.Map_ShowInfoBox;
            }
        }


        private void cb_MyCall_TextChanged(object sender, EventArgs e)
        {
            Console.WriteLine("cb_MyCall_TextChanged: " + cb_MyCall.Text);
            int i = cb_MyCall.SelectionStart;
            Properties.Settings.Default.MyCall = cb_MyCall.Text;
            // clear locator entries
            cb_MyLoc.Text = "";
            cb_MyLoc.Items.Clear();
            Properties.Settings.Default.MyLat = double.NaN;
            Properties.Settings.Default.MyLon = double.NaN;
            Properties.Settings.Default.MyElevation = 0;
            if (Callsign.Check(cb_MyCall.Text))
            {
                // select last recent loc
                LocationDesignator ld = StationData.Database.LocationFind(cb_MyCall.Text);
                if (ld != null)
                {
                    Properties.Settings.Default.MyLat = ld.Lat;
                    Properties.Settings.Default.MyLon = ld.Lon;
                    Properties.Settings.Default.MyElevation = GetElevation(ld.Lat, ld.Lon);
                }
            }
            UpdateStatus();
            cb_MyCall.Select(i, 0);
        }

        private void cb_MyCall_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cb_MyCall.SelectedItem != null)
            {
                cb_MyCall.Text = cb_MyCall.SelectedItem.ToString();
                LocationDesignator ld = StationData.Database.LocationFind(cb_MyCall.Text);
                if (Callsign.Check(cb_MyCall.Text) && (ld != null))
                {
                    // update Settings
                    cb_MyLoc.Text = MaidenheadLocator.LocFromLatLon(ld.Lat, ld.Lon, Properties.Settings.Default.Locator_SmallLettersForSubsquares, (int)Properties.Settings.Default.Locator_MaxLength / 2, Properties.Settings.Default.Locator_AutoLength);
                    Properties.Settings.Default.MyCall = cb_MyCall.Text;
                    Properties.Settings.Default.MyLat = ld.Lat;
                    Properties.Settings.Default.MyLon = ld.Lon;
                    Properties.Settings.Default.MyElevation = GetElevation(ld.Lat, ld.Lon);
                    Properties.Settings.Default.MyHeight = 10;
                }
                else
                {
                    Properties.Settings.Default.MyLat = double.NaN;
                    Properties.Settings.Default.MyLon = double.NaN;
                    Properties.Settings.Default.MyElevation = 0;
                    Properties.Settings.Default.MyHeight = 0;
                }
                UpdateStatus();
            }
        }

        private void cb_MyCall_DropDown(object sender, EventArgs e)
        {
            // poulate from MyCalls last recent collection
            // populate drop down list
            cb_MyCall.Items.Clear();
            foreach (string call in Properties.Settings.Default.MyCalls)
            {
                cb_MyCall.Items.Add(call);
            }
        }

        private void cb_MyLoc_TextChanged(object sender, EventArgs e)
        {
            if (cb_MyLoc.Focused)
            {
                if (MaidenheadLocator.Check(cb_MyLoc.Text) && (cb_MyLoc.Text.Length >= 6))
                {
                    Properties.Settings.Default.MyLat = cb_MyLoc.GeoLocation.Lat;
                    Properties.Settings.Default.MyLon = cb_MyLoc.GeoLocation.Lon;
                    Properties.Settings.Default.MyElevation = GetElevation(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
                    Properties.Settings.Default.MyHeight = 10;
                    // colour Textbox if more precise lat/lon information is available
                    if (MaidenheadLocator.IsPrecise(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, 3))
                    {
                        cb_MyLoc.BackColor = Color.PaleGreen;
                    }
                    UpdateStatus();
                }
                else
                {
                    cb_MyLoc.BackColor = Color.FloralWhite;
                }
            }
        }

        private void cb_MyLoc_DropDown(object sender, EventArgs e)
        {
            // fill MyLoc combo box with all known locators
            cb_MyLoc.BackColor = Color.FloralWhite;
            cb_MyLoc.SilentText = "";
            cb_MyLoc.Items.Clear();
            List<LocationDesignator> lds = StationData.Database.LocationFindAll(cb_MyCall.Text);
            if (lds != null)
            {
                // fill item list of locator box
                foreach (LocationDesignator ld in lds)
                {
                    LocatorDropDownItem ldd = new LocatorDropDownItem(MaidenheadLocator.LocFromLatLon(ld.Lat, ld.Lon, Properties.Settings.Default.Locator_SmallLettersForSubsquares, (int)Properties.Settings.Default.Locator_MaxLength / 2, Properties.Settings.Default.Locator_AutoLength), new LatLon.GPoint(ld.Lat, ld.Lon));
                    cb_MyLoc.Items.Add(ldd);
                }
            }
        }

        private void cb_MyLoc_SelectedIndexChanged(object sender, EventArgs e)
        {
            LocatorDropDownItem ldd = (LocatorDropDownItem)cb_MyLoc.SelectedItem;
            if (ldd != null)
            {
                // update properties
                cb_MyLoc.Precision = (int)Properties.Settings.Default.Locator_MaxLength / 2;
                cb_MyLoc.SmallLettersForSubsquares = Properties.Settings.Default.Locator_SmallLettersForSubsquares;
                cb_MyLoc.AutoLength = Properties.Settings.Default.Locator_AutoLength;
                // set geolocation instead of text
                cb_MyLoc.GeoLocation = ldd.GeoLocation;
            }
        }

        private void cb_MyLoc_SelectionChangeCommittedWithNoUpdate(object sender, EventArgs e)
        {

        }

        private void cb_DXCall_TextChanged(object sender, EventArgs e)
        {
            int i = cb_DXCall.SelectionStart;
            Properties.Settings.Default.DXCall = cb_DXCall.Text;
            // clear locator entries
            cb_DXLoc.Text = "";
            cb_DXLoc.Items.Clear();
            Properties.Settings.Default.DXLat = double.NaN;
            Properties.Settings.Default.DXLon = double.NaN;
            Properties.Settings.Default.DXElevation = 0;
            if (Callsign.Check(cb_DXCall.Text))
            {
                // select last recent loc
                LocationDesignator ld = StationData.Database.LocationFind(cb_DXCall.Text);
                if (ld != null)
                {
                    Properties.Settings.Default.DXLat = ld.Lat;
                    Properties.Settings.Default.DXLon = ld.Lon;
                    Properties.Settings.Default.DXElevation = GetElevation(ld.Lat, ld.Lon);
                }
            }
            UpdateStatus();
            cb_DXCall.Select(i, 0);
        }

        private void cb_DXCall_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cb_DXCall.SelectedItem != null)
            {
                cb_DXCall.Text = cb_DXCall.SelectedItem.ToString();
                LocationDesignator ld = StationData.Database.LocationFind(cb_DXCall.Text);
                if (Callsign.Check(cb_DXCall.Text) && (ld != null))
                {
                    // update Settings
                    cb_DXLoc.Text = MaidenheadLocator.LocFromLatLon(ld.Lat, ld.Lon, Properties.Settings.Default.Locator_SmallLettersForSubsquares, (int)Properties.Settings.Default.Locator_MaxLength / 2, Properties.Settings.Default.Locator_AutoLength);
                    Properties.Settings.Default.DXCall = cb_DXCall.Text;
                    Properties.Settings.Default.DXLat = ld.Lat;
                    Properties.Settings.Default.DXLon = ld.Lon;
                    Properties.Settings.Default.DXElevation = GetElevation(ld.Lat, ld.Lon);
                    Properties.Settings.Default.DXHeight = 10;
                }
                else
                {
                    Properties.Settings.Default.DXLat = double.NaN;
                    Properties.Settings.Default.DXLon = double.NaN;
                    Properties.Settings.Default.DXElevation = 0;
                    Properties.Settings.Default.DXHeight = 0;
                }
                UpdateStatus();
            }
        }

        private void cb_DXCall_DropDown(object sender, EventArgs e)
        {
            // populate from watchlist
            // return on empty watchlist
            if (Properties.Settings.Default.Watchlist == null)
                return;
            // populate drop down list
            List<string> dxcalls = new List<string>();
            foreach (WatchlistItem item in Properties.Settings.Default.Watchlist)
            {
                // nasty!! Should never be null!
                if (item == null)
                    continue;
                dxcalls.Add(item.Call);
            }
            dxcalls.Sort();
            cb_DXCall.Items.Clear();
            cb_DXCall.Items.AddRange(dxcalls.ToArray());
        }


        private void cb_DXLoc_TextChanged(object sender, EventArgs e)
        {
            if (cb_DXLoc.Focused)
            {
                if (MaidenheadLocator.Check(cb_DXLoc.Text) && (cb_DXLoc.Text.Length >= 6))
                {
                    Properties.Settings.Default.DXLat = cb_DXLoc.GeoLocation.Lat;
                    Properties.Settings.Default.DXLon = cb_DXLoc.GeoLocation.Lon;
                    Properties.Settings.Default.DXElevation = GetElevation(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon);
                    Properties.Settings.Default.DXHeight = 10;
                    // colour Textbox if more precise lat/lon information is available
                    if (MaidenheadLocator.IsPrecise(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon, 3))
                    {
                        cb_DXLoc.BackColor = Color.PaleGreen;
                    }
                    UpdateStatus();
                }
                else
                {
                    cb_DXLoc.BackColor = Color.FloralWhite;
                }
            }
        }

        private void cb_DXLoc_DropDown(object sender, EventArgs e)
        {
            // fill DXLoc combo box with all known locators
            cb_DXLoc.BackColor = Color.FloralWhite;
            cb_DXLoc.SilentText = "";
            cb_DXLoc.Items.Clear();
            List<LocationDesignator> lds = StationData.Database.LocationFindAll(cb_DXCall.Text);
            if (lds != null)
            {
                // fill item list of locator box
                foreach (LocationDesignator ld in lds)
                {
                    LocatorDropDownItem ldd = new LocatorDropDownItem(MaidenheadLocator.LocFromLatLon(ld.Lat, ld.Lon, Properties.Settings.Default.Locator_SmallLettersForSubsquares, (int)Properties.Settings.Default.Locator_MaxLength / 2, Properties.Settings.Default.Locator_AutoLength), new LatLon.GPoint(ld.Lat, ld.Lon));
                    cb_DXLoc.Items.Add(ldd);
                }
            }
        }

        private void cb_DXLoc_SelectedIndexChanged(object sender, EventArgs e)
        {
            LocatorDropDownItem ldd = (LocatorDropDownItem)cb_DXLoc.SelectedItem;
            if (ldd != null)
            {
                // update properties
                cb_DXLoc.Precision = (int)Properties.Settings.Default.Locator_MaxLength / 2;
                cb_DXLoc.SmallLettersForSubsquares = Properties.Settings.Default.Locator_SmallLettersForSubsquares;
                cb_DXLoc.AutoLength = Properties.Settings.Default.Locator_AutoLength;
                // set geolocation instead of text
                cb_DXLoc.GeoLocation = ldd.GeoLocation;
            }
        }

        private void cb_DXLoc_SelectionChangeCommittedWithNoUpdate(object sender, EventArgs e)
        {
        }

        #endregion

        #region Tab Multi

        private void tp_Control_Multi_Enter(object sender, EventArgs e)
        {
            if (PathMode != AIRSCOUTPATHMODE.MULTI)
            {
                CurrentMapSplitterDistance = sc_Map.SplitterDistance;
                PathMode = AIRSCOUTPATHMODE.MULTI;
                sc_Map.SplitterDistance = gm_Main.Height + tc_Main.Height;
            }
        }

        private void lv_Control_Watchlist_Resize(object sender, EventArgs e)
        {
            // list view resized
            // resize locator column to fit the client size
            lv_Control_Watchlist.Columns[1].Width = lv_Control_Watchlist.ClientSize.Width - lv_Control_Watchlist.Columns[0].Width;
        }

        private void lv_Control_Watchlist_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
        {
            // adjust width of Locator column to list view width
            if (e.ColumnIndex == 0)
            {
                // call sign column changed
                // resize locator column to fit the client size
                lv_Control_Watchlist.Columns[1].Width = lv_Control_Watchlist.ClientSize.Width - lv_Control_Watchlist.Columns[0].Width;
            }
        }

        private void lv_Control_Watchlist_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            // ignore event while populating list view
            if (WatchlistUpdating)
                return;
            // sync watchlist with list view
            ListViewItem lvi = e.Item;
            if (lvi == null)
                return;
            // search item in watchlist
            int index = Properties.Settings.Default.Watchlist.IndexOf(lvi.Text, lvi.SubItems[1].Text);
            if (index >= 0)
                Properties.Settings.Default.Watchlist[index].Checked = lvi.Checked;
            if (WatchlistAllCheckedChanging)
                return;
            // maintain AllChecked checkbox
            foreach (ListViewItem item in lv_Control_Watchlist.Items)
            {
                // stop on first different checked state
                if ((item != null) && (item.Checked != lvi.Checked))
                {
                    WatchlistAllCheckedState = CheckBoxState.MixedNormal;
                    lv_Control_Watchlist.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.HeaderSize);
                    return;
                }
            }
            // all items in the same state
            WatchlistAllCheckedState = (lvi.Checked) ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;
            lv_Control_Watchlist.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.HeaderSize);
        }

        private void lv_Control_Watchlist_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            WatchlistAllCheckedChanging = true;
            if (!WatchlistAllChecked)
            {
                WatchlistAllChecked = true;
                WatchlistAllCheckedState = CheckBoxState.CheckedPressed;
                foreach (ListViewItem item in lv_Control_Watchlist.Items)
                {
                    // check all items within range
                    if ((item.Tag == null) || (item.Tag.ToString() != "OOR"))
                        item.Checked = true;
                }

                Invalidate();
            }
            else
            {
                WatchlistAllChecked = false;
                WatchlistAllCheckedState = CheckBoxState.UncheckedNormal;
                Invalidate();

                foreach (ListViewItem item in lv_Control_Watchlist.Items)
                {
                    item.Checked = false;
                }
            }
            WatchlistAllCheckedChanging = false;
        }

        private void lv_Control_Watchlist_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
        {
            TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding;
            e.DrawBackground();
            CheckBoxRenderer.DrawCheckBox(e.Graphics, new System.Drawing.Point(ClientRectangle.Location.X + 4, ClientRectangle.Location.Y), WatchlistAllCheckedState);
            e.DrawText(flags);
        }

        private void lv_Control_Watchlist_DrawItem(object sender, DrawListViewItemEventArgs e)
        {
            e.DrawDefault = true;
        }

        private void lv_Control_Watchlist_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
        {
            e.DrawDefault = true;
        }

        private void btn_Control_Manage_Watchlist_Click(object sender, EventArgs e)
        {
            WatchlistDlg Dlg = new WatchlistDlg();
            if (Dlg.ShowDialog() == DialogResult.OK)
            {
                // sync watchlist
                foreach (WatchlistItem item in Properties.Settings.Default.Watchlist)
                {
                    // nasty!! Should never be null!
                    if (item == null)
                        continue;
                    item.Remove = true;
                }
                foreach (ListViewItem lvi in Dlg.lv_Watchlist_Selected.Items)
                {
                    // search item in watchlist
                    int index = Properties.Settings.Default.Watchlist.IndexOf(lvi.Text);
                    // reset remove flag if found, create and add new entry if not
                    if (index >= 0)
                        Properties.Settings.Default.Watchlist[index].Remove = false;
                    else
                    {
                        // try to find last recent locator from database and add to watchlist
                        LocationDesignator dxcall = StationData.Database.LocationFindLastRecent(lvi.Text);
                        if (dxcall != null)
                        {
                            double qrb = LatLon.Distance(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, dxcall.Lat, dxcall.Lon);
                            Properties.Settings.Default.Watchlist.Add(new WatchlistItem(dxcall.Call, dxcall.Loc, qrb > Properties.Settings.Default.Path_MaxLength));
                        }
                    }
                }
                // remove the rest of items
                Properties.Settings.Default.Watchlist.RemoveAll(item => item.Remove);
                // refersh watchlist view
                RefreshWatchlistView();
            }
        }

        private void ShowToolTip(ToolTip tt, string text, Control control, System.Drawing.Point p, int ms = 5000)
        {
            if (String.IsNullOrEmpty(text))
                return;
            // int BorderWidth = this.Width – this.ClientSize.Width)/2;
            // int TitlebarHeight = this.Height – this.ClientSize.Height – 2 * BorderWidth;
            int BorderWith = (this.Width - this.ClientSize.Width) / 2;
            int TitleBarHeight = this.Height - this.ClientSize.Height - 2 * BorderWith;
            p = control.PointToScreen(p);
            p = this.PointToClient(p);
            p.X = p.X + BorderWith;
            p.Y = p.Y + TitleBarHeight + Cursor.Size.Height;
            tt.Show(text, this, p, ms);
        }

        private void lv_Control_Watchlist_ItemMouseHover(object sender, ListViewItemMouseHoverEventArgs e)
        {
        }

        private void lv_Control_Watchlist_MouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                System.Drawing.Point p = new System.Drawing.Point(e.X, e.Y);
                ListViewHitTestInfo info = lv_Control_Watchlist.HitTest(p);
                if ((WatchlistOldMousePos != p) && (info != null) && (info.SubItem != null))
                {
                    WatchlistOldMousePos = p;
                    // check whether the column name is one of the following
                    if (info.SubItem.Name == "Call")
                    {
                        string dxcall = info.Item.SubItems[0].Text;
                        string dxloc = info.Item.SubItems[1].Text;
                        LocationDesignator ld = StationData.Database.LocationFind(dxcall, dxloc);
                        // location found --> show Tooltip with details
                        if (ld != null)
                        {
                            string qrb = LatLon.Distance(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, ld.Lat, ld.Lon).ToString("F0", CultureInfo.InvariantCulture);
                            string qtf = LatLon.Bearing(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, ld.Lat, ld.Lon).ToString("F0", CultureInfo.InvariantCulture);
                            ShowToolTip(tt_Control_Watchlist, dxcall + "\n" + dxloc + "\n" + qrb + " km\n" + qtf + "°", lv_Control_Watchlist, p);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        #endregion

        #region Tab Options

        private void tp_Control_Options_Enter(object sender, EventArgs e)
        {
            // set plane filter
            try
            {
                cb_Planes_Filter_Min_Cat.SelectedItem = PlaneCategories.GetStringValue(Properties.Settings.Default.Planes_Filter_Min_Category);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void gb_Map_Zoom_MouseClick(object sender, MouseEventArgs e)
        {
            // get font size in pixels
            double pixels;
            using (Graphics g = this.CreateGraphics())
            {
                pixels = gb_Map_Zoom.Font.SizeInPoints * g.DpiX / 72;
            }
            if ((e.Y <= pixels) && (e.Button == MouseButtons.Left))
            {
                Properties.Settings.Default.Map_ShowZoomBox = !Properties.Settings.Default.Map_ShowZoomBox;
            }
        }

        private void gb_Map_Filter_MouseClick(object sender, MouseEventArgs e)
        {
            // get font size in pixels
            double pixels;
            using (Graphics g = this.CreateGraphics())
            {
                pixels = gb_Map_Filter.Font.SizeInPoints * g.DpiX / 72;
            }
            if ((e.Y <= pixels) && (e.Button == MouseButtons.Left))
            {
                Properties.Settings.Default.Map_ShowFilterBox = !Properties.Settings.Default.Map_ShowFilterBox;
            }
        }

        private void gb_Map_Alarms_MouseClick(object sender, MouseEventArgs e)
        {
            // get font size in pixels
            double pixels;
            using (Graphics g = this.CreateGraphics())
            {
                pixels = gb_Map_Alarms.Font.SizeInPoints * g.DpiX / 72;
            }
            if ((e.Y <= pixels) && (e.Button == MouseButtons.Left))
            {
                Properties.Settings.Default.Map_ShowAlarmBox = !Properties.Settings.Default.Map_ShowAlarmBox;
            }
        }

        private void btn_Zoom_In_Click(object sender, EventArgs e)
        {
            if (gm_Main.Zoom < 20)
                gm_Main.Zoom++;
        }

        private void btn_Zoom_Out_Click(object sender, EventArgs e)
        {
            if (gm_Main.Zoom > 0)
                gm_Main.Zoom--;
        }

        private void cb_Planes_Filter_Min_Category_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                Properties.Settings.Default.Planes_Filter_Min_Category = PlaneCategories.ParseStringValue((string)cb_Planes_Filter_Min_Cat.SelectedItem);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }


        #endregion

        #endregion

        #region Buttons

        private void btn_Options_Click(object sender, EventArgs e)
        {
            ShowOptionsDlg();
        }

        private void btn_Map_Save_Click(object sender, EventArgs e)
        {
            MapSave();
        }

        private void btn_Map_PlayPause_Click(object sender, EventArgs e)
        {
            if (PlayMode != AIRSCOUTPLAYMODE.FORWARD)
            {
                Play();
            }
            else
            {
                Pause();
            }
        }


        #endregion

        #endregion

        #region Main Tab Control

        private void tc_Main_SelectedIndexChanged(object sender, EventArgs e)
        {
            // restore splitter distance
            if (tc_Main.SelectedTab != tp_News)
                sc_Map.SplitterDistance = CurrentMapSplitterDistance;
        }

        private void tc_Main_DrawItem(object sender, DrawItemEventArgs e)
        {
            // This event is called once for each tab button in your tab control
            // First paint the background with a color based on the current tab
            // e.Index is the index of the tab in the TabPages collection.
            // fill background
            e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.Bounds);
            // Then draw the current tab button text 
            Rectangle paddedBounds = e.Bounds;
            paddedBounds.Inflate(-2, -2);
            e.Graphics.DrawString(tc_Main.TabPages[e.Index].Text, tc_Main.Font, (tc_Main.Enabled) ? SystemBrushes.ControlText : SystemBrushes.GrayText, paddedBounds);

        }

        #region Tab Page News

        private void tp_News_Enter(object sender, EventArgs e)
        {
            CurrentMapSplitterDistance = sc_Map.SplitterDistance;
            // adjust splitter to maximum if not running under Linux/Mono
            if (!SupportFunctions.IsMono)
                sc_Map.SplitterDistance = 200;
        }

        #endregion

        #region Tab Page Spectrum

        private void tp_Spectrum_Enter(object sender, EventArgs e)
        {
            // adjust splitter back to normal
            sc_Map.SplitterDistance = CurrentMapSplitterDistance;
        }

        private void tp_Spectrum_Resize(object sender, EventArgs e)
        {
            // adjust group boxes
            // get quadratical nearest plane box
            gb_NearestPlaneMap.Width = gb_NearestPlaneMap.Height;
            gb_NearestPlaneMap.Left = tp_Spectrum.Width - gb_NearestPlaneMap.Width - 5;
            // adjust plane info box
            gb_Spectrum_NearestInfo.Left = tp_Spectrum.Width - gb_NearestPlaneMap.Width - gb_Spectrum_NearestInfo.Width - 10;
            // adjust spectrum box
            gb_Spectrum.Width = tp_Spectrum.Width - gb_NearestPlaneMap.Width - gb_Spectrum_NearestInfo.Width - 30;
            tb_Spectrum_Status.Left = 5;
            tb_Spectrum_Status.Width = pv_Spectrum.Width - 10;
        }

        #endregion

        #region Tab Page Elevation

        private void tp_Elevation_Enter(object sender, EventArgs e)
        {
            // adjust splitter back to normal
            sc_Map.SplitterDistance = CurrentMapSplitterDistance;
        }

        private void tp_Elevation_Resize(object sender, EventArgs e)
        {
            // adjust charts
            pv_Path.Location = new System.Drawing.Point(0, 0);
            pv_Path.Width = tp_Elevation.Width;
            pv_Path.Height = tp_Elevation.Height / 2;
            pv_Elevation.Location = new System.Drawing.Point(0, tp_Elevation.Height / 2);
            pv_Elevation.Width = tp_Elevation.Width;
            pv_Elevation.Height = tp_Elevation.Height / 2;
        }


        #endregion

        #region Tab Page Analysis

        private void UpdatePlayer()
        {
            // set players bounds
            sb_Analysis_Play.Minimum = 0;
            sb_Analysis_Play.Maximum = (int)(dtp_Analysis_MaxValue.Value - dtp_Analysis_MinValue.Value).TotalSeconds;
            sb_Analysis_Play.Value = 0;
        }

        private void tp_Analysis_Enter(object sender, EventArgs e)
        {
            if (LifeMode == AIRSCOUTLIFEMODE.LIFE)
            {
                btn_Analysis_ON.Enabled = true;
                btn_Analysis_ON.BackColor = Color.YellowGreen;
                btn_Analysis_OFF.Enabled = false;
                btn_Analysis_OFF.BackColor = Color.Gray;
            }
            else
            {
                btn_Analysis_ON.Enabled = false;
                btn_Analysis_ON.BackColor = Color.Gray;
                btn_Analysis_OFF.Enabled = true;
                btn_Analysis_OFF.BackColor = Color.LightCoral;
            }
            SayAnalysis("Press <ON> to start analysis.");
        }

        private void tp_Analysis_Leave(object sender, EventArgs e)
        {
        }

        private void btn_Analysis_ON_Click(object sender, EventArgs e)
        {
            SayAnalysis("Getting database properties, please wait...");
            // go into offline mode
            btn_Analysis_ON.Enabled = false;
            btn_Analysis_ON.BackColor = Color.Gray;
            btn_Analysis_OFF.Enabled = true;
            btn_Analysis_OFF.BackColor = Color.LightCoral;
            btn_Map_PlayPause.Enabled = false;
            LifeMode = AIRSCOUTLIFEMODE.HISTORY;
            PlayMode = AIRSCOUTPLAYMODE.PAUSE;
            if (!bw_Analysis_DataGetter.IsBusy)
                bw_Analysis_DataGetter.RunWorkerAsync();
        }

        private void btn_Analysis_OFF_Click(object sender, EventArgs e)
        {
            if (bw_Analysis_FileSaver.IsBusy)
                bw_Analysis_FileSaver.CancelAsync();
            if (bw_Analysis_FileLoader.IsBusy)
                bw_Analysis_FileLoader.CancelAsync();
            if (bw_Analysis_DataGetter.IsBusy)
            {
                SayAnalysis("Cancelling background thread, please wait...");
                bw_Analysis_DataGetter.CancelAsync();
            }
            else
            {
                btn_Analysis_ON.Enabled = true;
                btn_Analysis_ON.BackColor = Color.YellowGreen;
            }
            lock (AllPositions)
            {
                AllPositions.Clear();
            }
            GC.Collect();
            // go into online mode
            btn_Analysis_OFF.Enabled = false;
            btn_Analysis_OFF.BackColor = Color.Gray;
            btn_Analysis_Planes_Load.Enabled = false;
            btn_Analysis_Planes_Save.Enabled = false;
            btn_Analysis_Planes_Clear.Enabled = false;
            btn_Analysis_Planes_History.Enabled = false;
            btn_Analysis_Planes_ShowTraffic.Enabled = false;
            btn_Analysis_Path_SaveToFile.Enabled = false;
            btn_Analysis_CrossingHistory.Enabled = false;
            btn_Analysis_Plane_History.Enabled = false; 
            btn_Analysis_Rewind.Enabled = false;
            btn_Analysis_Back.Enabled = false;
            btn_Analysis_Pause.Enabled = false;
            btn_Analysis_Forward.Enabled = false;
            btn_Analysis_FastForward.Enabled = false;
            sb_Analysis_Play.Enabled = false;
            dtp_Analysis_MinValue.Enabled = false;
            dtp_Analysis_MaxValue.Enabled = false;
            btn_Map_PlayPause.Enabled = true;
            LifeMode = AIRSCOUTLIFEMODE.LIFE;
            PlayMode = AIRSCOUTPLAYMODE.PAUSE;
            UpdateStatus();
            btn_Map_PlayPause.Focus();
        }

        private void btn_Analysis_Planes_Save_Click(object sender, EventArgs e)
        {
            try
            {
                SaveFileDialog Dlg = new SaveFileDialog();
                Dlg.AddExtension = true;
                Dlg.Filter = "Java Script Object Notation File|*.json|Comma Separated Values|*.csv";
                Dlg.DefaultExt = "json";
                Dlg.FileName = "Plane Positions " + dtp_Analysis_MinValue.Value.ToString("yyyy_MM_dd_HH_mm_ss") + " to " + dtp_Analysis_MaxValue.Value.ToString("yyyy_MM_dd_HH_mm_ss");
                Dlg.InitialDirectory = TmpDirectory;
                Dlg.OverwritePrompt = true;
                if (Dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    if (!bw_Analysis_FileSaver.IsBusy)
                    {
                        btn_Analysis_Planes_Save.Enabled = false;
                        bw_Analysis_FileSaver.RunWorkerAsync(Dlg.FileName);
                    }
                }
            }
            catch (Exception ex)
            {
                Say(ex.Message);
                Log.WriteMessage(ex.ToString());
            }
        }

        private void btn_Analysis_Planes_Load_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog Dlg = new OpenFileDialog();
                Dlg.Filter = "Java Script Object Notation File|*.json|Comma Separated Values|*.csv";
                Dlg.DefaultExt = "json";
                Dlg.CheckFileExists = true;
                Dlg.CheckPathExists = true;
                Dlg.Multiselect = false;
                Dlg.InitialDirectory = TmpDirectory;
                if (Dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    if (!bw_Analysis_FileLoader.IsBusy)
                    {
                        btn_Analysis_Planes_Load.Enabled = false;
                        bw_Analysis_FileLoader.RunWorkerAsync(Dlg.FileName);
                    }
                }
            }
            catch (Exception ex)
            {
                Say(ex.Message);
                Log.WriteMessage(ex.ToString());
            }
        }

        private void btn_Analysis_Planes_Clear_Click(object sender, EventArgs e)
        {
            AircraftPositionData.Database.AircraftPositionDeleteAll();
        }

        private void btn_Analysis_Planes_History_Click(object sender, EventArgs e)
        {
            HistoryFromURLDlg Dlg = new HistoryFromURLDlg();
            if (Dlg.ShowDialog() == DialogResult.OK)
            {
                btn_Analysis_Planes_History.Enabled = false;
                bw_HistoryDownloader.RunWorkerAsync(Properties.Settings.Default.Analysis_History_Date);
            }
        }

        private void btn_Analysis_Planes_ShowTraffic_Click(object sender, EventArgs e)
        {
            TrafficDlg Dlg = new TrafficDlg();
            Dlg.ShowDialog();
        }

        private void btn_Analysis_Path_SaveToFile_Click(object sender, EventArgs e)
        {
            SaveFileDialog Dlg = new SaveFileDialog();
            Dlg.CheckPathExists = true;
            Dlg.AddExtension = true;
            Dlg.DefaultExt = "csv";
            Dlg.Filter = "Comma Separated Values *.csv |csv";
            Dlg.FileName = "Path Information " + Properties.Settings.Default.MyCall.Replace("/", "_") + " to " + Properties.Settings.Default.DXCall.Replace("/", "_");
            Dlg.InitialDirectory = TmpDirectory;
            Dlg.OverwritePrompt = true;
            if (Dlg.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    // calculate propagation path

                    // check and update station database
                    LocationDesignator myloc = LocationFindOrUpdateOrCreate(Properties.Settings.Default.MyCall, Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
                    Properties.Settings.Default.MyElevation = myloc.Elevation;

                    // get qrv info or create default
                    QRVDesignator myqrv = StationData.Database.QRVFindOrCreateDefault(myloc.Call, myloc.Loc, Properties.Settings.Default.Band);
                    // set qrv defaults if zero
                    if (myqrv.AntennaHeight == 0)
                        myqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                    if (myqrv.AntennaGain == 0)
                        myqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                    if (myqrv.Power == 0)
                        myqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);
                    // check if there are a valid DX settings
                    if (!Callsign.Check(Properties.Settings.Default.DXCall) ||
                        !GeographicalPoint.Check(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon))
                        return;

                    // OK valid, lets continue
                    // check and update station database
                    LocationDesignator dxloc = LocationFindOrUpdateOrCreate(Properties.Settings.Default.DXCall, Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon);
                    Properties.Settings.Default.DXElevation = dxloc.Elevation;
                    // get qrv info or create default
                    QRVDesignator dxqrv = StationData.Database.QRVFindOrCreateDefault(dxloc.Call, dxloc.Loc, Properties.Settings.Default.Band);
                    // set qrv defaults if zero
                    if (dxqrv.AntennaHeight == 0)
                        dxqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                    if (dxqrv.AntennaGain == 0)
                        dxqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                    if (dxqrv.Power == 0)
                        dxqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);

                    // find local obstruction, if any
                    LocalObstructionDesignator o = ElevationData.Database.LocalObstructionFind(myloc.Lat, myloc.Lon, Properties.Settings.Default.ElevationModel);
                    double mybearing = LatLon.Bearing(myloc.Lat, myloc.Lon, dxloc.Lat, dxloc.Lon);
                    double myobstr = (o != null) ? o.GetObstruction(myqrv.AntennaHeight, mybearing) : double.MinValue;

                    // try to find elevation path in database or create new one and store
                    ElevationPathDesignator epath = ElevationData.Database.ElevationPathFindOrCreateFromLatLon(
                        null,
                        myloc.Lat,
                        myloc.Lon,
                        dxloc.Lat,
                        dxloc.Lon,
                        ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                        Properties.Settings.Default.ElevationModel);
                    // add additional info to ppath
                    epath.Location1 = myloc;
                    epath.Location2 = dxloc;
                    epath.QRV1 = myqrv;
                    epath.QRV2 = dxqrv;

                    // try to find propagation path in database or create new one and store
                    PropagationPathDesignator ppath = PropagationData.Database.PropagationPathFindOrCreateFromLatLon(
                        null,
                        myloc.Lat,
                        myloc.Lon,
                        GetElevation(myloc.Lat, myloc.Lon) + myqrv.AntennaHeight,
                        dxloc.Lat,
                        dxloc.Lon,
                        GetElevation(dxloc.Lat, dxloc.Lon) + dxqrv.AntennaHeight,
                        Bands.ToGHz(Properties.Settings.Default.Band),
                        LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor,
                        Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance,
                        ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                        Properties.Settings.Default.ElevationModel,
                        myobstr);

                    // add additional info to ppath
                    ppath.Location1 = myloc;
                    ppath.Location2 = dxloc;
                    ppath.QRV1 = myqrv;
                    ppath.QRV2 = dxqrv;
                    using (StreamWriter sw = new StreamWriter(Dlg.FileName))
                    {
                        sw.WriteLine("Distance[km];Lat[deg];Lon[deg];Elevation[m]; Min_h1[m]; Min_h2[m]; Min_h[m]; Max_h[m]; F1[m];eps1[deg];eps2[deg];eps1_min[deg];eps2_min[deg];ElevationModel");
                        for (int i = 0; i < epath.Count; i++)
                        {
                            double dist = (double)i * epath.StepWidth / 1000.0;
                            PropagationPoint p = ppath.GetInfoPoint(dist);
                            sw.WriteLine(dist.ToString() + ";" +
                                p.Lat.ToString() + ";" +
                                p.Lon.ToString() + ";" +
                                epath.Path[i].ToString() + ";" +
                                p.H1.ToString() + ";" +
                                p.H2.ToString() + ";" +
                                Math.Max(p.H1, p.H2).ToString() + ";" +
                                Properties.Settings.Default.Planes_MaxAlt.ToString() + ";" +
                                p.F1.ToString() + ";" +
                                (Propagation.EpsilonFromHeights(ppath.h1, dist, epath.Path[i],ppath.Radius) / Math.PI * 180.0).ToString() + ";" +
                                (Propagation.EpsilonFromHeights(ppath.h2, ppath.Distance-dist, epath.Path[i], ppath.Radius) / Math.PI * 180.0).ToString() + ";" +
                                (ppath.Eps1_Min / Math.PI * 180.0).ToString() + ";" +
                                (ppath.Eps2_Min / Math.PI * 180.0).ToString());
                    }
                }
                }
                catch (Exception ex)
                {
                    Log.WriteMessage("Error while saving path to file [" + Dlg.FileName + "]:" + ex.ToString());
                }
            }
        }

        private void btn_Analysis_CrossingHistory_Click(object sender, EventArgs e)
        {
            if (dtp_Analysis_MaxValue.Value <= dtp_Analysis_MinValue.Value)
                return;
            if (Time_Offline_Interval <= 0)
                Time_Offline_Interval = 1;
            if ((AllPositions == null) || (AllPositions.Count == 0))
                return;
            CrossingHistoryDlg Dlg = new CrossingHistoryDlg(dtp_Analysis_MinValue.Value, dtp_Analysis_MaxValue.Value, Time_Offline_Interval, ref AllPositions);
            Dlg.ShowDialog();
        }

        private void sb_Analysis_Play_SizeChanged(object sender, EventArgs e)
        {
            // handle change of scrollbar
            // set scrollbar markers
            // reduce them to one marker per pixel
            // get initial scrollbar width and build array
            int[] sb = new int[sb_Analysis_Play.Width];
            double stepwidth = (double)sb.Length / (sb_Analysis_Play.Maximum - sb_Analysis_Play.Minimum);
            foreach (DateTime dt in AllLastUpdated)
            {
                int index = (int)((dt - dtp_Analysis_MinValue.Value).TotalSeconds * stepwidth);
                int i = (int)(dt - dtp_Analysis_MinValue.Value).TotalSeconds;
                if ((i > sb_Analysis_Play.Minimum) && (i < sb_Analysis_Play.Maximum))
                    sb[index] = i;
            }
            sb_Analysis_Play.BackgroundMarkers = sb.ToList<int>();
        }

        private void gb_Analysis_Player_SizeChanged(object sender, EventArgs e)
        {
            // handle change of size --> arrange elements
            sb_Analysis_Play.Left = dtp_Analysis_MinValue.Right + 10;
            sb_Analysis_Play.Width = dtp_Analysis_MaxValue.Left - dtp_Analysis_MinValue.Width - 20;
            sb_Analysis_Play.Height = dtp_Analysis_MinValue.Height;
        }

        private void btn_Analysis_Rewind_Click(object sender, EventArgs e)
        {
            PlayMode = AIRSCOUTPLAYMODE.FASTBACK;
            Time_Offline_Interval -= 10;
            if (Time_Offline_Interval < 1)
                Time_Offline_Interval = 1;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
        }

        private void btn_Analysis_Back_Click(object sender, EventArgs e)
        {
            PlayMode = AIRSCOUTPLAYMODE.BACK;
            Time_Offline_Interval -= 1;
            if (Time_Offline_Interval < 1)
                Time_Offline_Interval = 1;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
        }

        private void btn_Analysis_Pause_Click(object sender, EventArgs e)
        {
            PlayMode = AIRSCOUTPLAYMODE.PAUSE;
            Time_Offline_Interval = 0;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
        }

        private void btn_Analysis_Forward_Click(object sender, EventArgs e)
        {
            PlayMode = AIRSCOUTPLAYMODE.FORWARD;
            Time_Offline_Interval += 1;
            if (Time_Offline_Interval > 3600)
                Time_Offline_Interval = 3600;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
        }

        private void btn_Analysis_FastForward_Click(object sender, EventArgs e)
        {
            PlayMode = AIRSCOUTPLAYMODE.FASTFORWARD;
            Time_Offline_Interval += 10;
            if (Time_Offline_Interval > 3600)
                Time_Offline_Interval = 3600;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
        }

        private void sb_Analysis_Play_Scroll(object sender, ScrollEventArgs e)
        {
//            PlayMode = AIRSCOUTPLAYMODE.PAUSE;
//            Time_Offline_Interval = 0;
            tb_Analysis_Stepwidth.Text = Time_Offline_Interval.ToString();
            Properties.Settings.Default.Time_Offline = dtp_Analysis_MinValue.Value.AddSeconds(sb_Analysis_Play.Value);
            if (Properties.Settings.Default.Time_Offline < dtp_Analysis_MinValue.Value)
                Properties.Settings.Default.Time_Offline = dtp_Analysis_MinValue.Value;
            if (Properties.Settings.Default.Time_Offline > dtp_Analysis_MaxValue.Value)
                Properties.Settings.Default.Time_Offline = dtp_Analysis_MaxValue.Value;
            CurrentTime = Properties.Settings.Default.Time_Offline;
            tb_Analysis_Time.Text = CurrentTime.ToString("yyyy-MM-hh HH:mm:ss");
            UpdatePlanes();

        }

        private void dtp_Analysis_MinValue_ValueChanged(object sender, EventArgs e)
        {
            // check bounds
            if (dtp_Analysis_MinValue.Value < History_OldestEntry)
                dtp_Analysis_MinValue.Value = History_OldestEntry;
            if (dtp_Analysis_MinValue.Value > History_YoungestEntry)
                dtp_Analysis_MinValue.Value = History_YoungestEntry;
            UpdatePlayer();
        }

        private void dtp_Analysis_MaxValue_ValueChanged(object sender, EventArgs e)
        {
            // check bounds
            if (dtp_Analysis_MinValue.Value < History_OldestEntry)
                dtp_Analysis_MinValue.Value = History_OldestEntry;
            if (dtp_Analysis_MinValue.Value > History_YoungestEntry)
                dtp_Analysis_MinValue.Value = History_YoungestEntry;
            UpdatePlayer();
        }

        private void btn_Analysis_Plane_History_Click(object sender, EventArgs e)
        {
            PlaneHistoryDlg Dlg = new PlaneHistoryDlg(dtp_Analysis_MinValue.Value, dtp_Analysis_MaxValue.Value, TmpDirectory);
            Dlg.ShowDialog();
        }

        #endregion

        #endregion

        #endregion

        #region Background Workers

        #region PlaneFeed

        private void bw_PlaneFeed_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == (int)PROGRESS.STATUS)
            {
                if (e.UserState == null)
                    return;
                Say((string)e.UserState);
            }
            else if (e.ProgressPercentage == (int)PROGRESS.PLANES)
            {
                List<PlaneInfo> planes = (List<PlaneInfo>)e.UserState;
                Planes.BulkInsertOrUpdateIfNewer(planes);
            }
        }

        #endregion

        #region WinTestReceive

        // suppress console output when in debug mode, needs "Just my code" set in Tools\Options\Debugging
        [System.Diagnostics.DebuggerNonUserCode]
        private void bw_WinTestReceive_DoWork(object sender, DoWorkEventArgs e)
        {
            // Background thread for receiving Win-Test messages
            // listens to the UDP broadcasts
            // use the ReportProgress method to
            // return status code and received message
            // status values are: <0 = error orrcured (not supported yet)
            //                     0 = function calls OK, but no bytes received
            //                    >0 = function calls OK, number of bytes received
            Log.WriteMessage("Started.");
            if (Thread.CurrentThread.Name == null)
                Thread.CurrentThread.Name = "bw_WinTestReceive";
            // Get own IP addresses
            string hostname;
            IPAddress[] hostaddresses = new IPAddress[256];
            try
            {
                hostname = Dns.GetHostName();
                hostaddresses = Dns.GetHostAddresses(hostname);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
            // initialize UDP socket
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.Server_Port);
            UdpClient u = new UdpClient();
            u.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            u.Client.ReceiveTimeout = 1000;
            try
            {
                u.Client.Bind(ep);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }

            // receive Win-Test messages in a loop
            while (!bw_WinTestReceive.CancellationPending)
            {
                try
                {
                    // receive bytes in blocking mode
                    // handle the receive timeout and cancellation
                    try
                    {
                        byte[] data = u.Receive(ref ep);
                        wtMessage Msg = new wtMessage(data);
                        // check if message is directed to server
                        if (Msg.Dst == Properties.Settings.Default.Server_Name)
                            DispatchWinTestMsg(Msg);
                    }
                    catch (SocketException ex)
                    {
                        // do nothing
                    }
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                }
            }
            Log.WriteMessage("Finished.");
        }

        private void ASShowPath(wtMessage msg)
        {
            // a show path message received
            try
            {
                Stopwatch st = new Stopwatch();
                st.Start();
                if (PlayMode != AIRSCOUTPLAYMODE.PAUSE)
                    Pause();
                // set single mode
                tc_Control.SelectedTab = tp_Control_Single;
                PathMode = AIRSCOUTPATHMODE.SINGLE;
                string[] a = msg.Data.Split(',');
                string qrgstr = a[0].Replace("\"", "");
                string mycallstr = a[1].Replace("\"", "");
                string myloc = a[2].Replace("\"", "").Substring(0, 6);
                double mylat = MaidenheadLocator.LatFromLoc(myloc);
                double mylon = MaidenheadLocator.LonFromLoc(myloc);
                string dxcallstr = a[3].Replace("\"", "");
                string dxloc = a[4].Replace("\"", "").Substring(0, 6);
                double dxlat = MaidenheadLocator.LatFromLoc(dxloc);
                double dxlon = MaidenheadLocator.LonFromLoc(dxloc);
                if (Callsign.Check(mycallstr) &&
                    Callsign.Check(dxcallstr) &&
                    MaidenheadLocator.Check(myloc) &&
                    MaidenheadLocator.Check(dxloc))
                {
                    Properties.Settings.Default.MyCall = mycallstr;
                    Properties.Settings.Default.MyLat = mylat;
                    Properties.Settings.Default.MyLon = mylon;
                    Properties.Settings.Default.MyHeight = 10;
                    Properties.Settings.Default.DXCall = dxcallstr;
                    Properties.Settings.Default.DXLat = dxlat;
                    Properties.Settings.Default.DXLon = dxlon;
                    Properties.Settings.Default.DXHeight = 10;
                    Properties.Settings.Default.MyElevation = GetElevation(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
                    LocationDesignator info = StationData.Database.LocationFind(mycallstr);
                    if ((info != null) && (MaidenheadLocator.LocFromLatLon(info.Lat, info.Lon, false, 3) == myloc))
                    {
                        // loc is matching with database --> use detailed info then
                        Properties.Settings.Default.MyLat = info.Lat;
                        Properties.Settings.Default.MyLon = info.Lon;
                        Properties.Settings.Default.MyElevation = GetElevation(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon);
                    }
                    else
                    {
                        // update database if not found or locator does not match
                        UpdateLocation(mycallstr, mylat, mylon, GEOSOURCE.FROMLOC);
                    }
                    info = StationData.Database.LocationFind(dxcallstr);
                    if ((info != null) && (MaidenheadLocator.LocFromLatLon(info.Lat, info.Lon, false, 3) == dxloc))
                    {
                        // loc is matching with database --> use detailed info then
                        Properties.Settings.Default.DXLat = info.Lat;
                        Properties.Settings.Default.DXLon = info.Lon;
                        Properties.Settings.Default.DXElevation = GetElevation(Properties.Settings.Default.DXLat, Properties.Settings.Default.DXLon);
                    }
                    else
                    {
                        // update database if not found or locator does not match
                        UpdateLocation(dxcallstr, dxlat, dxlon, GEOSOURCE.FROMLOC);
                    }
                    if (qrgstr == "500000")
                        Properties.Settings.Default.Band = BAND.B50M;
                    if (qrgstr == "700000")
                        Properties.Settings.Default.Band = BAND.B70M;
                    if (qrgstr == "1440000")
                        Properties.Settings.Default.Band = BAND.B144M;
                    if (qrgstr == "4320000")
                        Properties.Settings.Default.Band = BAND.B432M;
                    if (qrgstr == "12960000")
                        Properties.Settings.Default.Band = BAND.B1_2G;
                    if (qrgstr == "23200000")
                        Properties.Settings.Default.Band = BAND.B2_3G;
                    if (qrgstr == "34000000")
                        Properties.Settings.Default.Band = BAND.B3_4G;
                    if (qrgstr == "57600000")
                        Properties.Settings.Default.Band = BAND.B5_7G;
                    if (qrgstr == "103680000")
                        Properties.Settings.Default.Band = BAND.B10G;
                    if (qrgstr == "240480000")
                        Properties.Settings.Default.Band = BAND.B24G;
                    if (qrgstr == "470880000")
                        Properties.Settings.Default.Band = BAND.B47G;
                    if (qrgstr == "760320000")
                        Properties.Settings.Default.Band = BAND.B76G;
                    UpdateStatus();
                    Play();
                    // try different methods to bring the window to front under WinXP and Win7
                    bool max = this.WindowState == FormWindowState.Maximized;
                    // try to restore window from minimized and normal state --> normal state
                    this.TopMost = true;
                    SetForegroundWindow(this.Handle);
                    ShowWindow(this.Handle, ShowWindowCommands.ShowNoActivate);
                    this.BringToFront();
                    this.TopMost = false;
                    // maximize it if it was maximized before
                    if (max)
                        this.WindowState = FormWindowState.Maximized;
                    // set antenna direction 
                    double az = LatLon.Bearing(Properties.Settings.Default.MyLat,
                        Properties.Settings.Default.MyLon,
                        Properties.Settings.Default.DXLat,
                        Properties.Settings.Default.DXLon);
                    // set tracking values
                    Properties.Settings.Default.Track_SetAz = az;
                    Properties.Settings.Default.Track_SetEl = 0;
                }
                st.Stop();
                Log.WriteMessage("Processing ASSHOWPATH[" + mycallstr + "," + dxcallstr + "]: " + st.ElapsedMilliseconds.ToString() + " ms.");
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }

        }

        private void ASSetPath(wtMessage msg)
        {
            try
            {
                Stopwatch st = new Stopwatch();
                st.Start();
                string[] a = msg.Data.Split(',');
                int qrg = 1296;
                int.TryParse(a[0].Replace("\"", ""), out qrg);
                qrg = qrg / 10000;
                BAND band = BAND.BNONE;
                try
                {
                    band = (BAND)qrg;
                }
                catch
                {
                    // do nothing
                }
                string mycallstr = a[1].Replace("\"", "");
                string mylocstr = a[2].Replace("\"", "").Substring(0, 6);
                string dxcallstr = a[3].Replace("\"", "");
                string dxlocstr = a[4].Replace("\"", "").Substring(0, 6);
                int count = 0;
                // return on failure
                if (!Callsign.Check(mycallstr))
                    return;
                if (!Callsign.Check(dxcallstr))
                    return;
                if (!MaidenheadLocator.Check(mylocstr))
                    return;
                if (!MaidenheadLocator.Check(dxlocstr))
                    return;
                // get my location info --> if loc is matching, use precise information automatically
                LocationDesignator myloc = StationData.Database.LocationFindOrCreate(mycallstr, mylocstr);
                // get my QRV info
                QRVDesignator myqrv = StationData.Database.QRVFindOrCreateDefault(mycallstr, mylocstr, band);
                // set qrv defaults if zero
                if (myqrv.AntennaHeight == 0)
                    myqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                if (myqrv.AntennaGain == 0)
                    myqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                if (myqrv.Power == 0)
                    myqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);
                // get dx location info --> if loc is matching, use precise information automatically
                LocationDesignator dxloc = LocationFindOrCreate(dxcallstr, dxlocstr);
                // get dx QRV info
                QRVDesignator dxqrv = StationData.Database.QRVFindOrCreateDefault(dxcallstr, dxlocstr, band);
                // set qrv defaults if zero
                if (dxqrv.AntennaHeight == 0)
                    dxqrv.AntennaHeight = StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band);
                if (dxqrv.AntennaGain == 0)
                    dxqrv.AntennaGain = StationData.Database.QRVGetDefaultAntennaGain(Properties.Settings.Default.Band);
                if (dxqrv.Power == 0)
                    dxqrv.Power = StationData.Database.QRVGetDefaultPower(Properties.Settings.Default.Band);
                // find local obstruction, if any
                LocalObstructionDesignator o = ElevationData.Database.LocalObstructionFind(myloc.Lat, myloc.Lon, Properties.Settings.Default.ElevationModel);
                double mybearing = LatLon.Bearing(myloc.Lat, myloc.Lon, dxloc.Lat, dxloc.Lon);
                double myobstr = (o != null) ? o.GetObstruction(myqrv.AntennaHeight, mybearing) : double.MinValue;

                // try to find propagation path in database or create new one and store
                PropagationPathDesignator ppath = PropagationData.Database.PropagationPathFindOrCreateFromLatLon(
                    null,
                    myloc.Lat,
                    myloc.Lon,
                    GetElevation(myloc.Lat, myloc.Lon) + myqrv.AntennaHeight,
                    dxloc.Lat,
                    dxloc.Lon,
                    GetElevation(dxloc.Lat, dxloc.Lon) + dxqrv.AntennaHeight,
                    Bands.ToGHz(Properties.Settings.Default.Band),
                    LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor,
                    Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance,
                    ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel),
                    Properties.Settings.Default.ElevationModel,
                    myobstr);
                List<PlaneInfo> allplanes = Planes.GetAll(DateTime.UtcNow, Properties.Settings.Default.Planes_Position_TTL);
                List<PlaneInfo> nearestplanes = AircraftData.Database.GetNearestPlanes(DateTime.UtcNow, ppath, allplanes, Properties.Settings.Default.Planes_Filter_Max_Circumcircle, Properties.Settings.Default.Path_Band_Settings[band].MaxDistance, Properties.Settings.Default.Planes_MaxAlt);
                count = 0;
                string planes = "";
                wtMessage SendMsg = new wtMessage(WTMESSAGES.ASNEAREST,
                    Properties.Settings.Default.Server_Name,
                    msg.Src,
                    DateTime.UtcNow.ToString("u") + "," +
                    mycallstr + "," +
                    mylocstr + "," +
                    dxcallstr + "," +
                    dxlocstr + ",");
                if ((nearestplanes != null) && (nearestplanes.Count() > 0))
                {
                    foreach (PlaneInfo planeinfo in nearestplanes)
                    {
                        if ((planeinfo.IntPoint != null) && (planeinfo.Potential > 0))
                        {
                            int mins = 0;
                            if (planeinfo.Speed > 0)
                                mins = (int)(planeinfo.IntQRB / (double)planeinfo.Speed / 1.852 * 60.0);
                            planes = planes + planeinfo.Call + "," + planeinfo.Category + "," + ((int)planeinfo.IntQRB).ToString() + "," + planeinfo.Potential.ToString() + "," + mins.ToString() + ",";
                            count++;
                        }
                    }
                }
                planes = planes.TrimEnd(',');
                SendMsg.Data = SendMsg.Data + count.ToString() + "," + planes;
                SendMsg.Data = SendMsg.Data.TrimEnd(',');
                SendMsg.HasChecksum = true;
                // send message
                UdpClient client = new UdpClient();
                client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                client.Client.ReceiveTimeout = 10000;    // 10s Receive timeout
                IPEndPoint groupEp = new IPEndPoint(IPAddress.Broadcast, Properties.Settings.Default.Server_Port);
                client.Connect(groupEp);
                byte[] b = SendMsg.ToBytes();
                client.Send(b, b.Length);
                client.Close();
                st.Stop();
                Log.WriteMessage("Processing ASSETPATH[" + mycallstr + "," + dxcallstr + "]: " + count.ToString() + " Plane(s), " + st.ElapsedMilliseconds.ToString() + " ms.");
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
        }

        private void ASSWatchlist (wtMessage msg)
        {
            if (!Properties.Settings.Default.Watchlist_SyncWithKST)
                return;
            // maintain watchlist
            try
            {
                Stopwatch st = new Stopwatch();
                st.Start();
                // mark all watchlist items to remove
                foreach (WatchlistItem item in Properties.Settings.Default.Watchlist)
                {
                    // nasty!! Should never be null!
                    if (item == null)
                        continue;
                    item.Remove = true;
                }
                // split message
                string[] a = msg.Data.Split(',');
                // ignore band string so far
                string qrgstr = a[0];
                // iterate through calls
                for (int i = 1; i < a.Length - 1; i += 2)
                {
                    // get call
                    string dxcallstr = a[i].Trim().ToUpper();
                    // get loc
                    string dxlocstr = a[i + 1].Trim().ToUpper();
                    // skip when invalid
                    if (!Callsign.Check(dxcallstr))
                        continue;
                    if (!MaidenheadLocator.Check(dxlocstr))
                        continue;
                    // skip own callsign
                    if (dxcallstr == Callsign.Cut(Properties.Settings.Default.MyCall))
                        continue;
                    // find or create call & loc combination in station database
                    LocationDesignator dxcall = StationData.Database.LocationFindOrCreate(dxcallstr, dxlocstr);
                    double qrb = LatLon.Distance(Properties.Settings.Default.MyLat, Properties.Settings.Default.MyLon, dxcall.Lat, dxcall.Lon);
                    // add to watchlist
                    int index = Properties.Settings.Default.Watchlist.IndexOf(dxcallstr, dxlocstr);
                    // reset remove flag if item found, add to watchlist if not
                    if (index >= 0)
                    {
                        Properties.Settings.Default.Watchlist[index].Remove = false;
                        Properties.Settings.Default.Watchlist[index].OutOfRange = qrb > Properties.Settings.Default.Path_MaxLength;
                    }
                    else
                    {
                        Properties.Settings.Default.Watchlist.Add(new WatchlistItem(dxcallstr, dxlocstr, qrb > Properties.Settings.Default.Path_MaxLength));
                    }
                }

                // remove all items from watchlist which are not logged in anymore
                Properties.Settings.Default.Watchlist.RemoveAll(item => item.Remove);

                // update watchlist in map
                UpdateWatchlistInMap();

                // update ListView control
                RefreshWatchlistView();
                st.Stop();
                Log.WriteMessage("Processing ASWATCHLIST: " + Properties.Settings.Default.Watchlist.Count.ToString() + " call(s), " + st.ElapsedMilliseconds.ToString() + " ms.");
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void DispatchWinTestMsg(wtMessage msg)
        {
            // a Win-Test message was received by the background thread
            if (msg.Msg == WTMESSAGES.ASSHOWPATH)
            {
                // a show path message received
                // dispatch it to main thread 
                bw_WinTestReceive.ReportProgress(1, msg);
            }
            else if (msg.Msg == WTMESSAGES.ASSETPATH)
            {
                // a path calculation message received
                // keep it in the background thread and calculate
                ASSetPath(msg);
            }
            else if (msg.Msg == WTMESSAGES.ASWATCHLIST)
            {
                // set users list on watchlist
                // dispatch it to main thread 
                bw_WinTestReceive.ReportProgress(1, msg);
            }
        }

        private void bw_WinTestReceive_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // a Win-Test message was received by the background thread
            NumberFormatInfo provider = new NumberFormatInfo();
            provider.NumberDecimalSeparator = ".";
            provider.NumberGroupSeparator = ",";
            provider.NumberGroupSizes = new int[] { 3 };

            wtMessage msg = (wtMessage)e.UserState;
            if (msg.Msg == WTMESSAGES.ASSHOWPATH)
                ASShowPath(msg);
            else if (msg.Msg == WTMESSAGES.ASWATCHLIST)
                ASSWatchlist(msg);
        }

        private void bw_WinTestReceive_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
        }

        #endregion

        # region SpecLabReceive

        // suppress console output when in debug mode, needs "Just my code" set in Tools\Options\Debugging
        //        [System.Diagnostics.DebuggerNonUserCode]
        private void bw_SpecLab_Receive_DoWork(object sender, DoWorkEventArgs e)
        {
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_SpecLabReceive";
            Log.WriteMessage("Started.");
            // get the update interval
            int interval = System.Convert.ToInt32(Properties.Settings.Default.SpecLab_Update) * 1000;
            // check for minimum
            if (interval < 1000)
                interval = 1000;
            bw_SpecLab_Receive.ReportProgress(0);
            int maxcount = 60;
            List<SignalLevelDesignator> ads = new List<SignalLevelDesignator>();
            while (!bw_SpecLab_Receive.CancellationPending)
            {
                Thread.Sleep(interval);
                // do nothing when not in play mode
                if ((LifeMode != AIRSCOUTLIFEMODE.LIFE) || (PlayMode != AIRSCOUTPLAYMODE.FORWARD))
                    continue;
                try
                {
                    // get boundary frequencies
                    int f1 = Properties.Settings.Default.SpecLab_F1;
                    if (f1 < 0)
                        f1 = 0;
                    if (f1 > 3000)
                        f1 = 3000;
                    int f2 = Properties.Settings.Default.SpecLab_F2;
                    if (f2 < 1)
                        f2 = 1;
                    if (f2 > 3000)
                        f2 = 3000;
                    if (f1 >= f2)
                        f1 = f2 - 1;
                    // get the url
                    string url = Properties.Settings.Default.SpecLab_URL;
                    // get the filename
                    string filename = Properties.Settings.Default.SpecLab_FileName;
                    if (!url.EndsWith("/"))
                        url = url + "/";
                    url = url + filename + "?f1=" + f1.ToString() + "?f2=" + f2.ToString();
                    string msg = "";
                    bw_SpecLab_Receive.ReportProgress(1, "Trying to connect to Spectrum Lab...");
                    WebRequest myWebRequest = WebRequest.Create(url);
                    WebResponse myWebResponse = myWebRequest.GetResponse();
                    Stream ReceiveStream = myWebResponse.GetResponseStream();
                    Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
                    StreamReader readStream = new StreamReader(ReceiveStream, encode);
                    string json = readStream.ReadToEnd();
                    if (!json.StartsWith("["))
                        throw (new FormatException("Format Exception: Not a JSON file."));
                    // split the JSON string
                    // a[1] contains the header 
                    // h contains alle header items
                    // d contains all data items
                    string[] a = json.Split('[');
                    a[1] = a[1].Replace(":", ",");
                    string[] h = a[1].Split(',');
                    a[2] = a[2].Replace("\r\n", "");
                    a[2] = a[2].Remove(a[2].IndexOf("]") - 1);
                    a[2] = a[2].Replace(",", ";");
                    a[2] = a[2].Replace(".", ",");
                    double[] d = System.Array.ConvertAll(a[2].Split(';'), new Converter<string, double>(Double.Parse));
                    // get the time stamp
                    long l = (long)System.Convert.ToDouble(h[1], CultureInfo.InvariantCulture);
                    DateTime utc = new System.DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
                    utc = utc.AddSeconds(l);
                    // get number of bins
                    int count = System.Convert.ToInt32(h[13]);
                    msg = "FFT-Data received: " + count.ToString() + "bins. F1=" + f1.ToString() + "Hz, F2=" + f2.ToString() + "Hz";
                    bw_SpecLab_Receive.ReportProgress(1, msg);
                    double max = d.Max();
                    // collect and save maximum if in play mode and NearestPlane != null
                    if ((PlayMode == AIRSCOUTPLAYMODE.FORWARD) && (NearestPlane != null))
                        ads.Add(new SignalLevelDesignator(max, utc));
                    // bulk save maximum to database if maxcount is reached
                    if (ads.Count > maxcount)
                    {
                        SignalData.Database.SignalLevelBulkInsertOrUpdateIfNewer(ads);
                        ads.Clear();
                    }
                    // report maximum
                    bw_SpecLab_Receive.ReportProgress(100, max);
                }
                catch (WebException ex)
                {
                    // do nothing
                }
                catch (SocketException ex)
                {
                    // do nothing
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                    bw_SpecLab_Receive.ReportProgress(-1, ex.Message);
                }
            }
            Log.WriteMessage("Finished.");
        }

        private void bw_SpecLab_Receive_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage < 0)
                {
                    // an error occured
                    tb_Spectrum_Status.ForeColor = Color.White;
                    tb_Spectrum_Status.BackColor = Color.Red;
                    tb_Spectrum_Status.Text = (string)e.UserState;
                }
                if (e.ProgressPercentage == 0)
                {
                    // a init message occured
                    Spectrum.Points.Clear();
                    SpectrumPointsCount = 0;
                    Spectrum_X.Reset();
                    SpectrumRecord.Points.Clear();
                }
                if (e.ProgressPercentage == 1)
                {
                    // a status message occured
                    tb_Spectrum_Status.ForeColor = Color.Black;
                    tb_Spectrum_Status.BackColor = SystemColors.Control;
                    tb_Spectrum_Status.Text = (string)e.UserState;
                }
                if (e.ProgressPercentage == 100)
                {
                    // FFT - data received
                    if ((LifeMode == AIRSCOUTLIFEMODE.LIFE) && (PlayMode == AIRSCOUTPLAYMODE.FORWARD))
                    {
                        // draw data
                        double max = (double)e.UserState;
                        if (Spectrum.Points.Count >= SpectrumMaxPoints)
                        {
                            double pan = -(Spectrum_X.ScreenMax.X - Spectrum_X.ScreenMin.X) / (double)SpectrumMaxPoints;
                            Spectrum_X.Pan(pan);
                            Spectrum.Points.RemoveAt(0);
                            SpectrumRecord.Points.RemoveAt(0);
                        }
                        // add background area
                        SpectrumRecord.Fill = OxyColor.FromArgb(20, 255, 0, 255);
                        double on, off;
                        if (Spectrum_Y.Minimum < 0)
                        {
                            on = -1000;
                            off = 1000;
                        }
                        else
                        {
                            on = 1000;
                            off = -1000;
                        }
                        if (NearestPlane == null)
                            SpectrumRecord.Points.Add(new DataPoint(SpectrumPointsCount, off));
                        else
                            SpectrumRecord.Points.Add(new DataPoint(SpectrumPointsCount, on));
                        // add signal level point
                        Spectrum.Points.Add(new DataPoint(SpectrumPointsCount, max));
                        SpectrumPointsCount++;
                        // autoscale Y axis
                        double y_min = double.MaxValue;
                        double y_max = double.MinValue;
                        foreach (DataPoint p in Spectrum.Points)
                        {
                            if (p.Y > y_max)
                                y_max = p.Y;
                            if (p.Y < y_min)
                                y_min = p.Y;
                        }
                        // enlarge scaling Y-axis by 10% in both directions
                        double y_diff = (y_max - y_min) * 0.1;
                        Spectrum_Y.Minimum = y_min - y_diff;
                        Spectrum_Y.Maximum = y_max + y_diff;
                        pm_Spectrum.InvalidatePlot(true);
                    }
                }
                // maintain nearest plane map
                if (NearestPlane != null)
                {
                    lbl_Nearest_Call.Text = NearestPlane.Call;
                    lbl_Nearest_Type.Text = NearestPlane.Type;
                    lbl_Nearest_Cat.Text = PlaneCategories.GetStringValue(NearestPlane.Category);
                    lbl_Nearest_Alt.Text = NearestPlane.Alt_m.ToString("F0") + "m";
                    lbl_Nearest_Angle.Text = (NearestPlane.Angle / Math.PI * 180.0).ToString("F0") + "°";
                    lbl_Nearest_Dist.Text = NearestPlane.IntQRB.ToString("F0") + "km";
                    gmo_NearestPlanes.Clear();
                    gmo_NearestPlanes.Markers.Add(CreatePlaneDetailed(NearestPlane, false));
                    gm_Nearest.Position = new PointLatLng(NearestPlane.IntPoint.Lat, NearestPlane.IntPoint.Lon);
                    gm_Nearest.Zoom = 10;
                }
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.Message);
            }
        }

        private void bw_SpecLab_Receive_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

        # endregion

        #region Track

        private bool Track_DDE_HRD(DdeClient client, double az, double el)
        {
            // send Az/EL vie DDE to Ham Radio Deluxe Rotor Control (HRDRotator.exe)
            // no position feedback expected
            if (client == null)
                throw new NullReferenceException("[DDE]: Client not initialized.");
            if (!client.IsConnected)
                throw new InvalidOperationException("[DDE]: Client not connected.");

            byte[] data;
            if ((az >= 0) && (az <= 360))
            {
                // send azimuth data
                data = Encoding.ASCII.GetBytes("SET-AZ:" + az.ToString("F1"));
                client.TryPoke("PositionData", data, 1, 10000);
            }
            if ((el >= 0) && (el <= 90))
            {
                // send elevation data
                data = Encoding.ASCII.GetBytes("SET-EL:" + el.ToString("F1"));
                client.TryPoke("PositionData", data, 1, 10000);
            }
            return true;
        }

        private bool Track_UDP_WinTest(UdpClient client, IPEndPoint ip, double az, double el)
        {
            // send UDP broadcast like Win-Test
            // no position feedback expected
            if ((client == null) || (ip == null))
                if (client == null)
                    throw new NullReferenceException("[UDP]: Client and/or IP endpoint not initialized.");
            wtMessage msg = new wtMessage(WTMESSAGES.SETAZIMUTH, Properties.Settings.Default.Server_Name, "", "AUTO", " 00 " + az.ToString("000"));
            byte[] bytes = msg.ToBytes();
            client.Send(bytes, bytes.Length, ip);
            return true;
        }

        private bool Track_UDP_AirScout(UdpClient client, IPEndPoint ip, double az, double el)
        {
            // send UDP broadcast like Win-Test
            // no position feedback expected
            if ((client == null) || (ip == null))
                throw new NullReferenceException("[UDP]: Client and/or IP endpoint not initialized.");
            wtMessage msg;
            msg = new wtMessage(WTMESSAGES.SETAZIMUTH, Properties.Settings.Default.Server_Name, "", "AUTO", " 00 " + az.ToString("000"));
            msg = new wtMessage(WTMESSAGES.SETELEVATION, Properties.Settings.Default.Server_Name, "", "AUTO", " 00 " + el.ToString("000"));
            byte[] bytes = msg.ToBytes();
            client.Send(bytes, bytes.Length, ip);
            return true;
        }

        private string Serial_SendCommand(SerialPort sp, string command, bool waitanswer)
        {
            // sends a command via serial port (and optional wait fo answer)
            if ((sp == null) || (!sp.IsOpen))
                return "";
            string s = "";
            {
                sp.WriteLine(command);
                if (waitanswer)
                {
                    s = sp.ReadLine();
                    s = s.Replace("\n", "");
                }
            }
            return s;
        }


        private bool Track_SER__GS_232A_AZ(SerialPort sp, double az)
        {
            // send Az value via serial port (GS-232A protocol)
            // communictaion test --> get azimuth value
            if (sp == null)
                throw new NullReferenceException("[Serial]: Port not initialized.");
            if (!sp.IsOpen)
                throw new InvalidOperationException("[Serial]: Port not open.");
            // communictaion test --> get azimuth value
            string s = Serial_SendCommand(sp, "C", true);
            if (!s.StartsWith("+0"))
                throw new FormatException("[Serial]: Wrong serial data format.");
            try
            {
                double result = System.Convert.ToDouble(s.Substring(2, 3), CultureInfo.InvariantCulture);
            }
            catch
            {
                throw new FormatException("[Serial]: Wrong serial data format.");
            }
            // set azimuth value --> no feedback
            Serial_SendCommand(sp, "M" + az.ToString("000"), false);
            return true;
        }

        private bool Track_SER__GS_232A_AZEL(SerialPort sp, double az, double el)
        {
            // send Az/El value via serial port (GS-232A protocol)
            if (sp == null)
                throw new NullReferenceException("[Serial]: Port not initialized.");
            if (!sp.IsOpen)
                throw new InvalidOperationException("[Serial]: Port not open.");
            // communictaion test --> get azimuth and elevation value
            string s = Serial_SendCommand(sp, "C2", true);
            if (!s.StartsWith("+0"))
                throw new FormatException("[Serial]: Wrong serial data format.");
            try
            {
                double result = System.Convert.ToDouble(s.Substring(2, 3), CultureInfo.InvariantCulture);
            }
            catch
            {
                throw new FormatException("[Serial]: Wrong serial data format.");
            }
            // set azimuth value --> no feedback
            Serial_SendCommand(sp, "W" + az.ToString("000") + " " + el.ToString("000"), false);
            return true;
        }

        private void Track_File_Native(double az, double el)
        {
            // writes a file with Az/El values in a file (native)
            // Syntax: <Az value>,<El value> 
            using (StreamWriter sw = new StreamWriter(TmpDirectory + Path.DirectorySeparatorChar + "azel.dat"))
            {
                sw.WriteLine(az.ToString("F1", CultureInfo.InvariantCulture) + "," + el.ToString("F1", CultureInfo.InvariantCulture));
            }
        }

        private void Track_File_WSJT(double az, double el)
        {
            // writes a file with Az/El values in a file (WSJT)
            // the info is filled in the "Source" line (originally intended to follow a radio source
            using (StreamWriter sw = new StreamWriter(TmpDirectory + Path.DirectorySeparatorChar + "azel.dat"))
            {
                string utc = DateTime.UtcNow.ToString("HH:mm:ss");
                sw.WriteLine(utc + ",0,0,Moon");
                sw.WriteLine(utc + ",0,0,Sun");
                sw.WriteLine(utc + "," + az.ToString("F1", CultureInfo.InvariantCulture) + "," + el.ToString("F1", CultureInfo.InvariantCulture) + ",Source");
                sw.WriteLine("0,  0, 0,  0, 0,Doppler, R");
            }
        }

        private void bw_Track_DoWork(object sender, DoWorkEventArgs e)
        {
            Log.WriteMessage("Started.");
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_Track";
            // clients and ports
            DdeClient ddeclient = null;
            UdpClient udpclient = null;
            IPEndPoint udpip = null;
            SerialPort serialport = null;
            // error counters
            int ddeerr = 0;
            int udperr = 0;
            int serialerr = 0;
            int maxerr = 10;
            // outer loop
            do
            {
                // intializations
                if (Properties.Settings.Default.Track_DDE_HRD)
                {
                    ddeclient = new DdeClient("HRDRotator", "Position");
                    int result = ddeclient.TryConnect();

                }
                if (Properties.Settings.Default.Track_UDP_WinTest)
                {
                    udpclient = new UdpClient();
                    udpip = new IPEndPoint(IPAddress.Broadcast, Properties.Settings.Default.Track_UDP_WinTest_Port);
                }
                else if (Properties.Settings.Default.Track_UDP_AirScout)
                {
                    udpclient = new UdpClient();
                    udpip = new IPEndPoint(IPAddress.Broadcast, Properties.Settings.Default.Track_UDP_AirScout_Port);
                }
                if ((Properties.Settings.Default.Track_Serial_GS232_AZ) || (Properties.Settings.Default.Track_Serial_GS232_AZEL))
                {
                    serialport = new SerialPort(Properties.Settings.Default.Track_Serial_Port,
                        Properties.Settings.Default.Track_Serial_Baudrate,
                        Parity.None,
                        8,
                        StopBits.One);
                    serialport.Handshake = System.IO.Ports.Handshake.None;
                    serialport.NewLine = "\r";
                    serialport.Encoding = Encoding.ASCII;
                    serialport.ReadTimeout = 1000;
                    serialport.WriteTimeout = 1000;
                    serialport.Open();
                }
                // inner loop
                while (Properties.Settings.Default.Track_Activate && !bw_Track.CancellationPending)
                {
                    try
                    {
                        // tracking
                        double az = Properties.Settings.Default.Track_SetAz;
                        double el = Properties.Settings.Default.Track_SetEl;
                        if (!double.IsNaN(az) && !double.IsNaN(el))
                        {
                            if (Properties.Settings.Default.Track_DDE_HRD)
                                Track_DDE_HRD(ddeclient, az, el);
                            if (Properties.Settings.Default.Track_UDP_WinTest)
                                Track_UDP_WinTest(udpclient, udpip, az, el);
                            if (Properties.Settings.Default.Track_UDP_AirScout)
                                Track_UDP_WinTest(udpclient, udpip, az, el);
                            if (Properties.Settings.Default.Track_Serial_GS232_AZ)
                                Track_SER__GS_232A_AZ(serialport, az);
                            if (Properties.Settings.Default.Track_Serial_GS232_AZEL)
                                Track_SER__GS_232A_AZEL(serialport, az, el);
                            if (Properties.Settings.Default.Track_File_Native)
                                Track_File_Native(az, el);
                            if (Properties.Settings.Default.Track_File_WSJT)
                                Track_File_WSJT(az, el);
                        }
                        else
                        {
                            // no tracking!
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.WriteMessage(ex.Message);
                        //report error
                        bw_Track.ReportProgress(-1, ex.Message);
                        // increment error counters and switch off in case of subsequent errors
                        if (ex.Message.StartsWith("[DDE]:"))
                        {
                            ddeerr++;
                            if (ddeerr > maxerr)
                            {
                                // switch off DDE
                                Properties.Settings.Default.Track_DDE_None = true;
                                Properties.Settings.Default.Track_DDE_HRD = false;
                                bw_Track.ReportProgress(-1, "Tracking via DDE disabled.");
                            }
                        }
                        if (ex.Message.StartsWith("[UDP]:"))
                        {
                            udperr++;
                            if (udperr > maxerr)
                            {
                                // switch off UDP
                                Properties.Settings.Default.Track_UDP_None = true;
                                Properties.Settings.Default.Track_UDP_WinTest = false;
                                Properties.Settings.Default.Track_UDP_AirScout = false;
                                bw_Track.ReportProgress(-1, "Tracking via UDP disabled.");
                            }
                        }
                        if (ex.Message.StartsWith("[Serial]:"))
                        {
                            serialerr++;
                            if (serialerr > maxerr)
                            {
                                // switch off Serial
                                Properties.Settings.Default.Track_Serial_None = true;
                                Properties.Settings.Default.Track_Serial_GS232_AZ = false;
                                Properties.Settings.Default.Track_Serial_GS232_AZEL = false;
                                bw_Track.ReportProgress(-1, "Tracking via Serial disabled.");
                            }
                            if (ex.Message.StartsWith("[Serial]:"))
                                serialerr++;
                        }
                        // leave inner loop
                        break;
                    }
                    Thread.Sleep(1000);
                }
                Thread.Sleep(1000);
                // try to close all connections
                try
                {
                    if ((ddeclient != null) && (ddeclient.IsConnected))
                        ddeclient.Disconnect();
                    if (udpclient != null)
                        udpclient.Close();
                    if ((serialport != null) && (serialport.IsOpen))
                        serialport.Close();
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                }
            }
            while (!bw_Track.CancellationPending);
            Log.WriteMessage("Finished.");
        }

        private void bw_Track_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage < 0)
            {
                // report Error
                tsl_Status.Text = (string)e.UserState;
            }
            if (e.ProgressPercentage == 1)
            {
                // report Azimuth
                ag_Azimuth.Value = (float)(double)e.UserState;
            }
            if (e.ProgressPercentage == 2)
            {
                // report Elevation
                ag_Elevation.Value = (float)(double)e.UserState;
            }
        }

        private void bw_Track_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

        #endregion

        #region JSONWriter

        private void bw_JSONWriter_DoWork(object sender, DoWorkEventArgs e)
        {
            Log.WriteMessage("Started.");
            int interval = 60;
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_JSONWriter";
            while (!bw_JSONWriter.CancellationPending)
            {
                // get planes each minute
                List<PlaneInfo> list = Planes.GetAll(DateTime.UtcNow, Properties.Settings.Default.Planes_Position_TTL);
                // write json file
                try
                {
                    using (StreamWriter sw = new StreamWriter(TmpDirectory + Path.DirectorySeparatorChar + "planes.json"))
                    {
                        int major = Assembly.GetExecutingAssembly().GetName().Version.Major;
                        sw.Write("{\"full_count\":" + list.Count().ToString() + ",\"version\":" + major.ToString());
                        int i = 0;
                        foreach (PlaneInfo info in list)
                        {
                            string index = "\"" + i.ToString("x8") + "\"";
                            string hex = "\"" + info.Hex + "\"";
                            string lat = info.Lat.ToString("F4", CultureInfo.InvariantCulture);
                            string lon = info.Lon.ToString("F4", CultureInfo.InvariantCulture);
                            string track = info.Track.ToString();
                            string alt = info.Alt.ToString();
                            string speed = info.Speed.ToString();
                            string squawk = "\"" + "" + "\"";
                            string radar = "\"" + "" + "\"";
                            string type = "\"" + info.Type + "\"";
                            string reg = "\"" + info.Reg + "\"";
                            DateTime sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                            string time = ((long)(info.Time - sTime).TotalSeconds).ToString();
                            string dep = "\"\"";
                            string dest = "\"\"";
                            string flight = "\"\"";
                            string dummy1 = "0";
                            string dummy2 = "0";
                            string call = "\"" + info.Call + "\"";
                            string dummy3 = "0";
                            sw.WriteLine("," + index + ":[" +
                                hex + "," +
                                lat + "," +
                                lon + "," +
                                track + "," +
                                alt + "," +
                                speed + "," +
                                squawk + "," +
                                radar + "," +
                                type + "," +
                                reg + "," +
                                time + "," +
                                dep + "," +
                                dest + "," +
                                flight + "," +
                                dummy1 + "," +
                                dummy2 + "," +
                                call + "," +
                                dummy3 +
                                "]");
                        }
                        sw.WriteLine("}");
                        i++;
                    }
                }
                catch (Exception ex)
                {
                    Log.WriteMessage(ex.Message);
                    // do nothing
                }
                int ii = 0;
                while (!bw_JSONWriter.CancellationPending && (ii < interval))
                {
                    Thread.Sleep(1000);
                    ii++;
                }
            }
            Log.WriteMessage("Finished.");
        }


        #endregion

        #region NewsFeed

        private void bw_NewsFeed_DoWork(object sender, DoWorkEventArgs e)
        {
            Log.WriteMessage("Started.");
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_NewsFeed";
            Uri uri = Properties.Settings.Default.News_URL;
            int interval = Properties.Settings.Default.News_Interval;
            while (!bw_NewsFeed.CancellationPending)
            {
                try
                {
                    // get the last modified time of the website
                    AutoDecompressionWebClient cl = new AutoDecompressionWebClient();
                    DateTime dt = cl.GetWebCreationTimeUtc(Properties.Settings.Default.News_URL);
                    Log.WriteMessage("Checking news page: " + dt.ToString("yyyy-MM-dd HH:mm:ss") + "<> " + Properties.Settings.Default.News_LastUpdate.ToString("yyyy-MM-dd HH:mm:ss"));
                    Console.WriteLine("Checking news page: " + dt.ToString("yyyy-MM-dd HH:mm:ss") + "<> " + Properties.Settings.Default.News_LastUpdate.ToString("yyyy-MM-dd HH:mm:ss"));
                    // report latest news if updated
                    if (dt > Properties.Settings.Default.News_LastUpdate)
                    {
                        // report news to main window
                        bw_NewsFeed.ReportProgress(1, dt);
                    }
                }
                catch (Exception ex)
                {
                    // report error
                    Log.WriteMessage(ex.Message);
                    bw_NewsFeed.ReportProgress(-1, DateTime.UtcNow.ToString("[" + "HH:mm:ss") + "] Error while reading the website " + uri.ToString() + ": " + ex.Message);
                }
                int i = 0;
                while (!bw_NewsFeed.CancellationPending && (i < interval))
                {
                    Thread.Sleep(1000);
                    i++;
                }
            }
            Log.WriteMessage("Finished.");
        }

        private void bw_NewsFeed_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage < 0)
            {
                // report error
                Say((string)e.UserState);
            }
            else
            {
                // report website changes
                DateTime dt = (DateTime)e.UserState;
                if (!SupportFunctions.IsMono)
                {
                    if (MessageBox.Show("There are news on the website. Latest update: " + dt.ToString() + "\n Do you want to read it now?", "Website News", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
                    {
                        try
                        {
                            if (wb_News != null)
                                wb_News.Refresh();
                            tc_Main.SelectedTab = tp_News;
                            // save time to settings
                            Properties.Settings.Default.News_LastUpdate = dt;
                        }
                        catch (Exception ex)
                        {
                            // do nothing if wb_News fails to refresh
                        }
                    }
                }
                else
                {
                    if (MessageBox.Show("There are news on the website. Latest update: " + dt.ToString() + "\n Do you want to read it now?\n\n Under Linux/Mono open web browser of your choice and goto: \n" + Properties.Settings.Default.News_URL + "\n\n", "Website News", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
                    {
                        try
                        {
                            // save time to settings
                            Properties.Settings.Default.News_LastUpdate = dt;
                        }
                        catch (Exception ex)
                        {
                            // do nothing if wb_News fails to refresh
                        }
                    }

                }
            }
        }

        private void bw_NewsFeed_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

        #endregion

        #region HistoryDownloader

        private void HistoryDownloader_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            // get byte in GB
            double bytesin = double.Parse(e.BytesReceived.ToString()) / 1024.0 / 1024.0 / 1024.0;
            double totalbytes = double.Parse(e.TotalBytesToReceive.ToString()) / 1024.0 / 1024.0 / 1024.0;
            double percentage = bytesin / totalbytes * 100;
            try
            {
                if (bw_HistoryDownloader.IsBusy)
                    bw_HistoryDownloader.ReportProgress(1, "Downloaded " + bytesin.ToString("F2") + " GB of " + totalbytes.ToString("F2") + " GB  (" + percentage.ToString("F2") + "%).");
            }
            catch
            {

            }
        }

        private string ReadPropertyString(JObject o, string propertyname)
        {
            if (o.Property(propertyname) == null)
                return null;
            return o.Property(propertyname).Value.Value<string>();
        }

        private int ReadPropertyDoubleToInt(JObject o, string propertyname)
        {
            if (o.Property(propertyname) == null)
                return int.MinValue;
            double d = ReadPropertyDouble(o, propertyname);
            if ((d != double.MinValue) && (d >= int.MinValue) && (d <= int.MaxValue))
                return (int)d;
            return int.MinValue;
        }

        private double ReadPropertyDouble(JObject o, string propertyname)
        {
            if (o.Property(propertyname) == null)
                return double.MinValue;
            return o.Property(propertyname).Value.Value<double>();
        }

        private long ReadPropertyLong(JObject o, string propertyname)
        {
            if (o.Property(propertyname) == null)
                return long.MinValue;
            return o.Property(propertyname).Value.Value<long>();
        }

        private void HistoryDownloader_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            try
            {
                if (bw_HistoryDownloader.IsBusy)
                    bw_HistoryDownloader.ReportProgress(1, "Reading " + Properties.Settings.Default.Analysis_History_ZIPFileName);
                // unzip the file
                if (File.Exists(Properties.Settings.Default.Analysis_History_ZIPFileName))
                {
                    string filename = Properties.Settings.Default.Analysis_History_ZIPFileName;
                    // unzips a zip file content to the same directory
                    string downloaddir = Path.GetDirectoryName(Properties.Settings.Default.Analysis_History_ZIPFileName);
                    // set path to calling assembly's path if not otherwise specified
                    if (String.IsNullOrEmpty(downloaddir))
                        downloaddir = Assembly.GetCallingAssembly().Location;
                    // open the zip file
                    using (ZipFile zip = new ZipFile(filename))
                    {
                        zip.ZipErrorAction = ZipErrorAction.Skip;
                        // here, we extract every entry, but we could extract conditionally
                        // based on entry name, size, date, checkbox status, etc.  
                        foreach (ZipEntry ze in zip)
                        {
                            if (bw_HistoryDownloader.IsBusy)
                                bw_HistoryDownloader.ReportProgress(1, "Extracting " + ze.FileName);
                            try
                            {
                                /*
                                ze.Extract(downloaddir, ExtractExistingFileAction.OverwriteSilently);
                                string fname = Path.Combine(downloaddir, ze.FileName);
                                File.SetLastWriteTime(fname, ze.LastModified);
                                */
                            }
                            catch (Exception ex)
                            {
                                if (bw_HistoryDownloader.IsBusy)
                                    bw_HistoryDownloader.ReportProgress(-1, ex.Message);
                            }
                        }
                    }
                    // create csv to load into database
                    using (StreamWriter sw = new StreamWriter(Properties.Settings.Default.Analysis_History_ZIPFileName.ToLower().Replace(".zip", ".csv")))
                    {
                        sw.WriteLine("time;call;reg;hex;lat;lon;track;alt;speed;squawk;radar;type");
                        // read all files
                        for (int i = 0; i < 1440; i += (int)Properties.Settings.Default.Analysis_History_Stepwidth)
                        {
                            // calculate filename
                            int hours = i / 60;
                            int minutes = i % 60;
                            string fname = Path.Combine(Properties.Settings.Default.Analysis_History_Directory, Properties.Settings.Default.Analysis_History_Date.ToString("yyyy-MM-dd") + "-" + hours.ToString("00") + minutes.ToString("00") + "Z.json");
                            if (bw_HistoryDownloader.IsBusy)
                                bw_HistoryDownloader.ReportProgress(1, "Processing " + fname);
                            if (File.Exists(fname))
                            {
                                string json = "";
                                using (StreamReader sr = new StreamReader(fname))
                                    json = sr.ReadToEnd();
                                // analyze json string for planes data
                                JObject root = (JObject)JsonConvert.DeserializeObject(json);
                                foreach (JProperty proot in root.Children<JProperty>())
                                {
                                    // get the planes position list
                                    if (proot.Name == "acList")
                                    {
                                        foreach (JArray a in proot.Children<JArray>())
                                        {
                                            foreach (JObject o in a.Values<JObject>())
                                            {
                                                PlaneInfo info = new PlaneInfo();
                                                try
                                                {
                                                    info.Call = ReadPropertyString(o, "Call");
                                                    info.Lat = ReadPropertyDouble(o, "Lat");
                                                    info.Lon = ReadPropertyDouble(o, "Long");
                                                    info.Track = ReadPropertyDoubleToInt(o, "Trak");
                                                    // 2017-07-23: take "GAlt" (corrected altitude by air pressure) rather than "Alt"
                                                    info.Alt = ReadPropertyDoubleToInt(o, "GAlt");
                                                    //                                            info.Alt = ReadPropertyDoubleToInt(o, "Alt");
                                                    info.Speed = ReadPropertyDoubleToInt(o, "Spd");
                                                    info.Reg = ReadPropertyString(o, "Reg");
                                                    try
                                                    {
                                                        string squawk = ReadPropertyString(o, "Sqk");
                                                    }
                                                    catch
                                                    {
                                                    }
                                                    info.Hex = ReadPropertyString(o, "Icao");
                                                    info.Type = ReadPropertyString(o, "Type");
                                                    // complete type info
                                                    AircraftTypeDesignator td = AircraftData.Database.AircraftTypeFindByICAO(info.Type);
                                                    if (td != null)
                                                    {
                                                        info.Manufacturer = td.Manufacturer;
                                                        info.Model = td.Model;
                                                        info.Category = td.Category;
                                                    }
                                                    else
                                                    {
                                                        info.Manufacturer = "[unknown]";
                                                        info.Model = "[unknown]";
                                                        info.Category = PLANECATEGORY.NONE;
                                                    }
                                                    // CAUTION!! time is UNIX time in milliseconds
                                                    long l = ReadPropertyLong(o, "PosTime");
                                                    if (l != long.MinValue)
                                                    {
                                                        DateTime timestamp = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
                                                        timestamp = timestamp.AddMilliseconds(l);
                                                        info.Time = timestamp;
                                                    }
                                                    else
                                                    {
                                                        info.Time = DateTime.MinValue;
                                                    }
                                                    if (PlaneInfoChecker.Check(info) &&
                                                        (info.Alt_m >= Properties.Settings.Default.Planes_MinAlt) &&
                                                        (info.Alt_m <= Properties.Settings.Default.Planes_MaxAlt) &&
                                                        (info.Lat >= Properties.Settings.Default.MinLat) &&
                                                        (info.Lat <= Properties.Settings.Default.MaxLat) &&
                                                        (info.Lon >= Properties.Settings.Default.MinLon) &&
                                                        (info.Lon <= Properties.Settings.Default.MaxLon))
                                                    {
                                                        sw.WriteLine(info.Time.ToString("u") + ";" +
                                                            info.Call + ";" +
                                                            info.Reg + ";" +
                                                            info.Hex + ";" +
                                                            info.Lat.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                                                            info.Lon.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                                                            info.Track.ToString() + ";" +
                                                            info.Alt.ToString() + ";" +
                                                            info.Speed.ToString() + ";" +
                                                            "" + ";" +
                                                            "" + ";" +
                                                            info.Type);
                                                    }
                                                }
                                                catch (Exception ex)
                                                {
                                                    Log.WriteMessage(ex.Message);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (bw_HistoryDownloader.IsBusy)
                    bw_HistoryDownloader.ReportProgress(-1, ex.Message);
            }
            // job is done
            // cancel backgroundworker
            bw_HistoryDownloader.CancelAsync();
        }

        private void bw_HistoryDownloader_DoWork(object sender, DoWorkEventArgs e)
        {
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_HistoryDownloader";
            try
            {
                DateTime date = (DateTime)e.Argument;
                // check history directory first
                if (!Directory.Exists(Properties.Settings.Default.Analysis_History_Directory))
                    Properties.Settings.Default.Analysis_History_Directory = TmpDirectory;
                // check free disk space
                System.IO.DriveInfo drive = new System.IO.DriveInfo(Properties.Settings.Default.Analysis_History_Directory);
                System.IO.DriveInfo a = new System.IO.DriveInfo(drive.Name);
                if (a.AvailableFreeSpace < 50.0 * 1024.0 * 1024.0 * 1024.0)
                    throw new ArgumentException("Not enough disk space to run this operation.");
                string url = Properties.Settings.Default.Analysis_History_URL;
                Properties.Settings.Default.Analysis_History_ZIPFileName = Path.Combine(Properties.Settings.Default.Analysis_History_Directory, date.ToString("yyyy-MM-dd") + ".zip");
                if (!File.Exists(Properties.Settings.Default.Analysis_History_ZIPFileName))
                {
                    // file not found --> donwload it from url
                    // complete url with "/" and date 
                    if (!url.EndsWith("/"))
                        url = url + "/";
                    url = url + date.ToString("yyyy-MM-dd") + ".zip";
                    // create web client for download
                    WebClient client = new WebClient();
                    // register asynchronous file download event handler
                    client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(HistoryDownloader_DownloadProgressChanged);
                    client.DownloadFileCompleted += new AsyncCompletedEventHandler(HistoryDownloader_DownloadFileCompleted);
                    client.DownloadFileAsync(new Uri(url), Properties.Settings.Default.Analysis_History_ZIPFileName);
                    // remain here in a loop until job is finished
                    // cancellation will be initiated after download and unzip is complet
                }
                else
                {   // call download completed handler directly
                    HistoryDownloader_DownloadFileCompleted(this, null);
                }
            }
            catch (Exception ex)
            {
                bw_HistoryDownloader.ReportProgress(-1, ex.Message);
            }
        }

        private void bw_HistoryDownloader_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            string s = (string)e.UserState;
            if (String.IsNullOrEmpty(s))
                return;
            tb_Analysis_Status.Text = s;
        }

        private void bw_HistoryDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btn_Analysis_Planes_History.Enabled = true;
        }

        #endregion


        #region ElevationDatabaseUpdater

        private void bw_ElevationDatabaseUpdater_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage < 0)
                {
                    // error message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                }
                else if (e.ProgressPercentage == 0)
                {
                    // status message received
                    string msg = (string)e.UserState;
                    // redirect output to splash screen on first run
                    if (FirstRun && SplashDlg != null)
                        SplashDlg.Status("Preparing database for first run: " + msg + " (please wait)", Color.Yellow);
                    else
                    {
                        SayDatabase(msg);
                        Log.WriteMessage(msg);
                    }
                }
                else if (e.ProgressPercentage == 1)
                {
                    // database status update message received
                    if (sender == this.bw_GLOBEUpdater)
                    {
                        Properties.Settings.Default.Elevation_GLOBE_DatabaseStatus = (DATABASESTATUS)e.UserState;
                        Color color = DatabaseStatus.GetDatabaseStatusColor(Properties.Settings.Default.Elevation_GLOBE_DatabaseStatus);
                        if (tsl_Database_LED_GLOBE.BackColor != color)
                            tsl_Database_LED_GLOBE.BackColor = color;
                        string text = "GLOBE Database Status\n\n" + DatabaseStatus.GetDatabaseStatusText(Properties.Settings.Default.Elevation_GLOBE_DatabaseStatus);
                        if (tsl_Database_LED_GLOBE.ToolTipText != text)
                            tsl_Database_LED_GLOBE.ToolTipText = text;
                    }
                    else if (sender == this.bw_SRTM3Updater)
                    {
                        Properties.Settings.Default.Elevation_SRTM3_DatabaseStatus = (DATABASESTATUS)e.UserState;
                        Color color = DatabaseStatus.GetDatabaseStatusColor(Properties.Settings.Default.Elevation_SRTM3_DatabaseStatus);
                        if (tsl_Database_LED_SRTM3.BackColor != color)
                            tsl_Database_LED_SRTM3.BackColor = color;
                        string text = "SRTM3 Database Status\n\n" + DatabaseStatus.GetDatabaseStatusText(Properties.Settings.Default.Elevation_SRTM3_DatabaseStatus);
                        if (tsl_Database_LED_SRTM3.ToolTipText != text)
                            tsl_Database_LED_SRTM3.ToolTipText = text;
                    }
                    else if (sender == this.bw_SRTM1Updater)
                    {
                        Properties.Settings.Default.Elevation_SRTM1_DatabaseStatus = (DATABASESTATUS)e.UserState;
                        Color color = DatabaseStatus.GetDatabaseStatusColor(Properties.Settings.Default.Elevation_SRTM1_DatabaseStatus);
                        if (tsl_Database_LED_SRTM1.BackColor != color)
                            tsl_Database_LED_SRTM1.BackColor = color;
                        string text = "SRTM1 Database Status\n\n" + DatabaseStatus.GetDatabaseStatusText(Properties.Settings.Default.Elevation_SRTM1_DatabaseStatus);
                        if (tsl_Database_LED_SRTM1.ToolTipText != text)
                            tsl_Database_LED_SRTM1.ToolTipText = text;
                    }
                }
                ss_Main.Update();
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }


        #endregion

        #region ElevationPathCalculator

        private void bw_ElevationPathCalculator_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 0)
                SayCalculations((string)e.UserState);
        }


        #endregion

        #region StationDatabaseUpdater

        private void bw_StationDatabaseUpdater_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage < 0)
                {
                    // error message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                }
                else if (e.ProgressPercentage == 0)
                {
                    // status message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                    // redirect output to splash screen on first run
                    if (FirstRun && SplashDlg != null)
                        SplashDlg.Status("Preparing database for first run: " + msg + " (please wait)", Color.Yellow);
                    else
                    {
                        SayDatabase(msg);
                    }
                }
                else if (e.ProgressPercentage == 1)
                {
                    Properties.Settings.Default.StationsDatabase_Status = (DATABASESTATUS)e.UserState;
                    Color color = DatabaseStatus.GetDatabaseStatusColor(Properties.Settings.Default.StationsDatabase_Status);
                    if (tsl_Database_LED_Stations.BackColor != color)
                        tsl_Database_LED_Stations.BackColor = color;
                    string text = "Stations Database Status\n\n" + DatabaseStatus.GetDatabaseStatusText(Properties.Settings.Default.StationsDatabase_Status);
                    if (tsl_Database_LED_Stations.ToolTipText != text)
                        tsl_Database_LED_Stations.ToolTipText = text;
                }
                ss_Main.Update();
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void bw_StationDatabaseUpdater_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // refresh display
            if (!this.IsDisposed)
            {
                UpdateAirports();
                gm_Main.Refresh();
            }
        }

        #endregion

        #region AircraftDatabaseUpdater

        private void bw_AircraftDatabaseUpdater_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage < 0)
                {
                    // error message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                }
                else if (e.ProgressPercentage == 0)
                {
                    // status message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                    // redirect output to splash screen on first run
                    if (FirstRun && SplashDlg != null)
                        SplashDlg.Status("Preparing database for first run: " + msg + " (please wait)", Color.Yellow);
                    else
                    {
                        SayDatabase(msg);
                    }
                }
                else if (e.ProgressPercentage == 1)
                {
                    Properties.Settings.Default.AircraftDatabase_Status = (DATABASESTATUS)e.UserState;
                    Color color = DatabaseStatus.GetDatabaseStatusColor(Properties.Settings.Default.AircraftDatabase_Status);
                    if (tsl_Database_LED_Aircraft.BackColor != color)
                    {
                        tsl_Database_LED_Aircraft.BackColor = color;
                    }
                    string text = "Aircraft Database Status\n\n" + DatabaseStatus.GetDatabaseStatusText(Properties.Settings.Default.AircraftDatabase_Status);
                    if (tsl_Database_LED_Aircraft.ToolTipText != text)
                        tsl_Database_LED_Aircraft.ToolTipText = text;
                }
                ss_Main.Update();
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void bw_AircraftDatabaseUpdater_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // refresh data tables and display
            if (!this.IsDisposed)
            {
                // refresh all dictionnariees
                //                ScoutData.Database.UpdateFromDataTables(false);
                // refresh airports and map
                UpdateAirports();
                gm_Main.Refresh();
            }
        }

        #endregion

        #region AircraftDatabaseMaintainer

        private void bw_AircraftDatabaseMaintainer_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage < 0)
                {
                    // error message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                }
                else if (e.ProgressPercentage == 0)
                {
                    // status message received
                    string msg = (string)e.UserState;
                    Log.WriteMessage(msg);
                    // redirect output to splash screen on first run
                    if (FirstRun && SplashDlg != null)
                        SplashDlg.Status("Preparing database for first run: " + msg + " (please wait)", Color.Yellow);
                    else
                    {
                        SayDatabase(msg);
                    }
                }
                ss_Main.Update();
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
            }
        }

        private void bw_AircraftDatabaseMaintainer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
        }

        #endregion

        #endregion


        #region Analysis_DataGetter

        private void bw_Analysis_DataGetter_DoWork(object sender, DoWorkEventArgs e)
        {
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_Analysis_DataGetter";
            Stopwatch st = new Stopwatch();
            st.Start();
            bw_Analysis_DataGetter.ReportProgress(0, "Getting timespan of all positions in database...");
            // calculate min/max values for timespan
            History_OldestEntry = AircraftPositionData.Database.AircraftPositionOldestEntry();
            if (bw_Analysis_DataGetter.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            History_YoungestEntry = AircraftPositionData.Database.AircraftPositionYoungestEntry();
            if (bw_Analysis_DataGetter.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            bw_Analysis_DataGetter.ReportProgress(0, "Getting positions...");
            AircraftPositionsCount = AircraftPositionData.Database.AircraftPositionCount();
            // get all aircraft positions into cache
            lock (AllPositions)
            {
                AllPositions.Clear();
            }
            lock (AllPositions)
            {
                // get all positions from database, can be interrupted
                AllPositions = AircraftPositionData.Database.AircraftPositionGetAll(this.bw_Analysis_DataGetter);
                if (bw_Analysis_DataGetter.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
            }
            st.Stop();
            bw_Analysis_DataGetter.ReportProgress(0, "Getting positions finished, " + AllPositions.Count.ToString() + " positions, " + st.ElapsedMilliseconds.ToString() + " ms.");
        }

        private void bw_Analysis_DataGetter_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 0)
            {
                string msg = (string)e.UserState;
                // NASTY!! Add the total count of positions after the "of" in the message
                // total count is calculated once in DoWork
                if (msg.EndsWith("of)"))
                    msg = msg + " " + AircraftPositionsCount.ToString();
                SayAnalysis(msg);
            }
        }

        private void bw_Analysis_DataGetter_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // check if cancelled
            // return to default state
            if (e.Cancelled)
            {
                SayAnalysis("Cancelled.");
                btn_Analysis_ON.Enabled = true;
                btn_Analysis_ON.BackColor = Color.YellowGreen;
                return;
            }
            else
                SayAnalysis("Ready.");
            // nothing found in database --> show message box and do not enter analysis mode
            if ((History_YoungestEntry == DateTime.MinValue) || (History_OldestEntry == DateTime.MinValue))
            {
                MessageBox.Show("Nothing found for analysis. Please let AirScout run in PLAY mode for a while to collect some data.", "AirScout Analysis", MessageBoxButtons.OK);
                return;
            }
            // set scroll bar bounds
            dtp_Analysis_MinValue.Value = History_OldestEntry;
            dtp_Analysis_MaxValue.Value = History_YoungestEntry;
            // enable buttons
            btn_Analysis_Planes_Load.Enabled = true;
            btn_Analysis_Planes_Save.Enabled = true;
            btn_Analysis_Planes_Clear.Enabled = true;
            btn_Analysis_Planes_History.Enabled = true;
            btn_Analysis_Planes_ShowTraffic.Enabled = true;
            btn_Analysis_Path_SaveToFile.Enabled = true;
            btn_Analysis_CrossingHistory.Enabled = true;
            btn_Analysis_Plane_History.Enabled = true;
            btn_Analysis_Rewind.Enabled = true;
            btn_Analysis_Back.Enabled = true;
            btn_Analysis_Pause.Enabled = true;
            btn_Analysis_Forward.Enabled = true;
            btn_Analysis_FastForward.Enabled = true;
            sb_Analysis_Play.Enabled = true;
            dtp_Analysis_MinValue.Enabled = true;
            dtp_Analysis_MaxValue.Enabled = true;
            UpdatePlayer();
            // set time to oldest entry
            Properties.Settings.Default.Time_Offline = History_OldestEntry;
            gb_Analysis_Player_SizeChanged(this, null);
            UpdatePaths();
            UpdateStatus();
            btn_Analysis_Planes_Load.Focus();
        }

        #endregion

        #region Analysis_FileSaver


        private void Analysis_Planes_Save_JSON(string filename)
        {
            int saved = 0;
            using (StreamWriter sw = new StreamWriter(filename))
            {
                sw.WriteLine("[");
                for (int i = 0; i < AllPositions.Count; i++)
                {
                    if (AllPositions[i].LastUpdated < dtp_Analysis_MinValue.Value)
                        continue;
                    if (AllPositions[i].LastUpdated > dtp_Analysis_MaxValue.Value)
                        continue;
                    string json = AllPositions[i].ToJSON();
                    sw.Write(json);
                    if (i < AllPositions.Count - 1)
                    {
                        sw.WriteLine(",");
                    }
                    else
                        sw.WriteLine();
                    saved++;
                    if (saved % 1000 == 0)
                        bw_Analysis_FileSaver.ReportProgress(0, "Saving position " + saved.ToString() + "...");
                    if (bw_Analysis_FileSaver.CancellationPending)
                        return;
                }
                sw.WriteLine("]");
            }
        }

        private void Analysis_Planes_Save_CSV(string filename)
        {
            int saved = 0;
            using (StreamWriter sw = new StreamWriter(filename))
            {
                sw.WriteLine("time[utc];hex;call;lat[deg];lon[deg];alt[ft];track[deg];speed[kts]");
                foreach (AircraftPositionDesignator ap in AllPositions)
                {
                    if (ap.LastUpdated < dtp_Analysis_MinValue.Value)
                        continue;
                    if (ap.LastUpdated > dtp_Analysis_MaxValue.Value)
                        continue;
                    sw.WriteLine(ap.LastUpdated.ToString("yyyy-MM-dd HH:mm:ssZ") + ";" +
                        ap.Hex + ";" +
                        ap.Call + ";" +
                        ap.Lat.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                        ap.Lon.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                        ap.Alt.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                        ap.Track.ToString("F8", CultureInfo.InvariantCulture) + ";" +
                        ap.Speed.ToString("F8", CultureInfo.InvariantCulture)
                        );
                    saved++;
                    if (saved % 1000 == 0)
                        bw_Analysis_FileSaver.ReportProgress(0, "Saving position " + saved.ToString() + "...");
                    if (bw_Analysis_FileSaver.CancellationPending)
                        return;
                }
            }
        }

        private void bw_Analysis_FileSaver_DoWork(object sender, DoWorkEventArgs e)
        {
            string filename = (string)e.Argument;
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_Analysis_FileSaver";
            try
            {
                if (filename.ToLower().EndsWith(".json"))
                    Analysis_Planes_Save_JSON(filename);
                else if (filename.ToLower().EndsWith(".csv"))
                    Analysis_Planes_Save_CSV(filename);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
                bw_Analysis_FileSaver.ReportProgress(-1, ex.Message);
            }
            if (bw_Analysis_FileSaver.CancellationPending)
                e.Cancel = true;
        }

        private void bw_Analysis_FileSaver_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage <= 0)
                SayAnalysis((string)e.UserState);
        }

        private void bw_Analysis_FileSaver_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
                SayAnalysis("Cancelled.");
            else
            {
                btn_Analysis_Planes_Save.Enabled = true;
                SayAnalysis("Ready.");
            }
        }

        #endregion

        #region Analysis_FileLoader

        private void Analysis_Planes_Load_JSON(string filename)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
            settings.FloatFormatHandling = FloatFormatHandling.String;
            settings.Formatting = Newtonsoft.Json.Formatting.Indented;
            settings.Culture = CultureInfo.InvariantCulture;
            List<AircraftPositionDesignator> aps = new List<AircraftPositionDesignator>();
            bw_Analysis_FileLoader.ReportProgress(0, "Opening file...");
            using (StreamReader sr = new StreamReader(File.Open(filename, FileMode.Open)))
            {
                // check for starting bracket of array
                char c = (char)sr.Read();
                if (c != '[')
                    return;
                // read 1000 positions and update database
                int count = 0;
                int updated = 0;
                while (!sr.EndOfStream)
                {
                    aps.Clear();
                    int j = 0;
                    while ((j < 1000) && !sr.EndOfStream)
                    {
                        char nextChar;
                        StringBuilder line = new StringBuilder();
                        while ((j < 1000) && sr.Peek() > 0)
                        {
                            nextChar = (char)sr.Read();
                            line.Append(nextChar);
                            if (line[0] != '{')
                                line.Clear();
                            if (nextChar == '}')
                            {
                                AircraftPositionDesignator ap = JsonConvert.DeserializeObject<AircraftPositionDesignator>(line.ToString(), settings);
                                line.Clear();
                                aps.Add(ap);
                                j++;
                            }
                        }
                        if (bw_Analysis_FileLoader.CancellationPending)
                            return;
                    }
                    count = count + j;
                    updated = updated + AircraftPositionData.Database.AircraftPositionBulkInsertOrUpdateIfNewer(aps);
                    bw_Analysis_FileLoader.ReportProgress(0, "Updating position " + count.ToString() + ", " + updated.ToString() + " updated so far...");
                }

            }
        }

        private void Analysis_Planes_Load_CSV(string filename)
        {
            List<AircraftPositionDesignator> aps = new List<AircraftPositionDesignator>();
            bw_Analysis_FileLoader.ReportProgress(0, "Opening file...");
            using (StreamReader sr = new StreamReader(File.Open(filename, FileMode.Open)))
            {
                // read header
                string header = sr.ReadLine();
                // split header
                string[] a = header.Split(';');
                // remove unit brackets and lower all
                for (int i = 0; i < a.Length; i++)
                {
                    a[i] = a[i].ToLower();
                    if (a[i].IndexOf('[') >= 0)
                        a[i] = a[i].Substring(0, a[i].IndexOf('['));
                }
                int lastupdated_index = Array.IndexOf(a, "time");
                int hex_index = Array.IndexOf(a, "hex");
                int call_index = Array.IndexOf(a, "call");
                int lat_index = Array.IndexOf(a, "lat");
                int lon_index = Array.IndexOf(a, "lon");
                int alt_index = Array.IndexOf(a, "alt");
                int track_index = Array.IndexOf(a, "track");
                int speed_index = Array.IndexOf(a, "speed");
                bw_Analysis_FileLoader.ReportProgress(0, "Creating position list...");
                // read 1000 positions and update database
                int count = 0;
                int updated = 0;
                while (!sr.EndOfStream)
                {
                    aps.Clear();
                    int j = 0;
                    while ((j < 1000) && !sr.EndOfStream)
                    {
                        string s = sr.ReadLine();
                        if (!s.Contains(";"))
                            continue;
                        a = s.Split(';');
                        DateTime lastupdated = System.Convert.ToDateTime(a[lastupdated_index]).ToUniversalTime();
                        string hex = a[hex_index].ToUpper();
                        string call = a[call_index].ToUpper();
                        double lat = System.Convert.ToDouble(a[lat_index], CultureInfo.InvariantCulture);
                        double lon = System.Convert.ToDouble(a[lon_index], CultureInfo.InvariantCulture);
                        double alt = System.Convert.ToDouble(a[alt_index], CultureInfo.InvariantCulture);
                        double track = System.Convert.ToDouble(a[track_index], CultureInfo.InvariantCulture);
                        double speed = System.Convert.ToDouble(a[speed_index], CultureInfo.InvariantCulture);
                        AircraftPositionDesignator ap = new AircraftPositionDesignator(hex, call, lat, lon, alt, track, speed, lastupdated);
                        aps.Add(ap);
                        j++;
                        if (bw_Analysis_FileLoader.CancellationPending)
                            return;
                    }
                    count = count + j;
                    updated = updated + AircraftPositionData.Database.AircraftPositionBulkInsertOrUpdateIfNewer(aps);
                    bw_Analysis_FileLoader.ReportProgress(0, "Updating position " + count.ToString() + ", " + updated.ToString() + " updated so far...");
                }

            }
        }

        private void bw_Analysis_FileLoader_DoWork(object sender, DoWorkEventArgs e)
        {
            string filename = (string)e.Argument;
            // name the thread for debugging
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
                Thread.CurrentThread.Name = "bw_Analysis_FileLoader";
            try
            {
                if (filename.ToLower().EndsWith(".json"))
                    Analysis_Planes_Load_JSON(filename);
                else if (filename.ToLower().EndsWith(".csv"))
                    Analysis_Planes_Load_CSV(filename);
            }
            catch (Exception ex)
            {
                Log.WriteMessage(ex.ToString());
                bw_Analysis_FileLoader.ReportProgress(-1, ex.Message);
            }
            if (bw_Analysis_FileLoader.CancellationPending)
                e.Cancel = true;
        }

        private void bw_Analysis_FileLoader_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage <= 0)
                SayAnalysis((string)e.UserState);
        }

        private void bw_Analysis_FileLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
                SayAnalysis("Cancelled.");
            else
            {
                btn_Analysis_Planes_Load.Enabled = true;
                SayAnalysis("Ready.");
            }
        }

        #endregion


        #region AirportMapper

        private void bw_AirportMapper_DoWork(object sender, DoWorkEventArgs e)
        {
            bw_AirportMapper.ReportProgress(0, "Getting airports from database...");
            Stopwatch st = new Stopwatch();
            st.Start();
            // clear airports if any
            gmo_Airports.Clear();
            // fill the airports layer of maps
            // return if switched off
            if (!Properties.Settings.Default.Airports_Activate)
                return;
            List<AirportDesignator> airports = new List<AirportDesignator>();
            airports = AircraftData.Database.AirportGetAll(bw_AirportMapper);
            if (airports != null)
                bw_AirportMapper.ReportProgress(100, airports);
            st.Stop();
            bw_AirportMapper.ReportProgress(0, airports.Count.ToString() + " airports updated, " + st.ElapsedMilliseconds.ToString() + " ms.");
        }

        private void bw_AirportMapper_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage <= 0)
            {
                string msg = (string)e.UserState;
                Say(msg);
                Log.WriteMessage(msg);
            }
            else if (e.ProgressPercentage == 100)
            {
                lock (Airports)
                {
                    Airports = (List<AirportDesignator>)e.UserState;
                }
                UpdateAirports();
            }
        }

        private void bw_AirportMapper_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

        #endregion

        private void gm_Main_Load(object sender, EventArgs e)
        {

        }

    }


    public class ClippingToolStripRenderer : ToolStripSystemRenderer
    {
        protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
        {
            ToolStripStatusLabel label = e.Item as ToolStripStatusLabel;

            if (label != null)
            {
                TextRenderer.DrawText(e.Graphics, label.Text,
                    label.Font, e.TextRectangle,
                    label.ForeColor,
                    TextFormatFlags.EndEllipsis);
            }
            else
            {
                base.OnRenderItemText(e);
            }
        }
    }

    public class LocatorDropDownItem
    {
        public string Locator { get; set; }
        public LatLon.GPoint GeoLocation { get; set; }

        public LocatorDropDownItem(string locator, LatLon.GPoint geolocation)
        {
            Locator = locator;
            GeoLocation = geolocation;
        }
    }

}
