/*
LinphoneWrapper.cs
Copyright (C) 2017 Belledonne Communications SARL

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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
#if __IOS__
using ObjCRuntime;
#endif

namespace Linphone
{
#region Wrapper specifics
	/// <summary>
	/// Only contains the LIB_NAME value that represents the library in which all DllImport are made
	/// </summary>
	public class LinphoneWrapper
	{
		public const string VERSION = "{{version}}";
#if __IOS__
		public const string LIB_NAME = "linphone.framework/linphone";
#elif WINDOWS_UWP
		public const string LIB_NAME = "liblinphone"; // With this, it automatically finds liblinphone.dll
#else
		public const string LIB_NAME = "linphone"; // With this, it automatically finds liblinphone.so
#endif

#if WINDOWS_UWP
		public const string BELLE_SIP_LIB_NAME = "bellesip";
		public const string BCTOOLBOX_LIB_NAME = "bctoolbox";
#else
		public const string BELLE_SIP_LIB_NAME = LIB_NAME;
		public const string BCTOOLBOX_LIB_NAME = LIB_NAME;
#endif
/// https://docs.microsoft.com/fr-fr/xamarin/cross-platform/app-fundamentals/building-cross-platform-applications/platform-divergence-abstraction-divergent-implementation#android
#if __ANDROID__
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void setAndroidLogHandler();
#endif
#if __IOS__
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void linphone_iphone_enable_logs();
#endif

		/// <summary>
		/// Registers the native log handler in Linphone.
		/// </summary>
		public static void setNativeLogHandler()
		{
#if __ANDROID__
			setAndroidLogHandler();
#elif __IOS__
			linphone_iphone_enable_logs();
#endif
		}
	}

	/// <summary>
	/// All methods that returns a LinphoneStatus with a value != 0 as an error code in C are translated in C# by throwing a LinphoneException
	/// </summary>
#if WINDOWS_UWP
    public class LinphoneException : System.Exception
    {
        public LinphoneException() : base() { }
        public LinphoneException(string message) : base(message) { }
        public LinphoneException(string message, System.Exception inner) : base(message, inner) { }
    }
#else
    [Serializable()]
	public class LinphoneException : System.Exception
	{
		public LinphoneException() : base() { }
		public LinphoneException(string message) : base(message) { }
		public LinphoneException(string message, System.Exception inner) : base(message, inner) { }
		protected LinphoneException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
	}
#endif

	[StructLayout(LayoutKind.Sequential)]
	/// <summary>
	/// Parent class for a Linphone public objects
	/// </summary>
	public class LinphoneObject
	{
		internal IntPtr nativePtr;

		internal GCHandle handle;

		internal List<IntPtr> string_ptr_list;

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void OnLinphoneObjectDataDestroyed(IntPtr data);

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
#if WINDOWS_UWP
		static extern int belle_sip_object_data_set(IntPtr ptr, string name, IntPtr data, IntPtr cb);
#else
		static extern int belle_sip_object_data_set(IntPtr ptr, string name, IntPtr data, OnLinphoneObjectDataDestroyed cb);
#endif

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr belle_sip_object_data_get(IntPtr ptr, string name);

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr belle_sip_object_ref(IntPtr ptr);

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void belle_sip_object_unref(IntPtr ptr);

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void belle_sip_object_data_remove(IntPtr ptr, string name);

		[DllImport(LinphoneWrapper.BCTOOLBOX_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr bctbx_list_next(IntPtr ptr);

		[DllImport(LinphoneWrapper.BCTOOLBOX_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr bctbx_list_get_data(IntPtr ptr);

		[DllImport(LinphoneWrapper.BCTOOLBOX_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr bctbx_list_append(IntPtr elem, IntPtr data);

		~LinphoneObject()
		{
			//Console.WriteLine("Destroying " + this.ToString());
			if (nativePtr != IntPtr.Zero) {
				//Console.WriteLine("Unreffing " + this.ToString());
				belle_sip_object_data_remove(nativePtr, "cs_obj");
				belle_sip_object_unref(nativePtr);
				handle.Free();
			}
		}

		internal static T fromNativePtr<T>(IntPtr ptr, bool takeRef=true) where T : LinphoneObject, new()
		{
			if (ptr == IntPtr.Zero) return null;
			IntPtr objPtr = belle_sip_object_data_get(ptr, "cs_obj");
			if (objPtr != IntPtr.Zero)
			{
				T obj = null;
				GCHandle handle = GCHandle.FromIntPtr(objPtr);
				if (handle.IsAllocated)
				{
					obj = (T)handle.Target;
				}
				if (obj == null)
				{
					//Console.WriteLine("Handle target is null " + handle.Target);
					objPtr = IntPtr.Zero;
				}
				else
				{
					//Console.WriteLine("Using existing " + obj.ToString());
					return obj;
				}
			}
			if (objPtr == IntPtr.Zero)
			{
				T obj = new T();
				//Console.WriteLine("Creating " + obj.ToString());
				if (takeRef)
				{
					ptr = belle_sip_object_ref(ptr);
					//Console.WriteLine("Reffing " + obj.ToString());
				}
				obj.nativePtr = ptr;
				obj.handle = GCHandle.Alloc(obj, GCHandleType.WeakTrackResurrection);
				objPtr = GCHandle.ToIntPtr(obj.handle);
#if WINDOWS_UWP
				belle_sip_object_data_set(ptr, "cs_obj", objPtr, IntPtr.Zero);
#else
				belle_sip_object_data_set(ptr, "cs_obj", objPtr, null);
#endif

				return obj;
			}
			return null;
		}

		internal static IEnumerable<string> MarshalStringArray(IntPtr listPtr)
		{
			if (listPtr != IntPtr.Zero)
			{
				IntPtr ptr = listPtr;
				while (ptr != IntPtr.Zero)
				{
					IntPtr dataPtr = bctbx_list_get_data(ptr);
					if (dataPtr == IntPtr.Zero)
					{
						break;
					}
					string key = Marshal.PtrToStringAnsi(dataPtr);
					yield return key;
					ptr = bctbx_list_next(ptr);
				}
			}
		}

		internal static IEnumerable<T> MarshalBctbxList<T>(IntPtr listPtr) where T : LinphoneObject, new()
		{
			if (listPtr != IntPtr.Zero)
			{
				IntPtr ptr = listPtr;
				while (ptr != IntPtr.Zero)
				{
					IntPtr dataPtr = bctbx_list_get_data(ptr);
					if (dataPtr == IntPtr.Zero)
					{
						break;
					}
					T obj = fromNativePtr<T>(dataPtr);
					yield return obj;
					ptr = bctbx_list_next(ptr);
				}
			}
		}

		internal protected IntPtr StringArrayToBctbxList(IEnumerable<string> stringlist)
		{
			IntPtr bctbx_list = IntPtr.Zero;
			string_ptr_list = new List<IntPtr>();
			foreach (string s in stringlist)
			{
				IntPtr string_ptr = Marshal.StringToHGlobalAnsi(s);
				bctbx_list = bctbx_list_append(bctbx_list, string_ptr);
				string_ptr_list.Add(string_ptr);
			}
			return bctbx_list;
		}

		internal protected void CleanStringArrayPtrs()
		{
			foreach (IntPtr string_ptr in string_ptr_list)
			{
				Marshal.FreeHGlobal(string_ptr);
			}
		}

		internal static IntPtr ObjectArrayToBctbxList<T>(IEnumerable<T> objlist) where T : LinphoneObject, new()
		{
			IntPtr bctbx_list = IntPtr.Zero;
			foreach (T ptr in objlist)
			{
				bctbx_list = bctbx_list_append(bctbx_list, ptr.nativePtr);
			}
			return bctbx_list;
		}
	}

	public class MediastreamerFactory
	{
		public IntPtr nativePtr;

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern int ms_factory_enable_filter_from_name(IntPtr nativePtr, string name, char enabled);

		public void enableFilterFromName(string name, bool enabled)
		{
			ms_factory_enable_filter_from_name(nativePtr, name, enabled ? (char)1 : (char)0);
		}

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void ms_devices_info_add(IntPtr devices_info, string manufacturer, string model, string platform, uint flags, int delay, int recommended_rate);

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr ms_factory_get_devices_info(IntPtr factory);

		public void addDevicesInfo(string manufacturer, string model, string platform, uint flags, int delay, int recommended_rate)
		{
				ms_devices_info_add(ms_factory_get_devices_info(nativePtr), manufacturer, model, platform, flags, delay, recommended_rate);
		}
	}
#endregion

#region Enums
	{{#enums}}
	{{#enum}}
	{{#doc}}
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/doc}}{{#isFlag}}[Flags]{{/isFlag}}
	public enum {{enumName}}
	{
		{{#values}}
		{{#doc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/doc}}
		{{name}} = {{{value}}},
		{{/values}}
	}

	{{/enum}}
	{{/enums}}
#endregion

#region Listeners
	{{#interfaces}}
	{{#interface}}
	[StructLayout(LayoutKind.Sequential)]
	public class {{interfaceName}} : LinphoneObject
	{
        ~{{interfaceName}}()
        {
            unregister();
        }

        [DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
        static extern void {{set_user_data_name}}(IntPtr thiz, IntPtr listener);

        [DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
        static extern IntPtr {{get_user_data_name}}(IntPtr thiz);

		{{#methods}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		{{#cb_setter}}
#if WINDOWS_UWP
		static extern void {{name}}(IntPtr thiz, IntPtr cb);
#else
		static extern void {{name}}(IntPtr thiz, {{name_private}} cb);
#endif
		{{/cb_setter}}

		{{#delegate}}
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void {{name_private}}({{params_private}});

		public delegate void {{name_public}}({{{params_public}}});
		private {{name_private}} {{var_private}};
		private {{name_public}} {{var_public}};

#if __IOS__
		[MonoPInvokeCallback(typeof({{name_private}}))]
#endif
		private static void {{cb_name}}({{params_private}})
		{
			{{interfaceClassName}} thiz = fromNativePtr<{{interfaceClassName}}>({{first_param}});
			{{interfaceName}} current_listener = thiz.CurrentCallbacks;
			if (current_listener != null)
			{
				current_listener.{{var_public}}?.Invoke({{{params}}});
			}
		}

		public {{name_public}} {{name}}
		{
			get
			{
				return {{var_public}};
			}
			set
			{
				{{var_public}} = value;
#if WINDOWS_UWP
				{{var_private}} = {{cb_name}};
				IntPtr cb = Marshal.GetFunctionPointerForDelegate({{var_private}});
				{{c_name_setter}}(nativePtr, cb);
#else
				{{c_name_setter}}(nativePtr, {{cb_name}});
#endif
			}
		}
		{{/delegate}}
		{{/methods}}

		internal void register() {
			IntPtr listener = {{get_user_data_name}}(nativePtr);
			if (listener == IntPtr.Zero)
			{
				GCHandle _handle = GCHandle.Alloc(this, GCHandleType.Normal);
				listener = GCHandle.ToIntPtr(_handle);
			} else
			{
				GCHandle _handle = GCHandle.FromIntPtr(listener);
				if (_handle.Target == this)
				{
					return;
				} else
				{
					_handle.Free();
					_handle = GCHandle.Alloc(this, GCHandleType.Normal);
					listener = GCHandle.ToIntPtr(_handle);
				}
			}
			{{set_user_data_name}}(nativePtr, listener);
		}

		internal void unregister() {
			IntPtr listener = {{get_user_data_name}}(nativePtr);
			{{set_user_data_name}}(nativePtr, IntPtr.Zero);
			if (listener != IntPtr.Zero)
			{
				GCHandle.FromIntPtr(listener).Free();
			}
		}
	}

	{{/interface}}
	{{/interfaces}}
#endregion

#region Classes
	{{#classes}}
	{{#_class}}
	{{#doc}}
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/doc}}

	[StructLayout(LayoutKind.Sequential)]
	public class {{className}} : LinphoneObject
	{
		{{#isLinphoneFactory}}
#if  __ANDROID__
        static Factory()
        {
            Java.Lang.JavaSystem.LoadLibrary("c++_shared");
            Java.Lang.JavaSystem.LoadLibrary("bctoolbox");
            Java.Lang.JavaSystem.LoadLibrary("ortp");
            Java.Lang.JavaSystem.LoadLibrary("mediastreamer");
            Java.Lang.JavaSystem.LoadLibrary("linphone");
        }
#endif
		{{/isLinphoneFactory}}
		{{#isLinphoneCall}}
		/// Get the native window handle of the video window, casted as an unsigned long.
		public string NativeVideoWindowIdString
		{
			get
			{
				return Marshal.PtrToStringUni(linphone_call_get_native_video_window_id(nativePtr));
			}
			set
			{
				IntPtr string_ptr_to_remove = linphone_call_get_native_video_window_id(nativePtr);
				IntPtr string_ptr = Marshal.StringToHGlobalUni(value);
				linphone_call_set_native_video_window_id(nativePtr, string_ptr);
				if(string_ptr_to_remove != IntPtr.Zero)
					Marshal.FreeHGlobal(string_ptr_to_remove);
			}
		}
		{{/isLinphoneCall}}
		{{#isLinphoneCore}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_core_get_ms_factory(IntPtr thiz);

		public MediastreamerFactory MsFactory {
			get
			{
				IntPtr ptr = linphone_core_get_ms_factory(nativePtr);
				MediastreamerFactory factory = new MediastreamerFactory();
				factory.nativePtr = ptr;
				return factory;
			}
		}

		/// Get the native window handle of the video window.
		public string NativeVideoWindowIdString
		{
			get
			{
				return Marshal.PtrToStringUni(linphone_core_get_native_video_window_id(nativePtr));
			}
			set
			{
				IntPtr string_ptr_to_remove = linphone_core_get_native_video_window_id(nativePtr);
				IntPtr string_ptr = Marshal.StringToHGlobalUni(value);
				linphone_core_set_native_video_window_id(nativePtr, string_ptr);
				if(string_ptr_to_remove != IntPtr.Zero)
					Marshal.FreeHGlobal(string_ptr_to_remove);
			}
		}

		/// Get the native window handle of the video preview window.
		public string NativePreviewWindowIdString
		{
			get
			{
				return Marshal.PtrToStringUni(linphone_core_get_native_preview_window_id(nativePtr));
			}
			set
			{
				IntPtr string_ptr_to_remove = linphone_core_get_native_preview_window_id(nativePtr);
				IntPtr string_ptr = Marshal.StringToHGlobalUni(value);
				linphone_core_set_native_preview_window_id(nativePtr, string_ptr);
				if(string_ptr_to_remove != IntPtr.Zero)
					Marshal.FreeHGlobal(string_ptr_to_remove);
			}
		}
		{{/isLinphoneCore}}

		{{#listener}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_factory_create_{{listener_constructor}}_cbs(IntPtr factory);

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_{{listener_constructor}}_add_callbacks(IntPtr thiz, IntPtr cbs);

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_{{listener_constructor}}_remove_callbacks(IntPtr thiz, IntPtr cbs);

		~{{className}}() 
		{
			if (listener != null)
			{
				linphone_{{listener_constructor}}_remove_callbacks(nativePtr, listener.nativePtr);
			}
		}

		private {{interfaceName}} listener;

		public {{interfaceName}} Listener
		{
			get {
				if (listener == null)
				{
					IntPtr nativeListener = linphone_factory_create_{{listener_constructor}}_cbs(IntPtr.Zero);
					listener = fromNativePtr<{{interfaceName}}>(nativeListener, false);
					linphone_{{listener_constructor}}_add_callbacks(nativePtr, nativeListener);
					listener.register();
				}
				return listener;
			}
		}
		{{/listener}}

		{{#dllImports}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		{{{prototype}}}
		{{#has_second_prototype}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		{{second_prototype}}
		{{/has_second_prototype}}

		{{#has_property}}
		{{#doc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/doc}}
		{{property_static}}public {{{property_return}}} {{property_name}}
		{
		{{#has_getter}}
			get
			{
				{{#is_string}}
				IntPtr stringPtr = {{getter_c_name}}({{getter_nativePtr}});
				return Marshal.PtrToStringAnsi(stringPtr);
				{{/is_string}}
				{{#is_bool}}
				return {{getter_c_name}}({{getter_nativePtr}}) != 0;
				{{/is_bool}}
				{{#is_class}}
				IntPtr ptr = {{getter_c_name}}({{getter_nativePtr}});
				{{{property_return}}} obj = fromNativePtr<{{return}}>(ptr, {{takeRef}});
				return obj;
				{{/is_class}}
				{{#is_enum}}
				return {{getter_c_name}}({{getter_nativePtr}});
				{{/is_enum}}
				{{#is_generic}}
				return {{getter_c_name}}({{getter_nativePtr}});
				{{/is_generic}}
				{{#is_string_list}}
				return MarshalStringArray({{getter_c_name}}({{getter_nativePtr}}));
				{{/is_string_list}}
				{{#is_class_list}}
				return MarshalBctbxList<{{{list_type}}}>({{getter_c_name}}({{getter_nativePtr}}));
				{{/is_class_list}}
			}
		{{/has_getter}}
		{{#has_setter}}
			set
			{
				{{#is_string}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value);
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_string}}
				{{#is_bool}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value ? (char)1 : (char)0);
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_bool}}
				{{#is_class}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value.nativePtr);
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_class}}
				{{#is_enum}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}(int)value);
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_enum}}
				{{#is_generic}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}value);
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_generic}}
				{{#is_string_list}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}StringArrayToBctbxList(value));
				CleanStringArrayPtrs();
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_string_list}}
				{{#is_class_list}}
				{{#exception}}int exception_result = {{/exception}}{{setter_c_name}}({{setter_nativePtr}}ObjectArrayToBctbxList<{{{list_type}}}>(value));
				{{#exception}}if (exception_result != 0) throw new LinphoneException("{{property_name}} setter returned value " + exception_result);{{/exception}}
				{{/is_class_list}}
			}
		{{/has_setter}}
		}
		{{/has_property}}
		{{#has_impl}}
		{{#impl}}
		{{#doc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/doc}}
		public {{static}}{{override}}{{{type}}} {{name}}({{{args}}})
		{
			{{#is_string}}
			IntPtr stringPtr = {{c_name}}({{nativePtr}}{{{c_args}}});
			string returnVal = Marshal.PtrToStringAnsi(stringPtr);
			{{/is_string}}
			{{#is_bool}}
			{{#hasReturn}}bool returnVal = {{/hasReturn}}{{c_name}}({{nativePtr}}{{{c_args}}}) == (char)0 ? false : true;
			{{/is_bool}}
			{{#is_class}}
			IntPtr ptr = {{c_name}}({{nativePtr}}{{{c_args}}});
			{{type}} returnVal = fromNativePtr<{{type}}>(ptr, {{takeRef}});
			{{/is_class}}
			{{#is_enum}}
			{{#exception}}int exception_result = {{/exception}}{{#hasReturn}}{{{type}}} returnVal = {{/hasReturn}}{{c_name}}({{nativePtr}}{{{c_args}}});
			{{#exception}}if (exception_result != 0) throw new LinphoneException("{{name}} returned value " + exception_result);{{/exception}}
			{{/is_enum}}
			{{#is_generic}}
			{{#exception}}int exception_result = {{/exception}}{{#hasReturn}}{{{type}}} returnVal = {{/hasReturn}}{{c_name}}({{nativePtr}}{{{c_args}}});
			{{#exception}}if (exception_result != 0) throw new LinphoneException("{{name}} returned value" + exception_result);{{/exception}}
			{{/is_generic}}
			{{#is_string_list}}
			IEnumerable<string> returnVal = MarshalStringArray({{c_name}}({{nativePtr}}{{{c_args}}}));
			{{/is_string_list}}
			{{#is_class_list}}
			IEnumerable<{{{list_type}}}> returnVal = MarshalBctbxList<{{{list_type}}}>({{c_name}}({{nativePtr}}{{{c_args}}}));
			{{/is_class_list}}
			{{#clean_string_list_ptrs}}CleanStringArrayPtrs();{{/clean_string_list_ptrs}}
			{{#hasReturn}}return returnVal;{{/hasReturn}}
		}
		{{/impl}}
		{{/has_impl}}
		{{/dllImports}}
	}
	{{/_class}}
	{{/classes}}
#endregion
}
