/// <summary>
 /// Initializes a new instance of the <see cref="INTV.LtoFlash.Model.LfsUpdateOperation"/> class.
 /// </summary>
 /// <param name="updateOperation">The nature of the update operation.</param>
 public LfsUpdateOperation(LfsUpdateOperation updateOperation)
 {
     FileSystem  = updateOperation.FileSystem;
     Operation   = updateOperation.Operation;
     Directories = new HashSet <byte>(updateOperation.Directories);
     Files       = new HashSet <ushort>(updateOperation.Files);
     Forks       = new HashSet <ushort>(updateOperation.Forks);
     Failures    = new HashSet <LfsOperationFailure>();
 }
        /// <summary>
        /// Merge the specified operation with this one.
        /// </summary>
        /// <param name="operation">The operation whose entries are to be merged into this one.</param>
        /// <returns>The result of the merge.</returns>
        public LfsUpdateOperation Merge(LfsUpdateOperation operation)
        {
            var mergedOperation = new LfsUpdateOperation(this);

            mergedOperation.Operation |= operation.Operation;
            mergedOperation.Forks.UnionWith(operation.Forks);
            mergedOperation.Files.UnionWith(operation.Files);
            mergedOperation.Directories.UnionWith(operation.Directories);
            mergedOperation.Failures.UnionWith(operation.Failures);
            return(mergedOperation);
        }
 /// <summary>
 /// Removes elements from this operation that are included in the given operation.
 /// </summary>
 /// <param name="update">An operation whose entries are to be removed from this instance's entries.</param>
 public void Prune(LfsUpdateOperation update)
 {
     if (!object.ReferenceEquals(this, update))
     {
         Forks.ExceptWith(update.Forks);
         Files.ExceptWith(update.Files);
         Directories.ExceptWith(update.Directories);
         if (!Forks.Any() && !Files.Any() && !Directories.Any())
         {
             Operation = LfsOperations.None;
         }
     }
 }
        // NOTE: The specific task data (ExecuteDeviceCommandAsyncTaskData.Data) must be a Tuple<MenuLayout, bool, bool>.
        // The first Boolean value indicates whether the operation should attempt to force the removal of menu position data.
        // The second Boolean value indicates whether the operation should update the root file's names (device name, owner).
        private static void SyncHostToDevice(AsyncTaskData taskData)
        {
            var data   = (ExecuteDeviceCommandAsyncTaskData)taskData;
            var device = data.Device;

            data.Task.UpdateTaskProgress(0, Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ComputingChangesProgress);
            var currentDirtyFlags = GetDirtyFlags.Instance.Execute <LfsDirtyFlags>(device.Port, data);
            var deviceFileSystem  = DownloadFileSystemTables.Instance.Execute <FileSystem>(device.Port, data);

            deviceFileSystem.Status = currentDirtyFlags;
            if (data.AcceptCancelIfRequested())
            {
                return;
            }

            var hostData              = (Tuple <MenuLayout, bool, bool>)data.Data;
            var hostFileSystem        = hostData.Item1.FileSystem;
            var resetSaveMenuPosition = hostData.Item2;

            if (resetSaveMenuPosition)
            {
                hostFileSystem.ForceRemovalOfMenuPositionData(deviceFileSystem);
            }
            hostFileSystem.SuppressRootFileNameDifferences(device);
            hostFileSystem.PopulateSaveDataForksFromDevice(deviceFileSystem);

            // The first pass gathers all differences, including errors.
            var allDifferencesWithErrors = hostFileSystem.CompareTo(deviceFileSystem, device, true);

#if USE_SMART_COPY
            // Now, use a clone of the original file system to compute the actual work to do, and use the failures for a post-op error report.
            var hostFileSystemWorkingCopy = hostFileSystem.Clone();

            var ignoreRootFileNames = !hostData.Item3;
            if (ignoreRootFileNames)
            {
                hostFileSystemWorkingCopy.Files[GlobalFileTable.RootDirectoryFileNumber].ShortName = deviceFileSystem.Files[GlobalFileTable.RootDirectoryFileNumber].ShortName;
                hostFileSystemWorkingCopy.Files[GlobalFileTable.RootDirectoryFileNumber].LongName  = deviceFileSystem.Files[GlobalFileTable.RootDirectoryFileNumber].LongName;
            }

            var partialErrors  = hostFileSystemWorkingCopy.CleanUpInvalidEntries(deviceFileSystem, allDifferencesWithErrors, FileSystemHelpers.ShouldRemoveInvalidEntry, ShouldIncludeError);
            var allDifferences = hostFileSystemWorkingCopy.CompareTo(deviceFileSystem, device, true);
#else
            // This will try to copy all ROMs, even incompatible ones, and should eventually be removed.
            var hostFileSystemWorkingCopy = hostFileSystem;
            var partialErrors             = allDifferencesWithErrors.GetAllFailures(ShouldIncludeError);
            var allDifferences            = allDifferencesWithErrors;
#endif // USE_SMART_COPY

            var succeeded    = true;
            var phaseNumber  = 0;
            var updateString = string.Empty;

            // Run any delete operations...
            var total       = allDifferences.DirectoryDifferences.ToDelete.Count + allDifferences.FileDifferences.ToDelete.Count + allDifferences.ForkDifferences.ToDelete.Count + allDifferences.ForkDifferences.ToUpdate.Count;
            var numComplete = 0;
            if (data.AcceptCancelIfRequested())
            {
                return;
            }
            if (total > 0)
            {
                ++phaseNumber;

                if (!data.CancelRequsted && succeeded && allDifferences.DirectoryDifferences.ToDelete.Any())
                {
                    var deleteOpData = new DeleteOperationData(data, LfsEntityType.Directory)
                    {
                        Factory              = (gdn) => DeleteFolder.Create((byte)(uint)gdn[0]),
                        UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                        UpdateTitleInfo      = Resources.Strings.LfsOperation_Remove,
                        UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_DeletingItemProgressFormat,
                        UpdateProgressInfo   = Resources.Strings.DirectoriesInSentence,
                        Phase = phaseNumber, Total = total, Current = numComplete
                    };
                    currentDirtyFlags |= DeleteEntries(allDifferences.DirectoryDifferences.ToDelete, deleteOpData, currentDirtyFlags, deviceFileSystem);
                    numComplete        = deleteOpData.Current;
                    succeeded          = data.Succeeded;
                }

                if (!data.CancelRequsted && succeeded && allDifferences.FileDifferences.ToDelete.Any())
                {
                    var deleteOpData = new DeleteOperationData(data, LfsEntityType.File)
                    {
                        Factory              = (gfn) => DeleteFile.Create((ushort)(uint)gfn[0]),
                        UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                        UpdateTitleInfo      = Resources.Strings.LfsOperation_Remove,
                        UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_DeletingItemProgressFormat,
                        UpdateProgressInfo   = Resources.Strings.FilesInSentence,
                        Phase = phaseNumber, Total = total, Current = numComplete
                    };
                    currentDirtyFlags |= DeleteEntries(allDifferences.FileDifferences.ToDelete, deleteOpData, currentDirtyFlags, deviceFileSystem);
                    numComplete        = deleteOpData.Current;
                    succeeded          = data.Succeeded;
                }

                if (!data.CancelRequsted && succeeded && (allDifferences.ForkDifferences.ToDelete.Any() || allDifferences.ForkDifferences.ToUpdate.Any()))
                {
                    var deleteOpData = new DeleteOperationData(data, LfsEntityType.Fork)
                    {
                        Factory              = (gkn) => DeleteFork.Create((ushort)(uint)gkn[0]),
                        UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                        UpdateTitleInfo      = Resources.Strings.LfsOperation_Remove,
                        UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_DeletingItemProgressFormat,
                        UpdateProgressInfo   = Resources.Strings.DeviceMultistageCommand_RemovingFileContents,
                        Phase = phaseNumber, Total = total, Current = numComplete
                    };
                    var forks = allDifferences.ForkDifferences.ToDelete.ToList();
                    var updatedForksToTreatAsDeletions = allDifferences.ForkDifferences.ToUpdate.Select(f => (uint)f.GlobalForkNumber).Where(f => !allDifferences.ForkDifferences.FailedOperations.Any(o => (o.Value.GlobalFileSystemNumber == f) && FileSystemHelpers.IsMissingForkError(o.Value)));
                    forks.AddRange(updatedForksToTreatAsDeletions);
                    currentDirtyFlags |= DeleteEntries(forks, deleteOpData, currentDirtyFlags, deviceFileSystem);
                    numComplete        = deleteOpData.Current;
                    succeeded          = data.Succeeded;
                }
            }

            if (data.AcceptCancelIfRequested())
            {
                return;
            }

            data.CurrentlyExecutingCommand = ProtocolCommandId.UnknownCommand;
            data.FailureMessage            = Resources.Strings.SyncHostToDeviceCommand_GatherUpdatesFailedMessage;
            var updateOperations = hostFileSystemWorkingCopy.GatherAllUpdates(allDifferences, device.UniqueId);
            if (updateOperations.Any())
            {
                do
                {
                    if (data.AcceptCancelIfRequested())
                    {
                        break;
                    }
                    LfsUpdateOperation operation = null;
                    data.FailureMessage = Resources.Strings.SyncHostToDevice_FetchUpdateOperationFailedMessage;
                    updateOperations    = updateOperations.FetchUpdateOperation(out operation);
                    if (data.AcceptCancelIfRequested())
                    {
                        break;
                    }

                    ++phaseNumber;
                    numComplete = 0;
                    total       = operation.Forks.Count + operation.Files.Count + operation.Directories.Count;
                    foreach (var failure in operation.Failures)
                    {
                        partialErrors[failure.Description] = failure.Exception;
                    }

                    // Load forks.
                    var address = 0u;
                    if (!data.CancelRequsted && succeeded && operation.Forks.Any())
                    {
                        data.FailureMessage = null;
                        var uploadOpData = new UploadDataOperationData(data, address)
                        {
                            TargetType           = LfsEntityType.Fork,
                            Factory              = (upl) => UploadDataBlockToRam.Create((uint)upl[0], (ByteSerializer)upl[1], (uint)upl[2]),
                            GetSerializer        = FileSystemHelpers.ToForkByteSerializer,
                            ShouldUpdateProgress = (k) => true,
                            UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                            UpdateTitleInfo      = operation.ProgressTitle,
                            UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_TransferToRamProgressFormat,
                            Phase = phaseNumber, Total = total, Current = numComplete
                        };
                        address     = UploadEntries(operation.FileSystem.Forks, operation.Forks, uploadOpData);
                        numComplete = uploadOpData.Current;
                        succeeded   = data.Succeeded;
                    }

                    // Load GFT update.
                    if (!data.CancelRequsted && succeeded && operation.Files.Any())
                    {
                        data.FailureMessage = null;
                        var fileRange = operation.GfnRange;
                        var files     = Enumerable.Range(fileRange.Minimum, (fileRange.Maximum - fileRange.Minimum) + 1); // slower than a loop, but who cares?

                        var uploadOpData = new UploadDataOperationData(data, address)
                        {
                            TargetType           = LfsEntityType.File,
                            Factory              = (upl) => UploadDataBlockToRam.Create((uint)upl[0], (ByteSerializer)upl[1], (uint)upl[2]),
                            GetSerializer        = FileSystemHelpers.ToFileByteSerializer,
                            ShouldUpdateProgress = (f) => (f != null) && operation.Files.Contains(((ILfsFileInfo)f).GlobalFileNumber),
                            UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                            UpdateTitleInfo      = operation.ProgressTitle,
                            UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_TransferToRamProgressFormat,
                            Phase   = phaseNumber,
                            Total   = total,
                            Current = numComplete
                        };

                        // THIS COULD BE DONE WITH ONE LARGE BLOCK INSTEAD OF MANY SMALLER ONES
                        address     = UploadEntries(operation.FileSystem.Files, files, uploadOpData);
                        numComplete = uploadOpData.Current;
                        succeeded   = data.Succeeded;
                    }

                    // Load GDT update.
                    if (!data.CancelRequsted && succeeded && operation.Directories.Any())
                    {
                        data.FailureMessage = null;
                        var directoryRange = operation.GdnRange;
                        var directories    = Enumerable.Range(directoryRange.Minimum, (directoryRange.Maximum - directoryRange.Minimum) + 1); // slower than a loop, but who cares?

                        var uploadOpData = new UploadDataOperationData(data, address)
                        {
                            TargetType           = LfsEntityType.Directory,
                            Factory              = (upl) => UploadDataBlockToRam.Create((uint)upl[0], (ByteSerializer)upl[1], (uint)upl[2]),
                            GetSerializer        = FileSystemHelpers.ToDirectoryByteSerializer,
                            ShouldUpdateProgress = (d) => (d != null) && operation.Files.Contains(((IDirectory)d).GlobalDirectoryNumber),
                            UpdateTitleFormat    = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat,
                            UpdateTitleInfo      = operation.ProgressTitle,
                            UpdateProgressFormat = Resources.Strings.DeviceMultistageCommand_UpdatingFiles_TransferToRamProgressFormat,
                            Phase   = phaseNumber,
                            Total   = total,
                            Current = numComplete
                        };

                        // THIS COULD BE DONE WITH ONE LARGE BLOCK INSTEAD OF MANY SMALLER ONES
                        address     = UploadEntries(operation.FileSystem.Directories, directories, uploadOpData);
                        numComplete = uploadOpData.Current;
                        succeeded   = data.Succeeded;
                    }
                    if (data.AcceptCancelIfRequested())
                    {
                        break;
                    }

                    // Everything is in RAM now, so execute commands.
                    address     = 0; // All the data was initially loaded at location zero.
                    numComplete = 0;
                    total       = operation.Forks.Count;
                    if (operation.Files.Count > 0)
                    {
                        ++total;
                    }
                    if (operation.Directories.Count > 0)
                    {
                        ++total;
                    }

                    // Create forks.
                    if (!data.CancelRequsted && succeeded && operation.Forks.Any())
                    {
                        data.FailureMessage = null;
                        var numCreated  = 0;
                        var numToCreate = operation.Forks.Count;
                        foreach (var gkn in operation.Forks)
                        {
                            if (data.AcceptCancelIfRequested())
                            {
                                break;
                            }
                            address = address.Align();
                            var fork = operation.FileSystem.Forks[gkn];
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFiles_ProgressTitleFormat, operation.ProgressTitle, ++numComplete, total, phaseNumber);
                            data.Task.UpdateTaskTitle(updateString);
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFiles_CreatingItemProgressFormat, fork.Name, ++numCreated, numToCreate);
                            data.Task.UpdateTaskProgress((double)numCreated / numToCreate, updateString);
                            currentDirtyFlags |= currentDirtyFlags.UpdateFileSystemDirtyState(deviceFileSystem, data, gkn, LfsOperations.Add, LfsEntityType.Fork);
                            CreateForkFromRam.Create(address, fork).Execute(device.Port, data, out succeeded);
                            address += (uint)fork.SerializeByteCount;
                        }
                        address = address.Align();
                    }

                    // Update GFT.
                    if (!data.CancelRequsted && succeeded && operation.Files.Any())
                    {
                        data.FailureMessage = null;
                        if (data.AcceptCancelIfRequested())
                        {
                            break;
                        }
                        var gftRange = operation.GfnRange;
                        if (operation.Files.Count == 1)
                        {
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFileSystemTablesOneEntry_ProgressTitleFormat, Resources.Strings.File, gftRange.Minimum, phaseNumber);
                        }
                        else
                        {
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFileSystemTables_ProgressTitleFormat, Resources.Strings.File, gftRange.Minimum, gftRange.Maximum, phaseNumber);
                        }
                        data.Task.UpdateTaskTitle(updateString);
                        data.Task.UpdateTaskProgress((double)(++numComplete) / total, Resources.Strings.DeviceMultistageCommand_UpdatingGlobalFilesTable);
                        currentDirtyFlags |= currentDirtyFlags.UpdateFileSystemDirtyState(deviceFileSystem, data, gftRange.Minimum, LfsOperations.Update, LfsEntityType.File);
                        UpdateGlobalFileTable.Create(address, gftRange).Execute(device.Port, data, out succeeded);
                        address += (uint)(gftRange.Maximum - gftRange.Minimum + 1) * LfsFileInfo.FlatSizeInBytes;
                    }

                    // Update GDT.
                    if (!data.CancelRequsted && succeeded && operation.Directories.Any())
                    {
                        data.FailureMessage = null;
                        if (data.AcceptCancelIfRequested())
                        {
                            break;
                        }
                        var gdtRange = operation.GdnRange;
                        if (operation.Directories.Count == 1)
                        {
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFileSystemTablesOneEntry_ProgressTitleFormat, Resources.Strings.Directory, gdtRange.Minimum, phaseNumber);
                        }
                        else
                        {
                            updateString = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.DeviceMultistageCommand_UpdatingFileSystemTables_ProgressTitleFormat, Resources.Strings.Directory, gdtRange.Minimum, gdtRange.Maximum, phaseNumber);
                        }
                        data.Task.UpdateTaskTitle(updateString);
                        data.Task.UpdateTaskProgress((double)(++numComplete) / total, Resources.Strings.DeviceMultistageCommand_UpdatingGlobalDirectoriesTable);
                        currentDirtyFlags |= currentDirtyFlags.UpdateFileSystemDirtyState(deviceFileSystem, data, gdtRange.Minimum, LfsOperations.Update, LfsEntityType.Directory);
                        UpdateGlobalDirectoryTable.Create(address, gdtRange).Execute(device.Port, data, out succeeded);
                        address += (uint)(gdtRange.Maximum - gdtRange.Minimum + 1) * Directory.FlatSizeInBytes;
                    }
                }while (!data.CancelRequsted && succeeded && updateOperations.Any());
            }

            if (!data.CancelRequsted && data.Succeeded)
            {
                SetDirtyFlags.Create(LfsDirtyFlags.None).Execute(device.Port, data, out succeeded);
                deviceFileSystem.Status = LfsDirtyFlags.None;
            }

            if (!data.CancelRequsted && data.Succeeded)
            {
                var updatedFileSystem = DownloadFileSystemTables.Instance.Execute(device.Port, data, out succeeded) as FileSystem;
#if USE_SMART_COPY
                partialErrors = allDifferences.CombineAllFailures(partialErrors, ShouldIncludeError);
#endif // USE_SMART_COPY
                data.Result = new Tuple <FileSystem, IDictionary <string, FailedOperationException> >(updatedFileSystem, partialErrors);
            }
        }