/*
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 (_WIN32 || 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 (_WIN32 || WINDOWS_UWP)
		public const string BELLE_SIP_LIB_NAME = "bellesip";
		public const string BCTOOLBOX_LIB_NAME = "bctoolbox";
		public const string MEDIASTREAMER_LIB_NAME = "mediastreamer";
#else
		public const string BELLE_SIP_LIB_NAME = LIB_NAME;
		public const string BCTOOLBOX_LIB_NAME = LIB_NAME;
		public const string MEDIASTREAMER_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 (_WIN32 || 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 bool isListener;

		internal List<IntPtr> string_ptr_list;

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

		[DllImport(LinphoneWrapper.BELLE_SIP_LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
#if (_WIN32 || WINDOWS_UWP)
		internal static extern int belle_sip_object_data_set(IntPtr ptr, string name, IntPtr data, IntPtr cb);
#else
		internal 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)]
		internal static extern IntPtr belle_sip_object_data_get(IntPtr ptr, string name);

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

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

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

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

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

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

		internal OnLinphoneObjectDataDestroyed onLinphoneListenerObjectDestroyed;

#if __IOS__
		[MonoPInvokeCallback((typeof(OnLinphoneObjectDataDestroyed)))]
		internal static void on_linphone_cbs_data_destroyed(IntPtr data) {
#else
		internal void on_linphone_cbs_data_destroyed(IntPtr data) {
#endif
			if (data != IntPtr.Zero)
			{
				GCHandle handle = GCHandle.FromIntPtr(data);
				if (handle.IsAllocated) {
					handle.Free();
				}
			}
		}

		~LinphoneObject()
		{
			if (nativePtr != IntPtr.Zero && !isListener) {
				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, bool isListener=false) 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)
				{
					objPtr = IntPtr.Zero;
				}
				else
				{
					return obj;
				}
			}
			if (objPtr == IntPtr.Zero)
			{
				T obj = new T();
				if (takeRef)
				{
					ptr = belle_sip_object_ref(ptr);
				}
				obj.nativePtr = ptr;
				obj.isListener = isListener;

				if (isListener) {
					obj.handle = GCHandle.Alloc(obj, GCHandleType.Normal);
				} else {
					obj.handle = GCHandle.Alloc(obj, GCHandleType.WeakTrackResurrection);
				}
				objPtr = GCHandle.ToIntPtr(obj.handle);

				if (isListener) {
#if (_WIN32 || WINDOWS_UWP)
					obj.onLinphoneListenerObjectDestroyed = obj.on_linphone_cbs_data_destroyed;
					IntPtr cb = Marshal.GetFunctionPointerForDelegate(obj.onLinphoneListenerObjectDestroyed);
					belle_sip_object_data_set(ptr, "cs_obj", objPtr, cb);
#elif __IOS__
					belle_sip_object_data_set(ptr, "cs_obj", objPtr, on_linphone_cbs_data_destroyed);
#else
					belle_sip_object_data_set(ptr, "cs_obj", objPtr, obj.on_linphone_cbs_data_destroyed);
#endif
				} else {
#if (_WIN32 || 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, bool takeRef=true) 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, takeRef);
					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;
		}

		/// Safely (no throw) convert a C++ char* to a .NET string (which are UTF-16).
		/// If the raw string contains illegal UTF-8 multi-byte sequences,
		/// replace them silently.
		internal static string PtrToStringSafe(IntPtr str)
		{
			try  // the happy path first
			{
				return Marshal.PtrToStringAnsi(str);
			}
			catch (Exception)  // fallback to safe decoding
			{
				// Assume null-terminated and load into buffer
				var buffer = new List<byte>();
				byte current_byte;
				int offset = 0;
				do {
					current_byte = Marshal.ReadByte(str, offset);
					buffer.Add(current_byte);
					offset++;
				} while (current_byte != 0);
				// Decode as UTF-8
				return System.Text.Encoding.UTF8.GetString(buffer.ToArray());
			}
			// Notes:
			// We could get rid of the try{} and always use safe decoding, however:
			//  1. Most strings are correctly encoded
			//  2. Safe decoding is expensive: The data is copied twice (from C++
			//     to buffer, then from buffer to string). Since this is a hot path
			//     (used in log message callbacks), we need performance, and we can
			//     assume builtin marshalling will be faster (untested).
			//  3. The exception, "Illegal byte sequence encounted in the input." (sic),
			//     seems to happen only with the Mono runtime (Xamarin).
			//     .NET Core either does no check, or replaces invalid characters
			//     as part of marshalling, but does not throw an exception.
		}
	}

	public class MediastreamerFactory
	{
		public IntPtr nativePtr;

		[DllImport(LinphoneWrapper.MEDIASTREAMER_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.MEDIASTREAMER_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.MEDIASTREAMER_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}}
	/// <summary>
	{{#briefDoc}}
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/briefDoc}}
	{{#detailedDoc}}
	/// <para>
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/detailedDoc}}
	{{#isFlag}}[Flags]{{/isFlag}}
	public enum {{enumName}}
	{
		{{#values}}
		/// <summary>
		{{#briefDoc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/briefDoc}}
		{{#detailedDoc}}
		/// <para>
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/detailedDoc}}
		{{name}} = {{{value}}},
		{{/values}}
	}

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

#region Listeners
	{{#interfaces}}
	{{#interface}}
	/// <summary>
	{{#briefDoc}}
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/briefDoc}}
	{{#detailedDoc}}
	/// <para>
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/detailedDoc}}
	[StructLayout(LayoutKind.Sequential)]
	public class {{interfaceName}} : LinphoneObject
	{
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_{{interfaceCName}}_get_current_callbacks(IntPtr thiz);

		{{#methods}}
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		{{#cb_setter}}
#if (_WIN32 || 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}});

		/// <summary>
		{{#briefDoc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/briefDoc}}
		{{#detailedDoc}}
		/// <para>
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/detailedDoc}}
		{{#deprecated}}[Obsolete]
		{{/deprecated}}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}});

			IntPtr ptr = linphone_{{interfaceCName}}_get_current_callbacks(thiz.nativePtr);
			{{interfaceName}} current_listener = fromNativePtr<{{interfaceName}}>(ptr, false, true);

			if (current_listener != null)
			{
				current_listener.{{var_public}}?.Invoke({{{params}}});
			}
		}

		public {{name_public}} {{name}}
		{
			get
			{
				return {{var_public}};
			}
			set
			{
				{{var_public}} = value;
#if (_WIN32 || 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}}
	}

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

#region Classes
	{{#classes}}
	{{#_class}}
	/// <summary>
	{{#briefDoc}}
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/briefDoc}}
	{{#detailedDoc}}
	/// <para>
	{{#lines}}
	/// {{{line}}}
	{{/lines}}
	{{/detailedDoc}}
	[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);
			}
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct MSOglContextInfo
		{
			public System.IntPtr window;
			public System.UInt32 width;
			public System.UInt32 height;
			public System.IntPtr getProcAddress;
		};

		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_core_get_native_video_window_id(IntPtr thiz);
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void linphone_core_set_native_video_window_id(IntPtr thiz, IntPtr windowId);

		/// <summary>
		/// Get the native window handle of the video window. On Windows UWP/WindowsStore, this is a SwapChainPanel. eg: LinphoneManager.Instance.Core.NativeVideoWindowId = panel; The rendering is automatically done by Linphone.
		/// </summary>
#if WINDOWS_UWP
		public Windows.UI.Xaml.Controls.SwapChainPanel NativeVideoWindowId
		{
			get
			{
				return (Windows.UI.Xaml.Controls.SwapChainPanel)Marshal.GetObjectForIUnknown(((MSOglContextInfo)Marshal.PtrToStructure<MSOglContextInfo>(linphone_core_get_native_video_window_id(nativePtr)) ).window);
			}
			set
			{
				MSOglContextInfo c;
				if (value != null)
					c.window = Marshal.GetIUnknownForObject(value);
				else
					c.window = IntPtr.Zero;
				c.getProcAddress = IntPtr.Zero;
				c.width = 0;
				c.height = 0;
				IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(c));
				Marshal.StructureToPtr(c, pnt, false);
				IntPtr oldData = linphone_core_get_native_video_window_id(nativePtr);
				linphone_core_set_native_video_window_id(nativePtr, pnt);
				if (oldData != IntPtr.Zero){
					IntPtr window = ((MSOglContextInfo)Marshal.PtrToStructure<MSOglContextInfo>(oldData) ).window;
					if(window != IntPtr.Zero)
						Marshal.Release( window );
					Marshal.FreeHGlobal(oldData);
				}
			}
		}
#else
		public IntPtr NativeVideoWindowId
		{
			get
			{
				return linphone_core_get_native_video_window_id(nativePtr);
			}
			set
			{
				linphone_core_set_native_video_window_id(nativePtr, value);
			}
		}
#endif
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr linphone_core_get_native_preview_window_id(IntPtr thiz);
		[DllImport(LinphoneWrapper.LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
		static extern void linphone_core_set_native_preview_window_id(IntPtr thiz, IntPtr windowId);

		/// <summary>
		/// Get the native window handle of the video preview window. On Windows UWP/WindowsStore, this is a SwapChainPanel. eg: LinphoneManager.Instance.Core.NativePreviewWindowId = panel; The rendering is automatically done by Linphone.
		/// </summary>
		///
#if WINDOWS_UWP
		public Windows.UI.Xaml.Controls.SwapChainPanel NativePreviewWindowId
		{
			get
			{
				return(Windows.UI.Xaml.Controls.SwapChainPanel) Marshal.GetObjectForIUnknown(((MSOglContextInfo)Marshal.PtrToStructure<MSOglContextInfo>(linphone_core_get_native_preview_window_id(nativePtr)) ).window);
			}
			set
			{
				MSOglContextInfo c;
				if (value != null)
					c.window = Marshal.GetIUnknownForObject(value);
				else
					c.window = IntPtr.Zero;
				c.getProcAddress = IntPtr.Zero;
				c.width = 0;
				c.height = 0;
				IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(c));
				Marshal.StructureToPtr(c, pnt, false);
				IntPtr oldData = linphone_core_get_native_preview_window_id(nativePtr);
				linphone_core_set_native_preview_window_id(nativePtr, pnt);
				if (oldData != IntPtr.Zero){
					IntPtr window = ((MSOglContextInfo)Marshal.PtrToStructure<MSOglContextInfo>(oldData) ).window;
					if(window != IntPtr.Zero)
						Marshal.Release( window );
					Marshal.FreeHGlobal(oldData);
				}
			}
		}
#else
		public IntPtr NativePreviewWindowId
		{
			get
			{
				return linphone_core_get_native_preview_window_id(nativePtr);
			}
			set
			{
				linphone_core_set_native_preview_window_id(nativePtr, value);
			}
		}
#endif

		{{/isLinphoneCore}}

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

		[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);

		private {{interfaceName}} listener;

		public {{interfaceName}} Listener
		{
			get {
				if (listener == null)
				{
					IntPtr nativeListener = linphone_factory_create_{{listener_constructor}}_cbs(linphone_factory_get());
					listener = fromNativePtr<{{interfaceName}}>(nativeListener, false, true);
					linphone_{{listener_constructor}}_add_callbacks(nativePtr, nativeListener);
					belle_sip_object_unref(nativeListener);
				}
				return listener;
			}
			set {
				if (listener != null && value == null) {
					linphone_{{listener_constructor}}_remove_callbacks(nativePtr, listener.nativePtr);
				}
				listener = value;
			}
		}
		{{/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}}
		/// <summary>
		{{#briefDoc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/briefDoc}}
		{{#detailedDoc}}
		/// <para>
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/detailedDoc}}
		{{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}}), {{takeRef}});
				{{/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}}
		/// <summary>
		{{#briefDoc}}
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/briefDoc}}
		{{#detailedDoc}}
		/// <para>
		{{#lines}}
		/// {{{line}}}
		{{/lines}}
		{{/detailedDoc}}
		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}}}), {{takeRef}});
			{{/is_class_list}}
			{{#clean_string_list_ptrs}}CleanStringArrayPtrs();{{/clean_string_list_ptrs}}
			{{#hasReturn}}return returnVal;{{/hasReturn}}
		}
		{{/impl}}
		{{/has_impl}}
		{{/dllImports}}
	}
	{{/_class}}
	{{/classes}}
#endregion
}
