context: using System;
  using System.Collections.Generic;
  using System.Collections;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Security.Cryptography;
  using System.Text;
  using System.Threading.Tasks;
  using Cirrious.MvvmCross.Plugins.Sqlite;
  using Ninject;
  using TagLib;
  using WaveBox.Core;
  using WaveBox.Core.Extensions;
  using WaveBox.Core.Model;
  using WaveBox.Core.Model.Repository;
  using WaveBox.Core.OperationQueue;
  using WaveBox.Static;
  
  namespace WaveBox.FolderScanning {
      public class FolderScanOperation : AbstractOperation {
          private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  
          public override string OperationType { get { return String.Format("FolderScanOperation: {0}", FolderPath); } }
  
          private string folderPath;
          public string FolderPath { get { return folderPath; } }
  
          int testNumberOfFoldersInserted = 0;
          Stopwatch testTotalScanTime = new Stopwatch();
          Stopwatch testFolderObjCreateTime = new Stopwatch();
          Stopwatch testGetDirectoriesTime = new Stopwatch();
          Stopwatch testMediaItemNeedsUpdatingTime = new Stopwatch();
          Stopwatch testIsExtensionValidTime = new Stopwatch();
  
          public FolderScanOperation(string path, int delayMilliSeconds) : base(delayMilliSeconds) {
              folderPath = path;
          }
  
          public override void Start() {
              testTotalScanTime.Start();
              this.ProcessFolder(FolderPath);
              testTotalScanTime.Stop();
  
              logger.IfInfo("---------------- FOLDER SCAN ----------------");
              logger.IfInfo("total scan time: " + testTotalScanTime.ElapsedMilliseconds + "ms");
              logger.IfInfo("---------------------------------------------");
              logger.IfInfo("folders inserted: " + testNumberOfFoldersInserted);
              logger.IfInfo("folder object create time: " + testFolderObjCreateTime.ElapsedMilliseconds + "ms");
              logger.IfInfo("get directories time: " + testGetDirectoriesTime.ElapsedMilliseconds + "ms");
              logger.IfInfo("media file needs updating time: " + testMediaItemNeedsUpdatingTime.ElapsedMilliseconds + "ms");
              logger.IfInfo("extension valid check time: " + testIsExtensionValidTime.ElapsedMilliseconds + "ms");
              long total = testFolderObjCreateTime.ElapsedMilliseconds + testGetDirectoriesTime.ElapsedMilliseconds + testMediaItemNeedsUpdatingTime.ElapsedMilliseconds + testIsExtensionValidTime.ElapsedMilliseconds;
              logger.IfInfo("total: " + total + "ms = " + total / 1000 + "s");
              logger.IfInfo("---------------------------------------------");
          }
  
          public void ProcessFolder(int folderId) {
              Folder folder = Injection.Kernel.Get<IFolderRepository>().FolderForId(folderId);
              this.ProcessFolder(folder.FolderPath);
          }
  
          public void ProcessFolder(string folderPath) {
              if (isRestart) {
                  return;
              }
  
              try {
                  // if the file is a directory
                  if (Directory.Exists(folderPath)) {
                      testFolderObjCreateTime.Start();
                      Folder topFolder = Injection.Kernel.Get<IFolderRepository>().FolderForPath(folderPath);
                      testFolderObjCreateTime.Stop();
  
                      if (topFolder.FolderId == null) {
                          testNumberOfFoldersInserted++;
                          topFolder.InsertFolder(false);
                      }
  
                      testGetDirectoriesTime.Start();
                      string[] directories = Directory.GetDirectories(folderPath);
                      testGetDirectoriesTime.Stop();
                      foreach (string subfolder in directories) {
                          if (!subfolder.Contains(".AppleDouble")) {
                              testFolderObjCreateTime.Start();
                              Folder folder = Injection.Kernel.Get<IFolderRepository>().FolderForPath(subfolder);
                              testFolderObjCreateTime.Stop();
  
                              // if the folder isn't already in the database, add it.
                              if (folder.FolderId == null) {
                                  testNumberOfFoldersInserted++;
                                  folder.InsertFolder(false);
                              }
  
                              ProcessFolder(subfolder);
                          }
                      }
  
                      // NOTE: File processing was previously using Parallel.ForEach, but it has been changed to be
                      // single-threaded because:
                      //   1) Parallel scanning has the potential to cause race conditions on this code
                      //   2) Thanks to using "PRAGMA synchronous = OFF" in SQLite, scanning is much faster
                      //   3) Single-threaded scanning may be faster on HDDs, due to less thrashing of disk as the disk
                      //      seeks to the next file
                      // In the future, we may revisit the issue, and perhaps detect if media lies on a SSD, allowing
                      // us to benefit more from parallel scanning.
                      foreach (string file in Directory.GetFiles(folderPath)) {
                          ProcessFile(file, topFolder.FolderId);
                      }
                  }
              } catch (FileNotFoundException e) {
                  logger.Error("\"" + folderPath + "\" : Directory does not exist. " + e);
              } catch (DirectoryNotFoundException e) {
                  logger.Error("\"" + folderPath + "\" : Directory does not exist. " + e);
              } catch (IOException e) {
                  logger.Error("\"" + folderPath + "\" : " + e);
              } catch (UnauthorizedAccessException e) {
                  logger.Error("\"" + folderPath + "\" : Access denied. " + e);
              } catch (Exception e) {
                  logger.Error("\"" + folderPath + "\" : Error checking to see if the file was a directory: " + e);
              }
          }
  
          public void ProcessFile(string file, int? folderId) {
              if (isRestart) {
                  return;
              }
  
              try {
                  ItemType type = Injection.Kernel.Get<IItemRepository>().ItemTypeForFilePath(file);
  
                  if (type == ItemType.Song || type == ItemType.Video) {
                      testMediaItemNeedsUpdatingTime.Start();
                      bool isNew = true;
                      int? itemId = null;
                      bool needsUpdating = FileNeedsUpdating(file, folderId, out isNew, out itemId);
                      testMediaItemNeedsUpdatingTime.Stop();
  
                      if (needsUpdating) {
                          logger.IfInfo("Updating: " + file);
  
                          TagLib.File f = null;
                          try {
                              f = TagLib.File.Create(file);
                          } catch (Exception e) {
                              // Tag is invalid in some way
                              if (e is TagLib.CorruptFileException || e is ArgumentOutOfRangeException) {
                                  logger.Error(file + " has a corrupt tag and will not be inserted.");
                                  return;
                              } else {
                                  // Other exceptions
                                  logger.Error("Error processing file " + file + ": " + e);
                                  return;
                              }
                          }
  
                          if (f == null) {
                              // Must be something not supported by TagLib-Sharp
                              logger.IfInfo(file + " is not supported by taglib and will not be inserted.");
                          } else {
                              if (type == ItemType.Song) {
                                  // It's a song!  Do yo thang.
                                  if (isNew) {
                                      CreateSong(file, folderId, f).InsertMediaItem();
                                  } else if (itemId != null) {
                                      var oldSong = Injection.Kernel.Get<ISongRepository>().SongForId((int)itemId);
                                      var newSong = CreateSong(file, folderId, f);
                                      newSong.ItemId = oldSong.ItemId;
                                      newSong.InsertMediaItem();
                                  }
                              } else if (type == ItemType.Video) {
                                  if (isNew) {
                                      CreateVideo(file, folderId, f).InsertMediaItem();
                                  } else if (itemId != null) {
                                      Injection.Kernel.Get<IVideoRepository>().VideoForId((int)itemId).InsertMediaItem();
                                  }
                              }
                          }
  
                          // Dispose of file as it is no longer needed
                          f.Dispose();
                      }
                  } else if (type == ItemType.Art) {
                      if (ArtFileNeedsUpdating(file)) {
                          Folder folder = Injection.Kernel.Get<IFolderRepository>().FolderForId((int)folderId);
                          IList<Album> albumsForFolder = Injection.Kernel.Get<IFolderRepository>().AlbumsForFolderId((int)folderId);
  
                          // Find the old art id, if it exists
                          int? oldArtId = folder.ArtId;
                          int? newArtId = CreateArt(file).ArtId;
  
                          if ((object)oldArtId == null) {
                              logger.IfInfo("Adding new art for folderId: " + folderId);
  
                              // Insert the relationship
                              Injection.Kernel.Get<IArtRepository>().UpdateArtItemRelationship(newArtId, folderId, true);
  
                              // Update all matching albums, but only if they don't already have art (prefer song embedded for albums)
                              foreach (Album album in albumsForFolder) {
                                  // Insert the relationship for the album
                                  Injection.Kernel.Get<IArtRepository>().UpdateArtItemRelationship(newArtId, album.AlbumId, false);
                              }
                          } else {
                              Art oldArt = Injection.Kernel.Get<IArtRepository>().ArtForId((int)oldArtId);
  
                              // Check if the previous folder art was actually from embedded tag art
                              if ((object)oldArt.FilePath == null) {
                                  // This was embedded tag art, so only update the folder's relationship
                                  bool updated = Injection.Kernel.Get<IArtRepository>().UpdateArtItemRelationship(newArtId, folder.FolderId, true);
                                  logger.IfInfo(String.Format("It was embedded art, {0}, newArtId: {1}, folderId: {2}", updated, newArtId, folder.FolderId));
  
                                  // Update all matching albums, but only if they don't already have art (prefer song embedded for albums)
                                  foreach (Album album in albumsForFolder) {
                                      // Insert the relationship for the album
                                      Injection.Kernel.Get<IArtRepository>().UpdateArtItemRelationship(newArtId, album.AlbumId, false);
                                  }
                              } else {
                                  // Update any existing references, that would include both this folder
                                  // and any songs or albums that were using this art in lieu of embedded art
                                  Injection.Kernel.Get<IArtRepository>().UpdateItemsToNewArtId(oldArtId, newArtId);
                              }
                          }
  
                          // Add this art to any media items in this folder which have no art.
                          var items = folder.ListOfMediaItems();
  
                          foreach (MediaItem m in items) {
                              if (m.ArtId == null) {
                                  logger.IfInfo("Updating art id for item " + m.ItemId + ". (" + (m.ArtId == null ? "null" : m.ArtId.ToString()) + " -> " + newArtId + ")");
                                  Injection.Kernel.Get<IArtRepository>().UpdateArtItemRelationship(newArtId, m.ItemId, false);
                              }
                          }
                      }
                  }
              } catch (FileNotFoundException e) {
                  logger.Error("\"" + file + "\" : File does not exist. " + e);
              } catch (DirectoryNotFoundException e) {
                  logger.Error("\"" + file + "\" : Directory does not exist. " + e);
              } catch (IOException e) {
                  logger.Error("\"" + file + "\" : IO error. " + e);
              } catch (UnauthorizedAccessException e) {
  
gt:                 logger.Error("\"" + file + "\" : Access denied. " + e);