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

namespace Rokojori
{ 
  public class DuoWaveTable
  {
    float[] _samples = new float[ 0 ];
    float _lowPitch;
    float _highPitch;
    int _maxSamples;

    float _weight = 0.5f;

    public DuoWaveTable( float[] samples, float lowPitch, float highPitch )
    {
      _samples = samples;
      _lowPitch = lowPitch;
      _highPitch = highPitch;
      _maxSamples = samples.Length / 2;      
    }

    public static DuoWaveTable FromWaveTables( WaveTable low, WaveTable high, float lowPitch, float highPitch )
    {
      var samples = new float[ low.samples.Length * 2 ];

      for ( int i = 0; i < low.samples.Length; i++ )
      {
        samples[ i * 2 ] = low.samples[ i ];
      }

       for ( int i = 0; i < high.samples.Length; i++ )
      {
        samples[ i * 2 + 1 ] = high.samples[ i ] - low.samples[ i ];
      }

      return new DuoWaveTable( samples, lowPitch, highPitch );

    }


    public float ComputeWeightForPitch( float pitch )
    {
      return MathX.NormalizeClamped( pitch, _lowPitch, _highPitch );
    }

    public void SetWeight( float weight )
    {
      this._weight = weight;
    }

    public void SetWeightForPitch( float pitch )
    {
      SetWeight( ComputeWeightForPitch( pitch ) );
    }

    public float Get( float phase )
    {
      var max = _maxSamples;

      var floatingSamplePosition = phase * max;
      
      var p0 = (int) floatingSamplePosition;
      var p1 = p0 == ( max - 1 ) ? 0 : ( p0 + 1 );

      var mixAmount = floatingSamplePosition - p0;
      
      var p0Index = p0 << 1;
      var p1Index = p1 << 1;

      var low0  = _samples[ p0Index ];
      var diff0 = _samples[ p0Index + 1 ]; 

      var low1  = _samples[ p1Index ];
      var diff1 = _samples[ p1Index + 1 ]; 

      var mixed0 = low0 + _weight * diff0;
      var mixed1 = low1 + _weight * diff1;

      return mixed0 + mixAmount * ( mixed1 - mixed0 );
    }
  }


  public class BandLimitedWaveTable:iPhaseGenerator
  { 
    List<DuoWaveTable> entries = new List<DuoWaveTable>();
    int entriesPerOctave = 3;
    float lowestPitch;
    float highestPitch;
    

    int _activeEntryIndex = 0;
    DuoWaveTable _activeEntry;

    public int GetEntryForPitch( float pitch )
    {
      var entry = Mathf.Min( MathX.RemapClamped( pitch, lowestPitch, highestPitch, 0, entries.Count ), entries.Count - 1 );
      return (int) entry;
    } 

    public void SetPitchRange( float minPitch, float maxPitch )
    {
      _activeEntryIndex = GetEntryForPitch( maxPitch );
      _activeEntry = entries[ _activeEntryIndex ];
      _activeEntry.SetWeightForPitch( maxPitch );
    }

    public float Get( float phase )
    {
      return _activeEntry.Get( phase );
    }

    public BandLimitedWaveTable FromSines( Vector2[] sines, int size = 4096, int entriesPerOctave = 3, float lowestPitch = 0 )
    {
      var blwt = new BandLimitedWaveTable();
      blwt.entriesPerOctave = entriesPerOctave;
      blwt.lowestPitch = lowestPitch;

      float frequency = MathAudio.PitchToFrequency( blwt.lowestPitch );      
      float maxFrequency = 18000;
      
      while ( frequency < maxFrequency )
      {               
        for ( int i = 0; i < entriesPerOctave; i++ )
        {          
          var lowPitchOffset  = i / (float) entriesPerOctave;
          var lowFrequency  = MathAudio.OffsetFrequencyBySemitones( frequency, lowPitchOffset );

          if ( lowFrequency > maxFrequency )
          {
            continue;
          }
          
          var highPitchOffset = ( i + 1 ) / (float) entriesPerOctave;
          var highFrequency = MathAudio.OffsetFrequencyBySemitones( frequency, highPitchOffset ); 
          
          var lowWaveTable  = CreateWaveTable( lowFrequency, sines, size );
          var highWaveTable = CreateWaveTable( highFrequency, sines, size );

          var lowPitch  = MathAudio.FrequencyToPitch( lowFrequency );
          var highPitch = MathAudio.FrequencyToPitch( highFrequency );

          var duoWaveTable = DuoWaveTable.FromWaveTables( lowWaveTable, highWaveTable, lowPitch, highPitch );

          blwt.highestPitch = lowPitch;

          entries.Add( duoWaveTable );
        }


        frequency *= 2;
      }

      return blwt;
    }

    WaveTable CreateWaveTable( float frequency, Vector2[] sines, int size )
    {
      var filterStart = FindFilterStart( frequency, sines.Length );
      var filterEnd   = FindFilterEnd( frequency, sines.Length, filterStart );

      return WaveTable.FromSines( sines, filterStart, filterEnd, size );
    }

    int FindFilterStart( float baseFrequency, int numSines, float frequencyTreshold = 14000f )
    {
      for ( int i = 0; i < numSines; i++ )
      {
        var binFrequency = i * baseFrequency;

        if ( binFrequency >= frequencyTreshold )
        {
          return i;
        }
      }

      return numSines;
    }

    int FindFilterEnd( float baseFrequency, int numSines, int start, float frequencyTreshold = 18000f )
    {
      for ( int i = start; i < numSines; i++ )
      {
        var binFrequency = i * baseFrequency;

        if ( binFrequency >= frequencyTreshold )
        {
          return i;
        }
      }

      return numSines;
    }
  }
}