#region <<< Using Directives >>>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.Compression;
using System.Windows.Forms;

using Volpe.Cafe.EspressoUI.Charts;
using Volpe.Cafe.EspressoUI.LegacyControls;
using Volpe.Cafe.EspressoUI.Themes;
using Volpe.Cafe.EspressoUI.Utils;
using Volpe.Cafe.Logging;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Reporting;
using Volpe.Cafe.Resources;
using Volpe.Cafe.Utils;
#endregion

/****************************************************************************************************
 *                                                                                                  *
 *  Do not use the Windows Form Designer for editing the major elements of the GUI.                 *
 *                                                                                                  *
 *  This form relies on some external initialization (which cannot be done in the designer), such   *
 *  as assigning tooltips and icons for menu, toolbar, and statusbar items. Using the designer      *
 *  hybrid design-time/runtime initialization, which may lead to discrepancies (if, for example, a  *
 *  new menu entry was created at design-time, but the associated runtime initialization of an      *
 *  icon-name and/or tooltip was not performed).                                                    *
 *  Additionally, the designer does not handle shared resources very well (such as icons imported   *
 *  from another project), instead replicating them as embedded resources within the form.          *
 *                                                                                                  *
 *  Instead, adding new menu, toolbar, and statusbar items should be done entirely in code. The     *
 *  MenuItems class defines all menu entries, which includes dynamic properites that are defined    *
 *  at runtime, and which cannot be otherwise assigned at design-time.                              *
 *                                                                                                  *
 ****************************************************************************************************/
namespace Volpe.Cafe.EspressoUI
{
    /// <summary>
    /// Provides the main graphical user interface for the CAFE Model.
    /// </summary>
    public class Cafe2e : Form
    {

        #region <<< Nested Types >>>

        static class MenuItems
        {

            #region <<< Ctors >>>

            static MenuItems()
            {
                // declare tooltips for each menu item (if N/A or TBD, use null)
                // file menu
                Tooltips[FileNewSession                 ] = "Creates a new CAFE Model Session and displays the Modeling Settings window to the user.";
                Tooltips[FileOpenSession                ] = "Opens an existing CAFE Model Session.";
                Tooltips[FileCloseSession               ] = "Closes the currently open CAFE Model Session.";
                Tooltips[FileSaveSession                ] = "Saves the CAFE Model Session.";
                Tooltips[FileSaveSessionAs              ] = "Displays the Save As dialog for saving the CAFE Model Session.";
                Tooltips[FileStartModeling              ] = "Begins CAFE simulation modeling for the currently open CAFE Model Session.";
                Tooltips[FileStopModeling               ] = "Suspends CAFE simulation modeling.";
                Tooltips[FileExit                       ] = "Exits the CAFE Model.";
                // view menu
                Tooltips[ViewModelingSettings           ] = "Displays the Modeling Settings window, where all modeling options and settings can be configured.";
                Tooltips[ViewInputFileLocation          ] = "Browses the location of the input data files used by the current session.";
                Tooltips[ViewOutputLocation             ] = "Browses the location where the output files and reports of the current session are written to.";
                Tooltips[ViewArgonneSimulationResults   ] = "Copies the Vehicle Simulation Database results, produced at Argonne National Laboratory using the Autonomie model, to a user-specified directory.";
                Tooltips[ViewUpdateSpeed                ] = "Selects how often to refresh the simulation progress of the running CAFE Model Session.";
                Tooltips[ViewUpdateSpeedRealtime        ] = "Refresh the session eight times per second.";
                Tooltips[ViewUpdateSpeedHigh            ] = "Refresh the session twice per second.";
                Tooltips[ViewUpdateSpeedNormal          ] = "Refresh the session once per second.";
                Tooltips[ViewUpdateSpeedLow             ] = "Refresh the session every two seconds.";
                Tooltips[ViewUpdateSpeedPaused          ] = "Do not automatically refresh the session's simulation progress.";
                Tooltips[ViewRefresh                    ] = "Forces the session to refresh its simulation progress immediately.";
                Tooltips[ViewChangeTheme                ] = "Changes the CAFE Model's graphical user interface theme colors and accents.";
                Tooltips[ViewZoom                       ] = "Changes the zoom level of the CAFE Model's graphical user interface elements.";
                Tooltips[ViewZoomIn                     ] = "Increases the zoom level of the CAFE Model's graphical user interface elements.";
                Tooltips[ViewZoomOut                    ] = "Decreases the zoom level of the CAFE Model's graphical user interface elements.";
                Tooltips[ViewZoomReset                  ] = "Resets the CAFE Model's graphical user interface elements to a default zoom level.";
                Tooltips[ViewFullScreen                 ] = "Toggles the CAFE Model's graphical user interface into and out of full-screen.";
                // help menu
                Tooltips[HelpSoftwareManual             ] = "Displays the CAFE Model Software Manual, describing the GUI and the runtime features available within the modeling system.";
                Tooltips[HelpShowNoticeScreen           ] = "Displays the CAFE Model Notice screen.";
                Tooltips[HelpShowWarningsScreen         ] = "Displays the CAFE Model Warnings screen.";
                Tooltips[HelpAbout                      ] = "Displays the about dialog, containing product information for the CAFE Model.";
                // toolbar (these should use corresponding values defined for file-/view-menu items above)
                Tooltips[ToolbarNewSession              ] = Tooltips[FileNewSession      ];
                Tooltips[ToolbarOpenSession             ] = Tooltips[FileOpenSession     ];
                Tooltips[ToolbarCloseSession            ] = Tooltips[FileCloseSession    ];
                Tooltips[ToolbarSaveSession             ] = Tooltips[FileSaveSession     ];
                Tooltips[ToolbarModelingSettings        ] = Tooltips[ViewModelingSettings];
                Tooltips[ToolbarStartModeling           ] = Tooltips[FileStartModeling   ];
                Tooltips[ToolbarStopModeling            ] = Tooltips[FileStopModeling    ];
                Tooltips[ToolbarZoomOut                 ] = Tooltips[ViewZoomOut         ];
                Tooltips[ToolbarZoomIn                  ] = Tooltips[ViewZoomIn          ];
                Tooltips[ToolbarZoomReset               ] = Tooltips[ViewZoomReset       ];
                Tooltips[ToolbarToggleFullScreen        ] = Tooltips[ViewFullScreen      ];
                // statusbar
                Tooltips[StatusBarModelStatus           ] = null;
                Tooltips[StatusBarZoomLevel             ] = "The current zoom level of the CAFE Model's graphical user interface elements.";
                Tooltips[StatusBarBuildDate             ] = "The build date for this version of the CAFE Model.";

                // declare icon names for each menu item (if N/A or TBD, use IconName.None)
                // file menu
                IconNames[FileNewSession                ] = IconName.FileNew;
                IconNames[FileOpenSession               ] = IconName.FileOpen;
                IconNames[FileCloseSession              ] = IconName.FileClose;
                IconNames[FileSaveSession               ] = IconName.FileSave;
                IconNames[FileSaveSessionAs             ] = IconName.FileSaveAs;
                IconNames[FileStartModeling             ] = IconName.StartModel;
                IconNames[FileStopModeling              ] = IconName.StopModel;
                IconNames[FileExit                      ] = IconName.Exit;
                // view menu
                IconNames[ViewModelingSettings          ] = IconName.Configure;
                IconNames[ViewInputFileLocation         ] = IconName.Find;
                IconNames[ViewOutputLocation            ] = IconName.Folder;
                IconNames[ViewArgonneSimulationResults  ] = IconName.Document;
                IconNames[ViewUpdateSpeed               ] = IconName.None;
                IconNames[ViewUpdateSpeedRealtime       ] = IconName.Apply;
                IconNames[ViewUpdateSpeedHigh           ] = IconName.Apply;
                IconNames[ViewUpdateSpeedNormal         ] = IconName.Apply;
                IconNames[ViewUpdateSpeedLow            ] = IconName.Apply;
                IconNames[ViewUpdateSpeedPaused         ] = IconName.Apply;
                IconNames[ViewRefresh                   ] = IconName.Refresh;
                IconNames[ViewChangeTheme               ] = IconName.None;
                IconNames[ViewZoom                      ] = IconName.Zoom;
                IconNames[ViewZoomIn                    ] = IconName.ZoomPlus;
                IconNames[ViewZoomOut                   ] = IconName.ZoomMinus;
                IconNames[ViewZoomReset                 ] = IconName.ZoomRestore;
                IconNames[ViewFullScreen                ] = IconName.WindowFullscreen;
                // help menu
                IconNames[HelpSoftwareManual            ] = IconName.ModelManual;
                IconNames[HelpShowNoticeScreen          ] = IconName.Info;
                IconNames[HelpShowWarningsScreen        ] = IconName.Warning;
                IconNames[HelpAbout                     ] = IconName.None;
                // toolbar (these should use corresponding values defined for file-/view-menu items above)
                IconNames[ToolbarNewSession             ] = IconNames[FileNewSession      ];
                IconNames[ToolbarOpenSession            ] = IconNames[FileOpenSession     ];
                IconNames[ToolbarCloseSession           ] = IconNames[FileCloseSession    ];
                IconNames[ToolbarSaveSession            ] = IconNames[FileSaveSession     ];
                IconNames[ToolbarModelingSettings       ] = IconNames[ViewModelingSettings];
                IconNames[ToolbarStartModeling          ] = IconNames[FileStartModeling   ];
                IconNames[ToolbarStopModeling           ] = IconNames[FileStopModeling    ];
                IconNames[ToolbarZoomOut                ] = IconNames[ViewZoomOut         ];
                IconNames[ToolbarZoomIn                 ] = IconNames[ViewZoomIn          ];
                IconNames[ToolbarZoomReset              ] = IconNames[ViewZoomReset       ];
                IconNames[ToolbarToggleFullScreen       ] = IconNames[ViewFullScreen      ];
                // statusbar
                IconNames[StatusBarModelStatus          ] = IconName.None;
                IconNames[StatusBarZoomLevel            ] = IconName.Zoom;
                IconNames[StatusBarBuildDate            ] = IconName.None;

                // declare refresh intervals for the "Update Speed" sub-menu
                RefreshIntervals[ViewUpdateSpeedRealtime] = SessionPanel.RefreshIntervalRealtime;
                RefreshIntervals[ViewUpdateSpeedHigh    ] = SessionPanel.RefreshIntervalHigh;
                RefreshIntervals[ViewUpdateSpeedNormal  ] = SessionPanel.RefreshIntervalNormal;
                RefreshIntervals[ViewUpdateSpeedLow     ] = SessionPanel.RefreshIntervalLow;
                RefreshIntervals[ViewUpdateSpeedPaused  ] = SessionPanel.RefreshIntervalPaused;

                // declare refresh intervals for the "Zoom" sub-menu
                // view menu
                ZoomIncrements[ViewZoomIn               ] =  ZoomIncrementBig;
                ZoomIncrements[ViewZoomOut              ] = -ZoomIncrementBig;
                ZoomIncrements[ViewZoomReset            ] =  float.NaN;
                // toolbar (these should use corresponding values defined for view-menu items above)
                ZoomIncrements[ToolbarZoomIn            ] = ZoomIncrements[ViewZoomIn   ];
                ZoomIncrements[ToolbarZoomOut           ] = ZoomIncrements[ViewZoomOut  ];
                ZoomIncrements[ToolbarZoomReset         ] = ZoomIncrements[ViewZoomReset];

                // declare refresh intervals for the "Change Theme" sub-menu
                // **NOTES:
                //  - defining themes is a special case, since it requires updating the Themes
                //    dictionary as well as the Tooltips and IconNames dictionaries
                //  - unlike for all other menu items, themes do not have predefined name constants;
                //    instead, menu item names for each theme are derived from the theme itself
                foreach (Theme theme in Theme.AllThemes)
                {
                    string themeName = theme.Name + ThemeSuffix;

                    if (theme.CategoryName != null)
                    {   // - if a theme belongs to a category (and has not already been defined),
                        //   we start by adding it in as a "null" entry
                        // - when the "menu builder" runs, it'll recognize a theme category from the
                        //   "Themes" dictionary and create a menu sub-entry for nested themes
                        // - we also want to add "null" for tooltips and "next" for icon
                        string categoryName = theme.CategoryName + ThemeCategorySuffix;
                        if (!Themes.ContainsKey(categoryName))
                        {
                            Themes   [categoryName] = null;
                            Tooltips [categoryName] = null;
                            IconNames[categoryName] = IconName.Next;
                        }
                    }

                    // add theme reference, tooltip, and icon info for this theme
                    Themes   [themeName] = theme;
                    Tooltips [themeName] = "Changes the CAFE Model GUI to use the " +
                                           $"{(theme.IsDefault ? "default " : string.Empty)}\"{theme.DisplayName} Theme\".";
                    IconNames[themeName] = IconName.Apply;
                }
            }

