//
// System.Runtime.Remoting.Channels.ChannelServices.cs
//
// Author: Rodrigo Moya (rodrigo@ximian.com)
//         Dietmar Maurer (dietmar@ximian.com)
//         Lluis Sanchez Gual (lluis@ideary.com)
//
// 2002 (C) Copyright, Ximian, Inc.
//

using System.Collections;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Contexts;

namespace System.Runtime.Remoting
{
	[Serializable]
	internal class ChannelInfo : IChannelInfo
	{
		object [] channelData = null;

		public ChannelInfo ()
		{
			channelData = ChannelServices.GetCurrentChannelInfo ();
		}

		public ChannelInfo (object remoteChannelData)
		{
			channelData = new object[] { remoteChannelData };
		}
		
		public object[] ChannelData 
		{
			get {
				return channelData;
			}
			
			set {
				channelData = value;
			}
		}
	}
}

namespace System.Runtime.Remoting.Channels
{
	public sealed class ChannelServices
	{
		private static ArrayList registeredChannels = new ArrayList ();
		private static ArrayList delayedClientChannels = new ArrayList ();
		
		private static CrossContextChannel _crossContextSink = new CrossContextChannel();
		
		internal static string CrossContextUrl = "__CrossContext";

		private ChannelServices ()
		{
		}

		internal static CrossContextChannel CrossContextChannel
		{
			get { return _crossContextSink; }
		}

		internal static IMessageSink CreateClientChannelSinkChain(string url, object remoteChannelData, out string objectUri)
		{
			// Locate a channel that can parse the url. This channel will be used to
			// create the sink chain.

			object[] channelDataArray = (object[])remoteChannelData;

			lock (registeredChannels.SyncRoot)
			{
				// First of all, try registered channels
				foreach (IChannel c in registeredChannels) 
				{
					IChannelSender sender = c as IChannelSender;
					if (sender == null) continue;
	
					IMessageSink sink = CreateClientChannelSinkChain (sender, url, channelDataArray, out objectUri);
					if (sink != null) return sink;
				}
				
				// Not found. Try now creation delayed channels
				foreach (IChannelSender sender in delayedClientChannels) 
				{
					IMessageSink sink = CreateClientChannelSinkChain (sender, url, channelDataArray, out objectUri);
					if (sink != null) {
						delayedClientChannels.Remove (sender);
						RegisterChannel (sender);
						return sink;
					}
				}
			}
			
			objectUri = null;
			return null;
		}
		
		internal static IMessageSink CreateClientChannelSinkChain (IChannelSender sender, string url, object[] channelDataArray, out string objectUri)
		{
			objectUri = null;
			if (channelDataArray == null) {
				return sender.CreateMessageSink (url, null, out objectUri);
			}
			else {
				foreach (object data in channelDataArray) {
					IMessageSink sink = sender.CreateMessageSink (url, data, out objectUri);
					if (sink != null) return sink;		
				}
			}
			return null;
		}
		
		public static IChannel[] RegisteredChannels
		{
			get {
				lock (registeredChannels.SyncRoot)
				{
					IChannel[] channels = new IChannel[registeredChannels.Count];
	
					for (int i = 0; i < registeredChannels.Count; i++)
						channels[i] = (IChannel) registeredChannels[i];
	
					return channels;
				}
			}
		}

		public static IServerChannelSink CreateServerChannelSinkChain (
			IServerChannelSinkProvider provider, IChannelReceiver channel)
	    {
			IServerChannelSinkProvider tmp = provider;
			while (tmp.Next != null) tmp = tmp.Next;
			tmp.Next = new ServerDispatchSinkProvider ();

			// Every provider has to call CreateSink() of its next provider
			return  provider.CreateSink (channel);
		}

		public static ServerProcessing DispatchMessage (
			IServerChannelSinkStack sinkStack,
			IMessage msg,
			out IMessage replyMsg)
		{
			if (msg == null) throw new ArgumentNullException ("msg");
			
			// Async processing is not done here because there isn't any way
			// to know if a message is to be dispatched sync or asynchronously.

			replyMsg = SyncDispatchMessage (msg);

			if (RemotingServices.IsOneWay (((IMethodMessage) msg).MethodBase))
				return ServerProcessing.OneWay;
			else
				return ServerProcessing.Complete;
		}

