using System.Collections.Generic;
using System.Linq;

namespace Facepunch.InteropGen;

internal partial class ManagerWriter : BaseWriter
{
	public ManagerWriter( Definition definitions, string targetName ) : base( definitions, targetName )
	{

	}

	public override void Generate()
	{
		Header();

		HelperFunctions();

		Imports();
		PointerStructs();

		StartBlock( $"namespace {definitions.ManagedNamespace}" );
		{
			Exports();
			NativeInterop();
		}
		EndBlock();
	}

	private void Header()
	{
		WriteLine( "// <auto-generated>" );
		WriteLine( "// This file was generated by a tool." );
		WriteLine( "// Changes to this file may cause incorrect behavior and will be lost if" );
		WriteLine( "// the code is regenerated." );
		WriteLine( "// </auto-generated>" );

		WriteLine( "" );

		WriteLine( "using System;" );
		WriteLine( "using System.Collections.Generic;" );
		WriteLine( "using System.Runtime.CompilerServices;" );
		WriteLine( "using System.Runtime.InteropServices;" );

		WriteLine( "" );
	}

	private void HelperFunctions()
	{
		StartBlock( $"internal static class {definitions.CustomNamespace}" );
		{
			StartBlock( "public static int Convert( Type t )" );
			{
				foreach ( Class c in definitions.Classes.OrderByDescending( x => x.ClassDepth ) )
				{
					if ( c.Native )
					{
						continue;
					}

					if ( c.Static )
					{
						continue;
					}

					WriteLine( $"if ( typeof( global::{c.ManagedNameWithNamespace} ).IsAssignableFrom( t ) ) return {c.ClassIdent};" );
				}

				WriteLine();
				WriteLine( "throw new System.Exception( $\"Can't handle type {t}\" );" );

			}
			EndBlock();
		}
		EndBlock();
	}

