using System.Collections;
using System.Collections.Generic;
using System.Text;
using System;
using Godot;
using System.Threading.Tasks;
using System.Linq;

namespace Rokojori
{
  
  public class ListView<T>
  {
    List<T> _list;
    int _offset;
    int _length;

    
    public ListView( List<T> list, int offset, int length )
    {
      _list = list;
      _offset = offset;
      _length = length;
    }

    public T this[ int index ]
    {
      get => _list[ index - _offset ];
      set => _list[ index - _offset ] = value;
    }

    public int Count => _length;

    public void ForEach( Action<T> action )
    {
      for ( int i = 0; i < _length; i++ )
      {
        action( this[ i ] );
      }
    }

    public List<T> GetList()
    {
      return _list;
    }

    public List<T> SubList()
    {
      return _list.Sub( _offset, _length );
    }
  }

  public static class Lists
  {

    public static void Union<T>( this List<T> list, List<T> other )
    {
      var set = new HashSet<T>( list );
      set.UnionWith( other );
      list.Clear();
      list.AddRange( [.. set ] );
    }

    public static void AddIfNotPresent<T>( this List<T> values, T value )
    {
      if ( values.Contains( value ) )
      {
        return;
      }

      values.Add( value );    
    }

    public static bool IsOneOf<T>( T value, params T[] values )
    {
      for ( int i = 0; i < values.Length; i++ )
      {
        if ( EqualityComparer<T>.Default.Equals( values[ i ], value ) )
        {
          return true;
        }
      }

      return false;
    }

    public static bool ContainsEqual<T>( List<T> values, T value )
    {
      if ( value == null || values == null )
      {
        return false;
      }
      
      return values.Find( v => value.Equals( v ) ) != null;
    }

    public static void Sort<T>( List<T> data, Func<T,float> getValue )
    {
      ValueSorter<T>.SortList( data, getValue );
    }

    public class IndexLerpResult
    {
      public int closestIndex = -1;
      public int secondIndex = -1;
      public float lerpAmount = 0.5f;
    }

    
    
    public static List<T> Clone<T>( this List<T> data )
    {
      if ( data == null )
      {
        return null;
      }
      
      var cloned = new List<T>( data.Count );
      cloned.AddRange( data );
      return cloned;
    }

    public static List<T> TopologicalSort<T>( this List<T> items, Func<T, T, int> compare )
    {
      var result = new List<T>();
      var incomingEdges = new Dictionary<T, int>();
      var edges = new Dictionary<T, List<T>>();

      foreach ( var item in items )
      {
        incomingEdges[ item ] = 0;
        edges[ item ] = new List<T>();
      }

      var count = items.Count;

      for ( int i = 0; i < count; i++ )
      {
        for ( int j = i + 1; j < count; j++ )
        {
          var a = items[ i];
          var b = items[ j ];
          var cmp = compare( a, b );

          if ( cmp < 0 )
          {
            edges[ a ].Add( b );
            incomingEdges[ b ]++;
          }
          else if ( cmp > 0 )
          {
            edges[b].Add( a );
            incomingEdges[ a ]++;
          }
        }
      }

      var queue = new Queue<T>( incomingEdges.Where( pair => pair.Value == 0 ).Select( pair => pair.Key )  );

      while ( queue.Any() )
      {
        var current = queue.Dequeue();
        result.Add( current );

        foreach ( var next in edges[ current ] )
        {
          incomingEdges[ next ]--;

          if ( incomingEdges[ next ] == 0 )
          {
            queue.Enqueue( next );
          }
        }
      }

      if ( result.Count != items.Count )
      {
        return null;
      }

      return result;
    }

    public static T Lerp<T>( List<T> data, float value, Func<T,float> getValue, Func<IndexLerpResult,T> lerper, bool sort = false  )
    {
      var result = LerpIndex( data, value, getValue, sort );
      return lerper( result );
    } 

