2021-05-13 20:03:07 +02:00
using System ;
using TINK.Model.Bike ;
using TINK.Model.Station ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Response ;
2021-05-13 20:03:07 +02:00
using TINK.Model.User.Account ;
using System.Collections.Generic ;
using TINK.Model.State ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Exception ;
2021-05-13 20:03:07 +02:00
using Serilog ;
using BikeInfo = TINK . Model . Bike . BC . BikeInfo ;
using IBikeInfoMutable = TINK . Model . Bikes . Bike . BC . IBikeInfoMutable ;
using System.Globalization ;
namespace TINK.Model.Connector
{
/// <summary>
/// Connects TINK app to copri using JSON as input data format.
/// </summary>
/// <todo>Rename to UpdateFromCopri.</todo>
public static class UpdaterJSON
{
/// <summary> Loads a bike object from copri server cancel reservation/ booking update request.</summary>
/// <param name="bike">Bike object to load response into.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
public static void Load (
this IBikeInfoMutable bike ,
Bikes . Bike . BC . NotifyPropertyChangedLevel notifyLevel )
{
bike . State . Load ( InUseStateEnum . Disposable , notifyLevel : notifyLevel ) ;
}
/// <summary>
/// Gets all statsion for station provider and add them into station list.
/// </summary>
/// <param name="p_oStationList">List of stations to update.</param>
public static StationDictionary GetStationsAllMutable ( this StationsAllResponse p_oStationsAllResponse )
{
// Get stations from Copri/ file/ memory, ....
if ( p_oStationsAllResponse = = null
| | p_oStationsAllResponse . stations = = null )
{
// Latest list of stations could not be retrieved from provider.
return new StationDictionary ( ) ;
}
Version . TryParse ( p_oStationsAllResponse . copri_version , out Version l_oCopriVersion ) ;
var l_oStations = new StationDictionary ( p_oVersion : l_oCopriVersion ) ;
foreach ( var l_oStation in p_oStationsAllResponse . stations )
{
if ( l_oStations . GetById ( l_oStation . Value . station ) ! = null )
{
// Can not add station to list of station. Id is not unique.
throw new InvalidResponseException < StationsAllResponse > (
string . Format ( "Station id {0} is not unique." , l_oStation . Value . station ) , p_oStationsAllResponse ) ;
}
l_oStations . Add ( new Station . Station (
l_oStation . Value . station ,
l_oStation . Value . GetGroup ( ) ,
l_oStation . Value . GetPosition ( ) ,
l_oStation . Value . description ) ) ;
}
return l_oStations ;
}
/// <summary> Gets account object from login response.</summary>
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>
/// <param name="loginResponse">Response to get session cookie and debug level from.</param>
/// <param name="mail">Mail address needed to construct a complete account object (is not part of response).</param>
/// <param name="password">Password needed to construct a complete account object (is not part of response).</param>
public static IAccount GetAccount (
this AuthorizationResponse loginResponse ,
string merchantId ,
string mail ,
string password )
{
if ( loginResponse = = null )
{
throw new ArgumentNullException ( "p_oLoginResponse" ) ;
}
return new Account (
mail ,
password ,
loginResponse . authcookie ? . Replace ( merchantId , "" ) ,
loginResponse . GetGroup ( ) ,
loginResponse . debuglevel = = 1
? Permissions . All :
( Permissions ) loginResponse . debuglevel ) ;
}
/// <summary> Load bike object from booking response. </summary>
/// <param name="bike">Bike object to load from response.</param>
/// <param name="bikeInfo">Booking response.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
/// <param name="p_strSessionCookie">Session cookie of user which books bike.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
public static void Load (
this IBikeInfoMutable bike ,
BikeInfoReservedOrBooked bikeInfo ,
string mailAddress ,
Func < DateTime > dateTimeProvider ,
Bikes . Bike . BC . NotifyPropertyChangedLevel notifyLevel = Bikes . Bike . BC . NotifyPropertyChangedLevel . All )
{
var l_oDateTimeProvider = dateTimeProvider ! = null
? dateTimeProvider
: ( ) = > DateTime . Now ;
if ( bike is Bike . BluetoothLock . BikeInfoMutable btBikeInfo )
{
btBikeInfo . LockInfo . Load (
bikeInfo . GetBluetoothLockId ( ) ,
bikeInfo . GetBluetoothLockGuid ( ) ,
bikeInfo . GetSeed ( ) ,
bikeInfo . GetUserKey ( ) ,
bikeInfo . GetAdminKey ( ) ) ;
}
var l_oState = bikeInfo . GetState ( ) ;
switch ( l_oState )
{
case InUseStateEnum . Disposable :
bike . State . Load (
InUseStateEnum . Disposable ,
notifyLevel : notifyLevel ) ;
break ;
case InUseStateEnum . Reserved :
bike . State . Load (
InUseStateEnum . Reserved ,
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . timeCode ,
notifyLevel ) ;
break ;
case InUseStateEnum . Booked :
bike . State . Load (
InUseStateEnum . Booked ,
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . timeCode ,
notifyLevel ) ;
break ;
default :
throw new Exception ( string . Format ( "Unexpected bike state detected. state is {0}." , l_oState ) ) ;
}
}
/// <summary> Gets bikes available from copri server response.</summary>
/// <param name="p_oBikesAvailableResponse">Response to create collection from.</param>
/// <returns>New collection of available bikes.</returns>
public static BikeCollection GetBikesAvailable (
this BikesAvailableResponse p_oBikesAvailableResponse )
{
return GetBikesAll (
p_oBikesAvailableResponse ,
new BikesReservedOccupiedResponse ( ) , // There are no occupied bikes.
string . Empty ,
( ) = > DateTime . Now ) ;
}
/// <summary> Gets bikes occupied from copri server response. </summary>
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
/// <returns>New collection of occupied bikes.</returns>
public static BikeCollection GetBikesOccupied (
this BikesReservedOccupiedResponse p_oBikesOccupiedResponse ,
string p_strMail ,
Func < DateTime > p_oDateTimeProvider )
{
return GetBikesAll (
new BikesAvailableResponse ( ) ,
p_oBikesOccupiedResponse ,
p_strMail ,
p_oDateTimeProvider ) ;
}
/// <summary> Gets bikes occupied from copri server response. </summary>
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
/// <returns>New collection of occupied bikes.</returns>
public static BikeCollection GetBikesAll (
BikesAvailableResponse p_oBikesAvailableResponse ,
BikesReservedOccupiedResponse p_oBikesOccupiedResponse ,
string p_strMail ,
Func < DateTime > p_oDateTimeProvider )
{
2021-06-26 20:57:55 +02:00
var l_oBikesDictionary = new Dictionary < string , BikeInfo > ( ) ;
var l_oDuplicates = new Dictionary < string , BikeInfo > ( ) ;
2021-05-13 20:03:07 +02:00
// Get bikes from Copri/ file/ memory, ....
if ( p_oBikesAvailableResponse ! = null
& & p_oBikesAvailableResponse . bikes ! = null )
{
foreach ( var bikeInfoResponse in p_oBikesAvailableResponse . bikes . Values )
{
var l_oBikeInfo = BikeInfoFactory . Create ( bikeInfoResponse ) ;
if ( l_oBikeInfo = = null )
{
// Response is not valid.
continue ;
}
if ( l_oBikesDictionary . ContainsKey ( l_oBikeInfo . Id ) )
{
// Duplicates are not allowed.
Log . Error ( $"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes available. Bike status is {l_oBikeInfo.State.Value}." ) ;
if ( ! l_oDuplicates . ContainsKey ( l_oBikeInfo . Id ) )
{
l_oDuplicates . Add ( l_oBikeInfo . Id , l_oBikeInfo ) ;
}
continue ;
}
l_oBikesDictionary . Add ( l_oBikeInfo . Id , l_oBikeInfo ) ;
}
}
// Get bikes from Copri/ file/ memory, ....
if ( p_oBikesOccupiedResponse ! = null
& & p_oBikesOccupiedResponse . bikes_occupied ! = null )
{
foreach ( var l_oBikeInfoResponse in p_oBikesOccupiedResponse . bikes_occupied . Values )
{
BikeInfo l_oBikeInfo = BikeInfoFactory . Create (
l_oBikeInfoResponse ,
p_strMail ,
p_oDateTimeProvider ) ;
if ( l_oBikeInfo = = null )
{
continue ;
}
if ( l_oBikesDictionary . ContainsKey ( l_oBikeInfo . Id ) )
{
// Duplicates are not allowed.
Log . Error ( $"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes occupied. Bike status is {l_oBikeInfo.State.Value}." ) ;
if ( ! l_oDuplicates . ContainsKey ( l_oBikeInfo . Id ) )
{
l_oDuplicates . Add ( l_oBikeInfo . Id , l_oBikeInfo ) ;
}
continue ;
}
l_oBikesDictionary . Add ( l_oBikeInfo . Id , l_oBikeInfo ) ;
}
}
// Remove entries which are not unique.
foreach ( var l_oDuplicate in l_oDuplicates )
{
l_oBikesDictionary . Remove ( l_oDuplicate . Key ) ;
}
return new BikeCollection ( l_oBikesDictionary ) ;
}
}
/// <summary>
/// Constructs bike info instances/ bike info derived instances.
/// </summary>
public static class BikeInfoFactory
{
public static BikeInfo Create ( BikeInfoAvailable bikeInfo )
{
if ( bikeInfo . GetIsManualLockBike ( ) )
{
// Manual lock bikes are no more supported.
Log . Error (
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
"Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $" station number { bikeInfo . station } " : string.Empty)}."
) ;
return null ;
}
switch ( bikeInfo . GetState ( ) )
{
case InUseStateEnum . Disposable :
break ;
default :
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected." ) ;
return null ;
}
2021-06-26 20:57:55 +02:00
if ( string . IsNullOrEmpty ( bikeInfo . station ) )
2021-05-13 20:03:07 +02:00
{
// Bike available must always have a station id because bikes can only be returned at a station.
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. No station info set." ) ;
return null ;
}
try
{
return ! bikeInfo . GetIsBluetoothLockBike ( )
? new BikeInfo (
bikeInfo . bike ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description )
: new Bike . BluetoothLock . BikeInfo (
bikeInfo . bike ,
bikeInfo . GetBluetoothLockId ( ) ,
bikeInfo . GetBluetoothLockGuid ( ) ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description ) ;
}
catch ( ArgumentException ex )
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Invalid response detected. Available bike with id {bikeInfo.bike} skipped. {ex.Message}" ) ;
return null ;
}
}
/// <summary> Creates a bike info object from copri response. </summary>
/// <param name="bikeInfo">Copri response. </param>
/// <param name="mailAddress">Mail address of user.</param>
/// <param name="dateTimeProvider">Date and time provider function.</param>
/// <returns></returns>
public static BikeInfo Create (
BikeInfoReservedOrBooked bikeInfo ,
string mailAddress ,
Func < DateTime > dateTimeProvider )
{
if ( bikeInfo . GetIsManualLockBike ( ) )
{
// Manual lock bikes are no more supported.
Log . Error (
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
"Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $" , station number { bikeInfo . station } " : string.Empty)}."
) ;
return null ;
}
// Check if bike is a bluetooth lock bike.
var isBluetoothBike = bikeInfo . GetIsBluetoothLockBike ( ) ;
int lockSerial = bikeInfo . GetBluetoothLockId ( ) ;
Guid lockGuid = bikeInfo . GetBluetoothLockGuid ( ) ;
switch ( bikeInfo . GetState ( ) )
{
case InUseStateEnum . Reserved :
try
{
return ! isBluetoothBike
? new BikeInfo (
bikeInfo . bike ,
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . timeCode ,
dateTimeProvider )
: new Bike . BluetoothLock . BikeInfo (
bikeInfo . bike ,
lockSerial ,
lockGuid ,
bikeInfo . GetUserKey ( ) ,
bikeInfo . GetAdminKey ( ) ,
bikeInfo . GetSeed ( ) ,
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
dateTimeProvider ,
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ) ;
}
catch ( ArgumentException ex )
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Reserved bike with id {bikeInfo.bike} skipped. {ex.Message}" ) ;
return null ;
}
case InUseStateEnum . Booked :
try
{
return ! isBluetoothBike
? new BikeInfo (
bikeInfo . bike ,
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . timeCode )
: new Bike . BluetoothLock . BikeInfo (
bikeInfo . bike ,
lockSerial ,
bikeInfo . GetBluetoothLockGuid ( ) ,
bikeInfo . GetUserKey ( ) ,
bikeInfo . GetAdminKey ( ) ,
bikeInfo . GetSeed ( ) ,
bikeInfo . GetFrom ( ) ,
mailAddress ,
bikeInfo . station ,
bikeInfo . GetOperatorUri ( ) ,
#if ! NOTARIFFDESCRIPTION
Create ( bikeInfo . tariff_description ) ,
#else
Create ( ( TINK . Repository . Response . TariffDescription ) null ) ,
#endif
bikeInfo . GetIsDemo ( ) ,
bikeInfo . GetGroup ( ) ,
bikeInfo . GetWheelType ( ) ,
bikeInfo . GetTypeOfBike ( ) ) ;
}
catch ( ArgumentException ex )
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Booked bike with id {bikeInfo.bike} skipped. {ex.Message}" ) ;
return null ;
}
default :
Log . Error ( $"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected." ) ;
return null ;
}
}
public static Bikes . Bike . TariffDescription Create ( this TINK . Repository . Response . TariffDescription tariffDesciption )
{
return new Bikes . Bike . TariffDescription
{
Name = tariffDesciption ? . name ,
2021-06-26 20:57:55 +02:00
#if USCSHARP9
2021-05-13 20:03:07 +02:00
Number = int . TryParse ( tariffDesciption ? . number , out int number ) ? number : null ,
2021-06-26 20:57:55 +02:00
#else
Number = int . TryParse ( tariffDesciption ? . number , out int number ) ? number : ( int? ) null ,
#endif
2021-05-13 20:03:07 +02:00
FreeTimePerSession = double . TryParse ( tariffDesciption ? . free_hours , NumberStyles . Any , CultureInfo . InvariantCulture , out double freeHours ) ? TimeSpan . FromHours ( freeHours ) : TimeSpan . Zero ,
FeeEuroPerHour = double . TryParse ( tariffDesciption ? . eur_per_hour , NumberStyles . Any , CultureInfo . InvariantCulture , out double euroPerHour ) ? euroPerHour : double . NaN ,
AboEuroPerMonth = double . TryParse ( tariffDesciption ? . abo_eur_per_month , NumberStyles . Any , CultureInfo . InvariantCulture , out double aboEuroPerMonth ) ? aboEuroPerMonth : double . NaN ,
MaxFeeEuroPerDay = double . TryParse ( tariffDesciption ? . max_eur_per_day , NumberStyles . Any , CultureInfo . InvariantCulture , out double maxEuroPerDay ) ? maxEuroPerDay : double . NaN ,
} ;
}
}
}