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

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

}