using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using MySQL.Grt;
using MySQL.Workbench;
using MySQL.Utilities;
using MySQL.GUI.Shell;
using MySQL.Forms;

namespace MySQL.GUI.Workbench
{
	static class Program
  {
    #region Static Variables and Enums

    // The types of application metadata information
    public enum ApplicationMetaInfo { Company, Copyright, Version, Revision, Configuration };

		// The Workbench Context
		private static WbContext wbContext = null;

		// The GRT Manager
		private static GrtManager grtManager = null;

    // Timer to guarantee idle handler is called periodically when nothing happens
    private static System.Windows.Forms.Timer timer = null;

    // Used to synchronize with for messages triggered by background threads.
    private static MainForm mainForm = null;

    #endregion

    /// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] Args)
    {
      // Get the base directory of the executable and change the working dir to it
      // TODO: the working dir should be untouched and the app should work
      // from any basedir
      System.Reflection.Assembly asm = System.Reflection.Assembly.GetEntryAssembly();
      string baseDir = System.IO.Path.GetDirectoryName(asm.Location);
      string workdir = System.IO.Directory.GetCurrentDirectory();
      System.IO.Directory.SetCurrentDirectory(baseDir);

      // initialize forms stuff
      MySQL.Forms.Manager formsManager = new MySQL.Forms.Manager();

      #region Command line argument parsing and handling

      // Command line parsing
      Arguments CommandLine = new Arguments();

      CommandLine.AddOptionName("-verbose", true);
      CommandLine.AddOptionName("-v", true);
      CommandLine.AddOptionName("-help", true);
      CommandLine.AddOptionName("-h", true);
      CommandLine.AddOptionName("-open", false);
      CommandLine.AddOptionName("-nologo", true);
      CommandLine.AddOptionName("-version", true);
      CommandLine.AddOptionName("-grtversion", true);
      CommandLine.AddOptionName("-swrendering", false);
      CommandLine.AddOptionName("-log", true);

      CommandLine.Parse(Args);

      // Command line handling
      if (ProcessCommandLineArguments(CommandLine))
        return;

      #endregion

      #region Time checks

      // check the date of the executable and suggest to install a new version if this is a beta or rc
      if (GetApplicationMetaInfo(ApplicationMetaInfo.Configuration).ToUpper().IndexOf("BETA") >= 0 ||
        GetApplicationMetaInfo(ApplicationMetaInfo.Configuration).ToUpper().IndexOf("RC") >= 0)
      {
        DateTime fileDate = System.IO.File.GetCreationTime(Application.ExecutablePath);

        if (DateTime.Now.Subtract(fileDate).TotalDays > 45)
        {
          if (MessageBox.Show("This version of MySQL Workbench is older than 45 days and most probably outdated. "
            + Environment.NewLine
            + "It is recommended to upgrade to a newer version if available. "
            + Environment.NewLine
            + "Press [OK] to check for a new version and exit the application. "
            + "Press [Cancel] to continue using this version.",
            "MySQL Worbench Version Outdated", MessageBoxButtons.OKCancel,
            MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1) == DialogResult.OK)
          {
            CheckForNewVersion();
            return;
          }
        }
      }

      #endregion

      #region Variables and Splashscreen

      // GRT shell variable for easier access
			GrtShell grtShell = null;

      // Show splash screen if not turned off
      if (CommandLine["nologo"] == null)
        SplashScreen.Show(typeof(WorkbenchSplashScreen));

      #endregion

      #region Initialize GRT

      // Try to instantiate the Workbench context and the GRT Manager and catch exceptions
			try
			{
				// Create Workbench Context
				wbContext = new WbContext((CommandLine["verbose"] != null || CommandLine["v"] != null));

				if (wbContext != null)
				{
					// Create the GRT Manager instance
					grtManager = wbContext.get_grt_manager();

					// Get GRT Shell
					if (grtManager != null)
						grtShell = grtManager.get_shell();
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show(string.Format("Cannot create Workbench Context.\n\n({0})",
					ex.Message), "MySQL Grt");
      }

      #endregion

      #region Initialize Callbacks and Mainform

      // If the Workbench Context and GRT Manager were successfully created, 
			// initialize the application
			if (wbContext != null && grtManager != null && grtShell != null)
			{
				Application.EnableVisualStyles();
				Application.SetCompatibleTextRenderingDefault(false);

				mainForm = new MainForm(wbContext);

				// Create the GRT Shell form and add it to the Main Form
				GrtShellForm grtShellForm = new GrtShellForm(grtManager);
        grtShellForm.ScriptFilePath = System.IO.Path.Combine(System.IO.Path.Combine(System.IO.Path.Combine(
              Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
              "MySQL"), "Workbench"), "modules");
				mainForm.GrtShellForm = grtShellForm;

				// Initialize the Workbench context
				WbFrontendCallbacks wbFrontendCallback = new WbFrontendCallbacks(
					new WbFrontendCallbacks.VoidStrStrDelegate(ShowInternalError),
          new WbFrontendCallbacks.VoidStrStrDelegate(ShowError),
          new WbFrontendCallbacks.VoidStrStrDelegate(ShowWarning),
          new WbFrontendCallbacks.VoidStrStrDelegate(ShowInfo),
          new WbFrontendCallbacks.IntStrStrStrStrStrDelegate(ShowConfirmDialog),
          new WbFrontendCallbacks.StrStrStrStrDelegate(mainForm.ShowFileDialog),
					new WbFrontendCallbacks.VoidStrDelegate(mainForm.ShowStatusText),
					new WbFrontendCallbacks.BoolStrStrFloatDelegate(mainForm.ShowProgress),
          new WbFrontendCallbacks.BoolStrIntStrPtrDelegate(mainForm.RequestInput),
          new WbFrontendCallbacks.VoidStrDelegate(mainForm.PrintOutputText),
					new WbFrontendCallbacks.VoidStrDelegate(grtShellForm.PrintShellOutput),
					new WbFrontendCallbacks.VoidStrDelegate(grtShellForm.PrepareNextCommand),
					new WbFrontendCallbacks.CanvasViewStringStringDelegate(mainForm.CreateNewView),
					new WbFrontendCallbacks.VoidCanvasViewDelegate(mainForm.DestroyView),
					new WbFrontendCallbacks.VoidCanvasViewDelegate(mainForm.SwitchedView),
					new WbFrontendCallbacks.VoidCanvasViewDelegate(mainForm.ToolChanged),
					new WbFrontendCallbacks.IntPtrGRTManagerModuleStrStrGrtListFlagsDelegate(mainForm.OpenPlugin),
					new WbFrontendCallbacks.VoidIntPtrDelegate(mainForm.ShowPlugin),
					new WbFrontendCallbacks.VoidIntPtrDelegate(mainForm.HidePlugin),
					new WbFrontendCallbacks.VoidRefreshTypeStringIntPtrDelegate(mainForm.RefreshGUI),
          new WbFrontendCallbacks.VoidBoolDelegate(mainForm.LockGUI),
          new WbFrontendCallbacks.VoidMenuItemListIntIntDelegate(mainForm.PopupCanvasMenu),
          new WbFrontendCallbacks.VoidStrDelegate(mainForm.PerformCommand),
          new WbFrontendCallbacks.VoidDelegate(mainForm.QuitApplication));

        /// mainForm.PrintOutputText
        /// mainForm.ShowOutputForm

        string openAtStartup = CommandLine["open"];
        if (openAtStartup == null)
        {
          openAtStartup = "";
          if (CommandLine.Leftovers.Count > 0)
            openAtStartup = CommandLine.Leftovers[CommandLine.Leftovers.Count-1];
        }
        if (openAtStartup != null && openAtStartup != "")
        {
          if (!System.IO.Path.IsPathRooted(openAtStartup))
            openAtStartup = System.IO.Path.Combine(workdir, openAtStartup);
        }

				WbOptions wbOptions = new WbOptions(baseDir, // basedir
					baseDir, // plugin
          "",       // struct
					baseDir + "/modules", // modules
					baseDir, // library
					System.IO.Path.Combine(System.IO.Path.Combine(
							Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
							"MySQL"), "Workbench"),
          openAtStartup,
          (CommandLine["swrendering"] != null),
          (CommandLine["openglrendering"] != null),
          (CommandLine["log"] != null));

				wbContext.init(wbFrontendCallback, wbOptions);

				//grtManager.set_output_callback(new GrtManager.VoidStringDelegate(grtShellForm.PrintShellOutput));
				grtManager.set_error_callback(new GrtManager.VoidStringStringDelegate(grtShellForm.ErrorCallback));

				//grtShell.set_ready_handler(new GrtShell.VoidStringDelegate(grtShellForm.PrepareNextCommand));


				// Set the Application.Idle event handler
				//Application.Idle += new EventHandler(OnApplicationIdle);

        // Don't call the idle handler too often.
        timer = new Timer();
        timer.Interval = 100;
        timer.Tick += new EventHandler(timer_Tick);
        timer.Start();

				// Initialize the ShellForm
				grtShellForm.Initialize();

				// Trigger GRT idle tasks
				grtManager.perform_idle_tasks();

        // Setup Menus
        mainForm.SetupUI();

				// Create Overview Form
        //mainForm.ShowMainOverviewForm();
				mainForm.ShowPhysicalOverviewForm();

				// Tell the backend our main UI is ready.
        wbContext.finished_loading(wbOptions);

				// Start the Application
        Application.Run(new MySQLApplicationContext(mainForm));

				// After the application has run
				if (grtManager != null && grtShell != null)
				{
					// Store shell history
					grtShell.store_history();
				}

        // shutdown wb context
        if (wbContext != null)
        {
          while (wbContext.is_busy())
            wbContext.flush_idle_tasks();

          wbContext.finalize();
        }
      }

      #endregion
    }

    
    #region Application Idle Handler

    static void timer_Tick(object sender, EventArgs e)
    {
      // flush_idle_tasks needs to be reentrant so that functions
      // that crosses threads have their own idle tasks executed
      if (wbContext != null)
      {
        try
        {
          wbContext.flush_idle_tasks();
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message);
        }
      }
    }