    public static IndexLerpResult LerpIndex<T>( List<T> data, float value, Func<T,float> getValue, bool sort = false )
    {
      if ( sort )
      {
        Sort( data, getValue );
      }

      var result = new IndexLerpResult();
      result.closestIndex = ClosestIndex<T>( data, value, getValue );
      result.secondIndex  = SecondClosestIndex<T>( data, result.closestIndex, value, getValue );

      if ( result.closestIndex  == result.secondIndex )
      {
        return result;
      }

      var closestValue = getValue( data[ result.closestIndex ] );
      var secondValue  = getValue( data[ result.secondIndex ] );

      
      var min = closestValue;
      var max = secondValue;
      var flip = false;

      if ( closestValue > secondValue )
      {
        flip = true;
        min = secondValue;
        max = closestValue;
      }

      result.lerpAmount = MathX.Normalize( value, min, max );

      if ( flip )
      {
        result.lerpAmount = 1f - result.lerpAmount;
      }

      return result;      

    } 

    public static T GetWithHighestValue<T>( List<T> data, Func<T,float> getValue )
    {
      var index = IndexOfHighestValue( data, getValue );

      return index == -1 ? default(T) : data[ index ] ;
    }

    public static T GetWithLowestValue<T>( List<T> data, Func<T,float> getValue )
    {
      var index = IndexOfLowestValue( data, getValue );

      return index == -1 ? default(T) : data[ index ] ;
    }

    public static int IndexOfHighestValue<T>( List<T> data, Func<T,float> getValue )
    {
      var index = -1;
      var compareValue = -float.MaxValue;

      for ( int i = 0; i < data.Count; i++ )
      {
        var value = getValue( data[ i ] );

        if ( value > compareValue )
        {
          index = i;
          compareValue = value;
        }
      }

      return index;
    }

    public static int IndexOfLowestValue<T>( List<T> data, Func<T,float> getValue )
    {
      var index = -1;
      var compareValue = float.MaxValue;

      for ( int i = 0; i < data.Count; i++ )
      {
        var value = getValue( data[ i ] );

        if ( value < compareValue )
        {
          index = i;
          compareValue = value;
        }
      }

      return index;
    }

    public static int SecondClosestIndex<T>( this List<T> data, int closest, float compareValue, Func<T,float> getValue, bool sort = false )
    {
      if ( sort )
      {
        Sort( data, getValue );
      }

      if ( data.Count == 1 )
      {
        return 0;
      }

      if ( closest == 0 )
      {
        return closest + 1;
      }

      if ( closest == data.Count - 1 )
      {
        return data.Count - 2;
      }

      var before = data[ closest - 1 ];
      var after  = data[ closest + 1 ];

      var bD = Mathf.Abs( getValue( before ) - compareValue );
      var aD = Mathf.Abs( getValue( after ) - compareValue );

      if ( bD < aD )
      {
        return closest - 1 ;
      }

      return closest + 1;
    }

    public static int SecondClosestIndex<T>( this T[] data, int closest, float compareValue, Func<T,float> getValue )
    {

      if ( data.Length == 1 )
      {
        return 0;
      }

      if ( closest == 0 )
      {
        return closest + 1;
      }

      if ( closest == data.Length - 1 )
      {
        return data.Length - 2;
      }

      var before = data[ closest - 1 ];
      var after  = data[ closest + 1 ];

      var bD = Mathf.Abs( getValue( before ) - compareValue );
      var aD = Mathf.Abs( getValue( after ) - compareValue );

      if ( bD < aD )
      {
        return closest - 1 ;
      }

      return closest + 1;
    }

    public static int ClosestIndex<T>( this List<T> data, float compareValue, Func<T,float> getValue )
    {
      var index = -1;
      var distance = float.MaxValue;

      for ( int i = 0 ; i < data.Count; i++ )
      {
        var d = Mathf.Abs( getValue( data[ i ] ) - compareValue );

        if ( d < distance )
        {
          index = i;
          distance = d;
        }
      }

      return index;
    }

    public static int ClosestIndex<T>( this T[] data, float compareValue, Func<T,float> getValue )
    {
      var index = -1;
      var distance = float.MaxValue;

      for ( int i = 0 ; i < data.Length; i++ )
      {
        var d = Mathf.Abs( getValue( data[ i ] ) - compareValue );

        if ( d < distance )
        {
          index = i;
          distance = d;
        }
      }

      return index;
    }

