From eaec5ded0b480aba0e82c0463de5d4400043f1da Mon Sep 17 00:00:00 2001
From: Amy <amy@litdevs.org>
Date: Thu, 15 Feb 2024 20:39:12 +0200
Subject: [PATCH] Channel messages rework

---
 SharpQuark/Lightquark.cs           |  3 ++
 SharpQuark/Methods/Channel.cs      | 30 +++++++++---
 SharpQuark/Objects/Channel.cs      | 77 ++++++++++++++++++++++++++----
 SharpQuark/Objects/Id/BaseId.cs    | 19 ++++++++
 SharpQuark/Objects/Id/ChannelId.cs | 17 ++++++-
 SharpQuark/Objects/Id/MessageId.cs | 17 ++++++-
 SharpQuark/Objects/Id/QuarkId.cs   | 17 ++++++-
 SharpQuark/Objects/Id/UserId.cs    | 17 ++++++-
 SharpQuark/Objects/Message.cs      | 22 +++++++++
 SharpQuark/SharpQuark.csproj       |  4 +-
 10 files changed, 202 insertions(+), 21 deletions(-)

diff --git a/SharpQuark/Lightquark.cs b/SharpQuark/Lightquark.cs
index 86cb2ea..80eabf4 100644
--- a/SharpQuark/Lightquark.cs
+++ b/SharpQuark/Lightquark.cs
@@ -1,6 +1,7 @@
 using System.Net.Http.Headers;
 using System.Reflection;
 using Newtonsoft.Json;
+using SharpQuark.Objects;
 using SharpQuark.Token;
 
 namespace SharpQuark;
@@ -14,6 +15,7 @@ public partial class Lightquark
     private readonly Uri _baseUri;
     private readonly TokenCredential _tokenCredential;
     private readonly string _agent;