            #endregion

            #region <<< Menu Item names >>>

            // file menu
            public const string FileNewSession = "FileNewSession";
            public const string FileOpenSession = "FileOpenSession";
            public const string FileCloseSession = "FileCloseSession";
            public const string FileSaveSession = "FileSaveSession";
            public const string FileSaveSessionAs = "FileSaveSessionAs";
            public const string FileStartModeling = "FileStartModeling";
            public const string FileStopModeling = "FileStopModeling";
            public const string FileExit = "FileExit";
            // view menu
            public const string ViewModelingSettings = "ViewModelingSettings";
            public const string ViewInputFileLocation = "ViewInputFileLocation";
            public const string ViewOutputLocation = "ViewOutputLocation";
            public const string ViewArgonneSimulationResults = "ViewArgonneSimulationResults";
            public const string ViewUpdateSpeed = "ViewUpdateSpeed";
            public const string ViewUpdateSpeedRealtime = "ViewUpdateSpeedRealtime";
            public const string ViewUpdateSpeedHigh = "ViewUpdateSpeedHigh";
            public const string ViewUpdateSpeedNormal = "ViewUpdateSpeedNormal";
            public const string ViewUpdateSpeedLow = "ViewUpdateSpeedLow";
            public const string ViewUpdateSpeedPaused = "ViewUpdateSpeedPaused";
            public const string ViewRefresh = "ViewRefresh";
            public const string ViewChangeTheme = "ViewChangeTheme";
            public const string ViewZoom = "ViewZoom";
            public const string ViewZoomIn = "ViewZoomIn";
            public const string ViewZoomOut = "ViewZoomOut";
            public const string ViewZoomReset = "ViewZoomReset";
            public const string ViewFullScreen = "ViewFullScreen";
            // help menu
            public const string HelpSoftwareManual = "HelpSoftwareManual";
            public const string HelpShowNoticeScreen = "HelpShowNoticeScreen";
            public const string HelpShowWarningsScreen = "HelpShowWarningsScreen";
            public const string HelpAbout = "HelpAbout";
            // toolbar
            public const string ToolbarNewSession = "ToolbarNewSession";
            public const string ToolbarOpenSession = "ToolbarOpenSession";
            public const string ToolbarCloseSession = "ToolbarCloseSession";
            public const string ToolbarSaveSession = "ToolbarSaveSession";
            public const string ToolbarModelingSettings = "ToolbarModelingSettings";
            public const string ToolbarStartModeling = "ToolbarStartModeling";
            public const string ToolbarStopModeling = "ToolbarStopModeling";
            public const string ToolbarZoomOut = "ToolbarZoomOut";
            public const string ToolbarZoomIn = "ToolbarZoomIn";
            public const string ToolbarZoomReset = "ToolbarZoomReset";
            public const string ToolbarToggleFullScreen = "ToolbarToggleFullScreen";
            // statusbar
            public const string StatusBarModelStatus = "StatusBarStatus";
            public const string StatusBarZoomLevel = "StatusBarZoomLevel";
            public const string StatusBarBuildDate = "StatusBarBuildDate";

            // misc constants
            public const string ThemeSuffix = "Theme";
            public const string ThemeCategorySuffix = "ThemeCategory";

            #endregion