    public static List<int> CollectIndices<T>( List<T> list, Func<T,bool> evaluator )
    {
      var output = new List<int>();
      
      for ( int i = 0; i < list.Count; i++ )
      {
        if ( evaluator( list[ i ] ) )
        {
          output.Add( i );
        }
      }

      return output;
    }

    public static List<T> CombineAll<T>( params List<T>[] lists )
    {
      var list = new List<T>();

      foreach ( var l in lists )
      {
        list.AddRange( l );
      }

      return list;
    }

    public static List<T> From<T>( params T[] elements )
    {
      return ToList( elements );
    } 

    public static List<T> FromLists<T>( params List<T>[] elements )
    {
      var list = new List<T>();

      elements.ForEach( e => list.AddRange( e ) );

      return list;
    } 

    public static int MultiFindIndex<T>( this List<T> values, params Predicate<T>[] finder )
    {
      for ( int i = 0; i < finder.Length; i++ )
      {
        var index = values.FindIndex( 0, finder[ i ] );

        if ( index != -1 )
        {
          return index;
        }
      }
      

      return -1;
    }  

    public static List<T> SubList<T>( T[] elements, int offset, int length )
    {
      var list = new List<T>();

      for ( int i = 0; i < length; i++ )
      {
        list.Add( elements[ i + offset ] );
      }

      return list;
    }

    public static int Size<T>( List<T> list )
    {
      return list == null ? 0 : list.Count;
    }

    public static List<T> Sub<T>( this List<T> elements, int start, int length = 0 )
    {
      var end = length == 0 ? elements.Count : ( start + length );


      var list = new List<T>( end - start );

      for ( int i = start; i < end; i++ )
      {
        list.Add( elements[ i ] );
      }

      return list;
    }

    public static void ShuffleMultiple<T>( this List<T> list, int numShuffles, int step = 2 )
    {
      for ( int i = 0; i < numShuffles; i++ )
      {
        list.Shuffle( step );
      }
    }

    public static ListView<T> View<T>( this List<T> list, int start = 0, int length = 0 )
    {
      return new ListView<T>( list, start, length == 0 ? ( list.Count - start ) : length);
    }

   

    public static void Shuffle<T>( this List<T> list, int step = 2 )
    {

      var listStart = new List<T>();
      var listEnd = new List<T>();

      for ( int i = 0; i < list.Count; i++ )
      {
        if ( i % step == 0 )
        {
          listStart.Add( list[ i ] );
        }
        else
        {
          listEnd.Add( list[ i ] );
        }
        
      } 

      list.Clear();

      list.Add( listStart ).Add( listEnd );
    }

    public static List<T> Add<T>( this List<T> list, List<T> other )
    {
      list.AddRange( other );
      return list;
    }


    public static List<T> ToList<T>( T[] array )
    {
      var list = new List<T>();
      list.AddRange( array );
      return list;
    }

    public static bool Has<T>( List<T> list, T item )
    {
      return list.IndexOf( item ) != - 1;
    }

    public static T RemoveAt<T>( List<T> list, int index )
    {
      if ( list.Count == 0 || index < 0 ||  ( index > ( list.Count - 1 ) ) )
      { 
        return default(T); 
      }

      var first = list[ index ];
      list.RemoveAt( index );
      return first;
    }

    public static T RemoveFirst<T>( List<T> list )
    {
      return RemoveAt( list, 0 );
    }

    public static T RemoveLast<T>( List<T> list )
    {
      return RemoveAt( list, list.Count - 1 );
    }

    public static T PopLast<T>( List<T> list )
    {
      if ( list.Count == 0 ){ return default(T); }

      var element = list[ list.Count - 1 ];
      list.RemoveAt( list.Count - 1 );
      return element;
    }

    public static T Pop<T>( this List<T> list )
    {
      return PopLast( list );
    }

     public static T Shift<T>( this List<T> list )
    {
      if ( list.Count == 0 ){ return default(T); }

      var element = list[ 0 ];
      list.RemoveAt( 0 );
      return element;

    }

    public static List<T> Create<T>( int size, Func<int,T> initializer )
    {
      var list = new List<T>();

      for ( int i = 0; i < size; i++ )
      {
        list.Add( initializer( i ) );  
      }

      return list;
    }