    /* on vista this is called too often
    private static void OnApplicationIdle(object sender, EventArgs e)
		{
      timer.Stop();
			// Whenever the application is idle, check if there are GRT tasks to be executed
			if (wbContext != null)
			{
        try
        {
          wbContext.flush_idle_tasks();
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message);
        }
			}
      timer.Start();
    }*/

    #endregion

    #region Message Callbacks

    private static void ShowInternalError(String Err1, String Err2)
		{
      ShowMessage(Err1, Err2, "MySQL Workbench Internal Error", MessageBoxIcon.Stop);
		}

		private static void ShowError(String Err1, String Err2)
		{
      ShowMessage(Err1, Err2, "MySQL Workbench Error", MessageBoxIcon.Error);
		}

    private static void ShowWarning(String Err1, String Err2)
    {
      ShowMessage(Err1, Err2, "MySQL Workbench Warning", MessageBoxIcon.Warning);
    }

    private static void ShowInfo(String Err1, String Err2)
    {
      ShowMessage(Err1, Err2, "MySQL Workbench Information", MessageBoxIcon.Information);
    }

    // Used to synchronize ShowMessage, which can be called from any thread to the main thread.
    // This is necessary to make the appearing message box application modal, otherwise it could hide behind
    // the application window.
    private delegate void ShowMessageDelegate(String Err1, String Err2, String caption, MessageBoxIcon icon);