		public static IChannel GetChannel (string name)
		{
			lock (registeredChannels.SyncRoot)
			{
				foreach (IChannel chnl in registeredChannels) {
					if (chnl.ChannelName == name && !(chnl is CrossAppDomainChannel)) return chnl;
				}
				return null;
			}
		}

		public static IDictionary GetChannelSinkProperties (object obj)
		{
			if (!RemotingServices.IsTransparentProxy (obj))
				throw new ArgumentException ("obj must be a proxy","obj");
				
			ClientIdentity ident = (ClientIdentity) RemotingServices.GetRealProxy (obj).ObjectIdentity;
			IMessageSink sink = ident.ChannelSink;
			ArrayList dics = new ArrayList ();
			
			while (sink != null && !(sink is IClientChannelSink))
				sink = sink.NextSink;

			if (sink == null)
				return new Hashtable ();

			IClientChannelSink csink = sink as IClientChannelSink;
			while (csink != null)
			{
				dics.Add (csink.Properties);
				csink = csink.NextChannelSink;
			}

			IDictionary[] adics = (IDictionary[]) dics.ToArray (typeof(IDictionary[]));
			return new AggregateDictionary (adics);
		}

		public static string[] GetUrlsForObject (MarshalByRefObject obj)
		{
			string uri = RemotingServices.GetObjectUri (obj);
			if (uri == null) return new string [0];

			ArrayList list = new ArrayList ();

			lock (registeredChannels.SyncRoot)
			{
				foreach (object chnl_obj in registeredChannels) {
					if (chnl_obj is CrossAppDomainChannel) continue;
					
					IChannelReceiver chnl = chnl_obj as IChannelReceiver;
	
					if (chnl != null)
						list.AddRange (chnl.GetUrlsForUri (uri));
				}
			}
			
			return  (string[]) list.ToArray (typeof(string));
		}

		public static void RegisterChannel (IChannel chnl)
		{
			// Put the channel in the correct place according to its priority.
			// Since there are not many channels, a linear search is ok.

			lock (registeredChannels.SyncRoot)
			{
				int pos = -1;
				for (int n = 0; n < registeredChannels.Count; n++) 
				{
					IChannel regc = (IChannel) registeredChannels[n];
					
					if (regc.ChannelName == chnl.ChannelName)
						throw new RemotingException ("Channel " + regc.ChannelName + " already registered");
						
					if (regc.ChannelPriority < chnl.ChannelPriority && pos==-1)
						pos = n;
				}
				
				if (pos != -1) registeredChannels.Insert (pos, chnl);
				else registeredChannels.Add (chnl);
				
				IChannelReceiver receiver = chnl as IChannelReceiver;
				if (receiver != null) receiver.StartListening (null);
			}
		}

