using Newtonsoft.Json; using NJsonSchema; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Elwig.Helpers.Billing { public class BillingData { public enum CalculationMode { Elwig, WgMaster } public enum CurveMode { Oe, Kmw } public record struct Curve(CurveMode Mode, Dictionary Normal, Dictionary? Gebunden); public static JsonSchema? Schema { get; private set; } public static async Task Init() { var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Resources.Schemas.PaymentVariantData.json"); Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found")); } public readonly JsonObject Data; private readonly CalculationMode Mode; private readonly Dictionary Curves; private readonly Dictionary PaymentData; private readonly Dictionary QualityData; public bool ConsiderDelieryModifiers { get => GetConsider("consider_delivery_modifiers"); set => SetConsider(value, "consider_delivery_modifiers"); } public bool ConsiderContractPenalties { get => GetConsider("consider_contract_penalties"); set => SetConsider(value, "consider_contract_penalties"); } public bool ConsiderTotalPenalty { get => GetConsider("consider_total_penalty"); set => SetConsider(value, "consider_total_penalty"); } public bool ConsiderAutoBusinessShares { get => GetConsider("consider_auto_business_shares"); set => SetConsider(value, "consider_auto_business_shares"); } private bool GetConsider(string name, string? wgMasterName = null) { return ((Mode == CalculationMode.Elwig) ? Data[name] : Data[wgMasterName ?? ""])?.AsValue().GetValue() ?? false; } private void SetConsider(bool value, string name, string? wgMasterName = null) { if (Mode == CalculationMode.WgMaster && wgMasterName == null) { return; } else if (value) { Data[(Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""] = value; } else { Data.Remove((Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""); } } public BillingData(JsonObject data, IEnumerable attributeVariants) { if (attributeVariants.Any(e => e.Any(c => c < 'A' || c > 'Z'))) throw new ArgumentException("Invalid attributeVariants"); Data = data; var mode = Data["mode"]?.GetValue(); Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster; Curves = GetCurves(Data, Mode); PaymentData = GetPaymentData(attributeVariants); QualityData = GetQualityData(attributeVariants); } public static JsonObject ParseJson(string json) { if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first"); try { var errors = Schema.Validate(json); if (errors.Count != 0) throw new ArgumentException("Invalid JSON data"); return JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException("Invalid JSON data"); } catch (JsonReaderException) { throw new ArgumentException("Invalid JSON data"); } } public static BillingData FromJson(string json) { return FromJson(json, []); } public static BillingData FromJson(string json, IEnumerable attributeVariants) { return new(ParseJson(json), attributeVariants); } private static Dictionary GetCurveData(JsonObject data, CurveMode mode) { var dict = new Dictionary(); foreach (var (index, price) in data) { double idx; bool? gtlt = index.StartsWith('>') ? true : index.StartsWith('<') ? false : null; if (index.EndsWith("kmw")) { idx = double.Parse(index[(gtlt != null ? 1 : 0)..^3], CultureInfo.InvariantCulture); if (mode == CurveMode.Oe) { idx = Utils.KmwToOe(idx); } } else if (index.EndsWith("oe")) { idx = double.Parse(index[(gtlt != null ? 1 : 0)..^2], CultureInfo.InvariantCulture); if (mode == CurveMode.Kmw) { idx = Utils.OeToKmw(idx); } } else { throw new InvalidOperationException(); } if (gtlt == true) { idx = Math.BitIncrement(idx); } else if (gtlt == false) { idx = Math.BitDecrement(idx); } dict[idx] = price?.AsValue().GetValue() ?? throw new InvalidOperationException(); } return dict; } public static Dictionary GetCurves(JsonObject data, CalculationMode mode) { var dict = new Dictionary(); var curves = data[mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException(); foreach (var c in curves) { var obj = c?.AsObject() ?? throw new InvalidOperationException(); var id = obj["id"]?.GetValue() ?? throw new InvalidOperationException(); var cMode = (obj["mode"]?.GetValue() == "kmw") ? CurveMode.Kmw : CurveMode.Oe; Dictionary c1; Dictionary? c2 = null; var norm = obj["data"]; if (norm is JsonObject) { c1 = GetCurveData(norm.AsObject(), cMode); } else if (norm?.AsValue().TryGetValue(out decimal v) == true) { c1 = new() { { cMode == CurveMode.Oe ? 73 : 15, v } }; } else { throw new InvalidOperationException(); } var geb = obj["geb"]; if (geb is JsonObject) { c2 = GetCurveData(geb.AsObject(), cMode); } else if (geb?.AsValue().TryGetValue(out decimal v) == true) { c2 = c1.ToDictionary(e => e.Key, e => e.Value + v); } dict.Add(id, new(cMode, c1, c2)); } return dict; } private Dictionary GetData(JsonObject data, IEnumerable attributeVariants) { Dictionary dict; if (data["default"] is JsonValue def) { var c = LookupCurve(def); dict = attributeVariants.ToDictionary(e => e, _ => c); } else { dict = []; } var variants = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2); var attributes = data.Where(p => p.Key.StartsWith('/')); var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2); foreach (var (idx, v) in variants) { var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException()); foreach (var i in attributeVariants.Where(e => e.StartsWith(idx[..^1]))) { dict[i] = curve; } } foreach (var (idx, v) in attributes) { var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException()); foreach (var i in attributeVariants.Where(e => e[2..] == idx[1..])) { dict[i] = curve; } } foreach (var (idx, v) in others) { var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException()); dict[idx.Replace("/", "")] = curve; } return dict; } public Dictionary GetPaymentData(IEnumerable attributeVariants) { var p = Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"]; if (p is JsonValue val) { var c = LookupCurve(val); return attributeVariants.ToDictionary(e => e, _ => c); } return GetData(p?.AsObject() ?? throw new InvalidOperationException(), attributeVariants); } public Dictionary GetQualityData(IEnumerable attributeVariants) { var q = Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject(); Dictionary dict = []; if (q == null) return dict; foreach (var (qualid, data) in q) { Dictionary qualDict; if (data is JsonValue val) { var c = LookupCurve(val); qualDict = attributeVariants.ToDictionary(e => e, _ => c); } else { qualDict = GetData(data?.AsObject() ?? throw new InvalidOperationException(), attributeVariants); } foreach (var (idx, d) in qualDict) { dict[$"{qualid}/{idx}"] = d; } } return dict; } public decimal CalculatePrice(string sortid, string? attrid, string qualid, bool gebunden, double oe, double kmw) { var curve = GetQualityCurve(qualid, sortid, attrid) ?? GetCurve(sortid, attrid); var d = (gebunden ? curve.Gebunden : null) ?? curve.Normal; if (d.Count == 1) return d.First().Value; var r = curve.Mode == CurveMode.Oe ? oe : kmw; var lt = d.Keys.Where(v => v <= r); var gt = d.Keys.Where(v => v >= r); if (!lt.Any()) { return d[gt.Min()]; } else if (!gt.Any()) { return d[lt.Max()]; } var max = lt.Max(); var min = gt.Min(); if (max == min) return d[r]; var p1 = ((decimal)r - (decimal)min) / ((decimal)max - (decimal)min); var p2 = 1 - p1; return d[min] * p2 + d[max] * p1; } private Curve LookupCurve(JsonValue val) { if (val.TryGetValue(out string? curve)) { var curveId = int.Parse(curve.Split(":")[1]); return Curves[curveId]; } else if (val.TryGetValue(out decimal value)) { return new(CurveMode.Oe, new() { { 73, value } }, null); } throw new InvalidOperationException(); } public Curve GetCurve(string sortid, string? attrid) { return PaymentData[$"{sortid}{attrid ?? ""}"]; } public Curve? GetQualityCurve(string qualid, string sortid, string? attrid) { return QualityData.TryGetValue($"{qualid}/{sortid}{attrid ?? ""}", out var curve) ? curve : null; } } }