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



namespace Rokojori
{
  public abstract class RandomEngine
  {
    public static RandomEngine CreateIfNull( RandomEngine r )
    {
      if ( r == null )
      {
        return new GodotRandom();
      }

      return r;
    }
    
    public abstract float Next();
        
    public float Value( float scalar )
    {
      return Next() * scalar;
    }

    public float Range( float a, float b)
    {
      return this.Next()*( b - a ) + a;
    }

    public float Sample( Curve curve, float min = 0, float max = 0 )
    {
      if ( curve == null )  
      {
        return Range( min, max );
      }

      return curve.Sample( Next() );
    }

    public int SampleInteger( Curve curve )
    {
      return Mathf.RoundToInt( curve.Sample( Next() ) );
    }

    public float Polar()
    {
      return this.Next() * 2f - 1f;
    }
    
    public float Polar( float value )
    {
      return Polar() * value;
    }

    public bool Bool()
    {
      return this.Next() > 0.5f;
    }


    public bool Chance( float value )
    {
      return value / 100f >= Next();
    }

    public bool FlipCoin()
    {
      return Chance( 50f );
    }

    public float PercentageVariation( float variation )
    {
      var value = ( 100f - variation ) / 100f;
      return Range( value, 1f / value );  
    }

    public bool WithChanceOf( float value )
    {
      return ( this.Next() * 100 ) <= value ;
    }

    public Vector3 Between( Vector3 a, Vector3 b )
    {
      return a.Lerp( b, this.Next() );
    }

    public Vector3 IndividualBetween( Vector3 a, Vector3 b )
    {
      var x = Mathf.Lerp( a.X, b.X, this.Next() );
      var y = Mathf.Lerp( a.Y, b.Y, this.Next() );
      var z = Mathf.Lerp( a.Z, b.Z, this.Next() );

      return new Vector3( x, y, z );
    }

    public Color RandomHue( float saturation = 1f, float luminance = 0.5f)
    {
      var hue = Range( 0, 360 );
      var color = new HSLColor( hue, saturation, luminance );

      return color;
    }
    
    public Vector2 InRectangle( Vector2 min, Vector2 max )
    {
      var x = Mathf.Lerp( min.X, max.X, Next() );
      var y = Mathf.Lerp( min.Y, max.Y, Next() );

      return new Vector2( x, y );
    }

    public float AngleRadians()
    {
      return Range( 0, Mathf.Pi * 2f );
    }

    public Vector3 InCube()
    {
      return new Vector3( Polar(), Polar(), Polar() );
    }

    public Vector3 InsideCube( float size )
    {
      return InCube() * ( size * 0.5f );
    }

    public Vector3 InsideSphere()
    {
      var inCube = InCube();

      if ( inCube.LengthSquared() > 1 )
      {
        inCube = inCube.Normalized() * Next();
      }

      return inCube;
    }

    public Vector2 OnCircle( float size = 1 )
    {
      var angle = Mathf.Pi * 2.0f * Next();

      return new Vector2( Mathf.Cos( angle ), Mathf.Sin( angle ) ) * size ; 
    }

    public Vector2 InsideCircle( float maxSize = 1, float minSize = 0f )
    {
      var size = Next() * ( maxSize - minSize ) + minSize;
      var angle = Mathf.Pi * 2.0f * Next();

      return new Vector2( Mathf.Cos( angle ), Mathf.Sin( angle ) ) * size ; 
    }


    public Vector3 OnSphere( float size )
    {
      return OnSphere() * size;
    }

    public Vector3 OnSphere()
    {
      var dir = InsideSphere();

      while ( dir.LengthSquared() == 0 )
      {
        dir = InsideSphere();
      }

      return dir;
    }


    public Vector3 InSphere( float size )
    {
      return InsideSphere() * ( size * 0.5f );
    }

    public Color HSL( float hMin = 0 , float hMax = 360, float sMin = 1, float sMax = 1, float lMin = 0.5f, float lMax = 0.5f )
    {
      var h = Range( hMin, hMax );
      var s = Range( sMin, sMax );
      var l = Range( lMin, lMax );


      return new HSLColor( h, s, l );
    }

    public int IntegerInclusive( int min, int max ) 
    {
      return (int) ( Mathf.Floor( this.Next() * ( max - min + 1 ) ) + min ) ;
    }

    public int IntegerInclusive( int max )
    {
      return IntegerInclusive( 0, max );
    }

    public int IntegerExclusive( int min, int max ) 
    {
      var nextValue = this.Next();
      var randomValue = nextValue * ( max - min ) + min;
      
      var value = (int) ( Mathf.Floor( randomValue ) );
      return Mathf.Min( max - 1, value );
    }

    public int IntegerExclusive( int max )
    {
      return IntegerExclusive( 0, max );
    }

    
    public char From( string source )
    {
      var index = IntegerExclusive( source.Length );
      return source[ index ];
    }

