context: // This file is part of SINFONI.
  //
  // This library is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser General Public
  // License as published by the Free Software Foundation; either
  // version 3.0 of the License, or (at your option) any later version.
  //
  // This library is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library.  If not, see <http://www.gnu.org/licenses/>.
  using System;
  using System.Collections.Generic;
  using System.Text;
  using System.Linq;
  using System.Net;
  using SINFONI.Exceptions;
  using System.Reflection;
  using Dynamitey;
  using System.IO;
  using System.Threading;
  using System.Threading.Tasks;
  
  namespace SINFONI
  {
      /// <summary>
      /// Represents a generated function wrapper. It allows calling the function with arbitrary arguments. The returned
      /// call object is then used to wait for the performed call to return and to receive the result value from the
      /// remote service.
      /// </summary>
      /// <returns>An object representing a call.</returns>
      public delegate IClientFunctionCall ClientFunction(params object[] parameters);
      public delegate object GenericWrapper(params object[] arguments);
  
  
      /// <summary>
      /// This class represenents a connection to the remote end. It may be used to load new IDL definition files,
      /// generate callable remote function  wrappers and to register local functions as implementations for remote calls.
      /// </summary>
      public class Connection
      {
  
          public SinTD SinTD { get; internal set; }
  
          public Guid SessionID { get; internal set; }
          /// <summary>
          /// Raised when a connection is closed.
          /// </summary>
          public event EventHandler<ClosedEventArgs> Closed;
  
          internal bool Initialized = false;
  
          public Connection() { }
  
          public Connection(ITransportConnection transportConnection, IProtocol protocol)
          {
              this.SessionID = Guid.NewGuid();
              this.TransportConnection = transportConnection;
              this.Protocol = protocol;
              this.TransportConnection.Message += new EventHandler<TransportMessageEventArgs>(HandleMessage);
              this.TransportConnection.Closed += new EventHandler<ClosedEventArgs>((o, e) =>
              {
                  if (this.Closed != null)
                      this.Closed(this, e);
              });
          }
  
          /// <summary>
          /// Closes the connection.
          /// </summary>
          public void Disconnect()
          {
              TransportConnection.Close();
              if (Closed != null)
                  Closed(this, new ClosedEventArgs("Disconnect of Connection was requested"));
          }
  
          /// <summary>
          /// Convenient wrapper around GenerateFuncWrapper. Can be used to quickly create function wrapper and call it
          /// at once, e.g. <c>client["clientFunc"]("arg1", 42);</c>
          /// </summary>
          /// <param name="name">Function name.</param>
          /// <returns>Function wrapper.</returns>
          public ClientFunction this[string name]
          {            
              get
              {
                  string[] serviceName = name.Split('.');
                  return GenerateClientFunction(serviceName[0], serviceName[1]);
              }
          }
  
          /// <summary>
          /// Loads an IDL definition file from an URI or from the IDL contents specified in the server configuration
          /// into the connection.
          /// </summary>
          /// <param name="serverConfiguration">Configuration of the server that specifies the IDL</param>
          public void LoadIDL(Config serverConfiguration)
          {
              string contents = "";
              if (serverConfiguration.idlURL != null)
                  contents = webClient.DownloadString(serverConfiguration.idlURL);
              else if (serverConfiguration.idlContents != null)
                  contents = serverConfiguration.idlContents as string;
              else
                  throw new MissingIDLException();
              try
              {
                  SinTD = new IDLParser().ParseIDL(contents);
              }
              catch(Exception e)
              {
                  Console.WriteLine("Failed to parse IDL from " + serverConfiguration.idlURL + ". Reason: " + e.Message);
              }
          }
  
          public void LoadLocalIDL(string IdlPath)
          {
              string idlContents = File.ReadAllText(IdlPath);
              SinTD = new IDLParser().ParseIDL(idlContents);
          }
          /// <summary>
          /// Handles an incoming message.
          /// </summary>
          public void HandleMessage(object sender, TransportMessageEventArgs e)
          {
              var handleMessageTask = Task.Factory.StartNew(() =>
              {
                  HandleMessageAsync(e.Message);
              });
              handleMessageTask.ContinueWith(te =>
              {
                  Console.WriteLine("[SINFONI.Connection] Incoming message could not be processed: {0}", te);
              }, TaskContinuationOptions.OnlyOnFaulted);
          }
  
          private void HandleMessageAsync(object message)
          {
  
              IMessage receivedMessage = null;
  
              try
              {
                  // Deserializes Message according to loaded protocol. As client agreed with server on respective protocol
                  receivedMessage = Protocol.DeserializeMessage(message);
              }
              catch (Exception e)
              {
                  #if DEBUG
                  Console.WriteLine("An exception occurred while deserializing the message: " + e);
                  #endif
              }
  
              if (!Initialized || deferredMessagesInQueue > 0)
              {
                  #if DEBUG
                  Console.WriteLine("[SINFONI.Connection] Not ready, deferring message until initialized");
                  #endif
                  lock (deferredMessages)
                  {
                      deferredMessages.Enqueue(receivedMessage);
                      deferredMessagesInQueue++;
                  }
              }
              else
              {
                  ProcessMessage(receivedMessage);
              }
          }
          internal void FinishIntialization()
          {
              Initialized = true;
              while (deferredMessagesInQueue > 0)
              {
                  ProcessDeferredMessage();
              }
          }
  
          private void ProcessDeferredMessage()
          {
              lock (deferredMessages)
              {
                  #if DEBUG
                  Console.WriteLine("[SINFONI.Connection] Processing deferred message, {0} left ", deferredMessagesInQueue);
                  #endif
                  IMessage queuedMessage = deferredMessages.Dequeue();
                  deferredMessagesInQueue--;
                  ProcessMessage(queuedMessage);
              }
          }
          private void ProcessMessage(IMessage message)
          {
              if (message == null)
              {
                  #if DEBUG
                  Console.WriteLine("A message was discarded because it deserialized to null");
                  #endif
                  return;
              }
              MessageType msgType = message.Type;
              if (msgType == MessageType.RESPONSE)
                  HandleCallResponse(message);
              else if (msgType == MessageType.EXCEPTION)
                  HandleCallError(message);
              else if (msgType == MessageType.REQUEST)
                  HandleCall(message);
              else
                  SendException(-1, "Unknown message type: " + msgType);
          }
          /// <summary>
          /// Is called when Connection receives a message that is identified as service call. Upon receiving a call,
          /// SINFONI will check whether the called service exists, what parameters it expects and which local function
          /// implements the service. If the service exists and the parameter types match, the local function called
          /// and a call-reply object with the result is sent back to the client
          /// </summary>
          /// <param name="callMessage">The deserialized message object that was received by the connection</param>
          private void HandleCall(IMessage callMessage)
          {
              int callID = callMessage.ID;
              string methodName = callMessage.MethodName;
              string[] serviceDescription = methodName.Split('.');
  
              Delegate nativeMethod = null;
              lock (registeredFunctions)
              {
                  if (registeredFunctions.ContainsKey(methodName))
                      nativeMethod = registeredFunctions[methodName];
              }
  
              if (nativeMethod != null)
              {
                  object[] parameters;
                  try
                  {
                      var args = callMessage.Parameters;
                      var callbacks = callMessage.Callbacks;
                      var paramInfo = new List<ParameterInfo>(nativeMethod.Method.GetParameters());
                      parameters = ConvertParameters(methodName, args, callbacks, paramInfo);
                  }
                  catch (Exception e)
                  {
                      SendException(callID, e.Message);
                      return;
                  }
  
                  if (!IsOneWay(methodName))
                  {
                      object returnValue = null;
                      object exception = null;
                      bool success = true;
                      try
                      {
                          // Super Evil Hack Here! Existing unit tests assume that WSJON serializes in a fixed format that
                          // originates from serializing the native types correctly. Also, the tests do not take into account
                          // any SinTD from any IDL. To make them work, we have to pretend that there is no ServiceRegistry
                          // maintaining any service description, but bypass type check and automatic SinTD Conversion
                          // by setting service Registry to null
                          if (SinTD == null)
                          {
                              returnValue = nativeMethod.DynamicInvoke(parameters);
                          }
                          else
                          {
                              ServiceFunctionDescription service = SinTD.SINFONIServices
                                  .GetService(serviceDescription[0])
                                  .GetServiceFunction(serviceDescription[1]);
                              returnValue = service.ReturnType.AssignValuesFromObject(nativeMethod.DynamicInvoke(parameters));
                          }
                      }
                      catch (Exception e)
                      {
                          exception = e;
                          success = false;
                      }
                      SendResponse(callID, nativeMethod, success, returnValue, exception);
                  }
                  else
                  {
                      nativeMethod.DynamicInvoke(parameters);
                  }
              }
              else
              {
                  SendException(callID, "Method " + methodName + " is not registered");
                  return;
              }
          }
  
          private object[] ConvertParameters(string methodName, List<object> args, List<int> callbacks, List<ParameterInfo> paramInfo)
          {
              object[] parameters = new object[paramInfo.Count];
  
              // Special handling for the first parameter if it's of type Connection.
              if (paramInfo.Count > 0 && paramInfo[0].ParameterType.Equals(typeof(Connection)))
              {
                  parameters[0] = this;
                  var otherParams = ConvertParameters(methodName, args, callbacks, paramInfo.GetRange(1, paramInfo.Count - 1));
                  otherParams.CopyTo(parameters, 1);
                  return parameters;
              }
  
              if (paramInfo.Count != args.Count)
              {
                  throw new Exception("Incorrect number of arguments for a method. Expected: " +
                                                paramInfo.Count + ". Received: " + args.Count);
              }
  
              for (int i = 0; i < args.Count; i++)
              {
                  // TODO: Handling of callbacks will change in the future and callbacks should be derived from the IDL.
                  // Callbacks should not be transmitted as extra list, as it is unclear how list is treated in some protocols
                  if (callbacks != null  && callbacks.Contains(i))
                  {
  
gt:                     if (paramInfo[i].ParameterType == typeof(ClientFunction))