    public static T GetLast<T>( List<T> list )
    {
      return list.Count == 0 ? default(T) : list[ list.Count - 1 ];
    }

    public static T Last<T>( this List<T> list )
    {
      return GetLast( list );
    }

    public static T ReverseAt<T>( this List<T> list, int index )
    {
      return list[ list.Count - 1 - index ];
    }

    public static void RemoveIncreasingSortedIndices<T>( this List<T> list, List<int> increasinglySortedRemovals )
    {
      for ( var i = increasinglySortedRemovals.Count - 1; i >= 0; i-- )
      {
        var index = increasinglySortedRemovals[ i ];

        if ( index < 0 || index >= list.Count )
        { 
          // RJLog.Log( "Invalid index in removals:", index, list.Count, increasinglySortedRemovals );
          continue;
        }

        // RJLog.Log( "requestedRemovals remove:", index );
        list.RemoveAt( index );
      }
    }

    public static void RemoveRange<T>( List<T> list, List<T> removals )
    {
      var removalSet = new HashSet<T>();
      removalSet.UnionWith( removals );

      list.RemoveAll( e => removalSet.Contains( e ) );
    }

    public static int CountItems<T>( List<T> list, Predicate<T> test )
    {
      var result = 0;
      
      for ( int i = 0; i < list.Count; i++ )
      {
        if ( test( list[ i ] ) )
        {
          result++;
        }
      }

      return result;
    }

    public static int CountItems<T>( T[] list, Predicate<T> test )
    {
      var result = 0;
      
      for ( int i = 0; i < list.Length; i++ )
      {
        if ( test( list[ i ] ) )
        {
          result++;
        }
      }

      return result;
    }

    public static List<T> FromEnumerable<T>( IEnumerable enumerable )
    {
      var listA = new List<T>();

      foreach ( var it in enumerable )
      {
        listA.Add( (T) it );
      }

      return listA;
    }

    public static bool AreListsAndEntriesEqual( object objA, object objB )
    {
      if ( ! ( ReflectionHelper.IsList( objA ) && ReflectionHelper.IsList( objB ) ) )
      {
        return false;
      }

      return AreEntriesEqual( FromEnumerable<object>( objA as IEnumerable ), FromEnumerable<object>( objB as IEnumerable ) );
    }

    public static bool AreEntriesEqual<T>( List<T> a, List<T> b )
    {
      if ( a.Count != b.Count )
      {
        return false;
      }

      for ( int i = 0; i < a.Count; i++ )
      {
        var isEqual = EqualityComparer<T>.Default.Equals( a[ i ], b[ i ]);

        if ( ! isEqual )
        {
          return false;
        }
      }

      return true;
    }

    
    /*public static string Join<T>( this List<T> list, string seperator = "," )
    {
      var sb = new StringBuilder();
    }*/


    
    public static string Join<T>( this List<T> array, string seperator = ", " )
    {
      var sb = new StringBuilder();

      for ( var i = 0; i < array.Count; i++ )
      {
        if ( i != 0 )
        {
          sb.Append( seperator );
        }

        var obj =  (object) array[ i ];

        RJLog.Stringify( array[ i ], sb ); 
      }

      return sb.ToString();
    }

    public static string Join( List<float> array, string seperator = ", " )
    {
      var sb = new StringBuilder();

      for ( var i = 0; i < array.Count; i++ )
      {
        if ( i != 0 )
        {
          sb.Append( seperator );
        }

        sb.Append( RegexUtility.NumberToString( array[ i ] ) );        
      }

      return sb.ToString();
    }
    

    public static List<T> From<T>( List<T> list )
    {
      var copy = new List<T>();
      copy.AddRange( list );
      return copy;
    }

    public static List<T> From<[Godot.MustBeVariant] T>( Godot.Collections.Array<T> list )
    {
      var copy = new List<T>();
      copy.AddRange( list );
      return copy;
    }

    public static List<T> Combine<T,A,B>( List<A> a, List<B> b, Func<A,B,T> combiner )
    {
      var list = new List<T>();

      for ( int i = 0; i < a.Count; i++ )
      {
        for ( int j = 0; j < b.Count; j++ )
        {
          list.Add( combiner( a[ i ], b[ j ] ) );
        }
      }

      return list;
    }