    public T From<T>( T[] array )
    {
      if ( array.Length == 0 )
      {
        return default ( T );
      }
      var index = IntegerExclusive( array.Length );
      return array[ index ];
    }

    public T From<T>( HashSet<T> set )
    {
      var selectedIndex = IntegerExclusive( set.Count );
      
      var currentIndex = 0;

      foreach ( var e in set )
      {
        if ( selectedIndex == currentIndex )
        {
          return e;
        }

        currentIndex++;
      }

      return default( T );
    }

    public T FromValues<T>( T first, params T[] values )
    {
      if ( values.Length == 0 )
      {
        return first;
      }

      var index = IntegerExclusive( values.Length + 1 );

      return index == 0 ? first : values[ index - 1 ];
    }

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

      var index = this.IntegerExclusive( 0, list.Count );
      
      return list[ index ];
    }

     public T From<T>( List<T> list, int start, int end )
    {
      if ( list.Count == 0 )
      {
        return default ( T );
      }

      var index = this.IntegerInclusive( start, end );
      
      return list[ index ];
    }

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

      if ( list.Count == 1 )
      {
        var element = list[ 0 ];

        list.Clear();

        return element;
      }

      var index = this.IntegerExclusive( 0, list.Count );
      
      var item = list[ index ];

      list.RemoveAt( index );
      return item;  
    }

    public List<T> RemoveMutlipleFrom<T>( List<T> list, int amount )
    {
      if ( amount >= list.Count )
      {
        return list;
      }
      
      var items = new List<T>();

      for ( int i = 0; i < amount; i++ )
      {
        var itemIndex = this.IntegerExclusive( 0, list.Count );
        var item = list[ itemIndex ];
        list.RemoveAt( itemIndex );
        items.Add( item );
      }      

      return items;
    }

    public T RemoveWeightedFrom<T>( List<T> list, List<float> weights )
    {
      if ( list == null || list.Count == 0 )
      {
        return default(T);
      }

      if ( list.Count == 1 )
      {
        return list[ 0 ];
      }

      var weightedSelection = RemoveMultipleWeightedFrom( list, weights, 1 );


      return weightedSelection[ 0 ];
    }

    public List<T> RemoveMultipleWeightedFrom<T>( List<T> list, List<float> weights, int amount )
    {
      if ( amount >= list.Count )
      {
        return list;
      }

      var sumWeights = 0f;

      weights.ForEach( w => sumWeights += w );

      var selection = new List<T>();

      for ( int i = 0; i < amount; i++ )
      {
        var number = Range( 0, sumWeights );

        var index = _FindElementIndexWithWeights( weights, Range( 0, sumWeights ) );
        var element = list[ index ];
        var weight = weights[ index ];
        list.RemoveAt( index );
        sumWeights -= weight;
        selection.Add( element );
      } 

      return selection;
    }

    public int IndexFromUnnormalizedWeights( List<float> weights, float sumWeights = 0 )
    {
      if ( sumWeights <= 0 )
      {
        sumWeights = 0;
        weights.ForEach( w => sumWeights += w );
      }

      return _FindElementIndexWithWeights( weights, Next() * sumWeights );
    }

    public float SelectCurveWeights( Curve curve, int numSteps = 100 )
    {
      return IndexFromCurveWeights( curve, numSteps ) / (float) numSteps;
    }

    public int IndexFromCurveWeights( Curve curve, int numIndices )
    {
      var weights = new List<float>();
      var sumWeights = 0f;

      for ( int i = 0; i < numIndices; i++ )
      {
        var t = (float)i / ( numIndices - 1 );
        var w = curve.Sample( t );

        weights.Add( w ); 
        sumWeights += w;
      }

      return IndexFromUnnormalizedWeights( weights, sumWeights );
    }

    
    public int IndexFromNormalizedWeights( List<float> weights, float sumWeights = 0 )
    {

      return IndexFromUnnormalizedWeights( weights, 1 );
    }

    public List<int> Numbers( int numValues, int offset = 0 )
    {
      var list = new List<int>();
      
      for ( int i = 0; i < numValues; i++ )
      {
        list.Add( i + offset );
      }

      return new RandomList<int>( list, this ).GetList();
    }

    public List<int> Selection( int possibleValues, int selection )
    {
      selection = Mathf.Min( selection, possibleValues );
      
      var list = new List<int>();
      
      for ( int i = 0; i < possibleValues; i++ )
      {
        list.Add( i );
      }
      
      return new RandomList<int>( list, this ).GetList().GetRange( 0, selection );
    }

    int _FindElementIndexWithWeights( List<float> weights, float value )
    {
      var limit = 0f;

      for ( int i = 0; i < weights.Count; i++ )
      {
        var before = limit;
        limit += weights[ i ];

        if ( before <= value && value < limit )
        {
          return i;
        }
      }

      return weights.Count - 1;
    }

  }

  public abstract class SeedableRandomEngine:RandomEngine
  {
    public abstract void SetSeed( int number );
    public abstract object GetSeedState();
    public abstract void SetSeedState( object seedState );
  }
}
