using Godot;

using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using System.Reflection;
using Microsoft.VisualBasic;

namespace Rokojori
{  
  public static class Nodes
  {

    public static NodePath GetNodePath( this Node node )
    {
      return node.GetPath();
    }

    public static T Get<T>( this Node node ) where T:Node
    {
      return GetAnyChild<T>( node );
    }

    

    public static T GetSelfOrChildOf<T>( this Node node ) where T:Node
    {
      if ( node is T t )
      {
        return t;
      }

      return GetAnyChild<T>( node );
    }

    public static T GetChild<T>( this Node node ) where T:Node
    {
      return GetDirectChild<T>( node );
    }

    public static void CopyData<T>( T from, T to ) where T:Node
    {
      var memberInfos = ReflectionHelper.GetDataMemberInfos<T>( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly );

      memberInfos = memberInfos.Filter(
        ( mi ) =>
        {
          if ( mi.IsMemberType<Callable>() )
          {
            return false;
          }

          return true;
        }
      );
      var memberNames = Lists.Map( memberInfos, m => m.Name );

      // RJLog.Log( "Trying to copy:", memberNames );

      ReflectionHelper.CopyDataMembersFromTo( from, to, memberNames );
      
    }

    public static T FindSibling<T>( this Node node, Func<T,bool> evaluater = null ) where T:Node
    {
      if ( node == null )
      {
        return null;
      }

      var sibling = NodesWalker.Get().FindSibling( node, n => n as T != null );
      
      return sibling as T;
    }

    public static T Find<T>( Node root, NodePathLocatorType type = NodePathLocatorType.DirectChildren, int parentOffset = 0 ) where T:Node
    {
      var it = root;

      while ( parentOffset > 0 && it != null )
      {
        it = it.GetParent();
        parentOffset --;
      }

      if ( it == null )
      {
        return default(T);
      }

      switch ( type )
      {
        case NodePathLocatorType.DirectChildren:
        {
          return GetDirectChild<T>( it );
        }

        case NodePathLocatorType.Siblings:
        {
          return GetSibling<T>( it );
        }

        case NodePathLocatorType.DirectChildrenAndSiblings:
        {
          var child = GetDirectChild<T>( it );
          
          if ( child != null )
          {
            return child;
          }

          return GetSibling<T>( it );
        }

        case NodePathLocatorType.AnyChildren:
        {
          return GetAnyChild<T>( it );
        }

        case NodePathLocatorType.SiblingsAndAnyChildren:
        {
          var sibling = GetSibling<T>( it );

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

          return GetAnyChild<T>( it );;
        }

        
      }

      return default(T);
    }

    public static void ForEachInRoot<T>( this Node node, Action<T> callback ) where T:class
    {
      if ( node == null )
      {
        return;
      }

      var tree = node.IsInsideTree() ? node.GetTree() : null;

      if ( tree == null )
      {
        return;
      }


      var root = tree.Root;
      ForEach<T>( root, callback );
    }

    public static void ForEachInScene<T>( Action<T> callback ) where T:class
    {
      var root = Root.Window();
      // RJLog.Log( "Iterating:", root );
      ForEach<T>( root, callback );
    }

    

    public static List<T> AllInScene<T>( Func<T,bool> filter = null) where T:class
    {
      var list = new List<T>();
      ForEachInScene<T>( 
        t => 
        {
          if ( filter == null || filter( t ) )
          { 
            list.Add( t );
          }
        } 
      );

      return list;
    }

    public static List<T> GetAnyChildren<T>( T[] nodes ) where T:Node
    {
      var list = new List<T>();

      for ( int i = 0; i < nodes.Length; i++ )
      {
        list.Add( nodes[ i ] );

        ForEach<T>( nodes[ i ], list.Add );
      }

      return list;
    }

    public static List<T> GetAll<T>( this Node root, Func<T,bool> filter = null, bool includeRoot = true) where T:class
    {
      var list = new List<T>();
      
      ForEach<T>( root,
        t => 
        {
          if ( ! includeRoot && t == root )
          {
            return;          
          }

          if ( filter == null || filter( t ) )
          { 
            list.Add( t );
          }
        } 
      );
      
      return list;
    }

