//
// Mono.Unix/CdeclFunction.cs
//
// Authors:
//   Jonathan Pryor (jonpryor@vt.edu)
//
// (C) 2004 Jonathan Pryor
//
// 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;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;

namespace Mono.Unix.Native {

	// This class represents a single unmanaged function with "cdecl" calling
	// convention -- that is, it can accept a variable number of arguments which
	// are passed on the runtime stack.
	//
	// To use, create an instance:
	//
	//    CdeclFunction printf = new CdeclFunction ("the library", 
	//        "the function name", /* optional */ typeof (ReturnType));
	//
	// Then call the Invoke method with the appropriate number of arguments:
	//
	//    printf.Invoke (new object[]{"hello, %s\n", "world!"});
	//
	// In the background a P/Invoke definition for the method with the
	// requested argument types will be generated and invoked, invoking the
	// unmanaged function.  The generated methods are cached, so that subsequent
	// calls with the same argument list do not generate new code, speeding up
	// the call sequence.
	//
	// Invoking Cdecl functions is not guaranteed to be portable across all 
	// platforms.  For example, AMD64 requires that the caller set EAX to the 
	// number of floating point arguments passed in the SSE registers.  This 
	// is only required for variable argument/cdecl functions; consequently, 
	// the overload technique used by this class wouldn't normally work.  
	// Mono's AMD64 JIT works around this by always setting EAX on P/Invoke
	// invocations, allowing CdeclFunction to work properly, but it will not
	// necessarily always work.  See also: 
	//
	//     http://lwn.net/Articles/5201/?format=printable
	//
	// Due to potential portability issues, cdecl functions should be avoided 
	// on most platforms.
	//
	// This class is intended to be thread-safe.
	public sealed class CdeclFunction
	{
		// The readonly fields (1) shouldn't be modified, and (2) should only be
		// used when `overloads' is locked.
		private readonly string library;
		private readonly string method;
		private readonly Type returnType;
		private readonly AssemblyName assemblyName;
		private readonly AssemblyBuilder assemblyBuilder;
		private readonly ModuleBuilder moduleBuilder;

		private Hashtable overloads;

		public CdeclFunction (string library, string method)
			: this (library, method, typeof(void))
		{
		}

		public CdeclFunction (string library, string method, Type returnType)
		{
			this.library = library;
			this.method = method;
			this.returnType = returnType;
			this.overloads = new Hashtable ();
			this.assemblyName = new AssemblyName ();
			this.assemblyName.Name = "Mono.Posix.Imports." + library;
			this.assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly (
					assemblyName, AssemblyBuilderAccess.Run);
			this.moduleBuilder = assemblyBuilder.DefineDynamicModule (assemblyName.Name);
		}

		public object Invoke (object[] parameters)
		{
			Type[] parameterTypes = GetParameterTypes (parameters);
			MethodInfo m = CreateMethod (parameterTypes);
			return m.Invoke (null, parameters);
		}

		private MethodInfo CreateMethod (Type[] parameterTypes)
		{
			string typeName = GetTypeName (parameterTypes);

			lock (overloads) {
				MethodInfo mi = (MethodInfo) overloads [typeName];

				if (mi != null) {
					return mi;
				}

				TypeBuilder tb = CreateType (typeName);
				/* MethodBuilder mb = */ tb.DefinePInvokeMethod (
						method, 
						library, 
						MethodAttributes.PinvokeImpl | MethodAttributes.Static | MethodAttributes.Public,
						CallingConventions.Standard, 
						returnType, 
						parameterTypes, 
						CallingConvention.Cdecl,
						CharSet.Ansi);
				mi = tb.CreateType ().GetMethod (method);
				overloads.Add (typeName, mi);
				return mi;
			}
		}

		private TypeBuilder CreateType (string typeName)
		{
			return moduleBuilder.DefineType (typeName, TypeAttributes.Public);
		}

		private static Type GetMarshalType (Type t)
		{
			switch (Type.GetTypeCode (t)) {
				// types < sizeof(int) are marshaled as ints
				case TypeCode.Boolean: case TypeCode.Char: case TypeCode.SByte: 
				case TypeCode.Int16: case TypeCode.Int32: 
					return typeof(int);
				case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32:
					return typeof(uint);
				case TypeCode.Int64:
					return typeof(long);
				case TypeCode.UInt64:
					return typeof(ulong);
				case TypeCode.Single: case TypeCode.Double:
					return typeof(double);
				default:
					return t;
			}
		}

		private string GetTypeName (Type[] parameterTypes)
		{
			StringBuilder sb = new StringBuilder ();

			sb.Append ("[").Append (library).Append ("] ").Append (method);
			sb.Append ("(");

			if (parameterTypes.Length > 0)
				sb.Append (parameterTypes [0]);
			for (int i = 1; i < parameterTypes.Length; ++i)
				sb.Append (",").Append (parameterTypes [i]);

			sb.Append (") : ").Append (returnType.FullName);

			return sb.ToString ();
		}

		private static Type[] GetParameterTypes (object[] parameters)
		{
			Type[] parameterTypes = new Type [parameters.Length];
			for (int i = 0; i < parameters.Length; ++i)
				parameterTypes [i] = GetMarshalType (parameters [i].GetType ());
			return parameterTypes;
		}
	}
}