context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Drawing;
  using System.Linq;
  using System.Windows.Forms;
  using Ninject;
  using Realm.Admin.DAL;
  using Realm.DAL;
  using Realm.DAL.Common;
  using Realm.DAL.Models;
  using Realm.Edit.Editor;
  using Realm.Edit.EditorControls;
  using Realm.Edit.Extensions;
  using Realm.Edit.Properties;
  using Realm.Edit.Tags;
  using Realm.Library.Common.Extensions;
  using Color = System.Drawing.Color;
  
  namespace Realm.Edit
  {
      public partial class MainForm : Form
      {
          public bool Loading { get; private set; }
          public TreeNode DragNode { get; private set; }
          public TreeView BrowseTree => treeBrowse;
  
          public ContextMenuStrip BrowseFolder => contextBrowseFolder;
          public ToolStripProgressBar ProgressStatus => progressStatus;
  
          public MainForm()
          {
              InitializeComponent();
              Loading = true;
          }
  
          private void MainFormLoad(object sender, EventArgs e)
          {
              Show();
              Loading = true;
              InitAll();
              Loading = false;
          }
  
          private void InitAll()
          {
              try
              {
                  SystemTags.Init();
  
                  treeBrowse.Nodes.Clear();
  
                  imagesSystem.Images.Clear();
                  imagesSystem.Images.Add("openFolder", ShellIcon.extractIconFromFile("shell32.dll", 4, false));
                  foreach (var builder in EditorFactory.Builders.Values)
                      imagesSystem.Images.Add(builder.DisplayName, builder.Icon);
  
                  treeBrowse.BeginUpdate();
                  treeBrowse.ImageList = imagesSystem;
                  tabContent.ImageList = imagesSystem;
  
                  var filterText = txtFilter.Text.Trim();
                  Program.MainForm.ProgressStatus.Minimum = 0;
                  Program.MainForm.ProgressStatus.Maximum = EditorFactory.Builders.Count;
                  Program.MainForm.ProgressStatus.Value = Program.MainForm.ProgressStatus.Minimum;
  
                  // Initialize the interface with the registered editors
                  foreach (var builder in EditorFactory.Builders.Values)
                  {
                      if (!builder.IsVisible) continue;
  
                      var dbContext = Program.NinjectKernel.Get<IRealmDbContext>();
                      var classList = dbContext.SystemClasses
                          .Where(x => x.SystemType == builder.SystemType)
                          .Where(x => x.ParentClassId == null)
                          .ToList();
  
                      if (classList.Count > 1)
                          throw new EditorException("Multiple parent classes for system [" + builder.SystemType + "]");
  
                      if (!classList.Any())
                          throw new EditorException($"No parent class for system [{builder.SystemType}]");
  
                      var classId = classList.First().Id;
  
                      var rootNode = new TreeNode(builder.DisplayPlural);
                      var browseInfo = new EditorBrowseInfo(builder.SystemType, builder.DisplayName, classId, 0);
                      rootNode.Tag = browseInfo;
                      rootNode.ImageKey = Resources.ClosedFolderImageKey;
                      rootNode.ContextMenuStrip = contextBrowseFolder;
  
                      rootNode.SetupBrowseTree(builder, true, filterText);
                      if (!builder.HasDelete())
                      {
                          // This will disable for everything...?
                          // contextBrowseNode.Items[0].Enabled = false;
                      }
                      builder.PopulateBrowseNode(rootNode, browseInfo.ClassId, contextBrowseNode, txtFilter.Text.Trim());
                      treeBrowse.Nodes.Add(rootNode);
                      Program.MainForm.ProgressStatus.Value += 1;
                  }
  
                  treeBrowse.EndUpdate();
                  Application.DoEvents();
                  Program.MainForm.SetStatusMessage($"{EditorFactory.Builders.Count} builders initialized.");
                  Program.Log.InfoFormat(Resources.TEXT_BUILDER_INIT_SUMMARY.Replace("{0}",
                      EditorFactory.Builders.Count.ToString()));
              }
              catch (Exception ex)
              {
                  Program.Log.Error(ex.Message, ex);
              }
          }
  
          public void SetStatusMessage(string msg = "")
          {
              Program.Log.Info(msg);
              lblStatus.Text = msg;
              lblStatus.BackColor = !string.IsNullOrEmpty(msg.Trim()) ? Color.LightBlue : Color.Empty;
          }
  
          public void SetWarningMessage(string msg = "")
          {
              Program.Log.Warn(msg);
              lblStatus.Text = msg;
              lblStatus.BackColor = !string.IsNullOrEmpty(msg.Trim()) ? Color.Yellow : Color.Empty;
          }
  
          public void SetErrorMessage(string msg = "")
          {
              Program.Log.Error(msg);
              lblStatus.Text = msg;
              lblStatus.BackColor = !string.IsNullOrEmpty(msg.Trim()) ? Color.Red : Color.Empty;
          }
  
          public void OpenTab(EditorBrowseInfo browseInfo, bool openCopy, bool makeDirty)
          {
              if (browseInfo == null)
                  throw new ArgumentNullException(nameof(browseInfo), Resources.NullParameterErrorMessage);
  
              TabPage page = null;
  
              // If we are not opening a copy, always create a new page and do not use an already open page.
              if (!openCopy)
                  page = FindTab(browseInfo.SystemType, browseInfo.Id);
  
              if (page != null)
                  tabContent.SelectedTab = page;
              else
              {
                  var builder = EditorFactory.Builders[EnumerationExtensions.GetEnum<SystemTypes>((int)browseInfo.SystemType)];
                  if (!builder.HasEditor()) return;
  
                  // Create a new edit control based on the selected node 
                  var editControl =
                      EditorFactory.Create(EnumerationExtensions.GetEnum<SystemTypes>((int)browseInfo.SystemType),
                          browseInfo.ClassId);
  
                  // Initialize the new control with the content that was dclicked
                  editControl.InitContent(browseInfo.Id);
                  var tabName = editControl.ControlName;
                  if (openCopy)
                  {
                      editControl.MakeCopy();
                      tabName = "*" + editControl.ControlName + "*";
                  }
                  else if (makeDirty)
                  {
                      editControl.MakeDirty();
                      tabName = "*" + editControl.ControlName + "*";
                  }
  
                      tabName = $"[{editControl.Id}] {tabName}";
  
                  CreateContentTab(tabName, editControl);
              }
          }
  
          public bool CloseTabs(IList tabPages, bool promptSave)
          {
              if (tabPages == null)
                  throw new ArgumentNullException(nameof(tabPages), Resources.NullParameterErrorMessage);
  
              // Prompt to save all of the dirty tabs first
              // If there are any cancels, nobody closes
              var closeAll = true;
              if (promptSave)
              {
                  foreach (var tabPage in tabPages.OfType<TabPage>()
                      .Where(x => x.Controls.Count > 0))
                  {
                      var editorControl = tabPage.Controls[0] as BaseEditorControl;
                      if (editorControl == null || !editorControl.IsDirty) continue;
                      if (editorControl.PromptSave() != DialogResult.Cancel) continue;
  
                      closeAll = false;
                      break;
                  }
              }
  
              // If everything is ok, close them all
              if (closeAll)
              {
                  foreach (TabPage tabPage in tabPages)
                  {
                      tabContent.TabPages.Remove(tabPage);
                      var editorControl = tabPage.Controls[0] as BaseEditorControl;
                      editorControl?.Dispose();
                  }
              }
  
              return closeAll;
          }
  
          public TabPage FindTab(SystemTypes systemType, long id)
          {
              foreach (var page in tabContent.TabPages.OfType<TabPage>()
                  .Where(x => x.Controls.Count > 0))
              {
                  var editControl = page.Controls[0] as BaseEditorControl;
                  if (editControl != null
                      && editControl.SystemType == systemType
                      && editControl.Id == id)
                      return page;
              }
  
              return null;
          }
  
          public void CloseTab(TabPage page, bool promptSave)
          {
              var pageList = new List<TabPage> { page };
              CloseTabs(pageList, promptSave);
          }
  
          private void CreateContentTab(string name, BaseEditorControl control)
          {
              tabContent.SuspendLayout();
              var newPage = new TabPage(name)
              {
                  Padding = new Padding(6, 6, 6, 6)
              };
              newPage.Controls.Add(control);
  
              // This works around a bug it seems.  
              // You can't set the ImageKey property of a dynamic tab.  
              // Yet you can set the ImageIndex
              var keyIndex = 0;
              if (EditorFactory.Builders.ContainsKey(control.SystemType))
                  keyIndex = tabContent.ImageList.Images.IndexOfKey(EditorFactory.Builders[control.SystemType].DisplayName);
              newPage.ImageIndex = keyIndex;
  
              control.Dock = DockStyle.Fill;
              tabContent.TabPages.Add(newPage);
              tabContent.SelectedTab = newPage;
              tabContent.ResumeLayout(true);
  
              control.DirtyChanged += EditorControlDirtyChanged;
          }
  
          private void EditorControlDirtyChanged(object sender, EventArgs e)
          {
              var editorControl = sender as BaseEditorControl;
              if (editorControl == null) return;
  
              foreach (var tabPage in tabContent.TabPages.Cast<TabPage>()
                  .Where(tabPage => tabPage.Controls[0].Equals(editorControl)))
              {
                  tabPage.Text = editorControl.IsDirty
                                     ? Resources.TEXT_DIRTY_NODE.Replace("{0}", editorControl.ControlName)
                                     : editorControl.ControlName;
  
                  tabPage.Text = Resources.TEXT_NODE.Replace("{0}", editorControl.Id.ToString()).Replace("{1}", tabPage.Text);
                  break;
              }
          }
  
          
  
          private void RefreshTreeView()
          {
              var dbContext = Program.NinjectKernel.Get<IRealmDbContext>();
  
              treeBrowse.Nodes.Clear();
              treeBrowse.ImageList.Images.Clear();
  
              var filterText = txtFilter.Text.Trim();
              Program.MainForm.ProgressStatus.Minimum = 0;
              Program.MainForm.ProgressStatus.Maximum = EditorFactory.Builders.Count;
              Program.MainForm.ProgressStatus.Value = Program.MainForm.ProgressStatus.Minimum;
  
              // Initialize the interface with the registered editors
              var iconIndex = 0;
              foreach (var builder in EditorFactory.Builders.Values)
              {
                  if (!builder.IsVisible) continue;
  
                  treeBrowse.ImageList.Images.Add(builder.Icon);
  
                  var classList = dbContext.SystemClasses
                      .Where(x => x.SystemType == builder.SystemType)
                      .Where(x => x.ParentClassId == null);
  
                  if (classList.Count() > 1)
                      throw new EditorException($"Multiple parent classes for system [{builder.SystemType}]");
  
                  if (!classList.Any())
                      throw new EditorException($"No parent class for system [{builder.SystemType}]");
  
                  var classId = classList.ToList().First().Id;
  
                  var rootNode = new TreeNode(builder.DisplayPlural);
                  var browseInfo = new EditorBrowseInfo(builder.SystemType, builder.DisplayName, classId, 0);
                  rootNode.Tag = browseInfo;
                  rootNode.ImageKey = "closedFolder";
                  rootNode.ContextMenuStrip = contextBrowseFolder;
  
                  rootNode.SetupBrowseTree(builder, !string.IsNullOrEmpty(filterText), filterText);
                  if (!builder.HasDelete())
                  {
                      // This will disable for everything...?
                      // contextBrowseNode.Items[0].Enabled = false;
                  }
                  builder.PopulateBrowseNode(rootNode, browseInfo.ClassId, contextBrowseNode, txtFilter.Text.Trim());
                  treeBrowse.Nodes.Add(rootNode);
                  Program.MainForm.ProgressStatus.Value += 1;
  
                  iconIndex++;
              }
          }
  
          private void SaveTabPage(Control tabPage)
          {
              var editControl = tabPage.Controls[0] as BaseEditorControl;
              if (editControl == null || !editControl.Save()) return;
  
              var builder = EditorFactory.Builders[editControl.SystemType];
              var browseInfo = editControl.BrowseInfo;
              browseInfo.Id = 0;  // Change to its parent node
              var node = treeBrowse.FindTreeNode(browseInfo);
  
              if (builder != null && node != null)
                  builder.PopulateBrowseNode(node, editControl.ClassId, contextBrowseNode, txtFilter.Text.Trim());
          }
  
          private void TreeBrowseNodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
          {
              if (e.Button == MouseButtons.Right)
              {
                  // Make sure the node that was right clicked is selected
                  treeBrowse.SelectedNode = e.Node;
              }
          }
  
          private void TreeBrowseBeforeExpand(object sender, TreeViewCancelEventArgs e)
          {
              var browseInfo = e.Node.Tag as EditorBrowseInfo;
              if (browseInfo == null) return;
  
              var builder = EditorFactory.Builders[browseInfo.SystemType];
              foreach (TreeNode node in e.Node.Nodes)
              {
                  browseInfo = node.Tag as EditorBrowseInfo;
                  if (browseInfo?.Id <= 0)
                      builder.PopulateBrowseNode(node, browseInfo.ClassId, contextBrowseNode, txtFilter.Text.Trim());
              }
          }
  
          private void TreeBrowseNodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
          {
              e.Node.OpenContentNode();
          }
  
          private void MnuCloseClick(object sender, EventArgs e)
          {
              var tabIndex = tabContent.SelectedIndex;
              if (tabIndex < 0) return;
              CloseTab(tabContent.SelectedTab, true);
  
              if (tabIndex > 0)
                  --tabIndex;
  
              if (tabContent.TabPages.Count > 0)
                  tabContent.SelectedIndex = tabIndex;
          }
  
          private void MnuCloseAllClick(object sender, EventArgs e)
          {
              CloseTabs(tabContent.TabPages, true);
          }
  
          private void MnuExitClick(object sender, EventArgs e)
          {
              if (CloseTabs(tabContent.TabPages, true))
                  Application.Exit();
          }
  
          private void MnuSaveClick(object sender, EventArgs e)
          {
              // Only save the active tab
              // To save all open content, see SaveAll
  
              if (tabContent.SelectedIndex >= 0)
              {
                  SaveTabPage(tabContent.SelectedTab);
              }
          }
  
          private void MnuSaveAll(object sender, EventArgs e)
          {
              foreach (TabPage tabPage in tabContent.TabPages)
              {
                  SaveTabPage(tabPage);
              }
          }
  
          private void TreeBrowseMouseMove(object sender, MouseEventArgs e)
          {
              if (DragNode == null)
                  return;
  
              if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
              {
                  treeBrowse.DoDragDrop(DragNode, DragDropEffects.Link);
              }
  
              DragNode = null;
          }
  
          private void TreeBrowseMouseDown(object sender, MouseEventArgs e)
          {
              DragNode = treeBrowse.GetNodeAt(e.Location);
  
              var browseInfo = DragNode?.Tag as EditorBrowseInfo;
              if (browseInfo != null && browseInfo.Id <= 0)
                  DragNode = null;
          }
  
          private void MnuFileDropDownOpening(object sender, EventArgs e)
          {
              mnuSave.Enabled = false;
              if (tabContent.SelectedIndex < 0) return;
  
              //BaseEditorControl editControl = tabContent.SelectedTab.Controls[0] as BaseEditorControl;
              mnuSave.Enabled = true;
          }
  
          private void ContextBrowseOpening(object sender, CancelEventArgs e)
          {
              var menuStrip = sender as ContextMenuStrip;
  
              var tv = menuStrip?.SourceControl as TreeView;
              if (tv == null || !tv.Equals(treeBrowse) || treeBrowse.SelectedNode == null) return;
  
              var browseInfo = treeBrowse.SelectedNode.Tag as EditorBrowseInfo;
              if (browseInfo == null) return;
  
              var builder = EditorFactory.Builders[browseInfo.SystemType];
              if (builder == null) return;
  
              mnuBrowseNewNode.Text = Resources.TEXT_NEW_NODE.Replace("{0}", builder.DisplayName);
              var topNode = treeBrowse.SelectedNode.Parent == null;
              mnuBrowseNewNode.Enabled = builder.HasEditor() && builder.HasCreate();
  
              mnuBrowseDeleteClass.Enabled = !topNode && treeBrowse.SelectedNode.Nodes.Count == 0 && builder.HasDelete();
              mnuBrowseRenameClass.Enabled = !topNode;
          }
  
          private void MnuBrowseNewNodeClick(object sender, EventArgs e)
          {
              // Create a new edit control based on the selected node 
              var browseInfo = treeBrowse.SelectedNode?.Tag as EditorBrowseInfo;
              if (browseInfo == null) return;
  
              var builder = EditorFactory.Builders[browseInfo.SystemType];
              if (builder == null || !builder.HasCreate()) return;
  
              var editControl = EditorFactory.Create(browseInfo.SystemType, browseInfo.ClassId);
              editControl.InitNew();
  
              // Throw the new edit control into its own tab
              CreateContentTab("[??] " + editControl.ControlName, editControl);
          }
  
          private void MnuBrowseNewClassClick(object sender, EventArgs e)
          {
              var browseInfo = treeBrowse.SelectedNode?.Tag as EditorBrowseInfo;
              if (browseInfo == null) return;
  
              var dbContext = Program.NinjectKernel.Get<IRealmDbContext>();
  
              var parentClass = dbContext.SystemClasses.FirstOrDefault(x => x.Id == browseInfo.ClassId);
              if (parentClass == null)
                  throw new EditorException($"Parent Class [{browseInfo.ClassId}] cannot be found.");
  
              var newClass = new SystemClass
              {
                  Id = dbContext.SystemClasses.Max(x => x.Id) + 1,
                  Name = "<New Class>",
                  SystemType = browseInfo.SystemType,
                  ParentClassId = parentClass.Id
              };
  
              dbContext.SystemClasses.Add(newClass);
              dbContext.SaveChanges();
  
              var classId = newClass.Id;
              if (classId <= 0)
              {
                  Program.Log.ErrorFormat($"Unable to create new Class {browseInfo.ClassId} for System Type {browseInfo.SystemType}");
                  return;
              }
  
              treeBrowse.SelectedNode.SetupBrowseTree(EditorFactory.Builders[browseInfo.SystemType], true,
                  txtFilter.Text.Trim());
  
              var nodes = treeBrowse.SelectedNode.Nodes.Find("class_" + classId, false);
              if (nodes.Length != 1) return;
  
              treeBrowse.SelectedNode = nodes[0];
              treeBrowse.LabelEdit = true;
              treeBrowse.SelectedNode.BeginEdit();
          }
  
          private void TreeBrowseBeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
          {
              var browseInfo = treeBrowse.SelectedNode.Tag as EditorBrowseInfo;
              if (browseInfo != null && browseInfo.Id > 0)
                  e.CancelEdit = true;
          }
  
          private void TreeBrowseAfterLabelEdit(object sender, NodeLabelEditEventArgs e)
          {
              if (treeBrowse.SelectedNode == null || e.Label == null) return;
              var browseInfo = treeBrowse.SelectedNode.Tag as EditorBrowseInfo;
              if (browseInfo == null) return;
  
              var dbContext = Program.NinjectKernel.Get<IRealmDbContext>();
              var foundClass = dbContext.SystemClasses.FirstOrDefault(x => x.Id == browseInfo.ClassId);
              if (foundClass == null) return;
  
  
gt:             if (e.Label.Length > 100)