    public static void ForEach<T>( this Node root, Action<T> callback ) where T:class
    {
      var walker = nodesWalker;

      walker.Iterate( root,
       ( n )=>
       {
        
         var t = n as T;

         if ( t == null )
         {
            return;
         }

         callback( t );

       }
       ,false );
    }

    public static async Task ForEachAsync<T>( this Node root, Func<T,Task> callback ) where T:class
    {
      var walker = nodesWalker;

      await walker.IterateAsync( root,
       async ( n )=>
       {
         var t = n as T;

         if ( t == null )
         {
            return;
         }

         await callback( t );

         return;

       }
       ,false );

      return;
    }

    public static T GetSibling<T>( Node node ) where T:Node
    {
      if ( node == null )
      {
        return null;
      }

      var parent = node.GetParent();

      if ( parent == null )
      {
        return null;
      }

      return GetDirectChild<T>( parent );
    } 

    public static T CreateChildIn<T>( Node parent, string name = null ) where T:Node,new()
    {
      var t = new T();
      parent.AddChild( t );
      
      t.Owner = parent.Owner == null ? parent : parent.Owner; 

      if ( name == null )
      {
        name = t.GetType().Name;
      }
      
      t.Name = name;
      

      return t;
    }

     public static Node CreateChildFromPath( this Node parent, NodePath nodePath, string name = null )
     { 
      var t = parent.GetNode( nodePath ).Duplicate();
      parent.AddChild( t );
      
      t.Owner = parent.Owner == null ? parent : parent.Owner; 

      if ( name == null )
      {
        name = t.GetType().Name;
      }
      
      t.Name = name;
      

      return t;
    }

    public static Node CreateChildFromDuplicate( this Node parent, Node nodeToDuplicate, string name = null )
     { 
      var t = nodeToDuplicate.Duplicate( (int)Node.DuplicateFlags.Scripts );
      parent.AddChild( t );
      
      t.Owner = parent.Owner == null ? parent : parent.Owner; 

      if ( name == null )
      {
        name = t.GetType().Name;
      }
      
      t.Name = name;
      

      return t;
    }

    public static Node CreateChildInWithType( Node parent, Type type, string name = null )
    {
      Node t = (Node) System.Activator.CreateInstance(type);

      parent.AddChild( t );

      t.Owner = parent.Owner;

      if ( name != null )
      {
        t.Name = name;
      }

      return t;
    }

    public static async Task RequestNextFrame( this Node node )
    {
      await node.ToSignal( RenderingServer.Singleton, RenderingServerInstance.SignalName.FramePostDraw );
      return;
    }

    public static double StartAsyncTimer( this Node node )
    {
      return Async.StartTimer();
    }

    public static async Task<double> WaitForAsyncTimer( this Node node, double time )
    {
      time = await Async.WaitIfExceeded( time, node );
      return time;
    }

    public static T CreateChild<T>( this Node parent, string name = null ) where T:Node,new()
    {
      return CreateChildIn<T>( parent, name );
    }

    
    public static T CreateChild<T>( this Node parent, PackedScene scene, string name = null ) where T:Node,new()
    {
      var instance = scene.Instantiate<T>();

      parent.AddChild( instance );
      instance.Owner = parent.Owner;

      if ( name != null )
      {
        instance.Name = name;
      }

      return instance;
    }

    public static T GetOrCreateChild<T>( this Node parent, string name = null ) where T:Node,new()
    {
      var child = Nodes.GetDirectChild<T>( parent );

      if ( child == null )
      {
        child = parent.CreateChild<T>( name );
      }

      return child;
    }

    public static T CreateChildLocal3D<T>( this Node parent, Vector3 position, Quaternion? rotation, string name = null ) where T:Node3D,new()
    {
      var c = CreateChildIn<T>( parent, name );

      c.Position = position;

      if ( rotation != null )
      {
        c.SetLocalQuaternion( (Quaternion)rotation );
      }

      return c;
    } 

    
    public static int CountAll<T>( this Node node, Predicate<T> predicate ) where T:Node
    {
      var number = 0;

      ForEach<T>( node, 
        ( n )=>
        {
          if ( predicate( n ) )
          {
            number ++;
          }
        }
      );

      return number;
    }



