using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Model.Connector.Updater;
using TINK.Model.Device;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
using TINK.Services.CopriApi.Exception;
namespace TINK.Services.CopriApi
{
public static class Polling
{
/// Timeout for open/ close operations.
private const int OPEN_CLOSE_TIMEOUT_MS = 50000;
/// Opens lock.
/// Instance to communicate with backend.
/// Bike object holding id of bike to open. Lock state of object is updated after open request.
public static async Task OpenAync(
this ICopriServerBase copriServer,
IBikeInfoMutable bike)
{
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
await cachedServer.OpenAync(bike);
}
/// Opens lock.
/// Instance to communicate with backend.
/// Bike object holding id of bike to open. Lock state of object is updated after open request.
public static async Task OpenAync(
this ICachedCopriServer cachedServer,
IBikeInfoMutable bike)
{
// Send command to close lock
await cachedServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.unlocking,
bike.OperatorUri);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Open
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
}
///
/// Books a bike and opens the lock.
///
/// Instance to communicate with backend.
/// Bike to book and open.
/// Mail address of user which books bike.
public static async Task BookAndOpenAync(
this ICopriServerBase corpiServer,
IBikeInfoMutable bike,
string mailAddress)
{
if (bike == null)
{
throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available.");
}
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to open lock
var response = bike.State.Value == Model.State.InUseStateEnum.Disposable
? (await corpiServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await corpiServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
// Upated locking state.
var lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState.HasValue /* if null bike is no more occupied*/ &&
lockingState.Value != LockingState.Open
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id);
Log.Debug($"Current lock state of bike {bike.Id} is {(lockingState.HasValue ? lockingState.Value.ToString() : "-")}.");
}
// Check if bike is still occupied.
if (lockingState == null)
{
// User did not take bike out of the station
throw new BikeStillInStationException("Booking was cancelled because bike is still in station.");
}
// Upate booking state.
bike.Load(
response,
mailAddress,
Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None);
// Update locking state.
bike.LockInfo.State = lockingState.Value;
}
///
/// Returns a bike and closes the lock.
///
/// Instance to communicate with backend.
/// Bike to close.
public static async Task CloseAync(
this ICopriServerBase corpiServer,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to close lock
await corpiServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.locking,
bike.OperatorUri);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Closed
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
}
///
/// Returns a bike and closes the lock.
///
/// Instance to communicate with backend.
/// Smart device on which app runs on.
/// Mail address of user which books bike.
public static async Task ReturnAndCloseAync(
this ICopriServerBase corpiServer,
ISmartDevice smartDevice,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to open lock
DoReturnResponse response =
await corpiServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
// Upate booking state
bike.Load(Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Closed
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
return response?.Create() ?? new BookingFinishedModel();
}
///
/// Queries the locking state from copri.
///
/// Service to use.
/// Bike id to query lock state for.
/// Locking state
private static async Task GetLockStateAsync(
this ICachedCopriServer corpiServer,
string bikeId)
{
// Querry reserved or booked bikes first for performance reasons.
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();
}
var bikeAvailable = (await corpiServer.GetBikesAvailable(false))?.Response.bikes?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeAvailable != null)
{
return bikeAvailable.GetCopriLockingState();
}
return LockingState.UnknownDisconnected;
}
///
/// Queries the locking state of a occupied bike from copri.
///
/// Service to use.
/// Bike id to query lock state for.
/// Locking state if bike is still occupied, null otherwise.
private static async Task GetOccupiedBikeLockStateAsync(
this ICachedCopriServer corpiServer,
string bikeId)
{
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();
}
return null;
}
}
}