    /// <summary>
    /// Central message method used by the other notification methods. The method takes care to show
    /// the message in the context of the main thread.
    /// </summary>
    private static void ShowMessage(String Err1, String Err2, String caption, MessageBoxIcon icon)
    {
      if (mainForm.InvokeRequired)
        mainForm.Invoke(new ShowMessageDelegate(ShowMessage), new Object[] { Err1, Err2, caption, icon });
      else
        MessageBox.Show(mainForm, Err1 + "\n\n" + Err2, caption, MessageBoxButtons.OK, icon);
    }

    private delegate int ShowConfirmDialogDelegate(String title, String message, String yes, String no, 
      String alternate);

    private static int ShowConfirmDialog(String title, String message, 
      String yes, String no, String alternate)
    {
      if (mainForm.InvokeRequired)
        return (int) mainForm.Invoke(new ShowConfirmDialogDelegate(ShowConfirmDialog), new Object[] { title, message, 
          yes, no, alternate});
      else
      {
        DialogResult result;
        result = MessageBox.Show(mainForm, message, title, alternate != "" ?
          MessageBoxButtons.YesNoCancel : MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if (result == DialogResult.Yes)
          return 1;
        else if (result == DialogResult.No)
          return 0;
        else
          return -1;
      }
    }

    private static int ShowStringInputDialog(String Title, String Prompt,
      out String InputString)
    {
      DialogResult result;
      result = StringInputForm.ShowModal(Title, "", Prompt, out InputString);
      if (result == DialogResult.OK)
        return 1;
      else
        return 0;
    }

    #endregion

    #region Various Static Functions

    /// <summary>
    /// Retrieves application metadata and returns it as a string
    /// </summary>
    /// <param name="kind">The type of metadata informatin to return</param>
    /// <returns></returns>
    static public string GetApplicationMetaInfo(ApplicationMetaInfo kind)
    {
      object[] attributes;
      Version version;

      Assembly assembly = Assembly.GetExecutingAssembly();

      string value = "";

      switch (kind)
      {
        case ApplicationMetaInfo.Company:
          attributes = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
          if (attributes.Length > 0)
            value = (attributes[0] as AssemblyCompanyAttribute).Company;
          break;

        case ApplicationMetaInfo.Copyright:
          attributes = assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
          if (attributes.Length > 0)
            value = (attributes[0] as AssemblyCopyrightAttribute).Copyright;
          break;

        case ApplicationMetaInfo.Configuration:
          attributes = assembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false);
          if (attributes.Length > 0)
            value = (attributes[0] as AssemblyConfigurationAttribute).Configuration;
          break;

        case ApplicationMetaInfo.Version:
          version = new Version(Application.ProductVersion);
          value = string.Format("{0}.{1}.{2}",
            version.Major, version.Minor, version.Build);
          break;

        case ApplicationMetaInfo.Revision:
          version = new Version(Application.ProductVersion);
          value = string.Format("{0}", version.MinorRevision);
          break;
      }

      return value;
    }

