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

namespace Rokojori
{  
  [Tool]
  [GlobalClass]
  public partial class GhostBloomEffect:GlowCompositorEffect
  {
    public GhostBloomEffect():base()
    {
      Initialize();
    } 

    [ExportGroup( "Main")]
    [Export( PropertyHint.Range, "0,1") ]
    public float amount = 0.5f;

    

    [ExportGroup( "Luma")]
    [Export]
    public Color lumaTint = Colors.White;

    [Export( PropertyHint.Range, "0,10") ]
    public int blurRadius = 0;

    [Export( PropertyHint.Range, "0,10") ]
    public int blurRadiusX = 0;

     [Export( PropertyHint.Range, "0,10") ]
    public int blurRadiusY = 0;

    [Export( PropertyHint.Range, "1,10") ]
    public float blurSpread = 1f;

    [Export( PropertyHint.Range, "0,100") ]
    public float blurNoise = 0f;

    [Export]
    public Vector3 lumaWeights = new Vector3( 0.3f, 0.5f, 0.2f );

    [Export]
    public float lumaWeightPower =1f;

    [Export]
    public Vector2 lumaInputRange = new Vector2( 0.5f, 1.5f ); 

    [Export]
    public float lumaNormalizationPower = 1f;

    [Export]
    public Vector2 lumaOutputRange = new Vector2( 0f, 1.5f ); 
    
    [Export]
    public float desaturation = 1f;


    int _downSampling = 16;

    public enum DownSampling
    {
      _1x_,
      _2x_,
      _4x_,
      _8x_,
      _16x_,
      _32x_            
    }

    public int downSamplingRate => Mathf.RoundToInt( Mathf.Pow( 2, (int) downSampling ) );

    [Export]
    public DownSampling downSampling = DownSampling._4x_;

    [ExportGroup( "Ghost Layers")]

    [Export]
    public Vector2 pivotOffset = Vector2.Zero;

    [Export]
    public float scaleXYMultiplier = 1f;

    [Export]
    public Vector2 scaleXYIndexMultiplier = new Vector2( 1f, 1f );

    [Export]
    public Vector2 scaleMultiplier = Vector2.One;

    [Export]
    public Texture2D fading;

    [Export]
    public Texture2D radialFading;

    [Export]
    public GhostBloomSettings[] layerSettings = [];

    RG_ScreenColorTexure screenColorTexture;
    RG_BufferTexture bufferTexture;  
    RG_BufferTexture bufferTexture2;  
    RG_ImageTexture fadingTexture;
    RG_ImageTexture radialFadingTexture;

    
    RG_ExtractGlow extractGlow;
    RG_GaussBlur lumaBlurX;
    RG_GaussBlur lumaBlurY;

    int maxBlooms = 16;


    List<RG_GhostBloom> _blooms = [];  


    void Initialize()
    { 
      screenColorTexture = new RG_ScreenColorTexure( graph );
      bufferTexture = RG_BufferTexture.ScreenSize( graph, Vector2.One / (float) _downSampling  );
      bufferTexture2 = RG_BufferTexture.ScreenSize( graph, Vector2.One / (float) _downSampling  );
      fadingTexture = new RG_ImageTexture( graph );
      radialFadingTexture = new RG_ImageTexture( graph );

      lumaBlurX = new RG_GaussBlur( graph );
      lumaBlurY = new RG_GaussBlur( graph );

      extractGlow = new RG_ExtractGlow( graph );

      _blooms = new List<RG_GhostBloom>();

      for ( int i = 0; i < maxBlooms; i++ )
      {
        var b = new RG_GhostBloom( graph );
        _blooms.Add( b );
      }

      graph.InitializeNodes();

      extractGlow.SetTextureSlotInputs( screenColorTexture, bufferTexture );
      extractGlow.input.UseSampler();

      lumaBlurX.SetTextureSlotInputs( bufferTexture, bufferTexture2 );
      lumaBlurX.input.UseSampler();

      lumaBlurY.SetTextureSlotInputs( bufferTexture2, bufferTexture );
      lumaBlurY.input.UseSampler();

      _blooms.ForEach(
        ( b )=>
        {
          b.SetTextureSlotInputs( bufferTexture, screenColorTexture );
          b.input.UseSampler();

          b.AddSampledTextureSlotInput( fadingTexture );
          b.AddSampledTextureSlotInput( radialFadingTexture );
        }
      );
   
      graph.SetProcessOrder(
        screenColorTexture, 
        bufferTexture,      
        bufferTexture2,   
        fadingTexture,
        radialFadingTexture,
        extractGlow,
        lumaBlurX,
        lumaBlurY
      );
      
    }