    public static T CreateChildGlobal3D<T>( this Node parent, Vector3 position, Quaternion? rotation, string name = null ) where T:Node3D,new()
    {
      var c = CreateChildIn<T>( parent, name );

      c.GlobalPosition = position;

      if ( rotation != null )
      {
        c.SetGlobalQuaternion( (Quaternion)rotation );
      }

      return c;
    }

    public static Node3D CreateChildFromPathGlobal3D( this Node parent, NodePath path, Vector3 position, Quaternion? rotation, string name = null )
    {
      var c = CreateChildFromPath( parent, path, name ) as Node3D;

      c.GlobalPosition = position;

      if ( rotation != null )
      {
        c.SetGlobalQuaternion( (Quaternion)rotation );
      }

      return c;
    }

    public static Node3D CreateChildFromDuplicateGlobal3D( this Node parent, Node nodeToDuplicate, Vector3 position, Quaternion? rotation, string name = null )
    {
      var c = CreateChildFromDuplicate( parent, nodeToDuplicate, name ) as Node3D;

      c.GlobalPosition = position;

      if ( rotation != null )
      {
        c.SetGlobalQuaternion( (Quaternion)rotation );
      }

      return c;
    }

    public static Node CreateChildWithType( this Node parent, Type type, string name = null )
    {
      return CreateChildInWithType( parent, type, name );
    }

    public static Node CopyNode( Node node, Node parent )
    {
      var copy = node.Duplicate();
      
      parent.AddChild( copy );
      copy.Owner = parent.Owner;
      
      return copy;
    }

    public static void LogInfo( this Node node, params object[] messages )
    {
      RJLog.Log( node, messages );
    }

    public static void LogError( this Node node, params object[] messages )
    {
      RJLog.Error( node, messages );
    }

    public static void LogInfo( this Resource resource, params object[] messages )
    {
      RJLog.Log( resource, messages );
    }

    public static void LogInfoIf( this Resource resource, bool condition, params object[] messages )
    {
      if ( ! condition )
      {
        return;
      }

      RJLog.Log( resource, messages );
    }

    public static void LogError( this Resource resource, params object[] messages )
    {
      RJLog.Error( resource, messages );
    }


    public static Node DeepCopyTo( this Node node, Node parent )
    {
      return CopyNodeHierarchy( node, parent );
    }

    public static Node CopyNodeHierarchy( Node from, Node parent )
    {
      var copy = CopyNode( from, parent );

      for ( int i = 0; i < from.GetChildCount(); i++ )
      {
        var child = from.GetChild( i );
        CopyNodeHierarchy( child, copy );
      }

      return copy;
    }

    public static T EnsureValid<T>( T node ) where T:Node
    {
      if ( GodotObject.IsInstanceValid( node ) )
      {
        if ( node.IsQueuedForDeletion() )
        {
          return null;
        }

        return node;
      }

      return null;
    }

    public static void RemoveAndDelete( Node node )
    {
      if ( ! GodotObject.IsInstanceValid( node ) )
      {
        return;
      }

      if ( node.IsQueuedForDeletion() )
      {
        return;
      }

      var parent = node.GetParent();
      
      if ( parent != null )
      {
        parent.RemoveChild( node );
      }

      

      node.QueueFree();
    }

    public static void GetOwnershipOfChildren( this Node node )
    {
      node.ForEach<Node>(
        ( child )=>
        {
          if ( node == child )
          {
            return;
          }

          child.Owner = node;
        }
      );
    }

    public static Error SaveAs( this Node node, string path, bool forceUpdate = false, ResourceSaver.SaverFlags saverFlags = ResourceSaver.SaverFlags.None )
    {
      var duplicatedNode = node.Duplicate();
      duplicatedNode.GetOwnershipOfChildren();

      var packedScene = new PackedScene();
      packedScene.Pack( duplicatedNode );
      var error = ResourceSaver.Save( packedScene, path, saverFlags );

      if ( forceUpdate )
      {
        EditorInterface.Singleton.GetResourceFilesystem().Scan();
      }

      return error;
    }

