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 ;
2021-07-20 23:06:09 +02:00
using TINK.Model.Station.Operator ;
using Xamarin.Forms ;
2021-08-01 17:24:15 +02:00
using System.Linq ;
using TINK.Model.MiniSurvey ;
2021-05-13 20:03:07 +02:00
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>
2021-07-20 23:06:09 +02:00
public static StationDictionary GetStationsAllMutable ( this StationsAvailableResponse stationsAllResponse )
2021-05-13 20:03:07 +02:00
{
// Get stations from Copri/ file/ memory, ....
2021-07-20 23:06:09 +02:00
if ( stationsAllResponse = = null
| | stationsAllResponse . stations = = null )
2021-05-13 20:03:07 +02:00
{
// Latest list of stations could not be retrieved from provider.
return new StationDictionary ( ) ;
}
2021-07-20 23:06:09 +02:00
Version . TryParse ( stationsAllResponse . copri_version , out Version copriVersion ) ;
2021-05-13 20:03:07 +02:00
2021-07-20 23:06:09 +02:00
var stations = new StationDictionary ( p_oVersion : copriVersion ) ;
2021-05-13 20:03:07 +02:00
2021-07-20 23:06:09 +02:00
foreach ( var station in stationsAllResponse . stations )
2021-05-13 20:03:07 +02:00
{
2021-07-20 23:06:09 +02:00
if ( stations . GetById ( station . Value . station ) ! = null )
2021-05-13 20:03:07 +02:00
{
// Can not add station to list of station. Id is not unique.
2021-07-20 23:06:09 +02:00
throw new InvalidResponseException < StationsAvailableResponse > (
string . Format ( "Station id {0} is not unique." , station . Value . station ) , stationsAllResponse ) ;
2021-05-13 20:03:07 +02:00
}
2021-07-20 23:06:09 +02:00
stations . Add ( new Station . Station (
station . Value . station ,
station . Value . GetGroup ( ) ,
station . Value . GetPosition ( ) ,
station . Value . description ,
new Data ( station . Value . operator_data ? . operator_name ,
station . Value . operator_data ? . operator_phone ,
station . Value . operator_data ? . operator_hours ,
station . Value . operator_data ? . operator_email ,
! string . IsNullOrEmpty ( station . Value . operator_data ? . operator_color )
? Color . FromHex ( station . Value . operator_data ? . operator_color )
: ( Color ? ) null ) ) ) ;
2021-05-13 20:03:07 +02:00
}
2021-07-20 23:06:09 +02:00
return stations ;
2021-05-13 20:03:07 +02:00
}
/// <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 )
{
2021-11-08 23:11:56 +01:00
throw new ArgumentNullException ( nameof ( loginResponse ) ) ;
2021-05-13 20:03:07 +02:00
}
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>
2021-11-08 23:11:56 +01:00
/// <param name="bikesAvailableResponse">Response to create collection from.</param>
2021-05-13 20:03:07 +02:00
/// <returns>New collection of available bikes.</returns>
public static BikeCollection GetBikesAvailable (
2021-11-08 23:11:56 +01:00
this BikesAvailableResponse bikesAvailableResponse )
2021-05-13 20:03:07 +02:00
{
return GetBikesAll (
2021-11-08 23:11:56 +01:00
bikesAvailableResponse ,
2021-05-13 20:03:07 +02:00
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 (
2021-07-12 19:30:14 +02:00
this BikesReservedOccupiedResponse bikesOccupiedResponse ,
string mail ,
Func < DateTime > dateTimeProvider )
2021-05-13 20:03:07 +02:00
{
return GetBikesAll (
new BikesAvailableResponse ( ) ,
2021-07-12 19:30:14 +02:00
bikesOccupiedResponse ,
mail ,
dateTimeProvider ) ;
2021-05-13 20:03:07 +02:00
}
/// <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 (
2021-07-12 19:30:14 +02:00
BikesAvailableResponse bikesAvailableResponse ,
BikesReservedOccupiedResponse bikesOccupiedResponse ,
2021-11-08 23:11:56 +01:00
string mail ,
Func < DateTime > dateTimeProvider )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
var bikesDictionary = new Dictionary < string , BikeInfo > ( ) ;
var duplicates = new Dictionary < string , BikeInfo > ( ) ;
2021-05-13 20:03:07 +02:00
// Get bikes from Copri/ file/ memory, ....
2021-07-12 19:30:14 +02:00
if ( bikesAvailableResponse ! = null
& & bikesAvailableResponse . bikes ! = null )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
foreach ( var bikeInfoResponse in bikesAvailableResponse . bikes . Values )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
var bikeInfo = BikeInfoFactory . Create ( bikeInfoResponse ) ;
if ( bikeInfo = = null )
2021-05-13 20:03:07 +02:00
{
// Response is not valid.
continue ;
}
2021-07-12 19:30:14 +02:00
if ( bikesDictionary . ContainsKey ( bikeInfo . Id ) )
2021-05-13 20:03:07 +02:00
{
// Duplicates are not allowed.
2021-07-12 19:30:14 +02:00
Log . Error ( $"Duplicate bike with id {bikeInfo.Id} detected evaluating bikes available. Bike status is {bikeInfo.State.Value}." ) ;
2021-05-13 20:03:07 +02:00
2021-07-12 19:30:14 +02:00
if ( ! duplicates . ContainsKey ( bikeInfo . Id ) )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
duplicates . Add ( bikeInfo . Id , bikeInfo ) ;
2021-05-13 20:03:07 +02:00
}
continue ;
}
2021-07-12 19:30:14 +02:00
bikesDictionary . Add ( bikeInfo . Id , bikeInfo ) ;
2021-05-13 20:03:07 +02:00
}
}
// Get bikes from Copri/ file/ memory, ....
2021-07-12 19:30:14 +02:00
if ( bikesOccupiedResponse ! = null
& & bikesOccupiedResponse . bikes_occupied ! = null )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
foreach ( var bikeInfoResponse in bikesOccupiedResponse . bikes_occupied . Values )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
BikeInfo bikeInfo = BikeInfoFactory . Create (
bikeInfoResponse ,
2021-11-08 23:11:56 +01:00
mail ,
dateTimeProvider ) ;
2021-05-13 20:03:07 +02:00
2021-07-12 19:30:14 +02:00
if ( bikeInfo = = null )
2021-05-13 20:03:07 +02:00
{
continue ;
}
2021-07-12 19:30:14 +02:00
if ( bikesDictionary . ContainsKey ( bikeInfo . Id ) )
2021-05-13 20:03:07 +02:00
{
// Duplicates are not allowed.
2021-07-12 19:30:14 +02:00
Log . Error ( $"Duplicate bike with id {bikeInfo.Id} detected evaluating bikes occupied. Bike status is {bikeInfo.State.Value}." ) ;
if ( ! duplicates . ContainsKey ( bikeInfo . Id ) )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
duplicates . Add ( bikeInfo . Id , bikeInfo ) ;
2021-05-13 20:03:07 +02:00
}
continue ;
}
2021-07-12 19:30:14 +02:00
bikesDictionary . Add ( bikeInfo . Id , bikeInfo ) ;
2021-05-13 20:03:07 +02:00
}
}
// Remove entries which are not unique.
2021-07-12 19:30:14 +02:00
foreach ( var l_oDuplicate in duplicates )
2021-05-13 20:03:07 +02:00
{
2021-07-12 19:30:14 +02:00
bikesDictionary . Remove ( l_oDuplicate . Key ) ;
2021-05-13 20:03:07 +02:00
}
2021-07-12 19:30:14 +02:00
return new BikeCollection ( bikesDictionary ) ;
2021-05-13 20:03:07 +02:00
}
}
/// <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 ( ) ,
2021-07-12 19:30:14 +02:00
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description ) ;
2021-05-13 20:03:07 +02:00
}
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 ( ) ,
2021-07-12 19:30:14 +02:00
bikeInfo . GetTypeOfBike ( ) ,
bikeInfo . description ) ;
2021-05-13 20:03:07 +02:00
}
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 ;
}
}
2021-07-12 19:30:14 +02:00
public static Bikes . Bike . TariffDescription Create ( this TariffDescription tariffDesciption )
2021-05-13 20:03:07 +02:00
{
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 ,
2021-11-07 21:28:13 +01:00
OperatorAgb = tariffDesciption ? . operator_agb ,
TrackingInfo = tariffDesciption ? . track_info
2021-05-13 20:03:07 +02:00
} ;
}
2021-08-01 17:24:15 +02:00
/// <summary> Creates a survey object from response.</summary>
/// <param name="response">Response to create survey object from.</param>
public static MiniSurveyModel Create ( this ReservationCancelReturnResponse response )
{
if ( response ? . user_miniquery = = null )
{
return new MiniSurveyModel ( ) ;
}
var miniquery = response . user_miniquery ;
var survey = new MiniSurveyModel
{
Title = miniquery . title ,
Subtitle = miniquery . subtitle ,
Footer = miniquery . footer
} ;
foreach ( var question in miniquery ? . questions ? . OrderBy ( x = > x . Key ) ? ? new Dictionary < string , MiniSurveyResponse . Question > ( ) . OrderBy ( x = > x . Key ) )
{
if ( string . IsNullOrEmpty ( question . Key . Trim ( ) )
| | question . Value . query = = null )
{
// Skip invalid entries.
continue ;
}
survey . Questions . Add (
question . Key ,
new MiniSurveyModel . QuestionModel ( ) ) ;
}
return survey ;
}
2021-05-13 20:03:07 +02:00
}
}