    public static bool Has<T>( this List<T> list, Func<T,bool> evaluater )
    {
      foreach ( var t in list )
      {
        if ( evaluater( t ) )
        {
          return true;
        }
      }

      return false;
    }

    public static void SwapTowardsBegin<T>( this List<T> list, int index, int steps = 1 )
    {
      list.Swap( index, index - steps );
    }

    public static void SwapTowardsEnd<T>( this List<T> list, int index, int steps = 1 )
    {
      list.Swap( index, index + steps );
    }

    public static void Swap<T>( this List<T> list, int indexA, int indexB )
    {
      if ( list == null || indexA == indexB || indexA < 0 || indexB < 0 || indexA >= list.Count || indexA >= list.Count )
      {
        return;
      }

      var buffer = list[ indexA ];
      list[ indexA ] = list[ indexB ];
      list[ indexB ] = buffer;
      
    }

    public static List<U> FilterAndMap<T,U>( List<T> list, Func<T,int,bool> filter, Func<T,U> map)
    {
      var mapped = new List<U>();
      
      for ( int i = 0; i < list.Count; i++ )
      {
        if ( ! filter( list[ i ], i ) )
        {
          continue;
        }

        mapped.Add( map( list[ i ] ) ); 
      }

      return mapped;
    } 

    public static async Task<List<T>> FilterAsync<T>( this List<T> list, Func<T,Task<bool>> filter )
    {
      var outputList = new List<T>();

      var index = 0;

      foreach ( var it in list )
      {
        var result = await filter( it ); 

        if ( result )
        {
          outputList.Add( it );
        }

        index++;
      }

      return outputList;
    }

    public static void Filter<T>( List<T> inputList, List<T> list, Func<T,int,bool> filter )
    {      
      for ( int i = 0; i < inputList.Count; i++ )
      { 
        if ( filter( inputList[ i ], i ) )
        {
          list.Add( inputList[ i ] );
        }
      }
    }

    public static List<T> Filter<T>( this List<T> list, Func<T,int,bool> filter )
    {
      var filteredList = new List<T>();
      Filter( list, filteredList, filter );
      return filteredList;
    }

    public static List<T> Filter<T>( this List<T> list, Func<T,bool> filter )
    {
      var filteredList = new List<T>();
      Filter( list, filteredList, filter );
      return filteredList;
    }

    public static List<T> FilterNulls<T>( this List<T> list )
    {
      return Filter( list, e => e != null );
    }

    public static void Filter<T>( List<T> inputList, List<T> list, Func<T,bool> filter )
    {      
      for ( int i = 0; i < inputList.Count; i++ )
      { 
        if ( filter( inputList[ i ] ) )
        {
          list.Add( inputList[ i ] );
        }
      }
    }
    
    public static List<T> FilterWith<T>( List<T> inputList, Func<T,bool> filter )
    {      
      var list = new List<T>();

      for ( int i = 0; i < inputList.Count; i++ )
      { 
        if ( filter( inputList[ i ] ) )
        {
          list.Add( inputList[ i ] );
        }
      }

      return list;
    } 

   
    public static List<R> FilterType<T,R>( this List<T> inputList ) where R:T
    {
      var list = new List<R>();

      inputList.ForEach
      ( 
        e => 
        {
          if ( e == null || ! typeof( R ).IsAssignableFrom( e.GetType() ) )
          {
            return;
          }

          list.Add( (R) e );
        }
      );

      return list;
    } 

    public static List<T> FilterType<T>( this List<T> inputList, Type type )
    {
      var list = new List<T>();

      inputList.ForEach
      ( 
        e => 
        {
          if ( e == null || ! type.IsAssignableFrom( e.GetType() ) )
          {
            return;
          }

          list.Add( e );
        }
      );

      return list;
    }

    public static void Add<T>( this List<T> list, params T[] entries )
    {
      list.AddRange( entries );
    }

    public static List<T> Concat<T>( this List<T> list, params T[] entries )
    {
      list.AddRange( entries );
      return list;
    }