	private void NativeInterop()
	{
		IEnumerable<Function> exports = definitions.Classes.Where( x => x.Native == true ).SelectMany( x => x.Functions );
		IEnumerable<Function> imports = definitions.Classes.Where( x => x.Native == false ).Where( x => !ShouldSkip( x ) ).SelectMany( x => x.Functions );

		StartBlock( "internal unsafe static partial class NativeInterop" );
		{
			WriteLine( "static IntPtr _nativeLibraryHandle;" );
			WriteLine( "static bool _initialized;" );
			WriteLine();

			ErrorFunction();

			WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
			WriteLine( "delegate void NetCoreImportDelegate( int hash, void* imports, void* exports, int* structSizes );" );
			WriteLine();

			StartBlock( "internal static void Initialize()" );
			{
				WriteLine( "if ( _initialized ) return;" );
				WriteLine();

				WriteLine( $"if ( !NativeLibrary.TryLoad( System.IO.Path.Combine( NetCore.NativeDllPath, \"{definitions.NativeDll}\" ), out var nativeDll ) )" );
				WriteLine( $"	Sandbox.Interop.NativeAssemblyLoadFailed( \"{definitions.NativeDll}\" );" );
				WriteLine( "_nativeLibraryHandle = nativeDll;" );

				WriteLine();
				WriteLine( $"IntPtr nativeInitPtr = NativeLibrary.GetExport( nativeDll, \"igen_{definitions.Ident}\" );" );
				WriteLine( $"if ( nativeInitPtr == IntPtr.Zero ) throw new System.Exception( \"Couldn't load from {definitions.NativeDll}\" );" );

				WriteLine();
				WriteLine( $"var nativeInit = Marshal.GetDelegateForFunctionPointer<NetCoreImportDelegate>( nativeInitPtr );" );
				WriteLine( $"if ( nativeInit == null ) throw new System.Exception( \"Couldn't load from {definitions.NativeDll}\" );" );

				int i = 0;

				//
				// Managed Functions
				//
				{
					WriteLine();
					WriteLine( $"var managedFunctions = new IntPtr[{imports.Count()}]" );
					StartBlock( null );
					foreach ( Function f in imports )
					{
						Class c = f.Class;
						IEnumerable<string> managedArgs = c.SelfArg( false, f.Static ).Concat( f.Parameters ).Concat( new[] { f.Return } ).Where( x => x.IsRealArgument ).Select( x => $"{x.GetManagedDelegateType( true )}" );
						string managedArgss = $"{string.Join( ", ", managedArgs )}";

						WriteLine( $"(IntPtr) (delegate* unmanaged<{managedArgss}>) &Exports.{f.MangledName}," );
					}
					EndBlock( ";" );
				}

				{
					int c = definitions.Classes.Where( x => x.Native == true ).Where( x => !ShouldSkip( x ) ).Sum( x =>
					{
						int i = 0;

						Class bc = x.BaseClass;

						while ( bc != null )
						{
							Class subclass = bc;
							i += 2;
							bc = bc.BaseClass;
						}

						i += x.Functions.Count;
						i += x.Variables.Count * 2;

						return i;

					} );

					c += 1;

					WriteLine();
					WriteLine( $"var nativeFunctions = new IntPtr[{c}];" );
				}

				{
					int c = definitions.Classes.Where( x => x.Native == true ).Where( x => !ShouldSkip( x ) ).Count();
					WriteLine();
					WriteLine( $"var structSizes = new int[]" );
					StartBlock( null );

					foreach ( Struct s in definitions.Structs )
					{
						if ( ShouldSkip( s ) )
						{
							continue;
						}

						string size_of = $"sizeof( {s.ManagedNameWithNamespace} )";

						WriteLine( $"{size_of}," );
						i++;
					}
					EndBlock( ";" );
				}

				WriteLine();
				WriteLine();
				WriteLine( "fixed ( void* m = managedFunctions )" );
				WriteLine( "fixed ( void* n = nativeFunctions )" );
				WriteLine( "fixed ( int* s = structSizes )" );
				StartBlock( null );
				WriteLine( $"nativeInit( {definitions.Hash}, m, n, s );" );
				EndBlock();
				WriteLine();

				i = 1;
				WriteLine( $"var onError = Marshal.GetDelegateForFunctionPointer<_ErrorFunction>( nativeFunctions[0] );" );
				WriteLine();

				StartBlock( "try" );

				i = 1;
				foreach ( Class c in definitions.Classes.Where( x => x.Native == true ) )
				{
					if ( ShouldSkip( c ) )
					{
						continue;
					}

					string namespc = $"{c.ManagedNamespace}.{c.ManagedName}".Trim( '.' );

					Class bc = c.BaseClass;

					while ( bc != null )
					{
						Class subclass = bc;

						WriteLine( $"{namespc}.{InternalNative}.From_{subclass.ManagedName}_To_{c.ManagedName} = (delegate* unmanaged[SuppressGCTransition]< IntPtr, IntPtr >) nativeFunctions[{i++}];" );
						WriteLine( $"{namespc}.{InternalNative}.To_{subclass.ManagedName}_From_{c.ManagedName} = (delegate* unmanaged[SuppressGCTransition]< IntPtr, IntPtr >) nativeFunctions[{i++}];" );

						bc = bc.BaseClass;
					}

					foreach ( Function f in c.Functions )
					{
						IEnumerable<string> managedArgs = c.SelfArg( false, f.Static ).Concat( f.Parameters ).Where( x => x.IsRealArgument ).Select( x => $"{x.GetManagedDelegateType( false )}" ).Concat( new[] { f.Return.GetManagedDelegateType( true ) } );
						string managedArgss = $"{string.Join( ", ", managedArgs )}";

						string nogc = "";

						if ( f.IsNoGC )
						{
							nogc = "[SuppressGCTransition]";
						}

						WriteLine( $"{namespc}.{InternalNative}.{f.MangledName} = (delegate* unmanaged{nogc}< {managedArgss} >) nativeFunctions[{i++}];" );
					}

					foreach ( Variable f in c.Variables )
					{
						WriteLine( $"{namespc}.{InternalNative}.Get__{f.MangledName} = (delegate* unmanaged[SuppressGCTransition]<IntPtr, {f.Return.GetManagedDelegateType( true )}>)( nativeFunctions[{i++}] );" );
						WriteLine( $"{namespc}.{InternalNative}.Set__{f.MangledName} = (delegate* unmanaged[SuppressGCTransition]<IntPtr, {f.Return.GetManagedDelegateType( true )}, void>)( nativeFunctions[{i++}] );" );
					}
				}

				EndBlock();
				StartBlock( "catch ( System.Exception ___e )" );
				{
					WriteLine( "onError( $\"{___e.Message}\\n\\n{___e.StackTrace}\" );" );
				}
				EndBlock();


				WriteLine( "_initialized = true;" );
			}
			EndBlock();

			StartBlock( "internal static void Free()" );
			{
				WriteLine( "if ( _nativeLibraryHandle == IntPtr.Zero ) return;" );
				WriteLine( "NativeLibrary.Free( _nativeLibraryHandle );" );
				WriteLine( "_nativeLibraryHandle = IntPtr.Zero;" );
				WriteLine( "_initialized = false;" );
			}
			EndBlock();
		}
		EndBlock();

		if ( definitions.InitFrom == "Managed" )
		{
			StartBlock( "internal unsafe static partial class Initialize" );
			{
				ManagedInit();
			}
			EndBlock();
		}

	}

