context: #region License
  
  // Copyright (c) 2013, ClearCanvas Inc.
  // All rights reserved.
  // http://www.ClearCanvas.ca
  //
  // This file is part of the ClearCanvas RIS/PACS open source project.
  //
  // The ClearCanvas RIS/PACS open source project is free software: you can
  // redistribute it and/or modify it under the terms of the GNU General Public
  // License as published by the Free Software Foundation, either version 3 of the
  // License, or (at your option) any later version.
  //
  // The ClearCanvas RIS/PACS open source project is distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
  // Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along with
  // the ClearCanvas RIS/PACS open source project.  If not, see
  // <http://www.gnu.org/licenses/>.
  
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Text;
  using System.Xml;
  using Macro.Common;
  using Macro.Common.Utilities;
  using Macro.Dicom;
  using Macro.Dicom.Utilities.Command;
  using Macro.Dicom.Utilities.Xml;
  using Macro.Enterprise.Core;
  using Macro.ImageServer.Common;
  using Macro.ImageServer.Common.Command;
  using Macro.ImageServer.Common.Utilities;
  using Macro.ImageServer.Core;
  using Macro.ImageServer.Core.Command;
  using Macro.ImageServer.Core.Data;
  using Macro.ImageServer.Core.Edit;
  using Macro.ImageServer.Core.Reconcile;
  using Macro.ImageServer.Core.Validation;
  using Macro.ImageServer.Model;
  using Macro.ImageServer.Model.EntityBrokers;
  
  
  namespace Macro.ImageServer.Services.WorkQueue.ProcessDuplicate
  {
      [StudyIntegrityValidation(ValidationTypes = StudyIntegrityValidationModes.Default, Recovery = RecoveryModes.Automatic)]
      class ProcessDuplicateItemProcessor : BaseItemProcessor
      {
          #region Private Member
  		
          private WorkQueueProcessDuplicateSop _processDuplicateEntry;
          private List<BaseImageLevelUpdateCommand> _studyUpdateCommands;
          private List<BaseImageLevelUpdateCommand> _duplicateUpdateCommands;
          private StudyInformation _currentStudyInfo;
          private PatientNameRules _patientNameRules;
  
          #endregion
  
          #region Protected Properties
  
          protected String DuplicateFolder
          {
              get
              {
                  return ServerPlatform.GetDuplicateGroupPath(StorageLocation, WorkQueueItem);
              }
          }
  
          protected bool HistoryLogged { get; set; }
  
          #endregion
  
          #region Overridden Protected Methods
  
          protected override bool CanStart()
          {
              // If the study is not in processing state, attempt to push it into this state
              // If it fails, postpone the processing instead of failing
              if (StorageLocation.QueueStudyStateEnum != QueueStudyStateEnum.ReconcileScheduled)
              {
              	string failureReason;
                  if (!ServerHelper.LockStudy(WorkQueueItem.StudyStorageKey, QueueStudyStateEnum.ReconcileScheduled, out failureReason))
                  {
                      Platform.Log(LogLevel.Debug,
                                   "ProcessDuplicate cannot start at this point. Study is being locked by another processor. WriteLock Failure reason={0}",
                                   failureReason);
                      PostponeItem(String.Format("Study is being locked by another processor: {0}", failureReason));
                      return false;
                  }
              }
  
              return true; // it is being locked by me
          }
  
  		protected override bool Initialize(Model.WorkQueue item, out string failureDescription)
          {
              if (!base.Initialize(item, out failureDescription))
              	return false;
  
              _processDuplicateEntry = new WorkQueueProcessDuplicateSop(item);
              _patientNameRules = new PatientNameRules(Study);
              HistoryLogged = _processDuplicateEntry.QueueData != null && _processDuplicateEntry.QueueData.State.HistoryLogged;
          	return true;
          }
  
          protected override void ProcessItem(Model.WorkQueue item)
          {
              Platform.CheckMemberIsSet(StorageLocation, "StorageLocation");
              Platform.CheckForNullReference(Study, "Study doesn't exist");
              
              if (WorkQueueUidList.Count == 0)
              {
                  // we are done. Just need to cleanup the duplicate folder
                  Platform.Log(LogLevel.Info, "{0} is completed. Cleaning up duplicate storage folder. (GUID={1}, action={2})",
                               item.WorkQueueTypeEnum, item.GetKey().Key, _processDuplicateEntry.QueueData.Action);
                  
                  CleanUpReconcileFolders();
  
                  PostProcessing(item, WorkQueueProcessorStatus.Complete, WorkQueueProcessorDatabaseUpdate.ResetQueueState);
              }
              else
              {
                  Platform.Log(LogLevel.Info, "Processing {0} entry (GUID={1}, action={2})",
                               item.WorkQueueTypeEnum, item.GetKey().Key, _processDuplicateEntry.QueueData.Action);
  
                  Platform.CheckTrue(Directory.Exists(DuplicateFolder), String.Format("Duplicate Folder {0} doesn't exist.", DuplicateFolder));
  
                  LogWorkQueueInfo();
  
                  EnsureStorageLocationIsWritable(StorageLocation);
  
                  _currentStudyInfo = StudyInformation.CreateFrom(Study);
  
                  ImageSetDetails duplicateSopDetails = null;
  
                  // If deleting duplicates then don't log the history
                  if (_processDuplicateEntry.QueueData.Action != ProcessDuplicateAction.Delete && !HistoryLogged)
                  {
                      duplicateSopDetails = LoadDuplicateDetails();
                  }
  
                  try
                  {
  
                      UpdateStudyOrDuplicates();
  
                      int count = ProcessUidList();
  
                      // If deleting duplicates then don't log the history
                      if (_processDuplicateEntry.QueueData.Action != ProcessDuplicateAction.Delete &&
                          !HistoryLogged && duplicateSopDetails != null && count > 0)
                      {
                          LogHistory(duplicateSopDetails);
                      }
  
                      PostProcessing(item, WorkQueueProcessorStatus.Pending, WorkQueueProcessorDatabaseUpdate.None);
                  }
                  finally
                  {
                      UpdateQueueData();
                  }
              }
  
          }
  
          private void CleanUpReconcileFolders()
          {
              try
              {
                  DirectoryInfo duplicateStudyFolder =
                      new DirectoryInfo(Path.Combine(DuplicateFolder, StorageLocation.StudyInstanceUid));
                  if (duplicateStudyFolder.Exists)
                  {
                      DirectoryUtility.DeleteIfEmpty(duplicateStudyFolder.FullName);
                  }
  
                  if (Directory.Exists(DuplicateFolder))
                  {
                      DirectoryUtility.DeleteIfEmpty(DuplicateFolder);
                  }
              }
              catch(Exception ex)
              {
                  Platform.Log(LogLevel.Info, ex, "Unable to cleanup some of the folders. Manual deletion is required");
              }
          }
  
          private static void EnsureStorageLocationIsWritable(StudyStorageLocation location)
          {
              FilesystemMonitor.Instance.EnsureStorageLocationIsWritable(location);
          }
  
          #endregion
  
          #region Private Methods
  
          private void UpdateStudyOrDuplicates()
          {
              // StorageLocation object must be reloaded if we are overwriting the study
              // with info in the duplicates. 
              bool needReload = false;
  
              switch (_processDuplicateEntry.QueueData.Action)
              {
                  case ProcessDuplicateAction.OverwriteUseDuplicates:
  
                      if (_processDuplicateEntry.QueueData.State.ExistingStudyUpdated)
                          Platform.Log(LogLevel.Info, "Existing Study has been updated before");
                      else
                      {
                          Platform.Log(LogLevel.Info, "Update Existing Study w/ Duplicate Info");
                          _studyUpdateCommands = BuildUpdateStudyCommandsFromDuplicate();
                          using (ServerCommandProcessor processor = new ServerCommandProcessor("Update Existing Study w/ Duplicate Info"))
                          {
                              processor.AddCommand(new UpdateStudyCommand(ServerPartition, StorageLocation, _studyUpdateCommands, ServerRuleApplyTimeEnum.SopProcessed));
                              if (!processor.Execute())
                              {
                                  throw new ApplicationException(processor.FailureReason, processor.FailureException);
                              }
  
                              needReload = true;
                              _processDuplicateEntry.QueueData.State.ExistingStudyUpdated = true;
                          }
                      }
                      
                      break;
                      
                  case ProcessDuplicateAction.OverwriteUseExisting:
                      ImageUpdateCommandBuilder commandBuilder = new ImageUpdateCommandBuilder();
                      _duplicateUpdateCommands = new List<BaseImageLevelUpdateCommand>();
                      _duplicateUpdateCommands.AddRange(commandBuilder.BuildCommands<StudyMatchingMap>(StorageLocation));
                      PrintCommands(_duplicateUpdateCommands);
                      break;
              }
  
              if (needReload)
              {
                  StudyStorageLocation updatedStorageLocation;
                  
                  //NOTE: Make sure we are loading the storage location fro the database instead of the cache.
                  if (!FilesystemMonitor.Instance.GetWritableStudyStorageLocation(WorkQueueItem.StudyStorageKey, out updatedStorageLocation))
                  {
                      // this is odd.. we just updated it and now it's no longer writable?
                      throw new ApplicationException("Filesystem is not writable");
                  }
                  StorageLocation = updatedStorageLocation;
              }
          }
  
          private int ProcessUidList()
          {
              int count = 0;
  
              Platform.Log(LogLevel.Info, "Processing {0} duplicates...", WorkQueueUidList.Count);
              foreach (WorkQueueUid uid in WorkQueueUidList)
              {
                  ProcessUid(uid);
                  count++;
              }
  
              return count;
          }
  
          private void UpdateQueueData()
          {
              using(IUpdateContext ctx = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush))
              {
                  // make a copy of the current queue data with updated info
                  ProcessDuplicateQueueEntryQueueData data = new ProcessDuplicateQueueEntryQueueData
                                                                 {
                                                                     Action = _processDuplicateEntry.QueueData.Action,
                                                                     DuplicateSopFolder = _processDuplicateEntry.QueueData.DuplicateSopFolder,
                                                                     UserName = _processDuplicateEntry.QueueData.UserName,
                                                                     State = new ProcessDuplicateQueueState
                                                                                 {
                                                                                     HistoryLogged = HistoryLogged,
                                                                                     ExistingStudyUpdated = _processDuplicateEntry.QueueData.State.ExistingStudyUpdated
                                                                                 }                                                                               
                                                                 };
                  
                  // update the queue data in db
                  IWorkQueueEntityBroker broker = ctx.GetBroker<IWorkQueueEntityBroker>();
                  WorkQueueUpdateColumns parameters = new WorkQueueUpdateColumns
                                                          {
                                                              Data = XmlUtils.SerializeAsXmlDoc(data)
                                                          };
                  if (broker.Update(WorkQueueItem.Key, parameters))
                  {
                      ctx.Commit();
                      HistoryLogged = _processDuplicateEntry.QueueData.State.HistoryLogged = true;
                  }                
              }
          }
  
          private ImageSetDetails LoadDuplicateDetails()
          {
              IList<WorkQueueUid> uids = LoadAllWorkQueueUids();
              ImageSetDetails details = null;
              foreach(WorkQueueUid uid in  uids)
              {
                  DicomFile file = LoadDuplicateDicomFile(uid, true);
  
                  if (details == null)
                      details = new ImageSetDetails(file.DataSet);
  
                  details.InsertFile(file);
              }
  
              return details;
          }
  
          private IList<WorkQueueUid> LoadAllWorkQueueUids()
          {
              using(IReadContext context = PersistentStoreRegistry.GetDefaultStore().OpenReadContext())
              {
                  IWorkQueueUidEntityBroker broker = context.GetBroker<IWorkQueueUidEntityBroker>();
                  WorkQueueUidSelectCriteria criteria = new WorkQueueUidSelectCriteria();
                  criteria.WorkQueueKey.EqualTo(WorkQueueItem.Key);
                  return broker.Find(criteria);
              }
          }
  
          private void LogWorkQueueInfo()
          {
              StringBuilder log = new StringBuilder();
              log.AppendLine(String.Format("\tGUID={0}", _processDuplicateEntry.GetKey().Key));
              log.AppendLine(String.Format("\tType={0}", _processDuplicateEntry.WorkQueueTypeEnum));
              log.AppendLine(String.Format("\tDuplicate Folder={0}", _processDuplicateEntry.GetDuplicateSopFolder()));
              log.AppendLine(String.Format("\tDuplicate Counts (this run)={0}", WorkQueueUidList.Count));
              log.AppendLine(String.Format("\tAction ={0}", _processDuplicateEntry.QueueData.Action));
  
              Platform.Log(LogLevel.Info, log);
          }
  
          private void LogHistory(ImageSetDetails details)
          {
              IPersistentStore store = PersistentStoreRegistry.GetDefaultStore();
              using (IUpdateContext ctx = store.OpenUpdateContext(UpdateContextSyncMode.Flush))
              {
                  Platform.Log(LogLevel.Info, "Logging study history record...");
                  IStudyHistoryEntityBroker broker = ctx.GetBroker<IStudyHistoryEntityBroker>();
                  StudyHistoryUpdateColumns recordColumns = CreateStudyHistoryRecord(details);
                  StudyHistory entry = broker.Insert(recordColumns);
  
gt:                 if (entry != null)