using System.Collections;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using Godot;

namespace Rokojori
{
  public class MinMaxSearch<T>
  {
    Func<T,T,float,T> lerp;    
    Func<T,float> getValue;
    Func<T,T,bool> validateLimits; 

    public bool cacheValues = true;
    public float interpolationAmount = 1f;

    Dictionary<T,float> _cachedValues = new Dictionary<T, float>();

    public MinMaxSearch( Func<T,T,float,T> lerp, Func<T,float> getValue,  Func<T,T,bool> validateLimits = null)
    {
      this.lerp = lerp;
      this.getValue = getValue;
      this.validateLimits = validateLimits;
    }

    public T Find( float searchValue, T low, T high, float treshold )
    {
      if ( validateLimits != null && ! validateLimits( low, high ) )
      {
        throw new Exception( "Limit validation failed" );
      }

      var tweened = GetTweened( searchValue, low, high );
      var tweenedValue = GetValue( tweened );
      var tweenedDifference = searchValue - tweenedValue;

      Safe.While ( () => Mathf.Abs( tweenedDifference ) > treshold ,
        ()=>
        {      
          if ( searchValue > tweenedValue )
          {
            low = tweened;
          }
          else 
          {
            high = tweened;
          }


          if ( validateLimits != null && ! validateLimits( low, high ) )
          {
            throw new Exception( "Limit validation failed" );
          }
          
          tweened = GetTweened( searchValue, low, high );
          tweenedValue = GetValue( tweened );
          tweenedDifference = searchValue - tweenedValue;
        }
      );

      return tweened;
    }

    public T GetTweened( float value, T low, T high )
    {
      var lowValue = getValue( low );
      var highValue = getValue( high );

      var position = MathX.Normalize( value, lowValue, highValue );
      position = Mathf.Lerp( 0.5f, position, interpolationAmount );

      return lerp( low, high, position );
    }

    float GetValue( T t )
    {
      if ( ! cacheValues )
      {
        return getValue( t );
      }

      if ( _cachedValues.ContainsKey( t ) )
      {
        return _cachedValues[ t ];
      }

      _cachedValues[ t ] = getValue( t );

      return _cachedValues[ t ];
    }
  }
}