            // dictionaries for looking up dynamic properites for each menu item by its name
            public static readonly Dictionary<string, string>   Tooltips = new Dictionary<string, string>();
            public static readonly Dictionary<string, IconName> IconNames = new Dictionary<string, IconName>();
            public static readonly Dictionary<string, int>      RefreshIntervals = new Dictionary<string, int>();
            public static readonly Dictionary<string, float>    ZoomIncrements = new Dictionary<string, float>();
            public static readonly Dictionary<string, Theme>    Themes = new Dictionary<string, Theme>();

        }

        #endregion

        #region <<< Ctors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="Cafe2e"/> class.
        /// </summary>
        public Cafe2e()
        {
            this.InitializeComponent();
            this.InitializeOther();
        }

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true, if managed resources should be disposed; false, otherwise.</param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }

        #endregion

        #region <<< Methods >>>

        #region < Initialization methods >

        void InitializeComponent()
        {
            this.ux_mainMenu = new MenuStrip();
            this.ux_fileMenu = new ToolStripMenuItem();
            this.ux_viewMenu = new ToolStripMenuItem();
            this.ux_helpMenu = new ToolStripMenuItem();
            this.ux_mainToolbar = new ToolStrip();
            this.ux_statusBar = new StatusStrip();
            this.ux_welcomeLabel = new Label();
            this.ux_mainMenu.SuspendLayout();
            this.ux_mainToolbar.SuspendLayout();
            this.ux_statusBar.SuspendLayout();
            this.SuspendLayout();
            // 
            // ux_fileMenu
            // 
            this.ux_fileMenu.AccessibleName = "File Menu";
            this.ux_fileMenu.ImageScaling = ToolStripItemImageScaling.None;
            this.ux_fileMenu.Name = "ux_fileMenu";
            this.ux_fileMenu.Text = "&File";
            // 
            // ux_viewMenu
            // 
            this.ux_viewMenu.AccessibleName = "View Menu";
            this.ux_viewMenu.ImageScaling = ToolStripItemImageScaling.None;
            this.ux_viewMenu.Name = "ux_viewMenu";
            this.ux_viewMenu.Text = "&View";
            // 
            // ux_helpMenu
            // 
            this.ux_helpMenu.AccessibleName = "Help Menu";
            this.ux_helpMenu.ImageScaling = ToolStripItemImageScaling.None;
            this.ux_helpMenu.Name = "ux_helpMenu";
            this.ux_helpMenu.Text = "&Help";
            // 
            // ux_mainMenu
            // 
            this.ux_mainMenu.AccessibleName = "Main Menu";
            this.ux_mainMenu.Items.AddRange(new ToolStripItem[] {this.ux_fileMenu, this.ux_viewMenu, this.ux_helpMenu});
            this.ux_mainMenu.Location = new Point(0, 0);
            this.ux_mainMenu.Name = "ux_mainMenu";
            this.ux_mainMenu.Padding = new Padding(2);
            this.ux_mainMenu.Stretch = true;
            this.ux_mainMenu.TabIndex = 0;
            this.ux_mainMenu.Text = "Main Menu";
            // 
            // ux_mainToolbar
            // 
            this.ux_mainToolbar.AccessibleName = "Main Toolbar";
            this.ux_mainToolbar.GripStyle = ToolStripGripStyle.Hidden;
            this.ux_mainToolbar.Name = "ux_mainToolbar";
            this.ux_mainToolbar.Padding = new Padding(2);
            this.ux_mainToolbar.Stretch = true;
            this.ux_mainToolbar.TabIndex = 1;
            this.ux_mainToolbar.Text = "Main Toolbar";
            // 
            // ux_statusBar
            // 
            this.ux_statusBar.AccessibleName = "Status Bar";
            this.ux_statusBar.Name = "ux_statusBar";
            this.ux_statusBar.Padding = new Padding(1, 0, 18, 0);
            this.ux_statusBar.RenderMode = ToolStripRenderMode.ManagerRenderMode;
            this.ux_statusBar.TabIndex = 2;
            this.ux_statusBar.Text = "Status Bar";
            // 
            // ux_welcomeLabel
            // 
            this.ux_welcomeLabel.BorderStyle = BorderStyle.FixedSingle;
            this.ux_welcomeLabel.Dock = DockStyle.Fill;
            this.ux_welcomeLabel.Margin = new Padding(4, 0, 4, 0);
            this.ux_welcomeLabel.Name = "ux_welcomeLabel";
            this.ux_welcomeLabel.Padding = new Padding(80);
            this.ux_welcomeLabel.TabIndex = 3;
            this.ux_welcomeLabel.Text = "To begin using the model, please create a New Session, or Open an existing one.";
            this.ux_welcomeLabel.TextAlign = ContentAlignment.MiddleCenter;
            // 
            // Cafe2e
            // 
            this.AutoScaleMode = AutoScaleMode.None;
            this.ClientSize = new Size(712, 449);
            this.Controls.Add(this.ux_welcomeLabel);
            this.Controls.Add(this.ux_statusBar);
            this.Controls.Add(this.ux_mainToolbar);
            this.Controls.Add(this.ux_mainMenu);
            this.Font = new Font("Microsoft Sans Serif", 8.25F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
            this.MainMenuStrip = this.ux_mainMenu;
            this.Margin = new Padding(4);
            this.Name = "Cafe2e";
            this.Text = "CAFE Model";
            this.ux_mainMenu.ResumeLayout(true);
            this.ux_statusBar.ResumeLayout(true);
            this.ux_mainToolbar.ResumeLayout(true);
            this.ResumeLayout(true);
        }
        void InitializeOther()
        {
            this.Size = GlobalSettings.PreferredSize;
            this.Icon = Program.GetAppIcon();
            this.IsFullScreen = false;

            this.InitMenusAndToolbars();
            this.InitSessionPanel();
            this.InitThemedElements();

            // set the initial zoom level and update speed
            // setting zoom also refreshes GUI fonts and menu/toolbar icons
            this.SetZoom(InitialZoomFactor);
            this.SetUpdateSpeed(SessionPanel.RefreshIntervalNormal);
        }
        // menu, toolbar, and statusbar initialization methods
        void InitMenusAndToolbars()
        {
            this.ux_mainMenu.SuspendLayout();
            this.ux_mainToolbar.SuspendLayout();
            this.ux_statusBar.SuspendLayout();
            //
            this.InitMainMenu();
            this.InitToolbar();
            this.InitStatusbar();
            //
            this.ux_statusBar.ResumeLayout(true);
            this.ux_mainToolbar.ResumeLayout(true);
            this.ux_mainMenu.ResumeLayout(true);
        }
        void InitMainMenu()
        {
            // init file menu
            this.ux_fileMenu.DropDownItems.AddRange(new ToolStripItem[] {
                this.InitMenuItem(MenuItems.FileNewSession, "&New Session", Keys.Control | Keys.N, this.FileNewSession_Click),
                this.InitMenuItem(MenuItems.FileOpenSession, "&Open Session", Keys.Control | Keys.O, this.FileOpenSession_Click),
                this.InitMenuItem(MenuItems.FileCloseSession, "&Close Session", Keys.Control | Keys.F4, this.FileCloseSession_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.FileSaveSession, "&Save Session", Keys.Control | Keys.S, this.FileSaveSession_Click),
                this.InitMenuItem(MenuItems.FileSaveSessionAs, "Save Session &As...", Keys.None, this.FileSaveSessionAs_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.FileStartModeling, "Start &Modeling", Keys.Control | Keys.M, this.FileStartModeling_Click),
                this.InitMenuItem(MenuItems.FileStopModeling, "Sto&p Modeling", Keys.Control | Keys.Shift | Keys.M, this.FileStopModeling_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.FileExit, "E&xit", Keys.Alt | Keys.F4, this.FileExit_Click)
            });

            // init view menu
            this.ux_viewMenu.DropDownItems.AddRange(new ToolStripItem[] {
                this.InitMenuItem(MenuItems.ViewModelingSettings, "Modeling Se&ttings", Keys.Control | Keys.T, this.ViewModelingSettings_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.ViewInputFileLocation, "&Input File Location(s)", Keys.None, this.ViewInputFileLocation_Click),
                this.InitMenuItem(MenuItems.ViewOutputLocation, "&Output Location", Keys.None, this.ViewOutputLocation_Click),
                this.InitMenuItem(MenuItems.ViewArgonneSimulationResults, "&Argonne Simulation Results", Keys.None, this.ViewArgonneSimulationResults_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.ViewUpdateSpeed, "Update &Speed", Keys.None, null,
                    new ToolStripItem[] {
                        this.InitMenuItem(MenuItems.ViewUpdateSpeedRealtime, "&Realtime", Keys.None, this.ViewUpdateSpeed_Click, displayStyle: ToolStripItemDisplayStyle.Text),
                        this.InitMenuItem(MenuItems.ViewUpdateSpeedHigh, "&High", Keys.None, this.ViewUpdateSpeed_Click, displayStyle: ToolStripItemDisplayStyle.Text),
                        this.InitMenuItem(MenuItems.ViewUpdateSpeedNormal, "&Normal", Keys.None, this.ViewUpdateSpeed_Click, displayStyle: ToolStripItemDisplayStyle.Text),
                        this.InitMenuItem(MenuItems.ViewUpdateSpeedLow, "&Low", Keys.None, this.ViewUpdateSpeed_Click, displayStyle: ToolStripItemDisplayStyle.Text),
                        new ToolStripSeparator(),
                        this.InitMenuItem(MenuItems.ViewUpdateSpeedPaused, "&Paused", Keys.None, this.ViewUpdateSpeed_Click, displayStyle: ToolStripItemDisplayStyle.Text),
                    }),
                this.InitMenuItem(MenuItems.ViewRefresh, "&Refresh", Keys.F5, this.ViewRefresh_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.ViewChangeTheme, "Change T&heme", Keys.None, null),
                this.InitMenuItem(MenuItems.ViewZoom, "&Zoom", Keys.None, null,
                    new ToolStripItem[] {
                        this.InitMenuItem(MenuItems.ViewZoomIn, "Zoom &In", Keys.Control | Keys.Oemplus, this.ViewZoom_Click, shortcutKeyDisplayString: "Ctrl++"),
                        this.InitMenuItem(MenuItems.ViewZoomOut, "Zoom &Out", Keys.Control | Keys.OemMinus, this.ViewZoom_Click, shortcutKeyDisplayString: "Ctrl+-"),
                        new ToolStripSeparator(),
                        this.InitMenuItem(MenuItems.ViewZoomReset, "&Reset", Keys.Control | Keys.D0, this.ViewZoom_Click)
                    })
            });
            // populate themes sub-menu
            bool addChangeThemeMenuSeparator = true;
            foreach (var boundItem in MenuItems.Themes)
            {
                string name = boundItem.Key;
                Theme theme = boundItem.Value;
                ToolStripMenuItem parentItem = (ToolStripMenuItem)this._boundMenus[MenuItems.ViewChangeTheme];
                ToolStripMenuItem menuItem = null;

                if (theme == null)
                {   // if theme is null, this item is a "theme category" entry
                    menuItem = this.InitMenuItem(name, name.Substring(0, name.Length - MenuItems.ThemeCategorySuffix.Length), Keys.None, null);
                }
                else
                {   // otherwise, this is a "theme" entry
                    menuItem = this.InitMenuItem(name, theme.DisplayName, Keys.None, this.ViewChangeTheme_Click, displayStyle: ToolStripItemDisplayStyle.Text);
                    if (theme.CategoryName != null)
                    {   // if the theme defines a category, then add it under the category's menu item
                        // (instead of under the main ViewChangeTheme menu item)
                        parentItem = (ToolStripMenuItem)this._boundMenus[theme.CategoryName + MenuItems.ThemeCategorySuffix];
                    }
                }

                // add separator before first non-default theme
                if (addChangeThemeMenuSeparator && theme != null && !theme.IsDefault)
                {
                    parentItem.DropDownItems.Add(new ToolStripSeparator());
                    addChangeThemeMenuSeparator = false;
                }

                // finally, add theme or category to the appropriate parent item
                parentItem.DropDownItems.Add(menuItem);
            }

            // init help menu
            this.ux_helpMenu.DropDownItems.AddRange(new ToolStripItem[] {
                this.InitMenuItem(MenuItems.HelpSoftwareManual, "CAFE Model Software &Manual", Keys.F1, this.HelpSoftwareManual_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.HelpShowNoticeScreen, "Show &Notice Screen", Keys.None, this.HelpShowNoticeScreen_Click),
                this.InitMenuItem(MenuItems.HelpShowWarningsScreen, "Show &Warnings Screen", Keys.None, this.HelpShowWarningsScreen_Click),
                new ToolStripSeparator(),
                this.InitMenuItem(MenuItems.HelpAbout, "&About CAFE Model", Keys.F12, this.HelpAbout_Click)
            });
        }
        void InitToolbar()
        {
            this.ux_mainToolbar.Items.AddRange(new ToolStripItem[] {
                // left-aligned elements
                this.InitToolbarItem(MenuItems.ToolbarNewSession, "New Session", this.FileNewSession_Click),
                this.InitToolbarItem(MenuItems.ToolbarOpenSession, "Open Session", this.FileOpenSession_Click),
                this.InitToolbarItem(MenuItems.ToolbarCloseSession, "Close Session", this.FileCloseSession_Click),
                this.InitToolbarItem(MenuItems.ToolbarSaveSession, "Save Session", this.FileSaveSession_Click),
                new ToolStripSeparator(),
                this.InitToolbarItem(MenuItems.ToolbarModelingSettings, "Modeling Settings", this.ViewModelingSettings_Click),
                new ToolStripSeparator(),
                this.InitToolbarItem(MenuItems.ToolbarStartModeling, "Start Modeling", this.FileStartModeling_Click),
                this.InitToolbarItem(MenuItems.ToolbarStopModeling, "Stop Modeling", this.FileStopModeling_Click),
                //
                // right-aligned elements (should be added in reverse order)
                this.InitToolbarItem(MenuItems.ToolbarZoomReset, "Reset Zoom", this.ViewZoom_Click, false),
                this.InitToolbarItem(MenuItems.ToolbarZoomIn, "Zoom In", this.ViewZoom_Click, false),
                this.InitToolbarItem(MenuItems.ToolbarZoomOut, "Zoom Out", this.ViewZoom_Click, false)
            });
        }
        void InitStatusbar()
        {
            this.ux_statusBar.Items.AddRange(new ToolStripItem[] {
                this.InitStatusbarItem(MenuItems.StatusBarModelStatus, "Ready", true),
                new ToolStripSeparator(),
                this.InitStatusbarItem(MenuItems.StatusBarZoomLevel, "100 %"),
                new ToolStripSeparator(),
                this.InitStatusbarItem(MenuItems.StatusBarBuildDate, ApplicationInfo.ProductBuildDate)
            });
        }
        ToolStripMenuItem InitMenuItem(string name, string text, Keys shortcutKeys, EventHandler onClick,
            ToolStripItem[] dropDownItems = null, string shortcutKeyDisplayString = null,
            ToolStripItemDisplayStyle displayStyle = ToolStripItemDisplayStyle.ImageAndText)
        {
            ToolStripMenuItem item = new ToolStripMenuItem()
            {
                DisplayStyle = displayStyle,
                ImageScaling = ToolStripItemImageScaling.SizeToFit,
                Name = name,
                ShortcutKeys = shortcutKeys,
                ShortcutKeyDisplayString = shortcutKeyDisplayString,
                Text = text,
            };

            // add in drop-down items if specified
            if (dropDownItems != null)
            {
                item.DropDownItems.AddRange(dropDownItems);
            }

            // perform additional initialization common to both menu and toolbar items
            // (ex: register events and add to dictionary)
            this.InitMenuAndToolbarItemCommon(name, item, onClick);

            return item;
        }
        ToolStripButton InitToolbarItem(string name, string text, EventHandler onClick, bool leftAlign = true)
        {
            ToolStripButton item = new ToolStripButton()
            {
                Alignment = (leftAlign) ? ToolStripItemAlignment.Left : ToolStripItemAlignment.Right,
                DisplayStyle = ToolStripItemDisplayStyle.Image,
                ImageScaling = ToolStripItemImageScaling.SizeToFit,
                ImageTransparentColor = Color.Magenta,
                Name = name,
                Text = text
            };

            // perform additional initialization common to both menu and toolbar items
            // (ex: register events and add to dictionary)
            this.InitMenuAndToolbarItemCommon(name, item, onClick);

            return item;
        }
        ToolStripStatusLabel InitStatusbarItem(string name, string text, bool spring = false)
        {
            ToolStripStatusLabel item = new ToolStripStatusLabel()
            {
                DisplayStyle = ToolStripItemDisplayStyle.ImageAndText,
                ImageScaling = ToolStripItemImageScaling.SizeToFit,
                ImageTransparentColor = Color.Magenta,
                Name = name,
                Spring = spring,
                Text = text,
                TextAlign = ContentAlignment.MiddleLeft
            };

            // perform additional initialization common to both menu and toolbar items
            // (ex: register events and add to dictionary)
            this.InitMenuAndToolbarItemCommon(name, item, null);

            return item;
        }
        void InitMenuAndToolbarItemCommon(string name, ToolStripItem item, EventHandler onClick)
        {
            // register custom click handler (to respond to menu click action)
            if (onClick != null) { item.Click += onClick; }

            // register additional events for handling tooltips
            item.MouseEnter += this.ToolStripItemTooltips_MouseEnter;
            item.MouseLeave += this.ToolStripItemTooltips_MouseLeave;
            item.EnabledChanged += this.ToolStripItemTooltips_EnabledChanged;

            // add new item to dictionary for easy lookups
            this._boundMenus.Add(name, item);
        }
        // all other initialization methods
        void InitSessionPanel()
        {
            this.ux_sessionPanel.Font = this.Font;
            this.ux_sessionPanel.BorderStyle = this.ux_welcomeLabel.BorderStyle;
            this.ux_sessionPanel.Dock = DockStyle.Fill;
            this.ux_sessionPanel.Visible = false;
            this.Controls.Add(this.ux_sessionPanel);
            this.ux_sessionPanel.BringToFront();
            //
            this.ux_sessionPanel.SessionUpdating += this.SessionPanel_SessionUpdating;
            this.ux_sessionPanel.SessionNameChanged += this.SessionPanel_SessionNameChanged;
        }
        void InitThemedElements()
        {
            // initialize renderer for toolstrips (this only needs to happen once)
            this.ux_mainMenu   .Renderer = ThemedToolStripRenderer.DefaultRenderer;
            this.ux_mainToolbar.Renderer = ThemedToolStripRenderer.DefaultRenderer;
            this.ux_statusBar  .Renderer = ThemedToolStripRenderer.DefaultRenderer;

            // refresh the rest of themed elements (this needs to happen each time theme changes)
            this.RefreshThemedElements();
        }

        #endregion

        /// <inheritdoc/>
        protected override void OnLoad(EventArgs e)
        {
            this.RefreshTitleBar();
            this.RefreshMenuItemEnabledState();
            this.EnableDragDrop();
            this.Focus();

            base.OnLoad(e);
        }

        /// <inheritdoc/>
        protected override void OnClosing(CancelEventArgs e)
        {
            if (!this.CloseSession(false))
            {
                e.Cancel = true;
            }
            //
            base.OnClosing(e);
        }

        /// <inheritdoc/>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (ModifierKeys == Keys.Control)
            {
                this.ChangeZoom((e.Delta < 0) ? -ZoomIncrementSmall : ZoomIncrementSmall);
            }
            //
            base.OnMouseWheel(e);
        }

