#if UNITY_EDITOR
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

public static class WindowHelper
{
	private class R_EditorWindow
	{
		private EditorWindow m_instance;
		private System.Type m_type;

		public R_EditorWindow( EditorWindow instance )
		{
			m_instance = instance;
			m_type = instance.GetType();
		}

		public object Parent
		{
			get
			{
				var field = m_type.GetField( "m_Parent", BindingFlags.Instance | BindingFlags.NonPublic );
				return field.GetValue( m_instance );
			}
		}

		public object Docked
		{
			get
			{
				var property = m_type.GetProperty( "docked", BindingFlags.Instance | BindingFlags.NonPublic );
				return property.GetValue( m_instance, null );
			}
		}
	}

	private class R_DockArea
	{
		private object m_instance;
		private System.Type m_type;

		public R_DockArea( object instance )
		{
			m_instance = instance;
			m_type = instance.GetType();
		}

		public object Window
		{
			get
			{
				var property = m_type.GetProperty( "window", BindingFlags.Instance | BindingFlags.Public );
				return property.GetValue( m_instance, null );
			}
		}

		public object ActualView
		{
			get
			{
				var field = m_type.GetField( "m_ActualView", BindingFlags.Instance | BindingFlags.NonPublic );
				return field.GetValue( m_instance );
			}
		}

		public object OriginalDragSource
		{
			set
			{
				var field = m_type.GetField( "s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic );
				field.SetValue( null, value );
			}
		}


		public void AddTab( EditorWindow pane )
		{
#if UNITY_2018_3_OR_NEWER
			var method = m_type.GetMethod( "AddTab", BindingFlags.Instance | BindingFlags.Public, null, new System.Type[] { typeof( EditorWindow ), typeof( bool ) }, null );
			if( method != null )
				method.Invoke( m_instance, new object[] { pane, true } );
#else
			var method = m_type.GetMethod( "AddTab", BindingFlags.Instance | BindingFlags.Public, null, new System.Type[] { typeof( EditorWindow ) }, null );
			if( method != null )
				method.Invoke( m_instance, new object[] { pane } );
#endif
		}

		public void RemoveTab( EditorWindow pane )
		{
			if( !pane.maximized )
			{
				var method = m_type.GetMethod( "RemoveTab", BindingFlags.Instance | BindingFlags.Public, null, new System.Type[] { typeof( EditorWindow ) }, null );
				if( method != null )
					method.Invoke( m_instance, new object[] { pane } );
			}
		}
	}

	private class R_ContainerWindow
	{
		private object m_instance;
		private System.Type m_type;

		public R_ContainerWindow( object instance )
		{
			m_instance = instance;
			m_type = instance.GetType();
		}

		public object RootSplitView
		{
			get
			{
				var property = m_type.GetProperty( "rootSplitView", BindingFlags.Instance | BindingFlags.Public );
				return property.GetValue( m_instance, null );
			}
		}

		public object RootView
		{
			get
			{
				var property = m_type.GetProperty( "rootView", BindingFlags.Instance | BindingFlags.Public );
				return property.GetValue( m_instance, null );
			}
		}

		public object WindowPtr
		{
			get
			{
				var all = m_type.GetNestedTypes();
				foreach( var item in all )
				{
					Debug.Log( item.Name );
				}
				var property = m_type.GetField( "m_WindowPtr", BindingFlags.Instance | BindingFlags.NonPublic );
				return property.GetValue( m_instance );
			}
		}
	}

	private class R_SplitView
	{
		private object m_instance;
		private System.Type m_type;

		public R_SplitView( object instance )
		{
			m_instance = instance;
			m_type = instance.GetType();
		}

		public object DragOver( EditorWindow child, Vector2 screenPoint )
		{
			var method = m_type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
			return method.Invoke( m_instance, new object[] { child, screenPoint } );
		}

		public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint )
		{
			var method = m_type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
			method.Invoke( m_instance, new object[] { child, dropInfo, screenPoint } );
		}
	}

	public enum DockPosition
	{
		Left,
		Top,
		Right,
		Bottom
	}

	public static bool IsDocked( this EditorWindow wnd )
	{
#if UNITY_2020_2_OR_NEWER
		return wnd.docked;
#else
		var parent = new R_EditorWindow( wnd );
		return (bool)parent.Docked;
#endif
	}

	public static void Undock( this EditorWindow wnd )
	{
		var parent = new R_EditorWindow( wnd );
		var dockArea = new R_DockArea( parent.Parent );
		dockArea.RemoveTab( wnd );
		wnd.Show( true );
	}

	public static void RemoveTab( this EditorWindow wnd )
	{
		var parent = new R_EditorWindow( wnd );
		var dockArea = new R_DockArea( parent.Parent );
		dockArea.RemoveTab( wnd );
	}

	/// <summary>
	/// Docks the second window to the first window at the given position
	/// </summary>
	public static void Dock( this EditorWindow wnd, EditorWindow other, DockPosition position )
	{
		var mousePosition = GetFakeMousePosition( wnd, position );

		var parent = new R_EditorWindow( wnd );
		var child = new R_EditorWindow( other );
		var dockArea = new R_DockArea( parent.Parent );
		var containerWindow = new R_ContainerWindow( dockArea.Window );
		var splitView = new R_SplitView( containerWindow.RootSplitView );
		var dropInfo = splitView.DragOver( other, mousePosition );
		dockArea.OriginalDragSource = child.Parent;
		splitView.PerformDrop( other, dropInfo, mousePosition );
	}


	/// <summary>
	/// Adds the the second window as a tab at the end of the first window tab list
	/// </summary>
	/// <param name="existingWindow"></param>
	/// <param name="newWindow"></param>
	public static void AddTab( this EditorWindow existingWindow, EditorWindow newWindow )
	{
		var parent = new R_EditorWindow( existingWindow );
		var child = new R_EditorWindow( newWindow );
		var dockArea = new R_DockArea( parent.Parent );
		dockArea.OriginalDragSource = child.Parent;
		dockArea.AddTab( newWindow );
	}

	private static Vector2 GetFakeMousePosition( EditorWindow wnd, DockPosition position )
	{
		Vector2 mousePosition = Vector2.zero;

		// The 20 is required to make the docking work.
		// Smaller values might not work when faking the mouse position.
		switch ( position )
		{
			case DockPosition.Left:
			mousePosition = new Vector2( 20, wnd.position.size.y / 2 );
			break;
			case DockPosition.Top:
			mousePosition = new Vector2( wnd.position.size.x / 2, 20 );
			break;
			case DockPosition.Right:
			mousePosition = new Vector2( wnd.position.size.x - 20, wnd.position.size.y / 2 );
			break;
			case DockPosition.Bottom:
			mousePosition = new Vector2( wnd.position.size.x / 2, wnd.position.size.y - 20 );
			break;
		}

		return GUIUtility.GUIToScreenPoint( mousePosition );
	}
}
#endif
