using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.BL.Entities;
using Tango.BL.Enumerations;
using Tango.Core;
using Tango.Logging;
using Tango.PMR.Printing;
using static Tango.Integration.Operation.AdditionalJobConfiguration;
namespace Tango.Integration.Operation
{
///
/// Represents a job handler.
///
///
public class JobHandler : ExtendedObject
{
protected Action _cancelAction;
protected List _effectiveSegments;
protected String _lastStatusMessage;
protected int _last_unit;
protected bool _finalizing;
protected JobHandlerModes _mode;
protected double _last_progress;
protected const int PROGRESS_REPORT_RANGE_METERS = 5;
protected bool loggedContinueMessage;
private DateTime _lastProgressLogDateTime;
#region Events
///
/// Occurs when a job status has been received.
///
public event EventHandler StatusChanged;
///
/// Occurs when the job has failed.
///
public event EventHandler Failed;
///
/// Occurs when the job has completed successfully.
///
public event EventHandler Completed;
///
/// Occurs when the job has been canceled.
///
public event EventHandler Canceled;
///
/// Occurs when the job has been canceled, failed or completed.
///
public event EventHandler Stopped;
///
/// Occurs when rolling the dryer buffer.
///
public event EventHandler Finalizing;
///
/// Occurs when a segment has started.
///
public event EventHandler SegmentStarted;
///
/// Occurs when a segment has completed.
///
public event EventHandler SegmentCompleted;
///
/// Occurs when a unit completes.
///
public event EventHandler UnitCompleted;
///
/// Occurs when the job is set to 1 segment per spool and it's time to replace the spool.
///
public event EventHandler SpoolChangeRequired;
///
/// Occurs when has changed.
///
public event EventHandler CanCancelChanged;
#endregion
#region Properties
private JobStatus _jobStatus;
///
/// Gets or sets the current job status that was used to invalidate this handler.
///
public JobStatus JobStatus
{
get { return _jobStatus; }
set { _jobStatus = value; RaisePropertyChangedAuto(); }
}
///
/// Gets a value indicating whether this handler job has been canceled.
///
public bool IsCanceled { get; internal set; }
private bool _canCancel;
///
/// Gets a value indicating whether the job can be canceled.
///
public bool CanCancel
{
get { return _canCancel; }
internal set { _canCancel = value; RaisePropertyChangedAuto(); CanCancelChanged?.Invoke(this, new EventArgs()); }
}
///
/// Gets the process parameters.
///
public ProcessParametersTable ProcessParameters { get; private set; }
private RunningJobStatus _Status;
///
/// Gets the current status.
///
public RunningJobStatus Status
{
get { return _Status; }
internal set { _Status = value; RaisePropertyChangedAuto(); }
}
///
/// Gets the job.
///
public Job Job { get; private set; }
///
/// Gets the PMR job ticket.
///
public JobTicket JobTicket { get; private set; }
public ResumeConfiguration ResumeConfig { get; private set; }
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public JobHandler()
{
CanCancel = true;
}
///
/// Initializes a new instance of the class.
///
/// The cancel action.
public JobHandler(Action cancelAction, Job job, JobTicket jobTicket, ProcessParametersTable processParameters, JobHandlerModes mode, ResumeConfiguration resumeConfig = null) : this()
{
_mode = mode;
ProcessParameters = processParameters;
Job = job;
JobTicket = jobTicket;
ResumeConfig = resumeConfig;
foreach (var s in Job.Segments)
{
s.Started = false;
s.Completed = false;
s.RemainingTime = TimeSpan.FromSeconds(0);
}
_effectiveSegments = Job.EffectiveSegments.ToList();
_cancelAction = () => { cancelAction(); };
Status = new RunningJobStatus();
Status.TotalTime = job.GetEstimatedDuration(processParameters);
Status.RemainingUnits = job.NumberOfUnits;
Status.TotalProgress = Job.LengthIncludingNumberOfUnits + processParameters.DryerBufferLengthMeters;
Status.FinalizingTotalProgress = processParameters.DryerBufferLengthMeters;
Status.TotalProgressWithoutFinalization = Job.LengthIncludingNumberOfUnits;
Status.Progress = 0;
Status.RemainingTime = Status.TotalTime;
Status.RemainingProgress = Status.TotalProgress;
Status.CurrentUnitSegments = _effectiveSegments.ToList();
Status.SettingUpTotalProgress = processParameters.DryerBufferLengthMeters;
if (resumeConfig != null && resumeConfig.GlobalStartPosition > 0)
{
//Status.SettingUpTotalProgress = resumeConfig.GlobalStartPosition;
//Status.CurrentUnitProgress = ResumeConfig.FirstUnitStartPosition;
}
Status.TotalProgressMinusSettingUp = Job.LengthIncludingNumberOfUnits;
Status.IsSettingUp = true;
if (mode == JobHandlerModes.Finalization)
{
Status.CurrentUnitTotalProgress = Job.Length;
}
else
{
Status.CurrentUnitTotalProgress = Status.SettingUpTotalProgress;
}
if (Job.EnableInterSegment && Job.NumberOfUnits > 1)
{
Status.CurrentUnitSegments.Add(Job.CreateInterSegment(Job.InterSegmentLength));
}
//Create all segments
int segment_index = 1;
for (int j = 0; j < Math.Max(Job.NumberOfUnits, 1); j++)
{
for (int i = 0; i < _effectiveSegments.Count; i++)
{
Segment seg = _effectiveSegments[i].Clone();
seg.EstimatedDuration = seg.GetEstimatedDuration(processParameters);
seg.SegmentIndex = segment_index++;
Status.Segments.Add(seg);
}
if (Job.EnableInterSegment)
{
var inter_segment = Job.CreateInterSegment(Job.InterSegmentLength);
inter_segment.SegmentIndex = segment_index++;
Status.Segments.Add(inter_segment);
}
}
var last_segment = Status.Segments.Last();
if (last_segment.IsInterSegment)
{
Status.Segments.Remove(last_segment);
}
Status.CurrentSegment = Status.Segments.First();
}
#endregion
#region Internal Methods
///
/// Raises the status received event.
///
/// The status.
public void RaiseStatusReceived(JobStatus status)
{
InvalidateJobProgress(status);
}
///
/// Raises the failed event.
///
/// The ex.
public void RaiseFailed(Exception ex)
{
LogManager.Log($"Job failed at position {Status.Progress}/{Status.TotalProgress}...");
Status.IsFailed = true;
RaiseStatusChanged();
RaisePropertyChanged(nameof(Status));
Failed?.Invoke(this, ex);
Stopped?.Invoke(this, new EventArgs());
}
///
/// Raises the completed event.
///
public void RaiseCompleted()
{
//This will compensate on any missing progress from Shlomo, but also will tell the wrong progress if job is really completed with a large progress mistake.
// Might be worth to compensate only on small drifts like the below (ProgressMinusSettingsUp)...
//InvalidateJobProgress(new JobStatus()
//{
// Progress = Status.TotalProgress,
// CurrentSegmentIndex = 0,
//});
LogManager.Log($"Job completed at position {Status.Progress}/{Status.TotalProgress}...");
//If drift is smaller than 10cm auto correct it.
if (Math.Abs(Status.TotalProgressMinusSettingUp - Status.ProgressMinusSettingUp) < 0.1)
{
LogManager.Log($"Job completed with a small drift in the progress minus setting up calculation. ({Status.ProgressMinusSettingUp}/{Status.TotalProgressMinusSettingUp}). Compensating...");
Status.ProgressMinusSettingUp = Status.TotalProgressMinusSettingUp;
}
//If the overall progress is correct? Fix the minus setting up. There is a problem with the delta calc!
else if (Status.Progress == Status.TotalProgress)
{
LogManager.Log($"Job completed with a small drift in the progress minus setting up calculation but the overall progress seems OK. ({Status.ProgressMinusSettingUp}/{Status.TotalProgressMinusSettingUp}). Compensating...");
Status.ProgressMinusSettingUp = Status.TotalProgressMinusSettingUp;
}
Status.Segments.Last().Completed = true;
Status.RemainingUnits = 0;
Status.IsFinalizing = false;
Status.IsCompleted = true;
RaiseStatusChanged();
RaisePropertyChanged(nameof(Status));
Completed?.Invoke(this, new EventArgs());
Stopped?.Invoke(this, new EventArgs());
}
///
/// Raises the canceled event.
///
public void RaiseCanceled()
{
LogManager.Log($"Job canceled at position {Status.Progress}/{Status.TotalProgress}...");
Status.IsCanceled = true;
RaiseStatusChanged();
RaisePropertyChanged(nameof(Status));
Canceled?.Invoke(this, new EventArgs());
Stopped?.Invoke(this, new EventArgs());
}
///
/// Raises the spool change required event.
///
internal void RaiseSpoolChangeRequired(Action confirmAction, Action abortAction)
{
SpoolChangeRequired?.Invoke(this, new SpoolChangeRequiredEventArgs(confirmAction, abortAction)
{
CurrentSegment = Status.CurrentSegment.SegmentIndex,
TotalSegments = Status.Segments.Count,
});
}
#endregion
#region Private Methods
protected virtual void InvalidateJobProgress(JobStatus s)
{
JobStatus = s;
if (DateTime.UtcNow > _lastProgressLogDateTime.AddSeconds(5))
{
_lastProgressLogDateTime = DateTime.UtcNow;
if (LogManager.Categories.Exists(x => x == LogCategory.Debug))
{
LogManager.Log($"Updating job progress {s.Progress}/{Status.TotalProgress}...");
}
else
{
if (s.Progress <= PROGRESS_REPORT_RANGE_METERS || s.Progress >= Status.TotalProgress - PROGRESS_REPORT_RANGE_METERS)
{
LogManager.Log($"Updating job progress {s.Progress}/{Status.TotalProgress}...");
}
else if (!loggedContinueMessage)
{
loggedContinueMessage = true;
LogManager.Log($"Progress logging will continue {PROGRESS_REPORT_RANGE_METERS} meters before completion...");
}
}
}
if (s.Progress < 0)
{
LogManager.Log($"Invalid job progress received '{s.Progress}'.", LogCategory.Error);
return;
}
if (s.Progress > Status.TotalProgress)
{
LogManager.Log($"Invalid job progress received '{s.Progress}' while total progress is '{Status.TotalProgress}'.", LogCategory.Error);
return;
}
if (s.Progress < _last_progress)
{
LogManager.Log($"Invalid job progress received '{s.Progress}' while last progress was '{_last_progress}'.");
}
_last_progress = s.Progress;
List unit_segments = new List();
Status.Progress = s.Progress;
Status.RemainingTime = Status.TotalTime - Job.TranslateProgressToTime(Status.Progress, ProcessParameters);
Status.RemainingProgress = Status.TotalProgress - Status.Progress;
if ((s.Progress < Status.SettingUpTotalProgress) || (Status.SettingUpProgress < Status.SettingUpTotalProgress))
{
Status.SettingUpProgress = Math.Min(s.Progress, this.Status.SettingUpTotalProgress);
Status.IsSettingUp = true;
//LogManager.Log($" Status.IsSettingUp = true , Status.SettingUpProgress = {Status.SettingUpProgress}' Status.SettingUpTotalProgress = {Status.SettingUpTotalProgress}.");
}
if (s.Progress >= ProcessParameters.DryerBufferLengthMeters)//Status.SettingUpTotalProgress)
{
if (Status.IsSettingUp && Status.Progress > 0)
{
Status.IsSettingUp = false;
//LogManager.Log($" Status.IsSettingUp = false , Status.SettingUpProgress = {Status.SettingUpProgress}'.");
}
if (ResumeConfig != null && ResumeConfig.GlobalStartPosition > 0)
{
Status.ProgressMinusSettingUp = s.Progress - ProcessParameters.DryerBufferLengthMeters;
//LogManager.Log($" JOB HANDLER Status.ProgressMinusSettingUp {Status.ProgressMinusSettingUp} progress = {s.Progress}");
}
else
{
Status.ProgressMinusSettingUp = s.Progress - this.Status.SettingUpTotalProgress;
}
}
int units = (int)Math.Max(Job.NumberOfUnits, 1);
if (s.Progress < Job.LengthIncludingNumberOfUnits || _mode == JobHandlerModes.SettingUp)
{
Status.ProgressWithoutFinalization = s.Progress;
unit_segments = _effectiveSegments.ToList();
double previousUnitsLengthWithoutThis = 0.0;
int currentUnit = Status.CurrentUnit;
double currentUnitProgress = Status.CurrentUnitProgress;
double jobLength = Job.Length;
for (int index = 0; index < units; ++index)
{
currentUnit = index;
double unitLength = !Job.EnableInterSegment || index >= units - 1 ? jobLength : jobLength + Job.InterSegmentLength;
if (_mode == JobHandlerModes.Finalization)
{
if (s.Progress < unitLength + previousUnitsLengthWithoutThis)
{
currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis;
break;
}
}
else if (ResumeConfig != null && ResumeConfig.GlobalStartPosition > 0)
{
if (!Status.IsSettingUp && s.Progress <= previousUnitsLengthWithoutThis + unitLength + ProcessParameters.DryerBufferLengthMeters)
{
currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis - ProcessParameters.DryerBufferLengthMeters;
//LogManager.Log($" JOB HANDLER currentUnitProgress before ={currentUnitProgress} progress = {s.Progress}");
break;
}
}
else if (s.Progress <= previousUnitsLengthWithoutThis + unitLength + Status.SettingUpProgress)
{
if (!Status.IsSettingUp)
{
currentUnitProgress = s.Progress - previousUnitsLengthWithoutThis - this.Status.SettingUpProgress;
break;
}
break;
}
previousUnitsLengthWithoutThis += unitLength;
}
Status.CurrentUnit = currentUnit;
Status.CurrentUnitProgress = currentUnitProgress;
//LogManager.Log($" JOB HANDLER CurrentUnit {Status.CurrentUnit} currentUnitProgress {Status.CurrentUnitProgress} ");
Status.RemainingUnits = this.Job.NumberOfUnits - this.Status.CurrentUnit;
if (Job.EnableInterSegment && Job.NumberOfUnits > 1 && Status.RemainingUnits > 1)
{
unit_segments.Add(Job.CreateInterSegment(Job.InterSegmentLength));
}
if (unit_segments.Count != Status.CurrentUnitSegments.Count)
{
Status.CurrentUnitSegments = unit_segments;
}
Status.CurrentUnitTotalProgress = Status.RemainingUnits > 1 && Job.EnableInterSegment ? Job.Length + (Job.InterSegmentLength) : Job.Length;
if (s.Message != _lastStatusMessage)
{
Status.Message = s.Message;
}
_lastStatusMessage = s.Message;
RaiseStatusChanged();
//Segments Completion
if (Status.CurrentUnit > _last_unit)
{
foreach (var segment in Status.CurrentUnitSegments)
{
segment.Started = false;
segment.Completed = false;
}
if (Job.NumberOfUnits > 1)
{
RaiseUnitCompleted(_last_unit);
}
}
_last_unit = Status.CurrentUnit;
for (int i = 0; i < Status.CurrentUnitSegments.Count; i++)
{
Segment segment = Status.CurrentUnitSegments[i];
double previousSegmentsLengthWithThis = Status.CurrentUnitSegments.Take(i + 1).Sum(x => x.LengthWithFactor);
TimeSpan segmentsDuration = Job.TranslateProgressToTime(previousSegmentsLengthWithThis, ProcessParameters);
TimeSpan segmentRemainingTime = segmentsDuration - Job.TranslateProgressToTime(Status.CurrentUnitProgress, ProcessParameters);
if (i == 0 && Status.CurrentUnitProgress > 0)
{
if (!segment.Started)
{
segment.Started = true;
RaiseSegmentStarted(segment);
}
}
if (Status.CurrentUnitProgress >= previousSegmentsLengthWithThis)
{
if (!segment.Completed)
{
segment.Completed = true;
RaiseSegmentCompleted(segment);
}
if (i < Status.CurrentUnitSegments.Count - 1)
{
if (!Status.CurrentUnitSegments[i + 1].Started)
{
Status.CurrentUnitSegments[i + 1].Started = true;
RaiseSegmentStarted(Status.CurrentUnitSegments[i + 1]);
}
}
}
if (segment.Started && !segment.Completed)
{
segment.RemainingTime = segmentRemainingTime;
}
}
//Set Segment Completion for All Segments List
for (int i = 0; i < Status.Segments.Count; i++)
{
Segment segment = Status.Segments[i];
double previousSegmentsLengthWithThis = Status.Segments.Take(i + 1).Sum(x => x.LengthWithFactor);
TimeSpan segmentsDuration = Job.TranslateProgressToTime(previousSegmentsLengthWithThis, ProcessParameters);
TimeSpan segmentRemainingTime = segmentsDuration - Job.TranslateProgressToTime(Status.Progress, ProcessParameters);
// segment.Progress = Math.Min(Math.Max((previousSegmentsLengthWithThis - segment.Length - Status.Progress) * -1, 0), segment.Length);
segment.Progress = Math.Min(Math.Max((previousSegmentsLengthWithThis - segment.Length - Status.ProgressMinusSettingUp) * -1, 0), segment.Length);
if (i == 0 && Status.Progress > 0)
{
if (!segment.Started)
{
segment.Started = true;
Status.CurrentSegment = segment;
}
}
//if (Status.Progress >= previousSegmentsLengthWithThis)
if (Status.ProgressMinusSettingUp >= previousSegmentsLengthWithThis)
{
if (!segment.Completed)
{
segment.Completed = true;
}
if (i < Status.Segments.Count - 1)
{
if (!Status.Segments[i + 1].Started)
{
Status.Segments[i + 1].Started = true;
Status.CurrentSegment = Status.Segments[i + 1];
}
}
}
if (segment.Started && !segment.Completed)
{
segment.RemainingTime = segmentRemainingTime;
}
}
}
else
{
//Finalizing
if (!_finalizing)
{
_finalizing = true;
Status.IsFinalizing = true;
var last_Segment = _effectiveSegments.Last().Clone();
last_Segment.Length = ProcessParameters.DryerBufferLengthMeters;
Status.CurrentUnitSegments = new List { last_Segment };
Status.CurrentUnitTotalProgress = last_Segment.Length;
Status.CurrentUnitProgress = 0;
Status.ProgressWithoutFinalization = Status.TotalProgressWithoutFinalization;
RaiseFinalizing();
}
Status.CurrentUnitProgress = s.Progress - Job.LengthIncludingNumberOfUnits;
Status.FinalizingProgress = s.Progress - Status.TotalProgressWithoutFinalization;
}
}
#endregion
#region Protected Methods
protected void RaiseStatusChanged()
{
StatusChanged?.Invoke(this, Status);
}
protected void RaiseSegmentStarted(Segment segment)
{
if (segment.IsInterSegment)
{
LogManager.Log($"Inter Segment started.");
}
else
{
LogManager.Log($"Segment {segment.SegmentIndex} of unit {Status.CurrentUnit + 1} started...");
}
SegmentStarted?.Invoke(this, segment);
}
protected void RaiseSegmentCompleted(Segment segment)
{
if (segment.IsInterSegment)
{
LogManager.Log($"Inter Segment completed.");
}
else
{
LogManager.Log($"Segment {segment.SegmentIndex} of unit {Status.CurrentUnit + 1} completed.");
}
SegmentCompleted?.Invoke(this, segment);
}
protected void RaiseUnitCompleted(int unit)
{
LogManager.Log($"Unit {unit + 1} completed...");
UnitCompleted?.Invoke(this, unit);
}
protected void RaiseFinalizing()
{
LogManager.Log($"Finalizing...");
Finalizing?.Invoke(this, new EventArgs());
}
#endregion
#region Public Methods
///
/// Cancels the associated job.
///
public void Cancel()
{
_cancelAction();
}
#endregion
}
}