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

namespace Rokojori
{
  public abstract class TreeWalker<N> where N:class
  {
    public abstract N Parent( N node );  
    
    public abstract N ChildAt( N node, int index );  
    
    public abstract int NumChildren( N node );  
    
    public bool HasChildren( N node )
    {
  		return NumChildren( node ) > 0;
  	}
  	
    public bool HasParent( N node )
    {
  		return Parent( node ) != null;
  	}
  	
    
    public int ChildIndexOf( N node )
    {
      var p = Parent( node );

      if ( p == null )
      {
        return -1;
      }
      
      var numKids = NumChildren( p );
      
      for ( var i = 0; i<numKids; i++ )
      {
        if ( ChildAt( p, i ) == node )
        {
          return i;
        }
      }
      
      return -1;
      
    }
  	
    public N SiblingAt( N node, int index )
  	{
  		var p = Parent( node );
      
  		if ( p == null || index < 0 || index >= NumChildren( p ) )
  		{ return null; }

  		return ChildAt( p, index );
  	}
    
    
    public N NextSibling( N node )
    {
      var index = ChildIndexOf( node );      
      return SiblingAt( node, index+1 );
    }
    
    
    public N PreviousSibling( N node )
    {
      var index = ChildIndexOf( node );      
      return SiblingAt( node, index-1 );
    }
    
    
    public bool HasSiblingAt( N node, int index )
  	{
  		var p = Parent( node );
  		if ( p == null || index < 0 || index >= NumChildren( p ) )
  		{ 
        return false; 
      }
  		return true;
  	}

    public int NumSiblings( N node )
    {
      if ( node == null )
      {
        return 0;
      }

      var p = Parent( node );

      if ( p == null )
      {
        return 0;
      }

      return NumChildren( p ) - 1;
    }

    public void IterateSiblings( N node, Action<N> action )
    {
      if ( node == null )
      {
        return;
      }

      var p = Parent( node );

      if ( p == null )
      {
        return;
      }

      var numChildren = NumChildren( p );
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var child = ChildAt( p, i );

        if ( child == node )
        {
          continue;
        }

        action( child );
      } 



    }

    public N FindSibling( N node, Func<N,bool> evaluater )
    {
      if ( node == null )
      {
        return null;
      }

      var p = Parent( node );

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

      var numChildren = NumChildren( p );
      
      for ( int i = 0; i < numChildren; i++ )
      {
        var child = ChildAt( p, i );

        if ( child == node )
        {
          continue;
        }

        var result = evaluater( child );

        if ( result )
        {
          return child;
        }
      } 

      return null;
    }
    
    public N FirstChild( N node )
    {
  		return NumChildren( node )<=0?null:ChildAt( node, 0 );
  	}
  	
  	
    public N LastChild( N node )
    {
  		var num = NumChildren( node );
  		return num <= 0 ? null : ChildAt( node, num - 1 );
  	}

    public void ForEachChild( N node, bool directChildrenOnly, Action<N> action )
    {
      if ( directChildrenOnly )
      {
        var numChildren = NumChildren( node );
        
        for ( int i = 0; i < numChildren; i++ )
        {
          var child = ChildAt( node, i );
          action( child );
        }
      }
      else
      {
        Iterate( node, action, true );
      }
    }

    public N FindDirectChild( N node, Predicate<N> selector )
    {
      return FindChild( node, true, selector );
    }

    public N FindAnyChild( N node, Predicate<N> selector )
    {
      return FindChild( node, false, selector );
    }

    public N FindChild( N node, bool directChildrenOnly, Predicate<N> selector )
    {
      if ( directChildrenOnly )
      { 
        var numChildren = NumChildren( node );
      
        for ( int i = 0; i < numChildren; i++ )
        {
          var child = ChildAt( node, i );

          if ( selector( child ) )
          {
            return child;
          }
        }
      }
      else
      {
        return Find( node, selector, true );
      }


      return null;
    }

    public N GetDirectChildWithLowestValue( N node, Func<N, float> getValue )
    {
      return GetChildWithLowestValue( node, true, getValue );
    }

