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 remainig time on demand for state of type reserved. /// private readonly Func m_oDateTimeNowProvider; // Holds the current disposable state value private StateInfo m_oStateInfo; /// /// Backs up remaining time of child object. /// private TimeSpan? m_oRemainingTime = null; /// Notifies clients about state changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Constructs a state object from source. /// /// State info to load from. public StateInfoMutable( Func p_oDateTimeNowProvider = null, IStateInfo p_oState = null) { // Back up date time provider to be able to pass this to requested- object if state changes to requested. m_oDateTimeNowProvider = p_oDateTimeNowProvider != null ? p_oDateTimeNowProvider : () => DateTime.Now; m_oStateInfo = Create(p_oState, p_oDateTimeNowProvider); } /// /// Loads state from immutable source. /// /// State to load from. public void Load(IStateInfo p_oState) { if (p_oState == 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 = m_oRemainingTime; // Create new state info object from source. m_oStateInfo = Create(p_oState, m_oDateTimeNowProvider); // Update remaining time value. m_oStateInfo.GetIsStillReserved(out m_oRemainingTime); if (l_oLastState == m_oStateInfo.Value && l_oLastRemainingTime == m_oRemainingTime) { 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 p_oState, Func p_oDateTimeNowProvider) { switch (p_oState != null ? p_oState.Value : InUseStateEnum.Disposable) { case InUseStateEnum.Disposable: return new StateInfo(); case InUseStateEnum.Reserved: // Todo: Handle p_oFrom == null here. // Todo: Handle p_oDuration == null here. return new StateInfo( p_oDateTimeNowProvider, p_oState.From.Value, p_oState.MailAddress, p_oState.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( p_oState.From.Value, p_oState.MailAddress, p_oState.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.", p_oState.Value)); } } /// /// Gets the state value of object. /// public InUseStateEnum Value { get { return m_oStateInfo.Value; } } /// /// Member for serialization purposes. /// [DataMember] private BaseState StateInfoObject { get { return m_oStateInfo.StateInfoObject; } set { var l_oStateOccupied = value as StateOccupiedInfo; if (l_oStateOccupied != null) { m_oStateInfo = new StateInfo(l_oStateOccupied.From, l_oStateOccupied.MailAddress, l_oStateOccupied.Code); return; } var l_oStateRequested = value as StateRequestedInfo; if (l_oStateRequested != null) { m_oStateInfo = new StateInfo(m_oDateTimeNowProvider, l_oStateRequested.From, l_oStateRequested.MailAddress, l_oStateRequested.Code); return; } m_oStateInfo = new StateInfo(); } } /// Transforms object to string. /// /// public new string ToString() { return m_oStateInfo.ToString(); } /// /// Checks and updates state if required. /// /// Value indicating wheter state has changed public void UpdateOnTimeElapsed() { switch (m_oStateInfo.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 (!m_oStateInfo.GetIsStillReserved(out m_oRemainingTime)) { // Time has elapsed, switch state to disposable and notfiy client m_oStateInfo = 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. /// Lenght of time span for which bike remains 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 p_oState, DateTime? p_oFrom = null, string p_strMailAddress = null, string p_strCode = null, Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All) { var l_oLastState = m_oStateInfo.Value; switch (p_oState) { case InUseStateEnum.Disposable: m_oStateInfo = new StateInfo(); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. m_oRemainingTime = null; break; case InUseStateEnum.Reserved: // Todo: Handle p_oFrom == null here. // Todo: Handle p_oDuration == null here. m_oStateInfo = new StateInfo( m_oDateTimeNowProvider, p_oFrom.Value, p_strMailAddress, p_strCode); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. m_oRemainingTime = 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. m_oStateInfo = new StateInfo( p_oFrom.Value, p_strMailAddress, p_strCode); // Set value to null. Otherwise potentially obsolete value will be taken remaining time. m_oRemainingTime = null; break; default: // Todo: New state Busy has to be defined. break; } if (l_oLastState != m_oStateInfo.Value && notifyLevel == Bikes.Bike.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 (m_oStateInfo.Value) { // State is either available or occupied. case InUseStateEnum.Disposable: case InUseStateEnum.Booked: return null; } if (m_oRemainingTime.HasValue == false) { // Value was not yet querried. // Do querry before returning object. m_oStateInfo.GetIsStillReserved(out m_oRemainingTime); } return m_oRemainingTime; } } public DateTime? From { get { return m_oStateInfo.From; } } public string MailAddress { get { return m_oStateInfo.MailAddress; } } public string Code { get { return m_oStateInfo.Code; } } } }