    int _lastNum = -1;

    void IterateLayers( System.Action<GhostBloomSettings, int> callback )
    {
      if ( layerSettings == null )
      {
        return;
      }

      var index = 0;

      for ( int i = 0; i < layerSettings.Length; i++ )
      {
        if ( layerSettings[ i ] == null )
        {
          continue;
        }

        for ( int j = 0; j < layerSettings[ i ].numInstances; j++ )
        {
          callback( layerSettings[ i ], index );
          index++;
        }
      }
    }

    int NumLayers()
    {
      var num = 0;

      IterateLayers( ( l, i ) => num ++ );

      return num;
    }


    protected override void ForAllViews()
    { 
      fadingTexture.SetImageTexture( fading );
      radialFadingTexture.SetImageTexture( radialFading );

      if ( downSamplingRate != _downSampling )
      {
        _downSampling = downSamplingRate;
        ( bufferTexture.GetCreator() as CEG_TextureCreator_ScreenSize ).scale = Vector2.One / (float) _downSampling;
        ( bufferTexture2.GetCreator() as CEG_TextureCreator_ScreenSize ).scale = Vector2.One / (float) _downSampling;
      }

      extractGlow.SetCustomComputeSize( context.internalSize / _downSampling  );
      lumaBlurX.SetCustomComputeSize( context.internalSize / _downSampling  );
      lumaBlurY.SetCustomComputeSize( context.internalSize / _downSampling  );
      

      extractGlow.constants.Set(
        lumaWeights.X, 
        lumaWeights.Y,
        lumaWeights.Z,
        lumaWeightPower,

        lumaInputRange.X, 
        lumaInputRange.Y,
        lumaNormalizationPower,
        lumaOutputRange.X,

        lumaOutputRange.Y,
        desaturation,
        lumaTint.R,
        lumaTint.G,

        lumaTint.B,
        0f,
        0f,
        0f
      );

      lumaBlurX.constants.Set(
        1f, 
        blurNoise,
        blurSpread,
        0f,

        (float)( blurRadiusX + blurRadius ), 
        0f, 
        0f,
        0f
      );

      lumaBlurY.constants.Set(
        1f, 
        blurNoise,
        blurSpread,
        0f,

        0f,
        (float)( blurRadiusY + blurRadius ), 
        0f,
        0f
      );
      
      var num = NumLayers();
      num = Mathf.Min( num, _blooms.Count );

      if ( _lastNum != num )
      {
        _lastNum = num;

         var list = new List<RGGraphProcessor>(){
          screenColorTexture, 
          bufferTexture,         
          bufferTexture2,
          fadingTexture,
          radialFadingTexture,
          extractGlow,
          lumaBlurX,
          lumaBlurY
         };
         
         for ( int i = 0; i < num; i++ )
         {
           list.Add( _blooms[ i ] );
         }

         graph.SetProcessOrder( list.ToArray() );
      }


      var scaleX = scaleMultiplier.X * scaleXYMultiplier;
      var scaleY = scaleMultiplier.Y * scaleXYMultiplier;
      

      var indexNormalizer = 1f / num; 

      IterateLayers( 
        ( settings, i )=>
        {      
          float t = i * indexNormalizer;
          float indexScale = Mathf.Lerp( scaleXYIndexMultiplier.X, scaleXYIndexMultiplier.Y, t );

          _blooms[ i ].constants.Set(
            amount * settings.intensity,
            1.0f + settings.redShift,
            1.0f + settings.greenShift,
            1.0f + settings.blueShift,

            (float) settings.smearingSteps,
            settings.smearing,
            settings.distortionAmount,
            settings.distortionAngle / 180f * Mathf.Pi,

            settings.offset.X,
            settings.offset.Y,
            indexScale * scaleX * settings.scaleXY * settings.scale.X * (settings.mirrorX ? -1f : 1f ),
            indexScale * scaleY * settings.scaleXY * settings.scale.Y * ( settings.mirrorY ? -1f : 1f ),

            settings.rotation,
            settings.pivot.X + pivotOffset.X,
            settings.pivot.Y + pivotOffset.Y,
            (float) settings.blurRadius,

            settings.blurSpread,
            settings.ellipseAmount,
            settings.ellipseSizeXY * settings.ellipseSize.X,
            settings.ellipseSizeXY * settings.ellipseSize.Y,

            settings.ellipseMinDistance,
            settings.ellipseKeepRatio,
            settings.distortionAngleFromEllipse,
            settings.radialFadingWrapping,

            settings.blendMode,
            0f,
            0f,
            0f
            
          );
        }
      );


      
    }

   
  }
}