//
// FaultConverter.cs
//
// Author:
//	Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2006 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.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace System.ServiceModel.Channels
{
	public abstract class FaultConverter
	{
		public static FaultConverter GetDefaultFaultConverter (MessageVersion version)
		{
			if (version == null)
				throw new ArgumentNullException ("version");
			return new SimpleFaultConverter (version);
		}

		protected FaultConverter ()
		{
		}

		[MonoTODO]
		protected abstract bool OnTryCreateException (
			Message message, MessageFault fault, out Exception error);

		[MonoTODO]
		protected abstract bool OnTryCreateFaultMessage (
			Exception error, out Message message);

		public bool TryCreateException (Message message, MessageFault fault, out Exception error)
		{
			return OnTryCreateException (message, fault, out error);
		}

		public bool TryCreateFaultMessage (Exception error, out Message message)
		{
			return OnTryCreateFaultMessage (error, out message);
		}
	}

	class SimpleFaultConverter : FaultConverter
	{
		static readonly Dictionary<Type,string> map;
		
		static SimpleFaultConverter ()
		{
			map = new Dictionary<Type,string> ();
			map [typeof (EndpointNotFoundException)] = "DestinationUnreachable";
			map [typeof (ActionNotSupportedException)] = "ActionNotSupported";
		}

		MessageVersion version;

		public SimpleFaultConverter (MessageVersion version)
		{
			this.version = version;
		}

		protected override bool OnTryCreateException (
			Message message, MessageFault fault, out Exception error)
		{
			if (message == null)
				throw new ArgumentNullException ("message");
			if (fault == null)
				throw new ArgumentNullException ("fault");

			error = null;

			FaultCode fc;
			if (version.Envelope.Equals (EnvelopeVersion.Soap11))
				fc = fault.Code;
			else
				fc = fault.Code.SubCode;

			if (fc == null)
				return false;

			string msg = fault.Reason.GetMatchingTranslation ().Text;
			if (fc.Namespace == message.Version.Addressing.Namespace) {
				switch (fc.Name) {
				case "ActionNotSupported":
					error = new ActionNotSupportedException (msg);
					return true;
				case "DestinationUnreachable":
					error = new EndpointNotFoundException (msg);
					return true;
				}
			}

			return false;
		}

		protected override bool OnTryCreateFaultMessage (Exception error, out Message message)
		{
			if (version.Envelope.Equals (EnvelopeVersion.None)) {
				message = null;
				return false;
			}

			string action;
			if (!map.TryGetValue (error.GetType (), out action)) {
				message = null;
				return false;
			}

			FaultCode fc;
			if (version.Envelope.Equals (EnvelopeVersion.Soap12))
				fc = new FaultCode ("Sender", version.Envelope.Namespace, new FaultCode (action, version.Addressing.Namespace));
			else
				fc = new FaultCode (action, version.Addressing.Namespace);

			OperationContext ctx = OperationContext.Current;
			// FIXME: support more fault code depending on the exception type.
#if !NET_2_1 && !XAMMAC_4_5
			// FIXME: set correct fault reason.
			if (ctx != null && ctx.EndpointDispatcher.ChannelDispatcher.IncludeExceptionDetailInFaults) {
				ExceptionDetail detail = new ExceptionDetail (error);
				message = Message.CreateMessage (version, fc,
					error.Message, detail, version.Addressing.FaultNamespace);
			}
			else
#endif
				message = Message.CreateMessage (version, fc, error.Message, version.Addressing.FaultNamespace);

			return true;
		}
	}
}