// Created with MonoDevelop
//
//    Copyright (C) 2010 Hiperscan GmbH Dresden, Germany
//
//    This program 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.
//
//    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;

using Hiperscan.Unix;


namespace Hiperscan.SGS
{
	
//	[Serializable()]
//	public class ClientInfo : System.Runtime.Serialization.ISerializable
//	{
//		public uint Port { get; private set; }
//		
//		public ClientInfo(uint port)
//		{
//			this.Port = port;
//		}
//		
//		public ClientInfo(SerializationInfo info, StreamingContext context)
//		{
//			this.Port = (uint)info.GetValue("Port", typeof(uint));
//		}
//
//		public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
//		{
//			info.AddValue("Port", this.Port);
//		}
//	}
	
	public class SharedUSB : MarshalByRefObject
	{
	
		internal class SharedUSBInfo
		{
			private IUSB        usb;
			private DeviceInfo  device_info;
			private VersionInfo version_info;
			private IChannel    channel;
			private Guid        session_id;
			private string      client_name;
			
			private volatile bool connected;
			
			public SharedUSBInfo(Device device, IUSB usb)
			{
				this.usb          = usb;
				this.device_info  = device.Info;
				this.version_info = device.VersionInfo;
				this.channel      = null;
				this.connected    = false;
				this.client_name  = string.Empty;
				this.session_id   = Guid.Empty;
			}
			
			public IUSB Usb
			{
				get { return this.usb; }
			}
			
			public DeviceInfo DeviceInfo
			{
				get { return this.device_info; }
			}
			
			public VersionInfo VersionInfo
			{
				get { return this.version_info; }
			}
			
			public IChannel Channel
			{
				get { return this.channel;  }
				internal set { this.channel = value; }
			}
			
			public bool IsConnected
			{
				get { return this.connected; }
				internal set { this.connected = value; }
			}
			
			public string ClientName
			{
				get { return this.client_name; }
				internal set { this.client_name = value; }
			}
			
			public Guid SessionId
			{
				get { return this.session_id; }
				internal set { this.session_id = value; }
			}
		}
		
		private class SharedUSBs
		{
			private Dictionary<uint,SharedUSBInfo> dict;

			public SharedUSBs()
			{
				this.dict = new Dictionary<uint,SharedUSBInfo>();
			}
			
			public bool Contains(uint port)
			{
				lock (this)
					return this.dict.ContainsKey(port);
			}
			
			public void Add(uint port, SharedUSBInfo info)
			{
				lock (this)
					this.dict.Add(port, info);
			}
			
			public void Remove(uint port)
			{
				lock (this)
					this.dict.Remove(port);
			}
			
			public uint[] Ports
			{
				get 
				{ 
					lock (this)
						return new List<uint>(this.dict.Keys).ToArray();
				}
			}
			
			public SharedUSBInfo this[uint port]
			{
				get 
				{
					lock (this)
						return this.dict[port];
				}
				set
				{
					lock (this)
						this.dict[port] = value;
				}
			}
			
			public int SharedCount
			{
				get 
				{ 
					lock (this)
						return this.dict.Count; 
				}
			}
			
			public int ConnectedCount
			{
				get
				{
					int count = 0;
					lock (this)
					{
						foreach (SharedUSBInfo info in this.dict.Values)
						{
							if (info.IsConnected)
								++count;
						}
					}
					return count;
				}
			}
		}

		private static SharedUSBs shared_usbs = new SharedUSBs();
		public static readonly string ServiceName = "hiperscan.remoteusb";
		
		public delegate void StateChangedHandler();
		public static event  StateChangedHandler StateChanged;
		
		internal SharedUSB()
		{
		}
		
		internal Guid Open(uint port, string client_name)
		{
			if (shared_usbs[port].IsConnected)
				throw new RemotingException(string.Format(Catalog.GetString("Shared device is already connected to a client ({0})."),
				                                          shared_usbs[port].ClientName));
			shared_usbs[port].IsConnected = true;
			shared_usbs[port].ClientName = client_name;
			shared_usbs[port].SessionId  = Guid.NewGuid();
			Console.WriteLine("Open shared device {0} from {1} (Session ID: {2}).", 
			                  shared_usbs[port].DeviceInfo.SerialNumber, 
			                  shared_usbs[port].ClientName,
			                  shared_usbs[port].SessionId);
			
			if (SharedUSB.StateChanged != null)
				SharedUSB.StateChanged();
			
			return shared_usbs[port].SessionId;
		}
		