        #region < File menu event handlers >

        void FileNewSession_Click(object sender, EventArgs e)
        {
            this.NewSession();
        }
        void FileOpenSession_Click(object sender, EventArgs e)
        {
            this.OpenSession(null, false);
        }
        void FileCloseSession_Click(object sender, EventArgs e)
        {
            this.CloseSession(false);
        }
        void FileSaveSession_Click(object sender, EventArgs e)
        {
            this.ux_sessionPanel.SaveSession();
        }
        void FileSaveSessionAs_Click(object sender, EventArgs e)
        {
            this.DisableDragDrop();
            this.ux_sessionPanel.SaveSessionAs();
            this.EnableDragDrop();
        }
        void FileStartModeling_Click(object sender, EventArgs e)
        {
            this.ux_sessionPanel.StartModeling();
        }
        void FileStopModeling_Click(object sender, EventArgs e)
        {
            this.ux_sessionPanel.StopModeling();
        }
        void FileExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        #endregion

        #region < View menu event handlers >

        void ViewModelingSettings_Click(object sender, EventArgs e)
        {
            if (!this.ux_sessionPanel.IsOpen) { return; }
            //
            this.DisableDragDrop();
            ManageModelingSettings dlg = new ManageModelingSettings(this.ux_sessionPanel);
            dlg.ShowDialog();
            this.RefreshMenuItemEnabledState();
            this.EnableDragDrop();
        }
        void ViewInputFileLocation_Click(object sender, EventArgs e)
        {
            IOSettings settings = this.ux_sessionPanel.Settings.IOSettings;
            string[] paths = new string[] {
                                              settings.MarketDataFile,
                                              settings.TechnologiesFile,
                                              settings.ParametersFile,
                                              settings.ScenariosFile
                                          };
            List<string> uniquePaths = new List<string>();

            // determine unique paths
            for (int i = 0; i < paths.Length; i++)
            {
                if (paths[i] != "")
                {
                    string path = Path.GetDirectoryName(paths[i]);
                    if (!uniquePaths.Contains(path))
                    {
                        uniquePaths.Add(path);
                    }
                }
            }

            // open the locations
            this.ExplorePaths(uniquePaths);
        }
        void ViewOutputLocation_Click(object sender, EventArgs e)
        {
            this.ExplorePaths(this.ux_sessionPanel.Settings.IOSettings.BaseOutputPath);
        }
        void ViewArgonneSimulationResults_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog
            {
                Description = "Please select the location where you would like to save the Vehicle Simulation Database results.",
                ShowNewFolderButton = true
            };

