#region <<< Using Directives >>>
using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows.Forms;

using Volpe.Cafe.EspressoUI.Themes;
using Volpe.Cafe.EspressoUI.Utils;
using Volpe.Cafe.Resources;
using Volpe.Cafe.UI;
using Volpe.Cafe.Utils;
using Volpe.Cafe.VehicleSimulation;
#endregion

namespace Volpe.Cafe.EspressoUI
{
    static class Program
    {
        /// <summary>
        /// Returns the application icon that is displayed in the form's system menu box and in the user's taskbar.
        /// </summary>
        public static Icon GetAppIcon()
        {
            return new Icon(AppResources.GetAppIconStream());
        }

        /// <summary>
        /// Returns the application setup icon.
        /// </summary>
        public static Icon GetSetupIcon()
        {
            return new Icon(AppResources.GetSetupIconStream());
        }

        /// <summary>
        /// Returns an icon image with the specified theme and name, with the <see cref="IconSize.x22"/> size.
        /// </summary>
        public static Bitmap GetThemeIcon(IconTheme theme, IconName name)
        {
            return GetThemeIcon(theme, IconSize.Default, name);
        }
        /// <summary>
        /// Returns an icon image with the specified theme, size, and name.
        /// </summary>
        public static Bitmap GetThemeIcon(IconTheme theme, IconSize size, IconName name)
        {
            return new Bitmap(AppResources.GetThemeIconStream(theme, size, name));
        }
        /// <summary>
        /// Returns an icon image with the specified theme, size (in pixels), and name.
        /// </summary>
        public static Bitmap GetThemeIcon(IconTheme theme, int sizeInPixels, IconName name)
        {
            return new Bitmap(AppResources.GetThemeIconStream(theme, sizeInPixels, name));
        }


        /// <summary>
        /// Defines the entry point of the application.
        /// </summary>
        [STAThread]
        private static void Main()
        {
            // add support for reading embedded assemblies
            AppDomain currentDomain = AppDomain.CurrentDomain;
            currentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_AssemblyResolve;
            currentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            // windows' native auto-scaling for high DPI monitors on windows-forms apps may cause blurriness
            // we set our app as DPI aware to prevent windows from auto-scaling visual elements
            Win32.SetDpiAwareness();

            // we need to override default culture info for all threads to better represent infinity values
            // by default, win10 and later use infinity symbol, while earlier versions print "Infinity" text
            SetDefaultCulture();

#if !DEBUG
            // get logfile and redirect output there
            StreamWriter logSw = GetLogFile();
#endif

            Console.WriteLine("Starting run at: {0}", DateTime.Now.ToString());
            Program.Run();
            Console.WriteLine("Exiting model at: {0}", DateTime.Now.ToString());

#if !DEBUG
            if (logSw != null) {
                logSw.Close();
            }
#endif
        }

        private static void Run()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            try {
                ReadSettings();

                if (ResourceViewer.ShowWarningsDialog(true) == DialogResult.OK) {
                    if (PrecacheSimulationDB()) {
                        Application.Run(new Cafe2e());
                        WriteSettings();
                    }
                }
            }
            catch (Exception ex) {
                MessageBox.Show(ex.Message, "CAFE Model Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Console.WriteLine(ex.ToString());
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            const string resourceBase = "Volpe.Cafe.EspressoUI.DLL.";
            string resource = resourceBase + (new AssemblyName(args.Name)).Name + ".dll";
            Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource);
            Assembly assembly = null;

            if (stream != null) {
                try {
                    byte[] data = new byte[stream.Length];
                    stream.Read(data, 0, data.Length);
                    assembly = Assembly.Load(data);
                }
                finally {
                    if (stream != null) {
                        stream.Close();
                    }
                }
            }
            return assembly;
        }

        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private static void SetDefaultCulture()
        {
            SystemInfo.SetDefaultCulture();
        }

        private static void ReadSettings()
        {
            //
            // TODO: 'ReadSettings' build & expand settings management
            //
            if (!File.Exists(ApplicationInfo.SettingsFilePath)) {
                return;
            }

            using (StreamReader sr = new StreamReader(ApplicationInfo.SettingsFilePath)) {
                string themeName = null;
                string line;
                while ((line = sr.ReadLine()) != null) {
                    line = line.Trim();
                    if (line.StartsWith("Theme=")) {
                        themeName = line.Substring(6);
                    }
                }

                Theme.SetCurrentTheme(themeName);
            }
        }
        private static void WriteSettings()
        {
            //
            // TODO: 'WriteSettings' build & expand settings management
            //
            Mutex mutex = new Mutex(false, "Global\\CAFE_Espresso");
            mutex.WaitOne();
            using (StreamWriter sw = new StreamWriter(ApplicationInfo.SettingsFilePath)) {
                sw.WriteLine("FullModelName=" + Cafe2e.FullModelName);
                sw.WriteLine("Theme=" + Theme.CurrentTheme.Name);
            }
            mutex.ReleaseMutex();
        }