    public static void RemoveAndDeleteAll<N>( List<N> nodes ) where N:Node
    {
      nodes.ForEach( n => RemoveAndDelete( n ) );
    } 

    public static void RemoveFromParent( this Node node )
    {
      var parent = node.GetParent();

      if ( parent != null )
      {
        parent.RemoveChild( node ); 
      }
    }

    public static void SetParent( this Node node, Node parent )
    {
      node.RemoveFromParent();

      parent.AddChild( node );
      node.Owner = parent.Owner;
    }
    
    public static void DeleteSelf( this Node node )
    {
      node.RemoveFromParent();
      node.QueueFree();
    }

    

    public static void RemoveAndDeleteChildrenOfType<T>( Node parent, bool includeInternal = false ) where T:Node
    {
      if ( parent == null )
      {
        return;
      }

      var numChildren = parent.GetChildCount( includeInternal );
      
      for ( int i = numChildren - 1; i >= 0; i-- )
      {
        var node = parent.GetChild( i, includeInternal );

        var t = node as T;

        if ( t == null )
        {
          continue;
        }

        parent.RemoveChild( node );
        node.QueueFree();
      }
    }


    public static void DestroyChildren( this Node parent, bool includeInternal = false, bool queue = true )
    {
      RemoveAndDeleteChildren( parent, includeInternal );
    }

    public static void RemoveAndDeleteChildren( Node parent, bool includeInternal = false, bool queue = true )
    {
      if ( parent == null )
      {
        return;
      }

      var numChildren = parent.GetChildCount( includeInternal );

      for ( int i = numChildren - 1; i >= 0; i-- )
      {
        var node = parent.GetChild( i, includeInternal );
        parent.RemoveChild( node );

        if ( ! queue )
        {
          node.Free();
        } 
        else
        {
          node.QueueFree();
        }
      }
    }

    public static void CallInProcess( this Node node, System.Action action )
    {
      var tm = TimeLineManager.Get();

      if ( ! tm.inProcess )
      {
        TimeLineManager.ScheduleCallback( 
          tm.realtimeTimeline, 
          ( cb ) =>
          { 
            try
            {
              action();
            }
            catch ( System.Exception e )
            {
              node.LogError( e );
            } 
            
            cb.done = true;
          }
        );
      }
      else
      {
        action();
      }
    }

    public static void SelfDestroy( this Node node, bool queue = true )
    {
      var parent = node.GetParent();
      
      if ( parent != null )
      {
        parent.RemoveChild( node );
      }

      // node.LogInfo( "Freeing queued:", queue, node);

      if ( queue )
      {
        node.QueueFree();
      }
      else
      { 
        node.Free();
      } 
      
    }

    


    public static T GetDirectChild<T>( this Node parent ) where T:Node
    {
      if ( parent == null || ! Node.IsInstanceValid( parent ) )
      {
        return null;
      }

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( node is T )
        {
          return (T) node;
        }
      }

      return null;
    }

    public static T FindDirectChild<T>( this Node parent, Predicate<T> evaluator ) where T:Node
    {
      if ( parent == null || ! Node.IsInstanceValid( parent ) )
      {
        return null;
      }

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );
        
