context: // <copyright file="DatabaseDisplayData.cs" company="APE Engineering GmbH">Copyright (c) APE Engineering GmbH. All rights reserved.</copyright>
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.IO;
  using System.Linq;
  using System.Threading.Tasks;
  using System.Windows;
  using System.Windows.Forms;
  using System.Windows.Input;
  using APE.CodeGeneration.Attributes;
  using APE.PostgreSQL.Teamwork.Model;
  using APE.PostgreSQL.Teamwork.Model.Setting;
  using APE.PostgreSQL.Teamwork.Model.Templates;
  using APE.PostgreSQL.Teamwork.ViewModel.Exceptions;
  using APE.PostgreSQL.Teamwork.ViewModel.Postgres;
  using APE.PostgreSQL.Teamwork.ViewModel.TestHelper;
  using log4net;
  using Microsoft.VisualBasic;
  using Npgsql;
  
  namespace APE.PostgreSQL.Teamwork.ViewModel
  {
      /// <summary>
      /// Contains one database and the corresponding commands and
      /// notify properties.
      /// </summary>
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Enabled", false)]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "EditMode", false)]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "ShowDetails", false)]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Exporting", false, "Indicates that the database is exporting at the moment")]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Importing", false, "Indicates that the database is importing at the moment")]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Testing", false, "Indicates that the database is testing at the moment")]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Resetting", false, "Indicates that the database is resetting at the moment")]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Undoing", false, "Indicates that the database is undoing changes at the moment")]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "Error", false)]
      [NotifyProperty(AccessModifier.Public, typeof(bool), "ImportableFilesFound", false)]
      [NotifyProperty(AccessModifier.PublicGetPrivateSet, typeof(bool), "CanCreateMinor", false)]
      [AllowNullNotifyProperty(AccessModifier.Public, typeof(string), "SelectedSchema")]
      [NotifyProperty(typeof(string), "ErrorMessage")]
      [AllowNullNotifyProperty(typeof(Database), "Database")]
      [NotifyProperty(AccessModifier.Public, typeof(DatabaseVersion), "TargetVersion")]
      [AllowNullNotifyProperty(AccessModifier.Public, typeof(List<DatabaseVersion>), "Versions")]
      [AllowNullNotifyProperty(typeof(ObservableCollection<SQLFileDisplayData>), "ApplicableSQLFiles")]
      [CtorParameter(typeof(IConnectionManager))]
      [CtorParameter(typeof(IFileSystemAccess))]
      [CtorParameter(typeof(IProcessManager))]
      [CtorParameter(typeof(IDifferenceCreator))]
      [CtorParameter(typeof(ISQLFileTester))]
      [CtorParameter(AccessModifier.Public, typeof(int), "Id", false, "id of the database for which the name and path are loaded")]
      [NotifyPropertySupport]
      public partial class DatabaseDisplayData
      {
          private const string DumpExtension = "SQL Dump (*" + SQLTemplates.DumpFile + ")|*" + SQLTemplates.DumpFile;
  
          private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  
          private bool dataInitialized = false;
  
          /// <summary>
          /// Design time constructor.
          /// </summary>
          public DatabaseDisplayData()
          {
              this.ShowDetails = true;
              this.ErrorMessage = "Test error message!";
          }
  
          /// <summary>
          /// Event is called when the <see cref="DatabaseDisplayData"/> is removed.
          /// </summary>
          public event EventHandler<EventArgs> Removed;
  
          // todo db make name notify property and make Database.Name/Path a normal property and use only these in GUI
          public string Name
          {
              get
              {
                  if (this.Database == null)
                  {
                      return DatabaseSetting.GetDatabaseSetting(this.Id).Name;
                  }
  
                  return this.Database.Name;
              }
          }
  
          public string Path
          {
              get
              {
                  if (this.Database == null)
                  {
                      return DatabaseSetting.GetDatabaseSetting(this.Id).Path;
                  }
  
                  return this.Database.Path;
              }
          }
  
          public ObservableCollection<string> IgnoredSchemas
          {
              get
              {
                  if (this.Database == null)
                      return new ObservableCollection<string>(DatabaseSetting.GetDatabaseSetting(this.Id).IgnoredSchemas);
  
                  return this.Database.IgnoredSchemas;
              }
          }
  
          /// <summary>
          /// Gets or sets a bool which indicates if the <see cref="Database"/> was auto expanded (not expanded by the user). Databases get
          /// auto expanded if the GUI has enough room or get shrunk if the GUI gets smaller.
          /// </summary>
          public bool AutoExpanded { get; set; }
  
          // Database Commands
          public ICommand ExportCommand { get; private set; }
  
          public ICommand OpenImportWindowCommand { get; private set; }
  
          public ICommand CreateDumpCommand { get; private set; }
  
          public ICommand ReduceVersionCommand { get; private set; }
  
          public ICommand TestCommand { get; private set; }
  
          public ICommand OpenPathCommand { get; private set; }
  
          public ICommand EditCommand { get; private set; }
  
          public ICommand SaveCommand { get; private set; }
  
          public ICommand EditPathCommand { get; private set; }
  
          public ICommand RemoveCommand { get; private set; }
  
          public ICommand ResetCommand { get; private set; }
  
          public ICommand UndoCommand { get; set; }
  
          public ICommand ImportCommand { get; set; }
  
          public ICommand CreateDatabaseCommand { get; set; }
  
          public ICommand ExpandCommand { get; private set; }
  
          public ICommand AddSchemaCommand { get; private set; }
  
          public ICommand RemoveSchemaCommand { get; private set; }
  
          public ICommand CreateMinorCommand { get; private set; }
  
          public ICommand DisconnectCommand { get; private set; }
  
          /// <summary>
          /// Updates the version and the not applied files.
          /// </summary>
          /// <param name="force">Forces a refresh of the SQL Files on directory level.</param>
          public void UpdateData(bool force = false)
          {
              if (this.Resetting || this.Error)
              {
                  return;
              }
  
              try
              {
                  this.Database.UpdateData(force);
                  this.Error = false;
                  this.UpdateApplicableSQLFiles();
  
                  // add all available versions to which the user can up or downgrade
                  var versions = new List<DatabaseVersion>
                  {
                      DatabaseVersion.StartVersion, // undo all
                  };
  
                  versions.AddRange(this.Database.DiffFiles.Select(f => f.Version));
  
                  // only update versions if they are changed
                  if (this.Versions == null || versions == null || !versions.SequenceEqual(this.Versions))
                  {
                      this.Versions = versions;
                  }
  
                  if (this.Database.CurrentVersion < this.Database.LastApplicableVersion)
                  {
                      this.CanCreateMinor = true;
                      this.ImportableFilesFound = true;
                  }
                  else
                  {
                      this.CanCreateMinor = false;
                      this.ImportableFilesFound = false;
                  }
  
                  // activate after ImportableFilesFound is set
                  if (!this.dataInitialized)
                  {
                      this.TargetVersion = this.Database.LastApplicableVersion;
                      this.dataInitialized = true;
                      this.Enabled = true;
                  }
              }
              catch (NpgsqlException ex)
              {
                  // could not connect to the database
                  this.Error = true;
                  this.ErrorMessage = ex.Message;
              }
          }
  
          /// <summary>
          /// Updates the database to the given main version by executing diffs or undo diffs.
          /// </summary>
          /// <param name="targetVersion">The target version.</param>
          /// <exception cref="TeamworkConnectionException">Is thrown when an error occurred while executing the SQL Statements.</exception>
          public void UpdateToVersion(DatabaseVersion targetVersion)
          {
              foreach (var applicableFile in this.ApplicableSQLFiles)
              {
                  applicableFile.ExecuteInTransaction();
                  this.UpdateData();
              }
          }
  
          /// <summary>
          /// Resets the target version to its default value (max applicable version).
          /// </summary>
          public void ResetTargetVersion()
          {
              this.TargetVersion = this.Database.LastApplicableVersion;
          }
  
          public void ToggleExpansion(bool autoExpand = false)
          {
              // user has shown the detail so we do not automatically close it
              if (autoExpand && this.AutoExpanded == false)
              {
                  return;
              }
  
              this.AutoExpanded = autoExpand;
              this.ShowDetails = !this.ShowDetails;
          }
  
          public void ExportWithoutErrorHandling(DatabaseVersion newVersion)
          {
              this.Database.Export(
                  newVersion,
                  SettingsManager.Get().Setting.PgDumpLocation,
                  SettingsManager.Get().Setting.Host,
                  SettingsManager.Get().Setting.Id,
                  SettingsManager.Get().Setting.Password,
                  SettingsManager.Get().Setting.Port);
  
              // open the diff files so user can verify them
              var diff = new SQLFileDisplayData(this.Database.DiffFiles.SingleOrDefault(f => f.Version == newVersion));
              var undoDiff = new SQLFileDisplayData(this.Database.UndoDiffFiles.SingleOrDefault(f => f.Version == newVersion));
  
              if (SettingsManager.Get().Setting.OpenFilesInDefaultApplication)
              {
                  this.processManager.Start(diff.SQLFile.Path);
                  this.processManager.Start(undoDiff.SQLFile.Path);
              }
              else
              {
                  BaseViewModel.OpenExportWindow(diff, undoDiff);
              }
          }
  
          /// <summary>
          ///  Returns a string that represents the current object.
          /// </summary>
          [return: NullGuard.AllowNull]
          public override string ToString()
          {
              return this.Database.Name;
          }
  
          /// <summary>
          /// Exports the database and shows a message box to the user if a error occurred.
          /// </summary>
          private async void Export()
          {
              this.Exporting = true;
              try
              {
                  this.ExportWithoutErrorHandling(this.Database.CurrentVersion.Next());
              }
              catch (Exception ex)
              {
                  var title = "Error while exporting database";
                  var message = ex.Message;
                  var buttons = MessageBoxButton.OK;
  
                  if (ex is FileNotFoundException)
                  {
                      message = $"{message}: {((FileNotFoundException)ex).FileName}";
                  }
                  else if (ex is TeamworkException && !((TeamworkException)ex).ShowAsError)
                  {
                      title = "Info";
                  }
                  else if (ex is TeamworkTestException)
                  {
                      message += " Do you want to open the diff file with the error?";
                      buttons = MessageBoxButton.YesNo;
                  }
  
                  Log.Warn(string.Format("Error while exporting database {0}", this.Database.Name), ex);
                  var messageBox = MainWindowViewModel.GetMessageBox(
                          $"Message: {message}",
                          title,
                          buttons);
                  var result = await MainWindowViewModel.ShowDialog(messageBox);
  
                  // yes applies if the file should be opened
                  if (result == MaterialMessageBoxResult.Yes && ex is TeamworkTestException testException)
                  {
                      this.processManager.Start(testException.FileWithExceptionSql);
                  }
              }
  
              this.Exporting = false;
          }
  
          partial void DatabaseDisplayDataCtor()
          {
              this.targetVersion = DatabaseVersion.StartVersion;
              this.InitializeCommands();
              this.AutoExpanded = true;
              this.ConnectDatabase();
          }
  
          private void ConnectDatabase()
          {
              // first check if database can be reached
              if (this.connectionManager.CheckConnection(this.Name))
              {
                  this.Database = new Database(
                      this.Name,
                      DatabaseSetting.GetDatabaseSetting(this.Id).Path,
                      DatabaseSetting.GetDatabaseSetting(this.Id).IgnoredSchemas,
                      this.connectionManager,
                      this.fileSystemAccess,
                      this.processManager,
                      this.differenceCreator,
                      this.sQLFileTester,
                      initializeData: false);
                  this.Error = false;
                  this.ErrorMessage = string.Empty;
              }
              else
              {
                  // could not connect to the database
                  this.ErrorMessage = $"Connection to Database '{this.Name}' cannot be established!";
                  this.Error = true;
              }
          }
  
          partial void EnabledAfterSet()
          {
              // cant set enabled to true if an error occured
              if (this.Enabled && this.Error)
                  this.Enabled = false;
          }
  
          partial void ErrorAfterSet()
          {
              // disable if error occured
              if (this.Error)
                  this.Enabled = false;
          }
  
          partial void TargetVersionAfterSet()
          {
              this.UpdateApplicableSQLFiles();
          }
  
          /// <summary>
          /// Sets the applicable SQL files.
          /// </summary>
          private void UpdateApplicableSQLFiles()
          {
              var applicableSQLFiles = new ObservableCollection<SQLFileDisplayData>();
              foreach (var file in this.Database.GetToBeAppliedSQLFiles(this.TargetVersion))
              {
                  applicableSQLFiles.Add(new SQLFileDisplayData(file));
              }
  
              if (this.ApplicableSQLFiles == null
                  || this.ApplicableSQLFiles.Count != applicableSQLFiles.Count
                  || applicableSQLFiles.Any(f => this.ApplicableSQLFiles.FirstOrDefault((oldFile) => oldFile.SQLFile.Version.Full == f.SQLFile.Version.Full) == null))
              {
                  this.ApplicableSQLFiles = applicableSQLFiles;
              }
          }
  
          /// <summary>
          /// Removes the given database and all of its settings.
          /// </summary>
          /// <remarks>Does not delete the Postgres SQL database.</remarks>
          private async void Remove()
          {
              var messageBox = MainWindowViewModel.GetMessageBox(
                  "Are you sure you want to remove the database? This will not delete the Database but will remove it from the list of the APE.PostgreSQL.Teamwork tool",
                  "Remove the database",
                  MessageBoxButton.YesNo);
              var result = await MainWindowViewModel.ShowDialog(messageBox);
  
              if (result == MaterialMessageBoxResult.Yes)
              {
                  DatabaseSetting.Remove(this.Id);
                  this.Removed?.Invoke(this, EventArgs.Empty);
              }
          }
  
          /// <summary>
          /// Saves the properties of the database in the settings file.
          /// </summary>
          private void Save()
          {
              var newDatabases = DatabaseSetting.GetDatabaseSettings();
              newDatabases.Single(d => d.Id == this.Id).Name = this.Database.Name;
              newDatabases.Single(d => d.Id == this.Id).Path = this.Database.Path;
              newDatabases.Single(d => d.Id == this.Id).IgnoredSchemas = this.Database.IgnoredSchemas.ToList();
              SettingsManager.Get().Setting.DatabaseSettings = newDatabases;
              SettingsManager.Get().Save();
              this.EditMode = false;
          }
  
          /// <summary>
          /// Shows a <see cref="FolderBrowserDialog"/> to the user and saves the selected path as the new database path.
          /// </summary>
          private void EditPath()
          {
              // todo db implement material folder browser
              var dialog = new FolderBrowserDialog()
              {
                  ShowNewFolderButton = true,
                  SelectedPath = this.Database.Path,
              };
  
              if (dialog.ShowDialog() != DialogResult.OK)
              {
                  return;
              }
  
              this.Database.Path = dialog.SelectedPath;
          }
  
          /// <summary>
          /// Initializes all commands.
          /// </summary>
          private void InitializeCommands()
          {
              this.ExpandCommand = new RelayCommand(() => this.ToggleExpansion(false));
              this.OpenPathCommand = new RelayCommand(async () =>
              {
                  if (this.fileSystemAccess.DirectoryExists(this.Database.Path))
                  {
                      this.processManager.Start(this.Database.Path);
                  }
                  else
                  {
                      var messageBox = BaseViewModel.GetMessageBox(
                          string.Format("Error while trying to open path. Possibly the directory {0} was deleted or the folder was renamed.", this.Database.Path),
                          "Directory not found.",
                          MessageBoxButton.OK);
                      await BaseViewModel.ShowDialog(messageBox);
                  }
              });
  
gt:             this.SaveCommand = new RelayCommand(() => this.Save());