context: using System;
  using System.IO;
  using System.IO.Compression;
  using System.Threading;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;
  
  namespace Server.Misc
  {
      public static class ArchivedSaves
      {
          public enum MergeType
          {
              Months,
              Days,
              Hours,
              Minutes
          }
  
          private static readonly string _DefaultDestination;
  
          private static string _Destination;
  
          public static string Destination
          {
              get
              {
                  if (string.IsNullOrWhiteSpace(_Destination) || _Destination.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
                      return _DefaultDestination;
  
                  return _Destination;
              }
              set
              {
                  if (_Destination == value)
                      return;
  
                  _Destination = value;
  
                  if (_Enabled)
                      Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: {0}", Destination);
              }
          }
  
          private static bool _Enabled;
  
          public static bool Enabled
          {
              get { return _Enabled; }
              set
              {
                  if (_Enabled == value)
                      return;
  
                  _Enabled = value;
  
                  var dest = _Enabled ? Destination : "Disabled";
  
                  Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: {0}", dest);
              }
          }
  
          public static bool Async { get; set; }
  
          public static TimeSpan ExpireAge { get; set; }
  
          public static MergeType Merge { get; set; }
  
          private static readonly List<IAsyncResult> _Tasks = new List<IAsyncResult>(0x40);
  
          private static readonly object _TaskRoot = ((ICollection)_Tasks).SyncRoot;
  
          private static readonly AutoResetEvent _Sync = new AutoResetEvent(true);
  
          private static readonly Action<string> _Pack = InternalPack;
          private static readonly Action<DateTime> _Prune = InternalPrune;
  
          public static int PendingTasks
          {
              get
              {
                  lock (_TaskRoot)
                      return _Tasks.Count - _Tasks.RemoveAll(t => t.IsCompleted);
              }
          }
  
          static ArchivedSaves()
          {
              _DefaultDestination = Path.Combine(Core.BaseDirectory, "Backups", "Archived");
  
              _Destination = Config.Get("AutoSave.ArchivesPath", (string)null);
  
              Enabled = Config.Get("AutoSave.ArchivesEnabled", false);
  
              Async = Config.Get("AutoSave.ArchivesAsync", true);
  
              ExpireAge = Config.Get("AutoSave.ArchivesExpire", TimeSpan.Zero);
  
              Merge = Config.GetEnum("AutoSave.ArchivesMerging", MergeType.Minutes);
          }
  
          [CallPriority(int.MaxValue - 10)]
          public static void Configure()
          {
              EventSink.Shutdown += Wait;
              EventSink.WorldSave += Wait;
          }
  
          public static bool Process(string source)
          {
              if (!Enabled)
                  return false;
  
              if (!Directory.Exists(Destination))
                  Directory.CreateDirectory(Destination);
  
              if (ExpireAge > TimeSpan.Zero)
                  BeginPrune(DateTime.UtcNow - ExpireAge);
  
              if (!string.IsNullOrWhiteSpace(source))
                  BeginPack(source);
  
              return true;
          }
  
          private static void Wait(WorldSaveEventArgs e)
          {
              WaitForTaskCompletion();
          }
  
          private static void Wait(ShutdownEventArgs e)
          {
              WaitForTaskCompletion();
          }
  
          private static void WaitForTaskCompletion()
          {
              if (!Core.Crashed && !Core.Closing)
                  return;
  
              var pending = PendingTasks;
  
              if (pending <= 0)
                  return;
  
              Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: Waiting for {0:#,0} pending tasks...", pending);
  
              while (pending > 0)
              {
                  _Sync.WaitOne(10);
  
                  pending = PendingTasks;
              }
  
              Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: All tasks completed.");
          }
  
          private static void InternalPack(string source)
          {
              Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: Packing started...");
  
              var sw = Stopwatch.StartNew();
  
              try
              {
                  var now = DateTime.Now;
  
                  var ampm = now.Hour < 12 ? "AM" : "PM";
                  var hour12 = now.Hour > 12 ? now.Hour - 12 : now.Hour <= 0 ? 12 : now.Hour;
  
                  string date;
  
                  switch (Merge)
                  {
                      case MergeType.Months: date = string.Format("{0}-{1}", now.Month, now.Year); break;
                      case MergeType.Days: date = string.Format("{0}-{1}-{2}", now.Day, now.Month, now.Year); break;
                      case MergeType.Hours: date = string.Format("{0}-{1}-{2} {3:D2} {4}", now.Day, now.Month, now.Year, hour12, ampm); break;
                      case MergeType.Minutes: default: date = string.Format("{0}-{1}-{2} {3:D2}-{4:D2} {5}", now.Day, now.Month, now.Year, hour12, now.Minute, ampm); break;
                  }
  
                  var file = string.Format("{0} Saves ({1}).zip", ServerList.ServerName, date);
                  var dest = Path.Combine(Destination, file);
  
                  try { File.Delete(dest); }
                  catch { }
  
                  ZipFile.CreateFromDirectory(source, dest, CompressionLevel.Optimal, false);
              }
              catch { }
  
              try { Directory.Delete(source, true); }
              catch { }
  
              sw.Stop();
  
              Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: Packing done in {0:F1} seconds.", sw.Elapsed.TotalSeconds);
          }
  
          private static void BeginPack(string source)
          {
              // Do not use async packing during a crash state or when closing.
              if (!Async || Core.Crashed || Core.Closing)
              {
                  _Pack.Invoke(source);
                  return;
              }
  
              _Sync.Reset();
  
              var t = _Pack.BeginInvoke(source, EndPack, source);
  
              lock (_TaskRoot)
                  _Tasks.Add(t);
          }
  
          private static void EndPack(IAsyncResult r)
          {
              _Pack.EndInvoke(r);
  
              lock (_TaskRoot)
                  _Tasks.Remove(r);
  
              _Sync.Set();
          }
  
          private static void InternalPrune(DateTime threshold)
          {
              if (!Directory.Exists(Destination))
                  return;
  
              Utility.WriteConsoleColor(ConsoleColor.Cyan, "Archives: Pruning started...");
  
              var sw = Stopwatch.StartNew();
  
              try
              {
  
gt:                 var root = new DirectoryInfo(Destination);