using System; namespace TINK.Model.State { using System.ComponentModel; using System.Runtime.Serialization; /// /// Manges the state of a bike. /// [DataContract] public class StateInfoMutable : INotifyPropertyChanged, IStateInfoMutable { /// /// Provider for current date time to calculate remaining time on demand for state of type reserved. /// private readonly Func _DateTimeNowProvider; // Holds the current disposable state value private StateInfo _StateInfo; /// /// Backs up remaining time of child object. /// private TimeSpan? _RemainingTime = null; /// Notifies clients about state changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Constructs a state object from source. /// /// State info to load from. public StateInfoMutable( Func dateTimeNowProvider = null, IStateInfo state = null) { // Back up date time provider to be able to pass this to requested- object if state changes to requested. _DateTimeNowProvider = dateTimeNowProvider != null ? dateTimeNowProvider : () => DateTime.Now; _StateInfo = Create(state, dateTimeNowProvider); } /// /// Loads state from immutable source. /// /// State to load from. public void Load(IStateInfo state) { if (state == null) { throw new ArgumentException("Can not load state info, object must not be null."); } // Back up last state value and remaining time value // to be able to check whether an event has to be fired or not. var l_oLastState = Value; var l_oLastRemainingTime = _RemainingTime; // Create new state info object from source. _StateInfo = Create(state, _DateTimeNowProvider); // Update remaining time value. _StateInfo.GetIsStillReserved(out _RemainingTime); if (l_oLastState == _StateInfo.Value && l_oLastRemainingTime == _RemainingTime) { return; } // State has changed, notify clients. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State))); } /// /// Creates a state info object. /// /// State to load from. private static StateInfo Create( IStateInfo state, Func dateTimeNowProvider) { switch (state != null ? state.Value : InUseStateEnum.Disposable) { case InUseStateEnum.Disposable: case InUseStateEnum.FeedbackPending: return new StateInfo(state != null ? state.Value == InUseStateEnum.FeedbackPending : false); case InUseStateEnum.Reserved: // Todo: Handle p_oFrom == null here. // Todo: Handle p_oDuration == null here. return new StateInfo( dateTimeNowProvider, state.From.Value, state.MailAddress, state.Code); case InUseStateEnum.Booked: // Todo: Handle p_oFrom == null here. // Todo: Clearify question: What to do if code changes form one value to another? This should never happen. // Todo: Clearify question: What to do if from time changes form one value to another? This should never happen. return new StateInfo( state.From.Value, state.MailAddress, state.Code); default: // Todo: New state Busy has to be defined. throw new Exception(string.Format("Can not create new state info object. Unknown state {0} detected.", state.Value)); } } /// /// Gets the state value of object. /// public InUseStateEnum Value => _StateInfo.Value; /// /// Member for serialization purposes. /// [DataMember] private BaseState StateInfoObject { get { return _StateInfo.StateInfoObject; } set { var l_oStateOccupied = value as StateOccupiedInfo; if (l_oStateOccupied != null) { _StateInfo = new StateInfo(l_oStateOccupied.From, l_oStateOccupied.MailAddress, l_oStateOccupied.Code); return; } var l_oStateRequested = value as StateRequestedInfo; if (l_oStateRequested != null) { _StateInfo = new StateInfo(_DateTimeNowProvider, l_oStateRequested.From, l_oStateRequested.MailAddress, l_oStateRequested.Code); return; } _StateInfo = new StateInfo(); } } /// Transforms object to string. /// /// public new string ToString() { return _StateInfo.ToString(); } /// /// Checks and updates state if required. /// /// Value indicating wheter state has changed public void UpdateOnTimeElapsed() { switch (_StateInfo.Value) { // State is disposable or booked. No need to update "OnTimeElapsed" case InUseStateEnum.Disposable: case InUseStateEnum.Booked: return; } // Check if maximum reserved time has elapsed. if (!_StateInfo.GetIsStillReserved(out _RemainingTime)) { // Time has elapsed, switch state to disposable and notfiy client _StateInfo = new StateInfo(); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State))); return; } PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RemainingTime))); } /// Updates state from webserver. /// State of the bike. /// Date time when bike was reserved/ booked. /// Mailaddress of the one which reserved/ booked. /// Booking code if bike is booked or reserved. /// Controls whether notify property changed events are fired or not. public void Load( InUseStateEnum state, DateTime? from = null, string mailAddress = null, string code = null, Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.All) { var lastState = _StateInfo.Value; switch (state) { case InUseStateEnum.Disposable: _StateInfo = new StateInfo(); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. _RemainingTime = null; break; case InUseStateEnum.Reserved: // Todo: Handle p_oFrom == null here. // Todo: Handle p_oDuration == null here. _StateInfo = new StateInfo( _DateTimeNowProvider, from.Value, mailAddress, code); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. _RemainingTime = null; break; case InUseStateEnum.Booked: // Todo: Handle p_oFrom == null here. // Todo: Clearify question: What to do if code changes form one value to another? This should never happen. // Todo: Clearify question: What to do if from time changes form one value to another? This should never happen. _StateInfo = new StateInfo( from.Value, mailAddress, code); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. _RemainingTime = null; break; default: // Todo: New state Busy has to be defined. break; } if (lastState != _StateInfo.Value && notifyLevel == Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.All) { // State has changed, notify clients. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State))); } } /// /// If bike is reserved time raimaining while bike stays reserved, null otherwise. /// public TimeSpan? RemainingTime { get { switch (_StateInfo.Value) { // State is either available or occupied. case InUseStateEnum.Disposable: case InUseStateEnum.Booked: return null; } if (_RemainingTime.HasValue == false) { // Value was not yet querried. // Do query before returning object. _StateInfo.GetIsStillReserved(out _RemainingTime); } return _RemainingTime; } } public DateTime? From { get { return _StateInfo.From; } } public string MailAddress { get { return _StateInfo.MailAddress; } } public string Code { get { return _StateInfo.Code; } } } }