context: /*
      Copyright 2014 Travel Modelling Group, Department of Civil Engineering, University of Toronto
  
      This file is part of XTMF.
  
      XTMF is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      XTMF 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 General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with XTMF.  If not, see <http://www.gnu.org/licenses/>.
  */
  using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.IO;
  using System.Net.Sockets;
  using System.Runtime.Serialization.Formatters.Binary;
  using System.Threading;
  using XTMF.Interfaces;
  
  namespace XTMF.Networking
  {
      internal class Client : IClient, IDisposable
      {
          private string _Address;
  
          private IConfiguration _Configuration;
  
          private volatile IModelSystemTemplate _CurrentRunningModelSystem;
  
          private ConcurrentDictionary<int, List<Action<object>>> _CustomHandlers = new ConcurrentDictionary<int, List<Action<object>>>();
  
          private ConcurrentDictionary<int, Func<Stream, object>> _CustomReceivers = new ConcurrentDictionary<int, Func<Stream, object>>();
  
          private ConcurrentDictionary<int, Action<object, Stream>> _CustomSenders = new ConcurrentDictionary<int, Action<object, Stream>>();
  
          private bool _Exit = false;
  
          private MessageQueue<Message> _Messages = new MessageQueue<Message>();
  
          private Thread _ModelSystemThread;
  
          private int _Port;
  
          private float _Progress = 0;
  
          private LinkedList<DelayedResult> ResourceRequests = new LinkedList<DelayedResult>();
  
          public Client(string address, int port, IConfiguration configuration)
          {
              _Configuration = configuration;
              _Address = address;
              _Port = port;
              new Thread(ClientMain).Start();
              Thread progressThread = new Thread(delegate ()
             {
                 while (!_Exit)
                 {
                     Thread.Sleep(100);
                     try
                     {
                         _Progress = _CurrentRunningModelSystem?.Progress ?? 0;
                         NotifyProgress();
                     }
                     catch
                     {
                     }
                     Thread.MemoryBarrier();
                 }
             });
              progressThread.Start();
          }
  
          public string UniqueID { get; internal set; }
  
          public void ClientMain()
          {
              TcpClient connection = null;
              bool done = false;
              try
              {
                  connection = new TcpClient(_Address, _Port);
                  var networkStream = connection.GetStream();
                  new Thread(delegate ()
                     {
                         try
                         {
                             BinaryReader reader = new BinaryReader(networkStream);
                             BinaryFormatter inputFormat = new BinaryFormatter();
                             // we need some connection every 60 minutes, the host should be trying to request progress
                             networkStream.ReadTimeout = Timeout.Infinite;
                             while (!done || _Exit)
                             {
                                 var msg = new Message((MessageType)reader.ReadInt32());
                                 switch (msg.Type)
                                 {
                                     case MessageType.RequestProgress:
                                         {
                                             msg.Type = MessageType.PostProgess;
                                             _Messages.Add(msg);
                                         }
                                         break;
                                     case MessageType.ReturningResource:
                                         {
                                             var name = reader.ReadString();
                                             bool exists = reader.ReadBoolean();
                                             object data = null;
                                             if (exists)
                                             {
                                                 data = inputFormat.Deserialize(reader.BaseStream);
                                             }
                                             Result res = new Result() { Name = name, Data = data };
                                             msg.Data = res;
                                             _Messages.Add(msg);
                                         }
                                         break;
                                     case MessageType.PostCancel:
                                         {
                                             _Messages.Add(msg);
                                         }
                                         break;
                                     case MessageType.SendModelSystem:
                                         {
                                             var length = reader.ReadInt32();
                                             byte[] data = new byte[length];
                                             var soFar = 0;
                                             while (soFar < length)
                                             {
                                                 soFar += reader.Read(data, soFar, length - soFar);
                                             }
                                             msg.Data = data;
                                             _Messages.Add(msg);
                                         }
                                         break;
                                     case MessageType.Quit:
                                         {
                                             done = true;
                                             _Exit = true;
                                             Console.WriteLine("Exiting.");
                                         }
                                         break;
                                     case MessageType.SendCustomMessage:
                                         {
                                             // Time to receive a new custom message
                                             var number = reader.ReadInt32();
                                             var length = reader.ReadInt32();
                                             var buff = new byte[length];
                                             MemoryStream buffer = new MemoryStream(buff);
                                             int soFar = 0;
                                             while (soFar < length)
                                             {
                                                 soFar += reader.Read(buff, soFar, length - soFar);
                                             }
                                             _Messages.Add(new Message(MessageType.ReceiveCustomMessage,
                                                 new ReceiveCustomMessageMessage()
                                                 {
                                                     CustomMessageNumber = number,
                                                     Stream = buffer
                                                 }));
                                         }
                                         break;
  
                                     default:
                                         {
                                             // We don't know how to deal with this
                                             done = true;
                                             _Exit = true;
                                             Console.WriteLine("Came across a message number " + msg.Type + " not sure what to do with it.  Exiting.");
                                         }
                                         break;
                                 }
                                 Thread.MemoryBarrier();
                             }
                         }
                         catch (IOException)
                         {
                             done = true;
                             Thread.MemoryBarrier();
                             _Exit = true;
                             Console.WriteLine("Host has disconnected Client");
                             Environment.Exit(0);
                         }
                         catch (Exception e)
                         {
                             done = true;
                             while(e.InnerException != null)
                             {
                                 e = e.InnerException;
                             }
                             Console.WriteLine("Client exception");
                             Console.WriteLine(e.Message);
                             Console.WriteLine(e.StackTrace);
                         }
                         finally
                         {
                             Console.WriteLine("Client reader has exited.");
                             connection.Close();
                             done = true;
                             _Exit = true;
                             Thread.MemoryBarrier();
                         }
                     }).Start();
                  BinaryWriter writer = new BinaryWriter(networkStream);
                  BinaryFormatter outputFormat = new BinaryFormatter();
                  networkStream.WriteTimeout = 20000;
                  while (!done && !_Exit)
                  {
                      var message = _Messages.GetMessageOrTimeout(200);
                      Thread.MemoryBarrier();
                      if (!done && message != null)
                      {
                          if (ProcessMessage(writer, outputFormat, message))
                          {
                              done = true;
                          }
                      }
                  }
              }
              catch (Exception e)
              {
                  Console.WriteLine(e.Message);
                  Console.WriteLine(e.StackTrace);
              }
              finally
              {
                  if (connection != null)
                  {
                      connection.Close();
                  }
                  done = true;
              }
              done = true;
              _Exit = true;
          }
  
          public void Dispose() => Dispose(true);
  
          public void NotifyComplete(int status = 0, string error = null) => _Messages.Add(new Message(MessageType.PostComplete));
  
          public void NotifyProgress() => _Messages.Add(new Message(MessageType.PostProgess));
  
          public void RegisterCustomMessageHandler(int customMessageNumber, Action<object> handler)
          {
              lock (this)
              {
                  if (!_CustomHandlers.TryGetValue(customMessageNumber, out List<Action<object>> port))
                  {
                      port = new List<Action<object>>();
                      _CustomHandlers[customMessageNumber] = port;
                  }
                  port.Add(handler);
              }
          }
  
          public void RegisterCustomReceiver(int customMessageNumber, Func<Stream, object> converter)
          {
              if (!_CustomReceivers.TryAdd(customMessageNumber, converter))
              {
                  throw new XTMFRuntimeException(null, "The Custom Receiver port " + customMessageNumber + " was attempted to be registered twice!");
              }
          }
  
          public void RegisterCustomSender(int customMessageNumber, Action<object, Stream> converter)
          {
              if (!_CustomSenders.TryAdd(customMessageNumber, converter))
              {
                  throw new XTMFRuntimeException(null, "The Custom Sender port " + customMessageNumber + " was attempted to be registered twice!");
              }
          }
  
          public object RetriveResource(string name, Type t)
          {
              DelayedResult result = new DelayedResult() { Name = name };
              _Messages.Add(new Message(MessageType.RequestResource, result));
              result.Lock.Wait();
              result.Lock.Dispose();
              return result.Data;
          }
  
          public void SendCustomMessage(object data, int customMessageNumber)
          {
              _Messages.Add(new Message(MessageType.SendCustomMessage,
                  new SendCustomMessageMessage() { CustomMessageNumber = customMessageNumber, Data = data }));
          }
  
          public bool SetResource(string name, object o)
          {
              Result data = new Result() { Name = name, Data = o };
  
gt:             _Messages.Add(new Message(MessageType.PostResource, data));