+    private List<Channel> _channels;
     
     public Lightquark(TokenCredential credential, NetworkInformation networkInformation, string? agent = null, string version = "v3", bool suppressStartupMessage = false)
     {
@@ -22,6 +24,7 @@ public partial class Lightquark
         _agent = agent ?? $"SharpQuark {Assembly.GetExecutingAssembly().GetName().Version}";
         if (networkInformation.BaseUrl == null) throw new Exception("Invalid network");
         _baseUri = new Uri(networkInformation.BaseUrl);
+        _channels = new List<Channel>();
         if (!suppressStartupMessage)
         {
             Console.WriteLine($"Running {_agent}");
diff --git a/SharpQuark/Methods/Channel.cs b/SharpQuark/Methods/Channel.cs
index b207246..9c6ee22 100644
--- a/SharpQuark/Methods/Channel.cs
+++ b/SharpQuark/Methods/Channel.cs
@@ -7,25 +7,41 @@ namespace SharpQuark;
 
 public partial class Lightquark
 {
-    public async Task<ChannelApiResult> ChannelById(ChannelId channelId)
+    public async Task<Channel> ChannelById(ChannelId channelId)
+    {
+        var channel = await ChannelByIdRaw(channelId);
+        return channel.Response.Channel;
+    }
+    
+    public async Task<ChannelApiResult> ChannelByIdRaw(ChannelId channelId)
     {
         var rawApiResult = await Call($"/channel/{channelId}");
         
         var parsedApiResult = JsonConvert.DeserializeObject<ChannelApiResult>(rawApiResult);
 
-        if (parsedApiResult != null) parsedApiResult.Response.Channel.Lq = this;
-
-        return parsedApiResult ?? throw new Exception("/channel/{channelId} API Result is null");
+        if (parsedApiResult == null) return parsedApiResult ?? throw new Exception("/channel/{channelId} API Result is null");
+        
+        parsedApiResult.Response.Channel.Lq = this;
+        await parsedApiResult.Response.Channel.InitialLoad();
+        
+        return parsedApiResult;
     }
 
-    public async Task<ChannelMessagesApiResult> ChannelMessages(ChannelId channelId)
+    internal async Task<ChannelMessagesApiResult> ChannelMessages(Channel channel)
     {
         
-        var rawApiResult = await Call($"/channel/{channelId}/messages");
+        var rawApiResult = await Call($"/channel/{channel.Id}/messages");
         
         var parsedApiResult = JsonConvert.DeserializeObject<ChannelMessagesApiResult>(rawApiResult);
+        
+        if (parsedApiResult == null) return parsedApiResult ?? throw new Exception("/channel/{channelId}/messages API Result is null");
 
-        return parsedApiResult ?? throw new Exception("/channel/{channelId}/messages API Result is null");
+        foreach (var message in parsedApiResult.Response.Messages)
+        {
+            message.Channel = channel;
+        }
+        
+        return parsedApiResult;
     }
     
 }
\ No newline at end of file
diff --git a/SharpQuark/Objects/Channel.cs b/SharpQuark/Objects/Channel.cs
index 71346a5..a030ffb 100644
--- a/SharpQuark/Objects/Channel.cs
+++ b/SharpQuark/Objects/Channel.cs
@@ -1,23 +1,84 @@
-using Newtonsoft.Json;
+using System.Diagnostics;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 using SharpQuark.Objects.Id;
 
 namespace SharpQuark.Objects;
 
+public class ChannelConverter : JsonConverter
+{
+    private static readonly Dictionary<ChannelId, Channel> Instances = new();
+
+    public override bool CanConvert(Type objectType)
+    {
+        return objectType == typeof(Channel);
+    }
+
+    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+    {
+        throw new NotImplementedException();
+    }
+
+    public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+    {
+        var jsonObject = JObject.Load(reader);
+        var channelId = jsonObject.GetValue("_id")?.ToObject<ChannelId>();
+                
+        if (Instances.TryGetValue(channelId ?? throw new Exception("Channel ID null"), out var channel))
+            return channel;
+
+        // Deserialize other properties
+        var name = jsonObject.GetValue("name")?.ToObject<string>();
+        var quarkId = jsonObject.GetValue("quark")?.ToObject<QuarkId>();
+        var description = jsonObject.GetValue("description")?.ToObject<string>();
+
+        // Create new Channel instance
+        channel = new Channel(channelId, name ?? throw new Exception("Channel Name null"), quarkId ?? throw new Exception("Channel Quark ID null"), description);
+        Instances[channelId] = channel;
+        return channel;
+    }
+}
+
+[JsonConverter(typeof(ChannelConverter))]
 public class Channel
 {
     [JsonProperty("_id")]
-    public required ChannelId Id;
+    public ChannelId Id;
     [JsonProperty("name")]
-    public required string Name;
+    public string Name;
     [JsonProperty("description")]
-    public string Description = string.Empty;
+    public string Description;
     [JsonProperty("quark")]
-    public required QuarkId QuarkId;
+    public QuarkId QuarkId;
+    [JsonProperty("messages")] 
+    public SortedSet<Message> Messages;
+
+    [JsonIgnore] public Lightquark Lq = null!;
+
+    [JsonIgnore] private bool _initialLoad;
 
-    [JsonIgnore] public Lightquark? Lq;
+    internal Channel(ChannelId id, string name, QuarkId quarkId, string? description)
+    {
+        Id = id;
+        Name = name;
+        QuarkId = quarkId;
+        Description = description ?? string.Empty;
+        Messages = new SortedSet<Message>(new TimestampComparer());
+    }
+        
+    private async Task<Message[]> GetMessages()
+    {
+        return (await Lq.ChannelMessages(this)).Response.Messages;
+    }
 
-    public async Task<Message[]> GetMessages()
+    public async Task InitialLoad()
     {
-        return (await Lq.ChannelMessages(Id)).Response.Messages;
+        if (_initialLoad)
+        {
+            Debug.WriteLine($"[c{Id}] Initial loaded before, skipping");
+            return;
+        }
+        Messages.UnionWith(await GetMessages());
+        _initialLoad = true;
     }
 };
\ No newline at end of file
diff --git a/SharpQuark/Objects/Id/BaseId.cs b/SharpQuark/Objects/Id/BaseId.cs
index 5081885..3e18eec 100644
--- a/SharpQuark/Objects/Id/BaseId.cs
+++ b/SharpQuark/Objects/Id/BaseId.cs
@@ -8,4 +8,23 @@ public class BaseId(string id)
     {
         return Id;
     }
+    
+    public override bool Equals(object? obj)
+    {
+        if (obj is BaseId other)
+        {
+            return Id == other.Id;
+        }
+        return false;
+    }
+
+    public override int GetHashCode()
+    {
+        return Id.GetHashCode();
+    }
+
+    public int CompareTo(BaseId other)
+    {
+        return string.Compare(Id, other.Id, StringComparison.Ordinal);
+    }
 }
\ No newline at end of file
diff --git a/SharpQuark/Objects/Id/ChannelId.cs b/SharpQuark/Objects/Id/ChannelId.cs
index febbe3d..04d7c7c 100644
--- a/SharpQuark/Objects/Id/ChannelId.cs
+++ b/SharpQuark/Objects/Id/ChannelId.cs
@@ -3,7 +3,22 @@
 namespace SharpQuark.Objects.Id;
 
 [JsonConverter(typeof(ChannelIdConverter))]
-public class ChannelId(string id) : BaseId(id);
+public class ChannelId(string id) : BaseId(id)
+{
+    public override bool Equals(object? obj)
+    {
+        if (obj is ChannelId other)
+        {
+            return Id == other.Id;
+        }
+        return false;
+    }
+    
+    public override int GetHashCode()
+    {
+        return Id.GetHashCode();
+    }
+}
 
 public class ChannelIdConverter : JsonConverter
 {
diff --git a/SharpQuark/Objects/Id/MessageId.cs b/SharpQuark/Objects/Id/MessageId.cs
index 94f4fdc..058311f 100644
--- a/SharpQuark/Objects/Id/MessageId.cs
+++ b/SharpQuark/Objects/Id/MessageId.cs
@@ -3,7 +3,22 @@
 namespace SharpQuark.Objects.Id;
 
 [JsonConverter(typeof(MessageIdConverter))]
-public class MessageId(string id) : BaseId(id);
+public class MessageId(string id) : BaseId(id)
+{
+    public override bool Equals(object? obj)
+    {
+        if (obj is MessageId other)
+        {
+            return Id == other.Id;
+        }
+        return false;
+    }
+    
+    public override int GetHashCode()
+    {
+        return Id.GetHashCode();
+    }
+}
 
 public class MessageIdConverter : JsonConverter
 {
diff --git a/SharpQuark/Objects/Id/QuarkId.cs b/SharpQuark/Objects/Id/QuarkId.cs
index 2ecffed..ef7c4fe 100644
--- a/SharpQuark/Objects/Id/QuarkId.cs
+++ b/SharpQuark/Objects/Id/QuarkId.cs
@@ -3,7 +3,22 @@
 namespace SharpQuark.Objects.Id;
 
 [JsonConverter(typeof(QuarkIdConverter))]
-public class QuarkId(string id) : BaseId(id);
+public class QuarkId(string id) : BaseId(id)
+{
+    public override bool Equals(object? obj)
+    {
+        if (obj is QuarkId other)
+        {
+            return Id == other.Id;
+        }
+        return false;
+    }
+    
+    public override int GetHashCode()
+    {
+        return Id.GetHashCode();
+    }
+}
 
 public class QuarkIdConverter : JsonConverter
 {
diff --git a/SharpQuark/Objects/Id/UserId.cs b/SharpQuark/Objects/Id/UserId.cs
index 5cbbcd3..010d2bc 100644
--- a/SharpQuark/Objects/Id/UserId.cs
+++ b/SharpQuark/Objects/Id/UserId.cs
@@ -3,7 +3,22 @@
 namespace SharpQuark.Objects.Id;
 
 [JsonConverter(typeof(UserIdConverter))]
-public class UserId(string id) : BaseId(id);
+public class UserId(string id) : BaseId(id)
+{
+    public override bool Equals(object? obj)
+    {
+        if (obj is UserId other)
+        {
+            return Id == other.Id;
+        }
+        return false;
+    }
+    
+    public override int GetHashCode()
+    {
+        return Id.GetHashCode();
+    }
+}
 
 public class UserIdConverter : JsonConverter
 {
diff --git a/SharpQuark/Objects/Message.cs b/SharpQuark/Objects/Message.cs
index 0d0045c..78ef6ce 100644
--- a/SharpQuark/Objects/Message.cs
+++ b/SharpQuark/Objects/Message.cs
@@ -19,6 +19,9 @@ public class Message
 
     [JsonProperty("timestamp")] 
     public required long JsTimestamp;
+    
+    [JsonIgnore]
+    public DateTimeOffset Timestamp => DateTimeOffset.FromUnixTimeMilliseconds(JsTimestamp);
 
     [JsonProperty("edited")] 
     public required bool Edited;
@@ -31,4 +34,23 @@ public class Message
 
     [JsonProperty("author")]
     public required User Author;
+
+    [JsonIgnore] public Channel Channel = null!;
+
+}
+
+
+public class TimestampComparer : IComparer<Message>
+{
+    public int Compare(Message? x, Message? y)
+    {
+        if (x == null || y == null)
+        {
+            throw new ArgumentException("Messages cannot be null");
+        }
+
+        // First, compare by JsTimestamp
+        var timestampComparison = x.JsTimestamp.CompareTo(y.JsTimestamp);
+        return timestampComparison != 0 ? timestampComparison : x.Id.CompareTo(y.Id);
+    }
 }
\ No newline at end of file
diff --git a/SharpQuark/SharpQuark.csproj b/SharpQuark/SharpQuark.csproj
index 8f54db0..334ae98 100644
--- a/SharpQuark/SharpQuark.csproj
+++ b/SharpQuark/SharpQuark.csproj
@@ -4,8 +4,8 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
-        <AssemblyVersion>2024.2.11.1</AssemblyVersion>
-        <Version>2024.2.11.1-beta</Version>
+        <AssemblyVersion>2024.2.15.0</AssemblyVersion>
+        <Version>2024.2.15.0-beta</Version>
     </PropertyGroup>
 
     <ItemGroup>
-- 
GitLab