    public static List<T> Concat<T>( this List<T> list, List<T> entries )
    {
      list.AddRange( entries );
      return list;
    }

    public static void Insert<T>( List<T> list, T item, int index )
    {
      if ( index >= list.Count )
      {
        list.Add( item );
        return;
      }

      index = index < 0 ? 0 : index;

      list.Insert( index, item );
    }

    public static T Find<T>( List<T> list, Func<T,bool> callback )
    {
      for ( int i = 0; i < list.Count; i++ )
      {
        if ( callback( list[ i ] ) )
        {
          return list[ i ];
        }
      }

      return default( T );
    }

    public static List<T> MultiMap<T>( this List<T> list, Func<T,int,List<T>> mapper )
    {
      var outputList = new List<T>();

      for ( int i = 0; i < list.Count; i++ )
      {
        var range = mapper( list[ i ], i );

        if ( range.Count == 0 )
        {
          continue;
        }

        outputList.AddRange( range );
      }

      return outputList;
    }

    public static List<U> Map<T,U>( this List<T> list, Func<T,int,U> mapper )
    {
      return Map( list, mapper, new List<U>() );
    }

    public static List<U> Map<T,U>( this List<T> list, Func<T,U> mapper )
    {
      return Map( list, mapper, new List<U>() );
    }

    public static Dictionary<T,int> CreateIndex<T>( this List<T> list )
    {
      var m = new Dictionary<T,int>();

      for ( int i = 0; i < list.Count; i++ )
      {
        m[ list[ i ] ] = i;
      }

      return m;
    }

    public static HashSet<T> CreateSet<T>( this List<T> list )
    {
      var set = new HashSet<T>();
      list.ForEach( e => set.Add( e ) );
      return set;
    }

    public static List<U> Map<T,U>( List<T> inputList, Func<T,int,U> mapper, List<U> list = null )
    {
      if ( list == null )
      {
        list = new List<U>();
      }

      for ( int i = 0; i < inputList.Count; i++ )
      { 
        list.Add( mapper( inputList[ i ], i ) );        
      }

      return list;
    }

    public static List<U> Map<T,U>( T[] inputList, Func<T,int,U> mapper, List<U> list = null ) 
    {
      if ( list == null )
      {
        list = new List<U>();
      }

      for ( int i = 0; i < inputList.Length; i++ )
      { 
        list.Add( mapper( inputList[ i ], i ) );        
      }

      return list;
    }

    public static List<U> Map<T,U>( List<T> inputList, Func<T,U> mapper, List<U> list = null  )
    {
      if ( list == null )
      {
        list = new List<U>();
      }

      for ( int i = 0; i < inputList.Count; i++ )
      { 
        list.Add( mapper( inputList[ i ] ) );        
      }

      return list;
    }

    public static Godot.Collections.Array<T> ToGodotArray<[MustBeVariant] T>( this List<T> values )
    {
      return [.. values];
    }

    public static void Remove<T>( this List<T> list, HashSet<T> removals )
    { 
      list.RemoveAll( m => removals.Contains( m ) );
    }

    public static void Remove<T>( this List<T> list, List<T> removals  )
    {
      if ( list.Count * removals.Count > 100 )  
      {
        var removalSet = new HashSet<T>( removals );
        list.Remove( removalSet );
        return; 
      }

      list.RemoveAll( m => removals.Contains( m ) );
    }

    public static List<T> CloneWithout<T>( this List<T> list, List<T> removals )
    {
      var cloned = list.Clone();
      cloned.Remove( removals );
      return cloned;
    }

    public static List<U> Map<[Godot.MustBeVariant]T,U>( Godot.Collections.Array<T> inputList, Func<T,U> mapper, List<U> list  = null )
    {
      if ( list == null )
      {
        list = new List<U>();
      }

      for ( int i = 0; i < inputList.Count; i++ )
      { 
        list.Add( mapper( inputList[ i ] ) );        
      }

      return list;
    }

    public static List<U> Map<T,U>( T[] inputList, Func<T,U> mapper, List<U> list  = null )
    {
      if ( list == null )
      {
        list = new List<U>();
      }

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

      return list;
    }

    
  }
}