    /// <summary>
    /// Deals with the given command line arguments
    /// </summary>
    /// <param name="CommandLine">The command line arguments passed to the application</param>
    /// <returns></returns>
    static bool ProcessCommandLineArguments(Arguments CommandLine)
    {
      if (CommandLine["help"] != null || CommandLine["h"] != null)
      {
        Console.WriteLine("MySQL Workbench {0} {1}. " 
          + "(C) {2} by {3}. All rights reserved." + Environment.NewLine,
          GetApplicationMetaInfo(ApplicationMetaInfo.Version), 
          GetApplicationMetaInfo(ApplicationMetaInfo.Configuration),
          GetApplicationMetaInfo(ApplicationMetaInfo.Copyright),
          GetApplicationMetaInfo(ApplicationMetaInfo.Company));
        Console.WriteLine("Usage: MySQLWorkbench [options] [model file]" + Environment.NewLine);

        Console.WriteLine("Options" + Environment.NewLine
          + "  -help (-h) ...... Print this output" + Environment.NewLine
          + "  -open filename .. Open the given filename at startup" + Environment.NewLine
          + "  -nologo ......... Do not display the splash screen" + Environment.NewLine
          + "  -verbose (-v) ... Print verbose output in the GRT Shell" + Environment.NewLine
          + "  -version ........ Print the version information" + Environment.NewLine
          + "  -grtversion ..... Print the GRT version information" + Environment.NewLine
          + "  -swrendering .... Force the canvas to use software rendering instead of OpenGL" +Environment.NewLine
          + "  -log ............ Instruction to save messages (other debug info) to file");

        /*Console.WriteLine("GRT Options" + Environment.NewLine
          + "{0}",
          Grt.option_description);*/

        return true;
      }
      else if (CommandLine["version"] != null)
      {
        Console.WriteLine("MySQL Workbench {0}.{1} {2}",
          GetApplicationMetaInfo(ApplicationMetaInfo.Version), 
          GetApplicationMetaInfo(ApplicationMetaInfo.Revision),
          GetApplicationMetaInfo(ApplicationMetaInfo.Configuration));
        return true;
      }
      else if (CommandLine["grtversion"] != null)
      {
        Console.WriteLine("GRT Environment {0}", GRT.version());
        return true;
      }

      return false;
    }

    static public bool CheckForNewVersion()
    {
      try
      {
				System.Diagnostics.Process.Start("http://dev.mysql.com/workbench/version-check.php?config="
          + GetApplicationMetaInfo(ApplicationMetaInfo.Configuration).Replace(' ', '_')
          + "&version="
          + GetApplicationMetaInfo(ApplicationMetaInfo.Version)
          + "&revision="
          + GetApplicationMetaInfo(ApplicationMetaInfo.Revision));
      }
      catch (Exception e)
      {
        MessageBox.Show(e.Message.ToString(), "Error Opening Browser", 
          MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
      }

      return true;
    }

    #endregion
  }

  #region Unused code

  /*class TabKeyCatcher : IMessageFilter
	{
		public const int WM_KEYDOWN = 0x0100;
		public bool PreFilterMessage(ref Message m)
		{
			if (m.Msg == WM_KEYDOWN)
			{
				if (m.WParam.ToInt32() == (int)Keys.Tab)
				{
					MessageBox.Show("Tab");
				}
			}
			return false;
		}
	}*/

  #endregion
}