            if (dlg.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    this.Visible = false;
                    string anlPath = dlg.SelectedPath;
                    if (Program.ExtractSimulationDB(anlPath))
                    {
                        this.Visible = true;
                        this.ExplorePaths(anlPath);
                    }
                }
                finally
                {
                    this.Visible = true;
                }
            }
        }
        void ViewUpdateSpeed_Click(object sender, EventArgs e)
        {
            this.SetUpdateSpeed((ToolStripItem)sender);
        }
        void ViewRefresh_Click(object sender, EventArgs e)
        {
            // refresh the session
            if (this.ux_sessionPanel.IsOpen)
            {
                this.ux_sessionPanel.RefreshSession();
            }
        }
        void ViewChangeTheme_Click(object sender, EventArgs e)
        {
            ToolStripItem item = (ToolStripItem)sender;
            if (MenuItems.Themes.TryGetValue(item.Name, out Theme newTheme))
            {
                if (Theme.CurrentTheme != newTheme)
                {
                    Theme.SetCurrentTheme(newTheme);
                    this.RefreshThemedElements();
                }
            }
        }
        void ViewZoom_Click(object sender, EventArgs e)
        {
            ToolStripItem item = (ToolStripItem)sender;
            if (MenuItems.ZoomIncrements.TryGetValue(item.Name, out float zoomIncrement))
            {
                this.ChangeZoom(zoomIncrement);
            }
        }

        #endregion

        #region < Help menu event handlers >

        void HelpSoftwareManual_Click(object sender, EventArgs e)
        {
            string path = ApplicationInfo.UserResourcesPath + "\\Software Manual\\v." + ApplicationInfo.ProductVersion;

            //---------------------------------------------------------------------------
            // generate html manual before displaying it
            //
            string zipFilePath = path + "\\SoftwareManual.zip";
            string extractPath = path + "\\";
            if (!File.Exists(path + "\\CAFE Model Software Manual.htm") || !Directory.Exists(path + "\\images"))
            {
                // clean up all contents of the "software manual" directory
                if (Directory.Exists(path)) { Directory.Delete(path, true); }
                Directory.CreateDirectory(path);

                // copy zip file first
                using (Stream stream = AppResources.GetResourceStream(EmbeddedResources.SoftwareManual))
                {
                    byte[] buffer = new byte[stream.Length];
                    stream.Read(buffer, 0, (int)stream.Length);
                    //
                    using (FileStream fs = new FileStream(zipFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        fs.Write(buffer, 0, buffer.Length);
                    }
                }
                // extract the zip file
                ZipFile.ExtractToDirectory(zipFilePath, extractPath);
                File.Delete(zipFilePath);
            }

            //---------------------------------------------------------------------------
            // open html manual in default browser first
            // if that fails, retry using internet explorer, in case default browser is
            // blocked by group policy
            //
            try { Process.Start(path + "\\CAFE Model Software Manual.htm"); }
            catch
            {
                try { Process.Start("iexplore", path + "\\CAFE Model Software Manual.htm"); }
                catch (Exception ex)
                {
                    MessageBox.Show("Could not open web browser.\n\n" + ex.Message, "CAFE Model Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
        void HelpShowNoticeScreen_Click(object sender, EventArgs e)
        {
            ResourceViewer.ShowNoticeDialog();
        }
        void HelpShowWarningsScreen_Click(object sender, EventArgs e)
        {
            ResourceViewer.ShowWarningsDialog(false);
        }
        void HelpAbout_Click(object sender, EventArgs e)
        {
            ResourceViewer.ShowAboutDialog();
        }

        #endregion

        #region < Tooltips helper event handlers for menu and toolbar items >

        void ToolStripItemTooltips_MouseEnter(object sender, EventArgs e)
        {
            var item = (ToolStripItem)sender;
            if (MenuItems.Tooltips.TryGetValue(item.Name, out string tooltip) && tooltip != null)
            {
                if (!item.Enabled)
                {
                    tooltip = "When enabled, " + (char)(tooltip[0] + 32) + tooltip.Remove(0, 1);
                }
                this._cachedModelStatus = this.ModelStatus;
                this.SetStatus(tooltip);
            }
            else
            {
                this.ToolStripItemTooltips_MouseLeave(sender, e);
            }
        }
        void ToolStripItemTooltips_MouseLeave(object sender, EventArgs e)
        {
            if (this._cachedModelStatus != null)
            {
                var item = (ToolStripItem)sender;
                if (MenuItems.Tooltips.TryGetValue(item.Name, out string tooltip))
                {
                    if (!item.Enabled)
                    {
                        tooltip = "When enabled, " + (char)(tooltip[0] + 32) + tooltip.Remove(0, 1);
                    }
                    if (tooltip == this.ModelStatus)
                    {   // only reset the status bar with a cached value if it still displays the tooltip for the original
                        // menu item (the one that tiggered the tooltip)
                        // if the status bar was updated externally (while the mouse was still over the menu item), we do
                        // not want to overwrite the newer status with an obsolete cached value
                        this.SetStatus(this._cachedModelStatus);
                    }
                }
                // always clear cached status after the mouse is not longer over the menu item
                this._cachedModelStatus = null;
            }
        }
        void ToolStripItemTooltips_EnabledChanged(object sender, EventArgs e)
        {
            if (sender is ToolStripItem item && !item.Enabled)
            {
                this.ToolStripItemTooltips_MouseLeave(sender, e);
            }
        }

        #endregion

        //----- session management helper methods -----
        bool NewSession()
        {
            // close previous session (return false if could not close last session)
            if (!this.CloseSession(true)) { return false; }

            // create new session
            this.ux_sessionPanel.CreateNewSession();
            this.RefreshMenuItemEnabledState();
            this.ViewModelingSettings_Click(this, EventArgs.Empty);
            //
            return true;
        }
        /// <summary>
        /// Opens a new session with the specified filename, or prompts a user to select a file if filename is null.
        /// </summary>
        /// <param name="filename">The name of the file to load, or null to display the <see cref="OpenFileDialog"/>.</param>
        /// <param name="silent">true, to silently accept all prompts; false, otherwise.</param>
        /// <returns>true, if the session was opened; false, otherwise.</returns>
        bool OpenSession(string filename, bool silent)
        {
            // close previous session (return false if could not close last session)
            if (!this.CloseSession(!silent)) { return false; }

            // open an existing session
            this.DisableDragDrop();
            bool opened = false;

            if (filename == null || filename == "")
            {   // prompt for session file to open
                OpenFileDialog dlg = new OpenFileDialog()
                {
                    Filter = "All Files (*.*)|*.*|CAFE Model Session Data (*.cmsd)|*.cmsd",
                    FilterIndex = 2,
                    Title = "Please select the session file you would like to open:"
                };
                if (dlg.ShowDialog() == DialogResult.OK) { filename = dlg.FileName; }
                else { filename = null; }
            }

            if (filename != null)
            {   // initialize a new session ...
                try
                {
                    this.ux_sessionPanel.OpenSession(filename, silent);
                    if (this.ux_sessionPanel.IsOpen)
                    {
                        this.RefreshMenuItemEnabledState();
                        this.SetUpdateSpeed(this.ux_sessionPanel.RefreshInterval);
                        this.ux_sessionPanel.RefreshSession();
                        this.ux_sessionPanel.Visible = true;

                        // special case when reporting or log levels are "disabled" due to older sessions
                        var ioSettings = this.ux_sessionPanel.Settings?.IOSettings;
                        if (ioSettings != null && (ioSettings.LogLevel.IsDisabled()
                                                || ioSettings.ReportingLevel.IsDisabled())) {
                            this.ViewModelingSettings_Click(this, EventArgs.Empty);
                        }

                        opened = true;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "CAFE Model Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

            this.EnableDragDrop();
            return opened;
        }
        bool CloseSession(bool confirmClose)
        {
            if (this.ux_sessionPanel.CloseSession(confirmClose))
            {
                this.ux_sessionPanel.Visible = false;
                this.RefreshMenuItemEnabledState();
                return true;
            }
            return false;
        }
        // event handlers from the session-panel
        void SessionPanel_SessionNameChanged(object sender, EventArgs e)
        {
            this.RefreshTitleBar();
        }
        void SessionPanel_SessionUpdating(object sender, EventArgs e)
        {
            this.SetStatus(this.ux_sessionPanel.Status);
            this.RefreshMenuItemEnabledState();
        }

        //----- GUI fullscreen and zooming helper methods -----
        // public zoom methods (to allow zoom controling from outside of GUI)
        /// <summary>
        /// Increases the zoom factor to apply to the GUI's visual elements.
        /// </summary>
        public void ZoomIn() { this.ChangeZoom(ZoomIncrementBig); }
        /// <summary>
        /// Decreases the zoom factor to apply to the GUI's visual elements.
        /// </summary>
        public void ZoomOut() { this.ChangeZoom(-ZoomIncrementBig); }
        // zoom calc & set methods
        void ChangeZoom(float increment)
        {
            if (float.IsNaN(increment))
            {   // float.NaN is supplied when we want to reset the zoom factor to initial state
                this.SetZoom(InitialZoomFactor);
            }
            else
            {   // set new zoom factor closest to rate of increment
                // - when calculating new zoom level to set (as a result of zoom-in/out operation),
                //   we want to change the zoom in multiples of the specified "increment"
                // - the increment may be "big" (if triggered from a menu or a toolbar), or it may
                //   be "small" (if triggered by the ctrl + mouse wheel)
                // - the "big" and "small" increments are not multiples of each other, so if a user
                //   started to zoom with a mouse wheel, then continued with the toolbar, we want
                //   to align the new zoom factor to the "big" increment
                //   > that is, if "big" increment is 0.25, the new zoom factors should always be
                //     in the range of ..., 1, 1.25, 1.5, ...
                //   > but if the last zoom factor (from mouse wheel operation) ended in 1.1, the
                //     next zoom factor should be 1.0 (decrement) or 1.25 (increment) instead of
                //     simply 1.1 +/- 0.25
                // - hence, the new zoom factor is calculated as follows:
                //      newZoomFactor = Floor(oldZoomFactor / increment + 1) * increment
                //   this calculation aligns the zoom factor to the given increment
                // - however, floating point math can be imprecise (which in this case is chaotic,
                //   given the dependence on the "floor" function) -- thus, to evade this issue, we
                //   round the result of the calculation at some intermediate steps
                float newZoomFactor = (float)Math.Round(Math.Floor(Math.Round(this.ZoomFactor / increment + 1, 4)) * increment, 4);
                this.SetZoom(newZoomFactor);
            }
        }
        void SetZoom(float value)
        {
            value = Math.Max(MinZoomFactor, Math.Min(value, MaxZoomFactor));
            if (this.ZoomFactor != value)
            {
                this.ZoomFactor = value;

                // toggle enabled state of view menu and toolbar zoom controls
                foreach (var item in MenuItems.ZoomIncrements)
                {
                    this._boundMenus[item.Key].Enabled = this.ZoomFactor !=
                        (float.IsNaN(item.Value) ? InitialZoomFactor :
                         item.Value < 0 ? MinZoomFactor :
                         item.Value > 0 ? MaxZoomFactor : 0);
                }

                // refresh GUI scaling in response to zoom changing
                this.RefreshMainGUIFonts();
                this.RefreshMenuAndToolbarIcons();
                this.ux_sessionPanel.ZoomCharts(this.ScaledZoomFactor, true);
            }
        }

        //----- update-speed menu selection helper methods -----
        void SetUpdateSpeed(int refreshInterval)
        {
            foreach (var item in MenuItems.RefreshIntervals)
            {
                if (item.Value == refreshInterval)
                {
                    this.SetUpdateSpeed(this._boundMenus[item.Key]);
                    return;
                }
            }
            // if user-requested refresh interval is undefiend, default to "normal"
            this.SetUpdateSpeed(this._boundMenus[MenuItems.ViewUpdateSpeedNormal]);
        }
        void SetUpdateSpeed(ToolStripItem item)
        {
            if (MenuItems.RefreshIntervals.TryGetValue(item.Name, out int refreshInterval))
            {   // update session's refresh interval
                this.ux_sessionPanel.RefreshInterval = refreshInterval;

                // update check image
                // - first, clear "checked" for all update speed menu items
                foreach (string subItemName in MenuItems.RefreshIntervals.Keys)
                {
                    this._boundMenus[subItemName].DisplayStyle = ToolStripItemDisplayStyle.Text;
                }
                // - next, set "checked" for the currently selected item
                item.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;

                // refresh menu item enabled states in case setting the refresh interval on a
                // session triggered a save-require
                this.RefreshMenuItemEnabledState();
            }
        }

        //----- GUI visual elements refreshing helper methods -----
        // these methods are triggered after changing selected theme, setting new zoom factor,
        // toggling fullscreen, opening/closing session, or session status updating
        void RefreshThemedElements()
        {
            Theme theme = Theme.CurrentTheme;
            theme.ApplyTheme(this);
            this.ux_welcomeLabel.ForeColor = theme.ForeColor;
            this.ux_welcomeLabel.BackColor = theme.GridBackColor;

            // refresh fonts and icon images to use the selected theme's values
            if (this.ZoomFactor > 0)
            {   // if zoom factor has not been set yet (form is still initializing),
                // do not refresh fonts or icons (they will be refreshed when zoom is set)
                this.RefreshMainGUIFonts();
                this.RefreshMenuAndToolbarIcons();
            }

            // refresh check image in the "Change Theme >" menu
            this.RefreshChangeThemeMenu();
        }
        void RefreshChangeThemeMenu()
        {
            Theme theme = Theme.CurrentTheme;
            foreach (var item in MenuItems.Themes)
            {
                this._boundMenus[item.Key].DisplayStyle = (item.Value == theme) ?
                    ToolStripItemDisplayStyle.ImageAndText :
                    ToolStripItemDisplayStyle.Text;
                if (theme.CategoryName != null)
                {   // if selected theme is nested under a category menu item, select that category also
                    this._boundMenus[theme.CategoryName + MenuItems.ThemeCategorySuffix].DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
                }
            }
        }
        void RefreshMainGUIFonts()
        {
            this._menuFont?.Dispose();
            this._labelFont?.Dispose();

            float fontScale = (float)Math.Sqrt(this.ScaledZoomFactor);
            this._menuFont = new Font("Segoe UI", 10F * fontScale, this.Font.Style, this.Font.Unit, this.Font.GdiCharSet, this.Font.GdiVerticalFont);
            this._labelFont = new Font("Microsoft Sans Serif", 18F * fontScale, this.Font.Style, this.Font.Unit, this.Font.GdiCharSet, this.Font.GdiVerticalFont);

            this.ux_mainMenu.Font = this._menuFont;
            this.ux_mainToolbar.Font = this._menuFont;
            this.ux_statusBar.Font = this._menuFont;
            this.ux_welcomeLabel.Font = this._labelFont;
        }
        void RefreshMenuAndToolbarIcons()
        {
            IconTheme   iconTheme = Theme.CurrentTheme.IconTheme;
            float             szf = this.ScaledZoomFactor;
            int      menuIconSize = Math.Max(16, (int)Math.Min(24 + (szf - 1) *  8 - Math.Max(0.0, 1.5 - szf) * 4, 48));
            int   toolbarIconSize = Math.Max(22, (int)Math.Min(32 + (szf - 1) * 16 - Math.Max(0.0, 1.0 - szf) * 4, 64));
            int statusbarIconSize = Math.Max(12, (int)Math.Min(20 + (szf - 1) *  4 - Math.Max(0.0, 1.0 - szf) * 4, 32));

            // pause redraw & event processing
            this.BeginUpdate();

            // set image scaling
            this.ux_mainMenu   .ImageScalingSize = new Size(menuIconSize, menuIconSize);
            this.ux_mainToolbar.ImageScalingSize = new Size(toolbarIconSize, toolbarIconSize);
            this.ux_statusBar  .ImageScalingSize = new Size(statusbarIconSize, statusbarIconSize);

            // update main menu icons
            foreach (var item in this._boundMenus.Values)
            {
                if (MenuItems.IconNames.TryGetValue(item.Name, out IconName iconName) && iconName != IconName.None)
                {
                    int iconSize = (item.Owner == this.ux_mainToolbar) ? toolbarIconSize :
                        (item.Owner == this.ux_statusBar) ? statusbarIconSize : menuIconSize;
                    item.Image = Program.GetThemeIcon(iconTheme, iconSize, iconName);

                    // fullscreen toggle toolbar icon is a special case --
                    // - when the window is in fullscreen mode, we should use "window-restore" icon
                    // - otherwise, we should use the default icon bound to this item
                    if (this.IsFullScreen && item.Name == MenuItems.ToolbarToggleFullScreen)
                    {
                        item.Image = Program.GetThemeIcon(iconTheme, iconSize, IconName.WindowRestore);
                    }
                }
            }

            // update zoom label on the status bar and "View>Zoom" sub-menu item
            float scaledZF = this.ScaledZoomFactor;
            this._boundMenus[MenuItems.StatusBarZoomLevel].Text = scaledZF.ToString("0 %");
            this._boundMenus[MenuItems.ViewZoom].Text = "Zoom" + (scaledZF != 1.0F ? scaledZF.ToString(" (0 %)") : string.Empty);

            // resume redraw & event processing
            this.EndUpdate();
        }
        void RefreshTitleBar()
        {
            if (!this.IsFullScreen)
            {
                this.Text = FullModelName + (this.ux_sessionPanel.IsOpen ? $" - [{this.ux_sessionPanel.Text}]" : string.Empty);
            }
        }
        void RefreshMenuItemEnabledState()
        {
            // updates enabled state of main-menu and toolbar items
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(this.RefreshMenuItemEnabledState));
            }
            else
            {
                bool    isInit = this.ux_sessionPanel.IsInitializing;
                bool    isOpen = this.ux_sessionPanel.IsOpen;
                bool isRunning = this.ux_sessionPanel.IsRunning;
                bool  canStart = isOpen && !isRunning && this.ux_sessionPanel.Compliance != null;
                bool   canSave = isOpen && !isRunning && this.ux_sessionPanel.SaveRequired;
                //
                // welcome label
                this.ux_welcomeLabel.Visible = !isOpen;
                //
                // file menu
                this._boundMenus[MenuItems.FileCloseSession         ].Enabled = isOpen && !isRunning;
                this._boundMenus[MenuItems.FileSaveSession          ].Enabled = canSave;
                this._boundMenus[MenuItems.FileSaveSessionAs        ].Enabled = isOpen && !isRunning;
                this._boundMenus[MenuItems.FileStartModeling        ].Enabled = canStart;
                this._boundMenus[MenuItems.FileStopModeling         ].Enabled = isRunning && !isInit;
                this._boundMenus[MenuItems.FileExit                 ].Enabled = !isRunning;
                // view menu
                this._boundMenus[MenuItems.ViewModelingSettings     ].Enabled = isOpen;
                this._boundMenus[MenuItems.ViewInputFileLocation    ].Enabled = isOpen;
                this._boundMenus[MenuItems.ViewOutputLocation       ].Enabled = isOpen;
                this._boundMenus[MenuItems.ViewUpdateSpeed          ].Enabled = isOpen;
                this._boundMenus[MenuItems.ViewRefresh              ].Enabled = isOpen;
                // help menu
                // ::: always enabled
                // toolbar
                this._boundMenus[MenuItems.ToolbarCloseSession      ].Enabled = isOpen && !isRunning;
                this._boundMenus[MenuItems.ToolbarSaveSession       ].Enabled = canSave;
                this._boundMenus[MenuItems.ToolbarModelingSettings  ].Enabled = isOpen;
                this._boundMenus[MenuItems.ToolbarStartModeling     ].Enabled = canStart;
                this._boundMenus[MenuItems.ToolbarStopModeling      ].Enabled = isRunning && !isInit;
            }
        }

        //----- misc helper methods -----
        // explore I/O paths
        void ExplorePaths(params string[] paths)
        {
            for (int i = 0; i < paths.Length; i++)
            {
                Process.Start("explorer", "/e," + paths[i]);
            }
        }
        void ExplorePaths(List<string> paths)
        {
            this.ExplorePaths(paths.ToArray());
        }
        // set status-bar status
        internal void SetStatus(string value)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<string>(this.SetStatus), new object[] { value });
            }
            else
            {
                this._boundMenus[MenuItems.StatusBarModelStatus].Text = value;
            }
        }

        //----- drag-and-drop support methods -----
        void DisableDragDrop()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(this.DisableDragDrop));
            }
            else
            {
                this.AllowDrop = false;
            }
        }
        void EnableDragDrop()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(this.EnableDragDrop));
            }
            else
            {
                this.AllowDrop = true;
            }
        }
        /// <summary>
        /// Raises the <see cref="Control.DragEnter"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnDragEnter(DragEventArgs e)
        {
            if (this.AllowDrop && e.Data.GetDataPresent(DataFormats.FileDrop, false))
            {
                string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop, false);
                e.Effect = (paths.Length == 1) ? DragDropEffects.All : DragDropEffects.None;
            }
            //
            base.OnDragEnter(e);
        }
        /// <summary>
        /// Raises the <see cref="Control.DragDrop"/> event.
        /// </summary>
        /// <param name="e">Event data to pass to the event.</param>
        protected override void OnDragDrop(DragEventArgs e)
        {
            if (this.AllowDrop)
            {   // parse drag-dropped items ...
                string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop, false);
                if (paths.Length == 1)
                {   // determine file-type by extension -- load only *.cmsd files
                    string ext = Path.GetExtension(paths[0]).Trim().ToUpper();
                    if (ext == ".CMSD")
                    {   // *.cmsd file found -- try to load session
                        this.OpenSession(paths[0], false);
                    }
                }
            }
            //
            base.OnDragDrop(e);
        }
        string[] GetDragDropFiles(string[] paths, int recurseDepth)
        {
            //
            // Converts drag-droped files and directories to a flat file-list
            //
            List<string> files = new List<string>(8);
            for (int i = 0, pathsCount = paths.Length; i < pathsCount; i++)
            {
                if (Directory.Exists(paths[i]) && recurseDepth > 0)
                {   // path is a directory -- recurse
                    files.AddRange(this.GetDragDropFiles(Directory.GetFiles(paths[i]), recurseDepth - 1));
                }
                else if (File.Exists(paths[i]))
                {   // path is a file -- add it
                    files.Add(paths[i]);
                }
            }
            return files.ToArray();
        }

        //----- begin/end update support methods -----
        /// <summary>
        /// Suspends redrawing of the control and disables events while the control is being updated.
        /// </summary>
        protected void BeginUpdate()
        {
            Win32.BeginUpdate(this, ref this._updateCount, ref this._eventMask);
        }
        /// <summary>
        /// Resumes redrawing of the control and enables events after the control has been updated.
        /// </summary>
        protected void EndUpdate()
        {
            if (Win32.EndUpdate(this, ref this._updateCount, ref this._eventMask))
            {
                base.Refresh();
            }
        }

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets whether the CAFE Model window is currently in full-screen mode.</summary>
        public bool IsFullScreen { get; private set; }
        /// <summary>Gets the zoom factor currently applied to the GUI's visual elements.</summary>
        public float ZoomFactor { get; private set; }
        /// <summary>Gets the zoom factor currently applied to the GUI's visual elements, scaled based on whether the window is
        ///   in full screen mode.</summary>
        public float ScaledZoomFactor => Math.Min(this.ZoomFactor * (this.IsFullScreen ? 1.5F : 1.0F), MaxZoomFactor);

        /// <summary>
        /// Gets whether a CAFE Model session is currently open.
        /// </summary>
        public bool IsSessionOpen => this.ux_sessionPanel != null && this.ux_sessionPanel.IsOpen;
        /// <summary>
        /// Gets whether a CAFE Model session is currently running.
        /// </summary>
        public bool IsSessionRunning => this.ux_sessionPanel != null && this.ux_sessionPanel.IsRunning;

        /// <summary>
        /// Gets the required creation parameters when the control handle is created.
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style &= ~0x02000000;  // turn off WS_CLIPCHILDREN
                return cp;
            }
        }

        /// <summary>
        /// Gets or sets the string that represents the current model's status, as displayed in the status bar.
        /// </summary>
        private string ModelStatus
        {
            get => this._boundMenus[MenuItems.StatusBarModelStatus].Text;
            set => this.SetStatus(value);
        }

        #endregion

        #region <<< Variables >>>

        #region < UI Variables >

