From ccd4a580070d36e98c0af1971bd8877e237eb7ac Mon Sep 17 00:00:00 2001
From: Lorenz Stechauner <lorenz.stechauner@necronda.net>
Date: Thu, 7 Mar 2024 10:42:26 +0100
Subject: [PATCH] BillingData: Compact data even more

---
 Elwig/Helpers/Billing/BillingData.cs | 35 +++++++++++-
 Tests/HelperTests/BillingDataTest.cs | 81 +++++++++++++++++++++++++++-
 2 files changed, 112 insertions(+), 4 deletions(-)

diff --git a/Elwig/Helpers/Billing/BillingData.cs b/Elwig/Helpers/Billing/BillingData.cs
index 0073b42..4230287 100644
--- a/Elwig/Helpers/Billing/BillingData.cs
+++ b/Elwig/Helpers/Billing/BillingData.cs
@@ -252,11 +252,11 @@ namespace Elwig.Helpers.Billing {
             return curve;
         }
 
-        protected static void CollapsePaymentData(JsonObject data, IEnumerable<RawVaribute> vaributes, bool useDefault = true) {
+        protected static (Dictionary<string, List<string>>, Dictionary<decimal, List<string>>) GetReverseKeys(JsonObject data, bool strict = true) {
             Dictionary<string, List<string>> rev1 = [];
             Dictionary<decimal, List<string>> rev2 = [];
             foreach (var (k, v) in data) {
-                if (k == "default" || k.StartsWith('/') || !k.Contains('/') || v is not JsonValue val) {
+                if (k == "default" || (strict && (k.StartsWith('/') || !k.Contains('/'))) || v is not JsonValue val) {
                     continue;
                 } else if (val.TryGetValue<decimal>(out var dec)) {
                     rev2[dec] = rev2.GetValueOrDefault(dec) ?? [];
@@ -266,6 +266,11 @@ namespace Elwig.Helpers.Billing {
                     rev1[cur].Add(k);
                 }
             }
+            return (rev1, rev2);
+        }
+
+        protected static void CollapsePaymentData(JsonObject data, IEnumerable<RawVaribute> vaributes, bool useDefault = true) {
+            var (rev1, rev2) = GetReverseKeys(data);
             if (!data.ContainsKey("default")) {
                 foreach (var (v, ks) in rev1) {
                     if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
@@ -322,6 +327,32 @@ namespace Elwig.Helpers.Billing {
                     data.Add(k.Replace("/-", "-"), val);
                 }
             }
+
+            (rev1, rev2) = GetReverseKeys(data, false);
+            var keyVaributes = data
+                .Select(e => e.Key.Split('-')[0])
+                .Where(e => e.Length > 0 && e != "default")
+                .Distinct()
+                .ToList();
+            foreach (var idx in keyVaributes) {
+                var len = data.Count(e => e.Key == idx || (e.Key.Length > idx.Length && e.Key.StartsWith(idx) && e.Key[idx.Length] == '-'));
+                foreach (var (v, ks) in rev1) {
+                    var myKs = ks.Where(k => k == idx || (k.Length > idx.Length && k.StartsWith(idx) && k[idx.Length] == '-' && !data.ContainsKey(k[idx.Length..]))).ToList();
+                    if (myKs.Count == len) {
+                        foreach (var k in myKs) {
+                            if (k != idx) data.Remove(k);
+                        }
+                    }
+                }
+                foreach (var (v, ks) in rev2) {
+                    var myKs = ks.Where(k => k == idx || (k.Length > idx.Length && k.StartsWith(idx) && k[idx.Length] == '-' && !data.ContainsKey(k[idx.Length..]))).ToList();
+                    if (myKs.Count == len) {
+                        foreach (var k in myKs) {
+                            if (k != idx) data.Remove(k);
+                        }
+                    }
+                }
+            }
         }
 
         public static JsonObject FromGraphEntries(
diff --git a/Tests/HelperTests/BillingDataTest.cs b/Tests/HelperTests/BillingDataTest.cs
index 954ee66..bf987e6 100644
--- a/Tests/HelperTests/BillingDataTest.cs
+++ b/Tests/HelperTests/BillingDataTest.cs
@@ -556,7 +556,7 @@ namespace Tests.HelperTests {
         }
 
         [Test]
-        public void TestWrite_06_Cultivation() {
+        public void TestWrite_06_Cultivation_1() {
             List<GraphEntry> entries = [
                 new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
                     [73] = 0.5m,
@@ -604,7 +604,84 @@ namespace Tests.HelperTests {
         }
 
         [Test]
-        public void TestWrite_07_AttributeAndCultivation() {
+        public void TestWrite_07_Cultivation_2() {
+            List<GraphEntry> entries = [
+                new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.6m,
+                    [84] = 1.0m
+                }, null), GetSelection(["GV/-", "GV/-B"])),
+                new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.75m,
+                }, null), GetSelection(["ZW/-", "ZW/-B"])),
+                 new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.8m,
+                }, null), GetSelection(["BP/-", "BP/-B"])),
+           ];
+            var data = BillingData.FromGraphEntries(entries);
+            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
+                {
+                  "mode": "elwig",
+                  "version": 1,
+                  "payment": {
+                    "GV": "curve:1",
+                    "ZW": 0.75,
+                    "BP": 0.8
+                  },
+                  "curves": [
+                    {
+                      "id": 1,
+                      "mode": "oe",
+                      "data": {
+                        "73oe": 0.6,
+                        "84oe": 1
+                      }
+                    }
+                  ]
+                }
+                """));
+        }
+
+        [Test]
+        public void TestWrite_08_Cultivation_3() {
+            List<GraphEntry> entries = [
+                new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.6m,
+                    [84] = 1.0m
+                }, null), GetSelection(["GV/-", "GV/-B"])),
+                new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.75m,
+                }, null), GetSelection(["BP/-B", "ZW/-B", "FV/-B"])),
+                 new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
+                    [73] = 0.8m,
+                }, null), GetSelection(["BP/-", "ZW/-", "FV/-", "WR/-", "BL/-"])),
+           ];
+            var data = BillingData.FromGraphEntries(entries);
+            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
+                {
+                  "mode": "elwig",
+                  "version": 1,
+                  "payment": {
+                    "default": 0.8,
+                    "GV": "curve:1",
+                    "GV-B": "curve:1",
+                    "-B": 0.75
+                  },
+                  "curves": [
+                    {
+                      "id": 1,
+                      "mode": "oe",
+                      "data": {
+                        "73oe": 0.6,
+                        "84oe": 1
+                      }
+                    }
+                  ]
+                }
+                """));
+        }
+
+        [Test]
+        public void TestWrite_09_AttributeAndCultivation() {
             List<GraphEntry> entries = [
                 new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
                     [73] = 0.75m,