//
// System.Runtime.Remoting.Identity.cs
//
// Author: Lluis Sanchez Gual (lluis@ideary.com)
//
// (C) 2002, Lluis Sanchez Gual
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Services;

namespace System.Runtime.Remoting
{
	internal abstract class ServerIdentity : Identity
	{
		protected Type _objectType;

		protected MarshalByRefObject _serverObject;

		// Message sink used in the server to dispatch a message
		// to the server object
		protected IMessageSink _serverSink = null;

		protected Context _context;
		protected Lease _lease;

		public ServerIdentity (string objectUri, Context context, Type objectType): base (objectUri)
		{
			_objectType = objectType;
			_context = context;
		}

		public Type ObjectType
		{
			get { return _objectType; }
		}

		public void StartTrackingLifetime (ILease lease)
		{
			// Adds this identity to the LeaseManager. 
			// _serverObject must be set.

			if (lease != null && lease.CurrentState == LeaseState.Null) lease = null;

			if (lease != null) 
			{
				if (! (lease is Lease)) lease = new Lease();  // This seems to be MS behavior
				_lease = (Lease) lease;
				LifetimeServices.TrackLifetime (this);
			}
		}

		public virtual void OnLifetimeExpired()
		{
			DisposeServerObject();
		}

		public override ObjRef CreateObjRef (Type requestedType)
		{
			if (_objRef != null)
			{
				// Just update channel info. It may have changed.
				_objRef.UpdateChannelInfo();
				return _objRef;
			}

			if (requestedType == null) requestedType = _objectType;
			_objRef = new ObjRef ();
			_objRef.TypeInfo = new TypeInfo(requestedType);
			_objRef.URI = _objectUri;

			if (_envoySink != null && !(_envoySink is EnvoyTerminatorSink))
				_objRef.EnvoyInfo = new EnvoyInfo (_envoySink);

			return _objRef;
		}

		public void AttachServerObject (MarshalByRefObject serverObject, Context context)
		{
			DisposeServerObject();

			_context = context;
			_serverObject = serverObject;
			
			if (RemotingServices.IsTransparentProxy (serverObject))
			{
				RealProxy rp = RemotingServices.GetRealProxy (serverObject);
				if (rp.ObjectIdentity == null)
					rp.ObjectIdentity = this;
			}
			else
			{
				if (_objectType.IsContextful)
					_envoySink = context.CreateEnvoySink (serverObject);
	
				_serverObject.ObjectIdentity = this;
			}
		}

		public Lease Lease
		{
			get { return _lease; }
		}

		public Context Context
		{
			get { return _context; }
			set { _context = value; }
		}

		public abstract IMessage SyncObjectProcessMessage (IMessage msg);
		public abstract IMessageCtrl AsyncObjectProcessMessage (IMessage msg, IMessageSink replySink);

		protected void DisposeServerObject()
		{
			// Detach identity from server object to avoid problems if the
			// object is marshalled again.
			
			if (_serverObject != null) {
				MarshalByRefObject obj = _serverObject;
				_serverObject.ObjectIdentity = null;
				_serverObject = null;
				_serverSink = null;
				TrackingServices.NotifyDisconnectedObject (obj);
			}
		}
	}

	internal class ClientActivatedIdentity : ServerIdentity
	{
		MarshalByRefObject _targetThis;
		
		public ClientActivatedIdentity (string objectUri, Type objectType): base (objectUri, null, objectType)
		{
		}
	
		public MarshalByRefObject GetServerObject ()
		{
			return _serverObject;
		}

		public MarshalByRefObject GetClientProxy ()
		{
			return _targetThis;
		}
		
		public void SetClientProxy (MarshalByRefObject obj)
		{
			_targetThis = obj;
		}

		public override void OnLifetimeExpired()
		{
			base.OnLifetimeExpired();
			RemotingServices.DisposeIdentity (this);
		}

