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 { /// /// 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. /// public static class TextToTypeHelper { /// Holds the text for demo bikes. private const string DEMOBIKEMARKER = "DEMO"; /// /// Gets the position from StationInfo object. /// /// Object to get information from. /// Position information. public static IPosition GetPosition(this StationInfo stationInfo) => GetPosition(stationInfo.gps); /// Gets the position from StationInfo object. /// Object to get information from. /// Position information. public static IEnumerable 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); } } /// Gets the position from StationInfo object. /// Object to get information from. /// Position information. public static IEnumerable 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(); } return new HashSet(group).ToList(); } /// Gets if user acknowledged AGBs or not. /// Object to get information from. /// Position information. public static bool GetIsAgbAcknowledged(this AuthorizationResponse authorizationResponse) => int.TryParse(authorizationResponse?.agb_checked, out int result) && result != 0; /// Gets the position from StationInfo object. /// Object to get information from. /// Position information. public static string GetGroup(this IEnumerable group) { return string.Join(",", group); } /// Gets the position from StationInfo object. /// Object to get information from. /// Position information. public static IEnumerable 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); } } /// /// Gets the position from BikeInfoBase object. /// /// Object to get information from. /// Rental state. public static InUseStateEnum GetState(this BikeInfoBase bikeInfo) { var stateText = bikeInfo.state?.ToLower()?.Trim(); if (string.IsNullOrEmpty(stateText)) { throw new InvalidResponseException( "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)); } /// /// Gets the position from BikeInfoAvailable object. /// /// Object to get information from. /// Rental state. 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; } /// /// Gets the from date information from JSON. /// /// JSON to get information from.. /// From information if bike hold this information 0001-01-01 (DateTime.MinValue) otherwise. public static DateTime GetFrom(this BikeInfoReservedOrBooked bikeInfo) => DateTime.TryParse(bikeInfo?.start_time, out DateTime dateFrom) ? dateFrom : DateTime.MinValue; /// /// Gets whether the bike is a trike or not. /// /// JSON to get information from.. /// From information. public static bool? GetIsDemo(this BikeInfoBase bikeInfo) { return bikeInfo?.description != null ? bikeInfo.description.ToUpper().Contains(DEMOBIKEMARKER) : (bool?)null; } /// /// Gets whether the bike is a trike or not. /// /// JSON to get information from. /// From information. public static IEnumerable GetGroup(this BikeInfoBase bikeInfo) { try { return bikeInfo?.bike_group?.GetGroup()?.ToList() ?? new List(); } catch (Exception l_oException) { throw new Exception($"Can not get group of user from text \"{bikeInfo.bike_group}\".", l_oException); } } /// Gets whether the bike has a bord computer or not. /// JSON to get information from. /// From information. public static bool GetIsManualLockBike(this BikeInfoBase bikeInfo) { return !string.IsNullOrEmpty(bikeInfo.system) && bikeInfo.system.ToUpper().StartsWith("LOCK"); } /// Gets whether the bike has a bord computer or not. /// JSON to get information from.. /// From information. public static bool GetIsBluetoothLockBike(this BikeInfoBase bikeInfo) { return !string.IsNullOrEmpty(bikeInfo.system) && bikeInfo.system.ToUpper().StartsWith("ILOCKIT"); } /// Gets whether the bike Is a Sigo bike or not. /// JSON to get information from.. /// True if bike is a Sigo. 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; } /// Gets whether the bike has a bord computer or not. /// JSON to get information from.. /// From information. public static int GetBluetoothLockId(this BikeInfoBase bikeInfo) { return TextToLockItTypeHelper.GetBluetoothLockId(bikeInfo?.Ilockit_ID); } /// Gets whether the bike has a bord computer or not. /// JSON to get information from.. /// From information. 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); } /// /// Get array of keys from string of format "[12, -9, 5]" /// /// /// 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]; } } /// /// Gets whether the bike is a trike or not. /// /// JSON to get information from.. /// From information. public static WheelType? GetWheelType(this BikeInfoBase bikeInfo) => Enum.TryParse(bikeInfo?.bike_type?.wheels, true, out WheelType wheelType) ? wheelType : (WheelType?)null; /// /// Gets the type of bike. /// /// Object to get bike type from. /// Type of bike. public static TypeOfBike? GetTypeOfBike(this BikeInfoBase bikeInfo) => Enum.TryParse(bikeInfo?.bike_type?.category, true, out TypeOfBike typeOfBike) ? typeOfBike : (TypeOfBike?)null; /// /// 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). /// /// Object to get AA info from. /// AA info. public static AaRideType? GetAaRideType(this BikeInfoBase bikeInfo) => Enum.TryParse(bikeInfo?.aa_ride, true, out AaRideType aaRide) ? aaRide : (AaRideType?)null; /// Get position from a ,- separated string. /// Text to extract position from. /// Position object. 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); /// Get position from a ,- separated string. /// Text to extract position from. /// Position object. 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); /// /// Gets the locking state from response. /// /// Response locking state from. /// Locking state 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; } /// /// Gets the operator Uri from response. /// /// Response to get uri from. /// Operator Uri 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; } /// Tries to get the copri version from response. /// Response to get version info from. /// COPRI version 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); } /// Gets the copri version from. /// Response to get version info from. /// COPRI version 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}."); /// /// Gets bike advanced bike state. If entry Co2Saving exists feedback is required. /// /// Bike get to state from. public static bool GetIsFeedbackPending(this BikeInfoAvailable bike) => bike.co2saving != null; /// /// Gets the count of bikes available at station. /// /// Object to get information from. /// Count of bikes available or null if information is unknown. public static int? GetBikesAvailableCount(this StationInfo stationInfo) => !string.IsNullOrWhiteSpace(stationInfo?.bike_count) && int.TryParse(stationInfo?.bike_count, out int bikeCount) ? bikeCount : (int?)null; /// /// Gets station object from response object. /// /// Response object to get station object from. /// Station object. 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() )); /// /// Gets the bike group object from response object. /// /// Response object to get station from. /// Name of the bike group. /// 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); /// /// Default value for reserve_timerange. /// private static int DEFAULTMAXRESERVATIONTIMESPAN = 15; /// /// Gets the reservation time span from response. /// /// Response to get time span from. /// Time span. public static TimeSpan GetMaxReservationTimeSpan(this RentalDescription description) => TimeSpan.FromMinutes(int.TryParse(description?.reserve_timerange, out int minutes) ? minutes : DEFAULTMAXRESERVATIONTIMESPAN ); } }