		internal void Close(uint port)
		{
			Console.WriteLine("Close shared device {0} from {1}.", 
			                  shared_usbs[port].DeviceInfo.SerialNumber,
			                  shared_usbs[port].ClientName);
			shared_usbs[port].IsConnected = false;
			shared_usbs[port].ClientName = string.Empty;
			shared_usbs[port].SessionId  = Guid.Empty;

			if (SharedUSB.StateChanged != null)
				SharedUSB.StateChanged();
		}
		
		internal void ResetDevice(uint port, Guid session_id)
		{
			SharedUSB.CheckSessionId(port, session_id);
			shared_usbs[port].Usb.ResetDevice();
		}
		
		internal void SetTimeouts(uint port, Guid session_id, int read_timeout, int write_timeout)
		{
			SharedUSB.CheckSessionId(port, session_id);
			shared_usbs[port].Usb.SetTimeouts(read_timeout, write_timeout);
		}
		
		internal void SetTimeouts(uint port, Guid session_id)
		{
			SharedUSB.CheckSessionId(port, session_id);
			shared_usbs[port].Usb.SetTimeouts();
		}
		
		internal void SetLatency(uint port, Guid session_id)
		{
			SharedUSB.CheckSessionId(port, session_id);
			shared_usbs[port].Usb.SetLatency();
		}
		
		internal void Purge(uint port, Guid session_id)
		{
			SharedUSB.CheckSessionId(port, session_id);
			shared_usbs[port].Usb.Purge();
		}
		
		internal uint Write(uint port, Guid session_id, byte[] data)
		{
			SharedUSB.CheckSessionId(port, session_id);
			return shared_usbs[port].Usb.Write(data);
		}
		
		internal uint Read(uint port, Guid session_id, out byte[] data)
		{
			SharedUSB.CheckSessionId(port, session_id);
			return shared_usbs[port].Usb.Read(out data);
		}
		
		internal uint Read(uint port, Guid session_id, out byte[] data, uint len)
		{
			SharedUSB.CheckSessionId(port, session_id);
			return shared_usbs[port].Usb.Read(out data, len);
		}
		
		internal byte[] ReadBlocking(uint port, Guid session_id, uint len, int timeout)
		{
			SharedUSB.CheckSessionId(port, session_id);
			return shared_usbs[port].Usb.ReadBlocking(len, timeout, null);
		}
		
		internal DriverType GetDriverType(uint port)
		{
			return shared_usbs[port].Usb.DriverType;
		}
		
		internal DeviceInfo GetDeviceInfo(uint port)
		{
			return shared_usbs[port].DeviceInfo;
		}
		
		internal VersionInfo GetVersionInfo(uint port)
		{
			return shared_usbs[port].VersionInfo;
		}
		
		private static void CheckSessionId(uint port, Guid id)
		{
			if (id != shared_usbs[port].SessionId)
				throw new RemotingException(Catalog.GetString("The session ID is invalid. Remoting session has been corrupted."));
		}

		internal static void Add(uint port, SharedUSBInfo info)
		{
			if (shared_usbs.Contains(port))
			    throw new RemotingException(string.Format(Catalog.GetString("Port {0} is already in use by device {1}."), 
				                                          port, shared_usbs[port].DeviceInfo.SerialNumber));
			
			string service_name = SharedUSB.ServiceName + port.ToString();
			
			IChannel channel = ChannelServices.GetChannel(service_name);
				
			if (channel == null)
			{
				System.Collections.IDictionary settings = new System.Collections.Hashtable();
				settings["name"]   = service_name;
				settings["port"]   = port;
				settings["secure"] = false;
				settings["rejectRemoteRequests"] = false;
					
				BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
				provider.TypeFilterLevel = TypeFilterLevel.Full;
				channel = new TcpChannel(settings, null, provider);
				ChannelServices.RegisterChannel(channel, false);
			}

			try
			{
				RemotingConfiguration.RegisterWellKnownServiceType(typeof(SharedUSB), 
				                                                   service_name, 
				                                                   WellKnownObjectMode.SingleCall);
			}
			catch (RemotingException)
			{
				// uri may be already registered
			}
			
			info.Channel = channel;
			shared_usbs.Add(port, info);
		}
		
		internal static void Remove(uint port)
		{
			if (!shared_usbs.Contains(port))
				return;
			
			SharedUSBInfo info = shared_usbs[port];

			if (info.Channel != null)
				ChannelServices.UnregisterChannel(info.Channel);
			
			shared_usbs.Remove(port);
		}
		