    public N GetAnyChildWithLowestValue( N node, Func<N, float> getValue )
    {
      return GetChildWithLowestValue( node, false, getValue );
    }

    public N GetChildWithLowestValue( N node, bool directChildrenOnly, Func<N, float> getValue )
    {
      var lowestValue = float.MaxValue;
      N childWithLowestValue = null;

      ForEachChild( node, directChildrenOnly,
        ( child )=>
        {
          var value = getValue( child );

          if ( value < lowestValue )
          {
            lowestValue = value;
            childWithLowestValue = child;
          }
        }
      );

      return childWithLowestValue;
    }

    public N GetDirectChildWithHighestValue( N node, Func<N, float> getValue )
    {
      return GetChildWithHighestValue( node, true, getValue );
    }

    public N GetAnyChildWithHighestValue( N node, Func<N, float> getValue )
    {
      return GetChildWithHighestValue( node, false, getValue );
    }

    public N GetChildWithHighestValue( N node, bool directChildrenOnly, Func<N, float> getValue )
    {
      var highestValue = -float.MaxValue;
      N childWithHighestValue = null;

      ForEachChild( node, directChildrenOnly,
        ( child )=>
        {
          var value = getValue( child );

          if ( value > highestValue )
          {
            highestValue = value;
            childWithHighestValue = child;
          }
        }
      );

      return childWithHighestValue;
    }

  	
    public N NextNode( N node )
    {
  		if ( HasChildren( node ) )
  		{
  			return FirstChild( node );
  		}
  		
  		var next = NextSibling( node );
  		
  		if ( next != null )
  		{
  			return next;
  		}

  		var parent = Parent( node );
  		
  		while ( parent != null )
  		{
  			var n = NextSibling( parent );

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

  			parent = Parent( parent );
  		}
  		return null;
  	}
  	
  	
    public N PreviousNode( N node )
    {
  		var prev = PreviousSibling( node );
  		
  		if ( prev != null )
  		{
  			while ( HasChildren( prev ) )
  			{
  				prev = LastChild( prev );
  			}
  			return prev;
  		}

  		return Parent( node );
  	}
    
    
    public N RootParent( N node )
    {
  		node = Parent( node );

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

  		while ( HasParent( node ) )
  		{
  			node = Parent( node );
  		}

  		return node;
  	}
    
    
    public N LastGrandChild( N node )
    {
  		if ( HasChildren( node ) )
  		{
  			node = LastChild( node );

  			while ( HasChildren( node ) )
  			{
  				node = LastChild( node );
  			}
        
  			return node;
  		}
      
  		return null;
  	}
    
    public N NextAfterChildren( N node )
    {
      var lastGrandChild = LastGrandChild( node );
      
      if ( lastGrandChild != null )
      {
        return NextNode( lastGrandChild );
      }

      return NextNode( node );
    }
    
    public bool IsChildOf( N child, N parent )
  	{
  		var p = Parent( child );

  		while ( p != null )
  		{
  			if ( p == parent )
  			{
  				return true;
  			}
  			p = Parent( p );
  		}

  		return false;
  	}
    
    
    public int NumParents( N node )
    {
  		var num = 0;
  		var p = Parent( node );

  		while ( p != null )
  		{
  			num++;
  			p = Parent( p );
  		}

  		return num;
  	}
    
    
    public N LastOuterNode( N node )
    {  	
      var its = 0;
      var max = 10000;

			while ( HasChildren( node ) )
			{
        its++;

        if ( its > max )
        {
          throw new Exception();
        }
        
				node = LastChild( node );
			}

  		return node;
  	}
  	
  	
    public N NextNonChild( N node )
    {
  		return NextNode( LastOuterNode( node ) );
  	}    
    
    public N IterationEndOf( N node )
    {
      return NextNonChild( node );
    }

    public N GetInParents( N node, Func<N,bool> predicate )
    {
      var p = Parent( node );

      while ( p != null )
      {
        if ( predicate( p ) )
        {  
          return p;
        }

        p = Parent( p );
      }

      return null;
    }

