using Godot;

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

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

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

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

    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> AllIn<T>( 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>( 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 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;

      if ( name != null )
      {
        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 T CreateChild<T>( this Node parent, string name = null ) where T:Node,new()
    {
      return CreateChildIn<T>( parent, name );
    }

    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 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 RemoveAndDeleteAll<N>( List<N> nodes ) where N:Node
    {
      nodes.ForEach( n => RemoveAndDelete( n ) );
    } 

    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 )
    {
      RemoveAndDeleteChildren( parent, includeInternal );
    }

    public static void RemoveAndDeleteChildren( Node parent, bool includeInternal = false )
    {
      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 );
        node.QueueFree();
      }
    }

    public static T GetDirectChild<T>( Node parent ) where T:Node
    {
      if ( parent == null )
      {
        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 List<T> GetDirectChildren<T>( 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 OnAllDirectChildren<T>( this Node parent, System.Action<T> action ) where T:Node
    {
      ForEachDirectChild<T>( parent, action );
    }

    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 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>( 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 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 );
      }
    }

   
  }

}