        #region < Simulation Database reading/loading >

        static string GetSimulationDBWarningMessage()
        {
            return
                "Some or all of the input files required by the Vehicle Simulation Database could not be loaded." +
                "\n\nThe database contains fuel economy adjustment factors and hybridization battery costs for " +
                "various technology combinations simulated by Argonne National Laboratory, and is used by the " +
                "modeling system during analysis to evaluate technology effectiveness on individual vehicles. " +
                "The model will not be able to continue." +
                "\n\nFor more details, please see " +
                ((LogFilePath == null) ? "console output." : $"model log file located at: {LogFilePath}.");
        }
        static void ShowSimulationDBWarning(string appendExitString = "Press OK to exit.")
        {
            string warningMessage = GetSimulationDBWarningMessage();
            if (appendExitString != null && appendExitString != string.Empty)
            {
                warningMessage += "\n\n" + appendExitString;
            }
            RtfDialog.ShowDialog("Simulation Database Warning", warningMessage,
                new Font(Cafe2e.DefaultFont.FontFamily, 10), Color.DarkRed, PromptOption.Ok, true, false);
        }

        static bool PrecacheSimulationDB()
        {
            ShowSplashScreen();
            bool isPrecached = SimulationDatabase.Precache();
            CloseSplashScreen();
            if (!isPrecached) {
                ShowSimulationDBWarning();
            }
            return isPrecached;
        }

        internal static bool InitializeSimulationDB()
        {
            bool isInitialized = SimulationDatabase.Initialize();
            if (!isInitialized) {
                ShowSimulationDBWarning("Press OK to continue.");
            }
            return isInitialized;
        }

        internal static bool ExtractSimulationDB(string path)
        {
            ShowSplashScreen("Please wait while the Vehicle Simulation Database is being copied ...");
            bool isExtracted = SimulationDatabase.ExtractSimulationData(path);
            CloseSplashScreen();

            if (!isExtracted) {
                string warningMessage =
                    "Some or all of the Vehicle Simulation Database files could not be extracted.\n\nPlease make " +
                    "sure that you have write access to the selected directory. If you do have write access, it is " +
                    "possible that the simulation files packaged with this version of the model were not configured " +
                    "correctly.";
                RtfDialog.ShowDialog("Simulation Database Warning", warningMessage,
                    new Font(Cafe2e.DefaultFont.FontFamily, 10), Color.DarkRed, PromptOption.Ok, true, false);
            }
            return isExtracted;
        }

        #endregion

        #region < Show Splash Screen while loading data >

        static SplashScreen SplashScreen;

        static void ShowSplashScreen(string title = null)
        {
            Thread thread = new Thread(new ThreadStart(
                delegate
                {
                    SplashScreen = new SplashScreen { Title = title };
                    Application.Run(SplashScreen);
                }));
            thread.Start();
            while (SplashScreen == null || !SplashScreen.Visible) {
                Thread.Sleep(125);
            }
        }

        static void CloseSplashScreen()
        {
            if (SplashScreen != null) {
                // make sure splash-screen is displayed for at least half a second before closing
                if (SplashScreen.ElapsedTime < 500) {
                    Thread.Sleep(500 - SplashScreen.ElapsedTime);
                }
                SplashScreen.PreventFromClosing = false;
                SplashScreen.Invoke(new MethodInvoker(SplashScreen.Close));
            }
        }

        #endregion

        #region < Log-file helper methods for redirected output from Console >

        static string LogFilePath = null;

        [MethodImpl(MethodImplOptions.NoInlining)]
        static StreamWriter GetLogFile()
        {
            StreamWriter sw;
            try {
                LogFilePath = $"{ApplicationInfo.LogPath}\\{DateTime.Now.Ticks}.log";
                sw = new StreamWriter(LogFilePath, false) {
                    AutoFlush = true
                };
                Console.SetOut(sw);
            }
            catch {
                sw = null;
            }
            return sw;
        }

        #endregion
    }
}