        if ( node is T t && evaluator( t ) )
        {
          return t;
        }
        
      }

      return null;
    }

    public static List<T> GetDirectChildren<T>( this Node parent ) where T:Node
    {
      if ( parent == null )
      {
        return null;
      }

      var list = new List<T>();

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );
        // var script = node.GetScript();

        // RJLog.Log( "Node is", typeof(T), node.Name, node.GetType(), script.GetType(), ">>", ( node is T ) );

        
        if ( ! ( node is T ) )
        {
          continue;
        }

        list.Add( node as T );
        
      }

      return list;
    }

    public static void AddChildAt( this Node parent, Node child, int index, bool setOwnership = true )
    {
      parent.AddChild( child );
      parent.MoveChild( child, index );

      if ( ! setOwnership )
      {
        return;
      }

      child.Owner = parent.Owner;
    }

    public static void AddSiblingBefore( this Node referenceSibling, Node siblingToInsert, bool includeInternal = false )
    {
      var parent = referenceSibling.GetParent();
      var index = referenceSibling.GetIndex( includeInternal );
      AddChildAt( parent, siblingToInsert, index );
    }

    public static void AddSiblingAfter( this Node referenceSibling, Node siblingToInsert, bool includeInternal = false )
    {
      var parent = referenceSibling.GetParent();
      var index = referenceSibling.GetIndex( includeInternal );

      AddChildAt( parent, siblingToInsert, index );
    }

    public static void Wrap( this Node reference, Node other )
    {
      other.AddSiblingBefore( reference );
      other.RemoveFromParent();
      reference.AddChild( other );
      other.Owner = reference.Owner;
    }

    public static int NumDirectChildrenOf<T>( this Node parent ) where T:Node
    {
      if ( parent == null )
      {
        return 0;
      }

      var numChildren = parent.GetChildCount();
      var typeIndex = 0;
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( node is T )
        {
          typeIndex ++;
        }

      }

      return typeIndex;  
    }

    public static T GetLastChild<T>( this Node parent ) where T:Node
    {
      var numChildren = parent.GetChildCount();

      for ( int i = numChildren - 1; i >= 0 ; i-- )
      {
        var child = parent.GetChild( i );

        if ( child is T t)
        {
          return t;
        }
      }

      return null;
    }

    public static T GetNthDirectChild<T>( this Node parent, int index ) where T:Node
    {
      if ( parent == null )
      {
        return null;
      }

      var numChildren = parent.GetChildCount();
      var typeIndex = 0;
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( node is T t )
        {
          if ( typeIndex == index )
          {
            return t;
          }

          typeIndex ++;
        }

      }

      return null;
    }


    public static void ForEachDirectChild<T>( this Node parent, System.Action<T> action ) where T:Node
    {
      if ( parent == null || action == null )
      {
        return;
      }

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( ! ( node is T ) )
        {
          continue;
        }

        action( node as T );
      }
    } 

     public static void ForEachDirectChild( this Node parent, Predicate<Node> selector, Action<Node> action )
    {
      if ( parent == null || action == null )
      {
        return;
      }

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( ! selector( node ) )
        {
          continue;
        }

        action( node );
      }
    } 

    public static List<U> MapDirectChildren<T,U>( Node parent, System.Func<T,U> mapper ) where T:Node
    {
      var list = new List<U>();

      ForEachDirectChild<T>( parent, c => list.Add( mapper( c ) ) );

      return list;
    }

    public static T FindInParents<T>( Node child )
    {
      return (T) (object) NodesWalker.Get().GetInParents( child, c => c is T );
    }

    public static T FindParentThatIs<T>( this Node child )
    {
      return FindInParents<T>( child );
    }

    public static int TypeIndex<T>( Node parent, T child ) where T:Node
    {
      var counter  = 0;

      var numChildren = parent.GetChildCount();
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var node = parent.GetChild( i );

        if ( node is T )
        {
          if ( node == child )
          {
            return counter;
          }

          counter++;
        }

      }

      return -1;
    }

    static NodesWalker nodesWalker = new NodesWalker();

    public static T GetAnyChild<T>( Node parent ) where T:Node
    {
      var result = nodesWalker.Find( parent, 
      ( n )=> 
      {
        

        var castedNode = n as T;

        // RJLog.Log( "Testing", n.UniqueNameInOwner, castedNode != null, n.GetType() ); 
         
        return castedNode != null; 
      
      }, 
      
      
      true );

      return (T) result;
    } 

    /*

    public static void Enable( Node n, bool affectProcess = true, bool affectPhysicsProcess = true, bool affectInput = true )
    {
      SetState.SetStateOfNode( NodeStateType.Enabled, n, affectProcess, affectPhysicsProcess, affectInput );
    }

    public static void Disable( Node n, bool affectProcess = true, bool affectPhysicsProcess = true, bool affectInput = true )
    {
      SetState.SetStateOfNode( NodeStateType.Disabled, n, affectProcess, affectPhysicsProcess, affectInput );
    }

    */

    public static void Iterate( Node[] nodes, System.Action<Node> callback )
    {
      for ( int i = 0; i < nodes.Length; i++ )
      {
        nodesWalker.Iterate( nodes[ i ], callback );
      }
    }

   
  }

}