    public int GetAncestorDistance( N child, N ancestor )
    {
      if ( ancestor == null )
      {
        return -1;
      }

      if ( child == ancestor )
      {
        return 0;
      }

      
      var p = Parent( child );
      var distance = 1;

      while ( p != null )
      {
        if ( p == ancestor )
        {  
          return distance;
        }

        p = Parent( p );
        distance ++;
      }

      return -1;
    }

    public int GetDepth( N node, Dictionary<N, int> depthMap = null )
    {
      if ( depthMap == null )
      {        
        var depth = 0;
        var it = Parent( node );
        var maxDepth = 1000;

        while ( it != null )
        {
          depth ++;

          if ( depth == maxDepth )
          {
            RJLog.Log( "reached max depth", it );
            return depth;
          }

          it = Parent( it );
        }

        return depth;
      }

      if ( depthMap.ContainsKey( node ) )
      {
        return depthMap[ node ];
      }

      var parent = Parent( node );

      if ( parent == null )
      {
        depthMap[ node ] = 0;
        return 0;
      }   

      if ( depthMap.ContainsKey( parent ) )
      {
        var parentDepth = depthMap[ parent ];
        var nodeDepth = parentDepth + 1;

        depthMap[ node ] = nodeDepth;

        return nodeDepth;
      }


      depthMap[ node ] = GetDepth( node );
      
      return depthMap[ node ];
    }

    public void DepthIterate( N node, Action<N, int> callback, bool childrenOnly = false, Dictionary<N, int> depthMap = null )
    {
      if ( depthMap == null )
      {
        depthMap = new Dictionary<N, int>();
      }

      var iterationCallback = ( N node )=>
      { 
        var depth = GetDepth( node, depthMap );
        callback( node, depth );
      };

      Iterate( node, iterationCallback, childrenOnly );
    }

    public void Iterate( N node, Action<N> callback, bool childrenOnly = false )
    {
      var end = IterationEndOf( node );
      var it = node;

      if ( childrenOnly )
      {
        it = NextNode( it ); 
      }

      while ( it != end )
      {
        callback( it );
        it = NextNode( it );
      } 
    }

    public async Task IterateAsync( N node, Func<N,Task> callback, bool childrenOnly = false )
    {
      var end = IterationEndOf( node );
      var it = node;

      if ( childrenOnly )
      {
        it = NextNode( it ); 
      }

      while ( it != end )
      {
        await callback( it );
        it = NextNode( it );
      } 

      return;
    }

    public N Find( N node, Predicate<N> predicate, bool childrenOnly )
    {
      var end = IterationEndOf( node );
      var it = node;

      if ( childrenOnly )
      {
        it = NextNode( it ); 
      }

      while ( it != end )
      {
        if ( predicate( it ) )
        {
          return it;
        }

        it = NextNode( it );
      } 

      return null;
    }

    public void Filter( List<N> list, N node, Predicate<N> predicate, bool childrenOnly )
    {
      Action<N> addToList = ( N n ) => 
      {
        if ( predicate( n ) )
        {
          list.Add( n );
        }
      };

      Iterate( node, addToList, childrenOnly );
    }

    public void FilterAndMap<U>( List<U> list, N node, Predicate<N> predicate, Func<N,U> mapper, bool childrenOnly )
    {
      Action<N> addToList = ( N n ) => 
      {
        if ( predicate( n ) )
        {
          list.Add( mapper( n ) );
        }
      };

      Iterate( node, addToList, childrenOnly );
    }

    public void Map<U>( List<U> list, N node, Predicate<N> predicate, Func<N,U> mapper, bool childrenOnly )
    {
      Action<N> addToList = ( N n ) => 
      {
        list.Add( mapper( n ) );
      };

      Iterate( node, addToList, childrenOnly );
    }


    public void PruneChildTraversal( N root, Func<N,bool> callback )
    {
      var it = NextNode( root );

      while ( it != null && IsChildOf( it, root ) )
      {
        var continueWithChildren = callback( it );

        if ( continueWithChildren )
        {
          it = NextNode( it );
        }
        else
        {
          it = NextAfterChildren( it );
        }
      }
    }
    
  }

}