using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace __NAMESPACE__;

public class JRpcClient
{
   private JRpcTransport Transport;
   private IDictionary<string, TaskCompletionSource<JsonNode?>> Requests;

   public JRpcClient(JRpcTransport transport)
   {
      this.Transport = transport;
      this.Requests = new Dictionary<string, TaskCompletionSource<JsonNode?>>();

      this.Transport.OnPacket += this.HandlePacket;
   }

   private void HandlePacket(string packet)
   {
      try
      {
         var parsed = JsonNode.Parse(packet);
         if (parsed == null || (string?)parsed["jsonrpc"] != "2.0")
            return;

         if (parsed["method"] != null)
         { // Request or Notification
            if (parsed["id"] != null) // Requests are not supported on the Client
               return;
            //TODO: implement Notifications
         }
         else if (parsed["id"] != null && parsed["method"] == null)
         {
            // Response
            //TODO:  Somehow remove this warning, since it has no meaning in this context. 
            //       ID has to be something, that was checked before
            var id = (string)parsed["id"]!;

            var task = this.Requests[id];
            if (task == null)
               return; //This Request was not from here

            if (parsed["error"] != null)
            {
               var err = parsed["error"].Deserialize<JRpcError>();
               if (err == null)
               {
                  task.SetException(new JRpcException("Internal Server Error"));
               }
               else
               {
                  task.SetException(err.ToException());
               }
            }
            else
            {
               task.SetResult(parsed["result"]);
            }
         }
         else
         {
            // Ignoring invalid packet!
            return;
         }
      }
      catch (Exception)
      {
         //TODO: Maybe log exception, but don't break!
      }
   }

   public async Task<JsonNode?> SendRequestRaw(string method, JsonArray param)
   {
      var id = Guid.NewGuid().ToString();
      var request = new JsonObject
      {
         ["jsonrpc"] = "2.0",
         ["id"] = id,
         ["method"] = method,
         ["params"] = param
      };

      var task = new TaskCompletionSource<JsonNode?>();
      this.Requests.Add(id, task);
      await this.Transport.Write(request.ToJsonString());

      return await task.Task;
   }

   public async Task<TResult?> SendRequest<TResult>(string method, JsonArray param)
   {
      var result = await this.SendRequestRaw(method, param);
      return result.Deserialize<TResult>();
   }

   public async void SendNotification(string method, JsonArray param)
   {
      var not = new JsonObject
      {
         ["jsonrpc"] = "2.0",
         ["method"] = method,
         ["params"] = param,
      };

      await this.Transport.Write(not.ToJsonString());
   }
}


class JRpcError
{
   public int code { get; set; }
   public string? message { get; set; }
   public JsonNode? data { get; set; }

   public JRpcException ToException()
   {
      return new JRpcException(this.message!);
   }
}

public class JRpcException : Exception
{
   public JRpcException(string message) : base(message) { }
}