		public override IMessage SyncObjectProcessMessage (IMessage msg)
		{
			if (_serverSink == null) {
				bool useProxy = _targetThis != null;
				_serverSink = _context.CreateServerObjectSinkChain ((useProxy ? _targetThis : _serverObject), useProxy);
			}
			return _serverSink.SyncProcessMessage (msg);
		}

		public override IMessageCtrl AsyncObjectProcessMessage (IMessage msg, IMessageSink replySink)
		{
			if (_serverSink == null) {
				bool useProxy = _targetThis != null;
				_serverSink = _context.CreateServerObjectSinkChain ((useProxy ? _targetThis : _serverObject), useProxy);
			}
			return _serverSink.AsyncProcessMessage (msg, replySink);
		}	
	}

	internal class SingletonIdentity : ServerIdentity
	{
		public SingletonIdentity (string objectUri, Context context, Type objectType): base (objectUri, context, objectType)
		{
		}

		public MarshalByRefObject GetServerObject ()
		{
			if (_serverObject != null) return _serverObject;

			lock (this) 
			{
				if (_serverObject == null) {
					MarshalByRefObject server = (MarshalByRefObject) Activator.CreateInstance (_objectType, true);
					AttachServerObject (server, Context.DefaultContext);
					StartTrackingLifetime ((ILease)server.InitializeLifetimeService ());
				}
			}
			return _serverObject;
		}

		public override IMessage SyncObjectProcessMessage (IMessage msg)
		{
			MarshalByRefObject obj = GetServerObject ();
			if (_serverSink == null) _serverSink = _context.CreateServerObjectSinkChain (obj, false);
			return _serverSink.SyncProcessMessage (msg);
		}

		public override IMessageCtrl AsyncObjectProcessMessage (IMessage msg, IMessageSink replySink)
		{
			MarshalByRefObject obj = GetServerObject ();
			if (_serverSink == null) _serverSink = _context.CreateServerObjectSinkChain (obj, false);
			return _serverSink.AsyncProcessMessage (msg, replySink);
		}	
	}

	internal class SingleCallIdentity : ServerIdentity
	{
		public SingleCallIdentity (string objectUri, Context context, Type objectType): base (objectUri, context, objectType)
		{
		}

		public override IMessage SyncObjectProcessMessage (IMessage msg)
		{
			// SingleCallIdentity creates and disposes an instance in each call

			MarshalByRefObject obj = (MarshalByRefObject)Activator.CreateInstance (_objectType, true);
			if (obj.ObjectIdentity == null) obj.ObjectIdentity = this;
			IMessageSink serverSink = _context.CreateServerObjectSinkChain (obj, false);
			IMessage result = serverSink.SyncProcessMessage (msg);
			if (obj is IDisposable) ((IDisposable)obj).Dispose();
			return result;
		}

		public override IMessageCtrl AsyncObjectProcessMessage (IMessage msg, IMessageSink replySink)
		{
			MarshalByRefObject obj = (MarshalByRefObject)Activator.CreateInstance (_objectType, true);
			IMessageSink serverSink = _context.CreateServerObjectSinkChain (obj, false);
			if (obj is IDisposable) replySink = new DisposerReplySink(replySink, ((IDisposable)obj));
			return serverSink.AsyncProcessMessage (msg, replySink);
		}	
	}

	internal class DisposerReplySink : IMessageSink
	{
		IMessageSink _next;
		IDisposable _disposable;

		public DisposerReplySink (IMessageSink next, IDisposable disposable)
		{
			_next = next;
			_disposable = disposable;
		}

		public IMessage SyncProcessMessage (IMessage msg)
		{
			_disposable.Dispose();
			return _next.SyncProcessMessage (msg);
		}

		public IMessageCtrl AsyncProcessMessage (IMessage msg, IMessageSink replySink)
		{
			throw new NotSupportedException();
		}

		public IMessageSink NextSink
		{
			get { return _next; }
		}
	}
}