#pragma warning disable IDE1006 // Naming Styles
        Label ux_welcomeLabel;
        ToolStripMenuItem ux_fileMenu;
        ToolStripMenuItem ux_viewMenu;
        ToolStripMenuItem ux_helpMenu;
        MenuStrip ux_mainMenu;
        ToolStrip ux_mainToolbar;
        StatusStrip ux_statusBar;
#pragma warning restore IDE1006 // Naming Styles

        #endregion

        //----- session panel variable -- all session management (new/open/close/etc.) routed through here -----
        SessionPanel ux_sessionPanel = new SessionPanel();

        //----- constants/read-only -----
        internal const string FullModelName = "CAFE Model";

        // zooming support variables
        const float InitialZoomFactor  = 1.0F;
        const float MinZoomFactor      = 0.5F;
        const float MaxZoomFactor      = 4.0F;
        const float ZoomIncrementBig   = 0.25F;
        const float ZoomIncrementSmall = 0.1F;

        //----- runtime variables -----
        // GUI fonts
        Font _menuFont = null;
        Font _labelFont = null;

        /// <summary>
        /// All menu items are bound to this dictionary for processing of certain dynamically-triggered actions
        /// (tooltips, zoom, etc.).
        /// </summary>
        Dictionary<string, ToolStripItem> _boundMenus = new Dictionary<string, ToolStripItem>();

        //----- cached states -----
        string _cachedModelStatus = null;

        //----- support variables for begin/end update methods -----
        int _updateCount = 0;
        int _eventMask   = 0;

        #endregion

    }
}