		public static uint GetTcpPort(Device device)
		{
			foreach (uint port in shared_usbs.Ports)
			{
				if (shared_usbs[port].DeviceInfo.SerialNumber == device.info.SerialNumber)
					return port;
			}
			throw new ArgumentException(Catalog.GetString("Device is not shared."));
		}
		
		public static bool IsConnected(Device device)
		{
			return shared_usbs[SharedUSB.GetTcpPort(device)].IsConnected;
		}
		
		public static string GetClientName(Device device)
		{
			return shared_usbs[SharedUSB.GetTcpPort(device)].ClientName;
		}
		
		internal static SharedUSB GetRemoteObject(string server, uint port)
		{
			IChannel channel = ChannelServices.GetChannel(SharedUSB.ServiceName + server + port.ToString());

            if (channel == null)
            {
                System.Collections.IDictionary settings = new System.Collections.Hashtable();
                settings["name"]   = ServiceName + server + port.ToString();
                settings["port"]   = 0;
                settings["secure"] = false;
				if (server == "localhost")
					settings["bindTo"] = "127.0.0.1";

                BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
                provider.TypeFilterLevel = TypeFilterLevel.Full;
                channel = new TcpChannel(settings, null, provider);
                ChannelServices.RegisterChannel(channel, false);
            }

            StringBuilder sb = new StringBuilder(@"tcp://");
            sb.Append(server);
            sb.Append(':');
            sb.Append(port);
            sb.Append('/');
            sb.Append(SharedUSB.ServiceName + port.ToString());

            return (SharedUSB)Activator.GetObject(typeof(SharedUSB), sb.ToString());
		}
		
		public static int SharedCount
		{
			get { return shared_usbs.SharedCount; }
		}
		
		public static int ConnectedCount
		{
			get { return shared_usbs.ConnectedCount; }
		}
	}
	
	public class RemoteUSBClient : IUSB
	{
		private SharedUSB rusb;

		private string server;
		private uint port;
		private Guid session_id;
		
		private DeviceInfo device_info_cache;
		
		
		public RemoteUSBClient(string server, uint port)
		{
			this.rusb = SharedUSB.GetRemoteObject(server, port);
			this.server = server;
			this.port   = port;
			this.session_id = Guid.Empty;
		}
		
		public void Disconnect()
		{
			this.rusb = null;
		}
		
		public uint GetNumberOfDevices()
		{
			// remote clients are not supposed to do that
			return 0;
		}
		
		public DeviceInfo[] GetDeviceList()
		{
			// remote clients are not supposed to do that
			return new DeviceInfo[0];
		}
		
		public void OpenBySerialNumber(string serial)
		{
			this.session_id = this.rusb.Open(this.port, Environment.MachineName);
		}
		
		public void Close()
		{
			this.rusb.Close(this.port);
			this.session_id = Guid.Empty;
		}
		
		public void ResetDevice()
		{
			this.rusb.ResetDevice(this.port, this.session_id);
		}
		
		public void SetTimeouts(int read_timeout, int write_timeout)
		{
			this.rusb.SetTimeouts(this.port, this.session_id, read_timeout, write_timeout);
		}
		
		public void SetTimeouts()
		{
			this.rusb.SetTimeouts(this.port, this.session_id);
		}
		
		public void SetLatency()
		{
			this.rusb.SetLatency(this.port, this.session_id);
		}
		
		public void Purge()
		{
			this.rusb.Purge(this.port, this.session_id);
		}
		
		public uint Write(byte[] data)
		{
			return this.rusb.Write(this.port, this.session_id, data);
		}
		
		public uint Read(out byte[] data)
		{
			return this.rusb.Read(this.port, this.session_id, out data);
		}
		
		public uint Read(out byte[] data, uint len)
		{
			return this.rusb.Read(this.port, this.session_id, out data, len);
		}
		
		public byte[] ReadBlocking(uint len, int timeout, IdleTaskHandler idle_task)
		{
			return this.rusb.ReadBlocking(this.port, this.session_id, len, timeout);
		}
		
		public DriverType DriverType 
		{ 
			get { return this.rusb.GetDriverType(this.port); }
		}
		
		public DeviceInfo DeviceInfo
		{
			get 
			{
				if (this.device_info_cache == null)
				{
					this.device_info_cache = this.rusb.GetDeviceInfo(this.port);
					this.device_info_cache.RemoteUsbClient = this;
				}
				return this.device_info_cache;
			}
		}
		
		public VersionInfo VersionInfo
		{
			get { return this.rusb.GetVersionInfo(this.port); }
		}
		
		public string Server
		{
			get { return this.server; }
		}

		public uint Port
		{
			get { return this.port; }
		}
	}
}