	private void ErrorFunction()
	{
		WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
		WriteLine( "internal delegate void _ErrorFunction( string message );" );
		WriteLine();
	}

	private void ManagedInit()
	{
		WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
		WriteLine( "internal delegate void _InitFunc( IntPtr initFunc, IntPtr function_pointers, int count );" );
		WriteLine();
		WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
		WriteLine( "internal delegate void _ManagedInit( int hash, IntPtr fp, int* struct_sizes );" );
		WriteLine();
		StartBlock( "public static void FromLibrary( string dllPath )" );
		{
			WriteLine( $"var library = NativeLibrary.Load( dllPath );" );
			WriteLine( $"if ( library == null ) throw new System.Exception( $\"Couldn't load {{dllPath}}\" );" );
			WriteLine( $"" );
			WriteLine( $"var initFunc = NativeLibrary.GetExport( library, \"InitInterop_{definitions.Hash}\" );" );
			WriteLine( $"if ( initFunc == null ) throw new System.Exception( $\"Couldn't find export in {{dllPath}} - possibly out of date?\" );" );
			WriteLine( $"" );
			WriteLine( $"var fp = Marshal.GetFunctionPointerForDelegate<_ManagedInit>( NativeInterop.Initialize );" );
			WriteLine( $"" );

			int exportCount = definitions.Classes.Where( x => x.Native == false ).SelectMany( x => x.Functions ).Count();

			if ( exportCount > 0 )
			{
				StartBlock( $"IntPtr[] functions = new[]" );
				{
					foreach ( Class c in definitions.Classes.Where( x => x.Native == false ) )
					{
						if ( ShouldSkip( c ) )
						{
							continue;
						}

						foreach ( Function f in c.Functions )
						{
							WriteLine( $"FunctionPointerFor<Exports.{f.MangledName}_d>( Exports.{f.MangledName} )," );
						}
					}
				}
				EndBlock( ";" );

				WriteLine( $"" );
				WriteLine( $"var init = Marshal.GetDelegateForFunctionPointer<_InitFunc>( initFunc );" );
				WriteLine( $"" );
				StartBlock( "fixed ( IntPtr* ptr = functions )" );
				{
					WriteLine( "init( fp, (IntPtr)ptr, functions.Length );" );
				}
				EndBlock();
			}
			else
			{
				WriteLine( $"var init = Marshal.GetDelegateForFunctionPointer<_InitFunc>( initFunc );" );
				WriteLine( $"" );
				WriteLine( "init( fp, IntPtr.Zero, 0 );" );
			}


		}
		EndBlock();

		WriteLine();
		WriteLine( $"// Store for delegates, to stop them getting garbage collected" );
		WriteLine( $"static List<Delegate> delegateStore = new List<Delegate>();" );
		WriteLine( $"" );
		WriteLine( $"// Store the delegate and return a function pointer" );
		StartBlock( $"static IntPtr FunctionPointerFor<T>( T del ) where T : Delegate" );

		WriteLine( $"	delegateStore.Add( del );" );
		WriteLine( $"	return Marshal.GetFunctionPointerForDelegate<T>( del );" );
		EndBlock();
	}
}