		internal static void RegisterChannelConfig (ChannelData channel)
		{
			IServerChannelSinkProvider serverSinks = null;
			IClientChannelSinkProvider clientSinks = null;
			
			// Create server providers
			for (int n=channel.ServerProviders.Count-1; n>=0; n--)
			{
				ProviderData prov = channel.ServerProviders[n] as ProviderData;
				IServerChannelSinkProvider sinkp = (IServerChannelSinkProvider) CreateProvider (prov);
				sinkp.Next = serverSinks;
				serverSinks = sinkp;
			}
			
			// Create client providers
			for (int n=channel.ClientProviders.Count-1; n>=0; n--)
			{
				ProviderData prov = channel.ClientProviders[n] as ProviderData;
				IClientChannelSinkProvider sinkp = (IClientChannelSinkProvider) CreateProvider (prov);
				sinkp.Next = clientSinks;
				clientSinks = sinkp;
			}

			// Create the channel
			
			Type type = Type.GetType (channel.Type);
			if (type == null) throw new RemotingException ("Type '" + channel.Type + "' not found");
			
			Object[] parms;			
			Type[] signature;			
			bool clienc = typeof (IChannelSender).IsAssignableFrom (type);
			bool serverc = typeof (IChannelReceiver).IsAssignableFrom (type);
			
			if (clienc && serverc) {
				signature = new Type [] {typeof(IDictionary), typeof(IClientChannelSinkProvider), typeof(IServerChannelSinkProvider)};
				parms = new Object[] {channel.CustomProperties, clientSinks, serverSinks};
			}
			else if (clienc) {
				signature = new Type [] {typeof(IDictionary), typeof(IClientChannelSinkProvider)};
				parms = new Object[] {channel.CustomProperties, clientSinks};
			}
			else if (serverc) {
				signature = new Type [] {typeof(IDictionary), typeof(IServerChannelSinkProvider)};
				parms = new Object[] {channel.CustomProperties, serverSinks};
			}
			else
				throw new RemotingException (type + " is not a valid channel type");
				
			ConstructorInfo ctor = type.GetConstructor (signature);
			if (ctor == null)
				throw new RemotingException (type + " does not have a valid constructor");

			IChannel ch;
			try
			{
				ch = (IChannel) ctor.Invoke (parms);
			}
			catch (TargetInvocationException ex)
			{
				throw ex.InnerException;
			}
			
			lock (registeredChannels.SyncRoot)
			{
				if (channel.DelayLoadAsClientChannel == "true" && !(ch is IChannelReceiver))
					delayedClientChannels.Add (ch);
				else
					RegisterChannel (ch);
			}
		}
		
		static object CreateProvider (ProviderData prov)
		{
			Type pvtype = Type.GetType (prov.Type);
			if (pvtype == null) throw new RemotingException ("Type '" + prov.Type + "' not found");
			Object[] pvparms = new Object[] {prov.CustomProperties, prov.CustomData};
			
			try
			{
				return Activator.CreateInstance (pvtype, pvparms);
			}
			catch (Exception ex)
			{
				if (ex is TargetInvocationException) ex = ((TargetInvocationException)ex).InnerException;
				throw new RemotingException ("An instance of provider '" + pvtype + "' could not be created: " + ex.Message);
			}
		}

		public static IMessage SyncDispatchMessage (IMessage msg)
		{
			IMessage ret = CheckIncomingMessage (msg);
			if (ret != null) return ret;
			return _crossContextSink.SyncProcessMessage (msg);
		}

		public static IMessageCtrl AsyncDispatchMessage (IMessage msg, IMessageSink replySink)
		{
			IMessage ret = CheckIncomingMessage (msg);
			if (ret != null) {
				replySink.SyncProcessMessage (ret);
				return null;
			}
			return _crossContextSink.AsyncProcessMessage (msg, replySink);		
		}
		
		static ReturnMessage CheckIncomingMessage (IMessage msg)
		{
			IMethodMessage call = (IMethodMessage)msg;
			ServerIdentity identity = RemotingServices.GetIdentityForUri (call.Uri) as ServerIdentity;

			if (identity == null) 
				return new ReturnMessage (new RemotingException ("No receiver for uri " + call.Uri), (IMethodCallMessage) msg);

			RemotingServices.SetMessageTargetIdentity (msg, identity);
			return null;
		}

		public static void UnregisterChannel (IChannel chnl)
		{
			if (chnl == null)
				throw new ArgumentNullException ();
				
			lock (registeredChannels.SyncRoot)
			{
				if (!registeredChannels.Contains ((object) chnl))
					throw new RemotingException ();
	
				registeredChannels.Remove ((object) chnl);
	
				IChannelReceiver chnlReceiver = chnl as IChannelReceiver;
				if(chnlReceiver != null)
					chnlReceiver.StopListening(null);
			}
		}

		internal static object [] GetCurrentChannelInfo ()
		{
			ArrayList list = new ArrayList ();
			
			lock (registeredChannels.SyncRoot)
			{
				foreach (object chnl_obj in registeredChannels) {
					IChannelReceiver chnl = chnl_obj as IChannelReceiver;
				
					if (chnl != null) {
						object chnl_data = chnl.ChannelData;
						if (chnl_data != null)
							list.Add (chnl_data);
					}
				}
			}
			
			return  list.ToArray ();
		}
	}
}
