sharee.bike-App/TINKLib/Model/Connector/TextToTypeHelper.cs
2023-06-06 12:05:48 +02:00

458 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using TINK.Repository.Exception;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations.Station;
using Xamarin.Forms;
namespace TINK.Model.Connector
{
/// <summary>
/// Converts weak typed JSON data (mostly string) to strong typed c# data (base types, enumerations, objects, ...).
/// JSON is received from COPRI and deserialized using Json.NET.
/// </summary>
public static class TextToTypeHelper
{
/// <summary> Holds the text for demo bikes. </summary>
private const string DEMOBIKEMARKER = "DEMO";
/// <summary>
/// Gets the position from StationInfo object.
/// </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IPosition GetPosition(this StationInfo stationInfo)
=> GetPosition(stationInfo.gps);
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="authorizationResponse">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this AuthorizationResponse authorizationResponse)
{
try
{
return authorizationResponse.user_group.GetGroup();
}
catch (Exception l_oException)
{
throw new Exception($"Can not get group of user from text \"{authorizationResponse.user_group}\".", l_oException);
}
}
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="group">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this string[] group)
{
if (group == null || group.Length == 0)
{
// If not logged in stations groups are empty form COPRI version v4.1.
Log.Debug("Can not get group form string. Group text can not be null.");
return new List<string>();
}
return new HashSet<string>(group).ToList();
}
/// <summary> Gets if user acknowledged AGBs or not. </summary>
/// <param name="authorizationResponse">Object to get information from.</param>
/// <returns>Position information.</returns>
public static bool GetIsAgbAcknowledged(this AuthorizationResponse authorizationResponse)
=> int.TryParse(authorizationResponse?.agb_checked, out int result)
&& result != 0;
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="group">Object to get information from.</param>
/// <returns>Position information.</returns>
public static string GetGroup(this IEnumerable<string> group)
{
return string.Join(",", group);
}
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this StationInfo stationInfo)
{
try
{
return stationInfo.station_group.GetGroup();
}
catch (Exception l_oException)
{
throw new Exception($"Can not get group of stations from text \"{stationInfo.station_group}\".", l_oException);
}
}
/// <summary>
/// Gets the position from BikeInfoBase object.
/// </summary>
/// <param name="bikeInfo">Object to get information from.</param>
/// <returns>Rental state.</returns>
public static InUseStateEnum GetState(this BikeInfoBase bikeInfo)
{
var stateText = bikeInfo.state?.ToLower()?.Trim();
if (string.IsNullOrEmpty(stateText))
{
throw new InvalidResponseException<BikeInfoBase>(
"Bike state must not be empty.",
bikeInfo);
}
if (Enum.TryParse(stateText, out InUseStateEnum state))
return state;
if (stateText == "available")
{
return InUseStateEnum.Disposable;
}
else if (stateText == "reserved" ||
stateText == "requested")
{
return InUseStateEnum.Reserved;
}
else if (stateText == "booked" ||
stateText == "occupied")
{
return InUseStateEnum.Booked;
}
throw new CommunicationException(string.Format("Unknown bike state detected. State is {0}.", stateText));
}
/// <summary>
/// Gets the position from BikeInfoAvailable object.
/// </summary>
/// <param name="bikeInfo">Object to get information from.</param>
/// <returns>Rental state.</returns>
public static InUseStateEnum GetState(this BikeInfoAvailable bikeInfo)
{
var state = GetState((BikeInfoBase)bikeInfo);
if (state != InUseStateEnum.Disposable)
return state;
return bikeInfo.GetIsFeedbackPending()
? InUseStateEnum.FeedbackPending
: InUseStateEnum.Disposable;
}
/// <summary>
/// Gets the from date information from JSON.
/// </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information if bike hold this information 0001-01-01 (DateTime.MinValue) otherwise.</returns>
public static DateTime GetFrom(this BikeInfoReservedOrBooked bikeInfo)
=> DateTime.TryParse(bikeInfo?.start_time, out DateTime dateFrom) ? dateFrom : DateTime.MinValue;
/// <summary>
/// Gets whether the bike is a trike or not.
/// </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns>
public static bool? GetIsDemo(this BikeInfoBase bikeInfo)
{
return bikeInfo?.description != null
? bikeInfo.description.ToUpper().Contains(DEMOBIKEMARKER)
: (bool?)null;
}
/// <summary>
/// Gets whether the bike is a trike or not.
/// </summary>
/// <param name="bikeInfo">JSON to get information from.</param>
/// <returns>From information.</returns>
public static IEnumerable<string> GetGroup(this BikeInfoBase bikeInfo)
{
try
{
return bikeInfo?.bike_group?.GetGroup()?.ToList() ?? new List<string>();
}
catch (Exception l_oException)
{
throw new Exception($"Can not get group of user from text \"{bikeInfo.bike_group}\".", l_oException);
}
}
/// <summary> Gets whether the bike has a bord computer or not. </summary>
/// <param name="bikeInfo">JSON to get information from.</param>
/// <returns>From information.</returns>
public static bool GetIsManualLockBike(this BikeInfoBase bikeInfo)
{
return !string.IsNullOrEmpty(bikeInfo.system)
&& bikeInfo.system.ToUpper().StartsWith("LOCK");
}
/// <summary> Gets whether the bike has a bord computer or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns>
public static bool GetIsBluetoothLockBike(this BikeInfoBase bikeInfo)
{
return !string.IsNullOrEmpty(bikeInfo.system)
&& bikeInfo.system.ToUpper().StartsWith("ILOCKIT");
}
/// <summary> Gets whether the bike Is a Sigo bike or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>True if bike is a Sigo.</returns>
public static bool GetIsSigoBike(this BikeInfoBase bikeInfo)
{
return !string.IsNullOrEmpty(bikeInfo.system)
&& bikeInfo.system.ToUpper().StartsWith("SIGO");
}
public static LockModel? GetLockModel(this BikeInfoBase bikeInfo)
{
if (GetIsBluetoothLockBike(bikeInfo))
return LockModel.ILockIt;
if (GetIsManualLockBike(bikeInfo))
return LockModel.BordComputer;
if (GetIsSigoBike(bikeInfo))
return LockModel.Sigo;
return null;
}
/// <summary> Gets whether the bike has a bord computer or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns>
public static int GetBluetoothLockId(this BikeInfoBase bikeInfo)
{
return TextToLockItTypeHelper.GetBluetoothLockId(bikeInfo?.Ilockit_ID);
}
/// <summary> Gets whether the bike has a bord computer or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns>
public static Guid GetBluetoothLockGuid(this BikeInfoBase bikeInfo)
{
// return new Guid("00000000-0000-0000-0000-e57e6b9aee16");
return Guid.TryParse(bikeInfo?.Ilockit_GUID, out Guid lockGuid)
? lockGuid
: TextToLockItTypeHelper.INVALIDLOCKGUID;
}
public static byte[] GetUserKey(this BikeInfoReservedOrBooked bikeInfo)
{
return GetKey(bikeInfo.K_u);
}
public static byte[] GetAdminKey(this BikeInfoReservedOrBooked bikeInfo)
{
return GetKey(bikeInfo.K_a);
}
public static byte[] GetSeed(this BikeInfoReservedOrBooked bikeInfo)
{
return GetKey(bikeInfo.K_seed);
}
/// <summary>
/// Get array of keys from string of format "[12, -9, 5]"
/// </summary>
/// <param name="keyArrayText"></param>
/// <returns></returns>
private static byte[] GetKey(string keyArrayText)
{
try
{
if (string.IsNullOrEmpty(keyArrayText))
return new byte[0];
return Regex.Replace(keyArrayText, @"\[(.*)\]", "$1").Split(',').Select(x => (byte)sbyte.Parse(x)).ToArray();
}
catch (Exception exception)
{
Log.Error("Can not extract K_u/ K_a/ or K_seed. Key {ArrayText} does not is not of expected format. {Exception}", keyArrayText, exception);
return new byte[0];
}
}
/// <summary>
/// Gets whether the bike is a trike or not.
/// </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns>
public static WheelType? GetWheelType(this BikeInfoBase bikeInfo)
=> Enum.TryParse(bikeInfo?.bike_type?.wheels, true, out WheelType wheelType)
? wheelType
: (WheelType?)null;
/// <summary>
/// Gets the type of bike.
/// </summary>
/// <param name="bikeInfo">Object to get bike type from.</param>
/// <returns>Type of bike.</returns>
public static TypeOfBike? GetTypeOfBike(this BikeInfoBase bikeInfo)
=> Enum.TryParse(bikeInfo?.bike_type?.category, true, out TypeOfBike typeOfBike)
? typeOfBike
: (TypeOfBike?)null;
/// <summary>
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
/// </summary>
/// <param name="bikeInfo">Object to get AA info from.</param>
/// <returns>AA info.</returns>
public static AaRideType? GetAaRideType(this BikeInfoBase bikeInfo)
=> Enum.TryParse(bikeInfo?.aa_ride, true, out AaRideType aaRide)
? aaRide
: (AaRideType?)null;
/// <summary> Get position from a ,- separated string. </summary>
/// <param name="gps">Text to extract position from.</param>
/// <returns>Position object.</returns>
public static IPosition GetPosition(Repository.Response.Position gps)
=> PositionFactory.Create(
double.TryParse(gps?.latitude, NumberStyles.Float, CultureInfo.InvariantCulture, out double latitude) ? latitude : double.NaN,
double.TryParse(gps?.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out double longitude) ? longitude : double.NaN);
/// <summary> Get position from a ,- separated string. </summary>
/// <param name="gps">Text to extract position from.</param>
/// <returns>Position object.</returns>
public static Map.IMapSpan GetMapSpan(this MapSpan mapSpan)
=> Map.MapSpanFactory.Create(
GetPosition(mapSpan?.center),
double.TryParse(mapSpan?.radius, NumberStyles.Float, CultureInfo.InvariantCulture, out double radius) ? radius : double.NaN);
/// <summary>
/// Gets the locking state from response.
/// </summary>
/// <param name="bikeInfo"> Response locking state from.</param>
/// <returns>Locking state</returns>
public static Bikes.BikeInfoNS.CopriLock.LockingState GetCopriLockingState(this BikeInfoBase bikeInfo)
{
if (string.IsNullOrEmpty(bikeInfo?.lock_state))
return Bikes.BikeInfoNS.CopriLock.LockingState.UnknownDisconnected;
if (bikeInfo.lock_state.ToUpper().Trim() == "locked".ToUpper())
return Bikes.BikeInfoNS.CopriLock.LockingState.Closed;
if (bikeInfo.lock_state.ToUpper().Trim() == "locking".ToUpper())
return Bikes.BikeInfoNS.CopriLock.LockingState.Closing;
if (bikeInfo.lock_state.ToUpper().Trim() == "unlocked".ToUpper())
return Bikes.BikeInfoNS.CopriLock.LockingState.Open;
if (bikeInfo.lock_state.ToUpper().Trim() == "unlocking".ToUpper())
return Bikes.BikeInfoNS.CopriLock.LockingState.Opening;
return Bikes.BikeInfoNS.CopriLock.LockingState.UnknownDisconnected;
}
/// <summary>
/// Gets the operator Uri from response.
/// </summary>
/// <param name="bikeInfo"> Response to get uri from.</param>
/// <returns>Operator Uri</returns>
public static Uri GetOperatorUri(this BikeInfoBase bikeInfo)
{
return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator)
? new Uri($"{bikeInfo.uri_operator}/{CopriServerUriList.REST_RESOURCE_ROOT}")
: null;
}
/// <summary> Tries to get the copri version from response.</summary>
/// <param name="response">Response to get version info from.</param>
/// <returns>COPRI version</returns>
public static bool TryGetCopriVersion(this CopriVersion response, out Version copriVersion)
{
copriVersion = new Version(0, 0);
return response != null
&& !string.IsNullOrEmpty(response.copri_version)
&& Version.TryParse(response.copri_version, out copriVersion);
}
/// <summary> Gets the copri version from.</summary>
/// <param name="response">Response to get version info from.</param>
/// <returns>COPRI version</returns>
public static Version GetCopriVersion(this CopriVersion response)
=> response.TryGetCopriVersion(out Version copriVersion)
? copriVersion
: throw new InvalidResponseException($"Can not get version info from copri response {response?.copri_version}.");
/// <summary>
/// Gets bike advanced bike state. If entry Co2Saving exists feedback is required.
/// </summary>
/// <param name="bike">Bike get to state from.</param>
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
=> bike.co2saving != null;
/// <summary>
/// Gets the count of bikes available at station.
/// </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Count of bikes available or null if information is unknown.</returns>
public static int? GetBikesAvailableCount(this StationInfo stationInfo)
=> !string.IsNullOrWhiteSpace(stationInfo?.bike_count)
&& int.TryParse(stationInfo?.bike_count, out int bikeCount)
? bikeCount
: (int?)null;
/// <summary>
/// Gets station object from response object.
/// </summary>
/// <param name="station">Response object to get station object from.</param>
/// <returns>Station object.</returns>
public static Station GetStation(this StationInfo station) =>
new Station(
station.station,
station.GetGroup(),
station.GetPosition(),
station.description,
new Data(station.operator_data?.operator_name,
station.operator_data?.operator_phone,
station.operator_data?.operator_hours,
station.operator_data?.operator_email,
!string.IsNullOrEmpty(station.operator_data?.operator_color)
? Color.FromHex(station.operator_data?.operator_color)
: (Color?)null),
new BikeGroupCol(station.station_type?.Select(x => new BikeGroupCol.Entry(
x.Key,
int.TryParse(x.Value.bike_count, out int count) ? count : 0,
x.Value.bike_group)) ?? new List<BikeGroupCol.Entry>()
));
/// <summary>
/// Gets the bike group object from response object.
/// </summary>
/// <param name="bikeGroup">Response object to get station from.</param>
/// <param name="name">Name of the bike group.</param>
/// <returns></returns>
public static BikeGroupCol.Entry GetBikeGroup(this BikeGroup bikeGroup, string name) =>
new BikeGroupCol.Entry(
name,
int.TryParse(bikeGroup?.bike_count ?? "0", out var countCity) ? countCity : 0,
bikeGroup?.bike_group ?? string.Empty);
/// <summary>
/// Default value for reserve_timerange.
/// </summary>
private static int DEFAULTMAXRESERVATIONTIMESPAN = 15;
/// <summary>
/// Gets the reservation time span from response.
/// </summary>
/// <param name="description">Response to get time span from.</param>
/// <returns>Time span.</returns>
public static TimeSpan GetMaxReservationTimeSpan(this RentalDescription description) =>
TimeSpan.FromMinutes(int.TryParse(description?.reserve_timerange, out int minutes)
? minutes
: DEFAULTMAXRESERVATIONTIMESPAN );
}
}