#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2007 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library 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
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
namespace Nuclex.Support.Tracking {
  /// Progression being observed by another class
  /// 
  ///   Type of the progression that is being observed
  /// 
  internal class ObservedWeightedProgression : IDisposable
    where ProgressionType : Progression {
    /// Delegate for reporting progress updates
    public delegate void ReportDelegate();
    /// Initializes a new observed progression
    /// Weighted progression being observed
    /// 
    ///   Callback to invoke when the progression's progress changes
    /// 
    /// 
    ///   Callback to invoke when the progression has ended
    /// 
    internal ObservedWeightedProgression(
      WeightedProgression weightedProgression,
      ReportDelegate progressUpdateCallback,
      ReportDelegate endedCallback
    ) {
      this.weightedProgression = weightedProgression;
      if(weightedProgression.Progression.Ended) {
        this.progress = 1.0f;
      } else {
        this.endedCallback = endedCallback;
        this.progressUpdateCallback = progressUpdateCallback;
        this.weightedProgression.Progression.AsyncEnded +=
          new EventHandler(asyncEnded);
        this.weightedProgression.Progression.AsyncProgressUpdated +=
          new EventHandler(asyncProgressUpdated);
      }
    }
    /// Immediately releases all resources owned by the object
    public void Dispose() {
      asyncDisconnectEvents();
    }
    /// Weighted progression being observed
    public WeightedProgression WeightedProgression {
      get { return this.weightedProgression; }
    }
    /// Amount of progress this progression has achieved so far
    public float Progress {
      get { return this.progress; }
    }
    /// Called when the observed progression has ended
    /// Progression that has ended
    /// Not used
    private void asyncEnded(object sender, EventArgs e) {
      ReportDelegate endedCallback = this.endedCallback;
      ReportDelegate progressUpdateCallback = this.progressUpdateCallback;
      asyncDisconnectEvents(); // We don't need those anymore!
      // If the progress hasn't reached 1.0 yet, make a fake report so that even
      // when a progression doesn't report any progress at all, the set or queue
      // owning us will have a percentage of progressions completed.
      //
      // There is the possibility of a race condition here, as a final progress
      // report could have been generated by a thread running the progression
      // that was preempted by this thread. This would cause the progress to
      // jump to 1.0 and then back to whatever the waiting thread will report.
      if(this.progress != 1.0f) {
        this.progress = 1.0f;
        progressUpdateCallback();
      }
      endedCallback();
    }
    /// Called when the progress of the observed progression changes
    /// Progression whose progress has changed
    /// Contains the updated progress
    private void asyncProgressUpdated(object sender, ProgressUpdateEventArgs e) {
      this.progress = e.Progress;
      this.progressUpdateCallback();
    }
    /// Unsubscribes from all events of the observed progression
    private void asyncDisconnectEvents() {
      // Make use of the double check locking idiom to avoid the costly lock when
      // the events have already been unsubscribed
      if(this.endedCallback != null) {
        // This is an internal class with special knowledge that there
        // is no risk of deadlock involved, so we don't need a fancy syncRoot!
        lock(this) {
          if(this.endedCallback != null) {
            this.weightedProgression.Progression.AsyncEnded -=
              new EventHandler(asyncEnded);
            this.weightedProgression.Progression.AsyncProgressUpdated -=
              new EventHandler(asyncProgressUpdated);
            this.endedCallback = null;
            this.progressUpdateCallback = null;
          }
        }
      } // endedCallback != null
    }
    /// The weighted progression that is being observed
    private WeightedProgression weightedProgression;
    /// Callback to invoke when the progress updates
    private volatile ReportDelegate progressUpdateCallback;
    /// Callback to invoke when the progression ends
    private volatile ReportDelegate endedCallback;
    /// Progress achieved so far
    private volatile float progress;
  }
} // namespace Nuclex.Support.Tracking