
using Godot;
using System.Collections.Generic;

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

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

    public enum Direction
    {
      Horizontal,
      Vertical
    }

    [Export ]
    public Direction direction = Direction.Horizontal;

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

    public enum MaxBlurMode
    {
      Maximum_per_Component,
      Average_of_All
    }

    [Export]
    public MaxBlurMode maxBlurMode;

    [Export]
    public Texture2D inputFading;

    [Export]
    public Texture2D outputFading;

    [Export]
    public Texture2D tint;

    [Export]
    public Vector2 tintScale = Vector2.One;

    [Export]
    public float tintScaleXY = 1f;

    [Export]
    public Vector2 tintOffset = Vector2.Zero;

    [ExportGroup( "Distortion")]

    [Export( PropertyHint.Range, "0,5") ]
    public float distortionAmount = 0.5f;

    [Export( PropertyHint.Range, "-1,1") ]
    public float offset = 0f;

    [Export( PropertyHint.Range, "1,32") ]
    public int smearingSteps = 5;

    [Export( PropertyHint.Range, "1,1.5") ]
    public float smearing = 1.1f;

    [Export( PropertyHint.Range, "-1,1") ]
    public float redShift = 0.1f;

    [Export( PropertyHint.Range, "-1,1") ]
    public float greenShift = 0f;

    [Export( PropertyHint.Range, "-1,1") ]
    public float blueShift = 0f;

    [ExportGroup( "Luma")]

    // [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 Color lumaTint = Colors.White;

    [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_;


    RG_ScreenColorTexure screenColorTexture;
    RG_BufferTexture bufferTexture;  
    RG_BufferTexture bufferTexture2;
    RG_ImageTexture inputFadeTexture;
    RG_ImageTexture outputFadeTexture;
    RG_ImageTexture tintTexture;

    
    RG_FadedMaxBlur firstMaxBlur;
    RG_SwapRepeat swapRepeat;
    RG_MaxBlur maxBlur;
    RG_MaxBloom maxBloom;

    RG_ExtractGlowPartial extractGlow;


    RG_Fill fill;
    void Initialize()
    { 
      screenColorTexture = new RG_ScreenColorTexure( graph );
      bufferTexture  = RG_BufferTexture.ScreenSize( graph );
      bufferTexture2 = RG_BufferTexture.ScreenSize( graph );
      inputFadeTexture = new RG_ImageTexture( graph );
      outputFadeTexture = new RG_ImageTexture( graph );
      tintTexture = new RG_ImageTexture( graph );

      fill = new RG_Fill( graph );

      firstMaxBlur = new RG_FadedMaxBlur( graph );
      
      swapRepeat = new RG_SwapRepeat( graph );
      maxBlur = new RG_MaxBlur( graph );

      extractGlow = new RG_ExtractGlowPartial( graph );

      maxBloom = new RG_MaxBloom( graph );

      graph.InitializeNodes();

      fill.SetTextureSlotInput( bufferTexture );
      
      firstMaxBlur.SetTextureSlotInputs( screenColorTexture, bufferTexture );
      firstMaxBlur.AddSampledTextureSlotInput( inputFadeTexture );

      swapRepeat.SetTextureSlotInputs( bufferTexture, bufferTexture2 );

      extractGlow.SetTextureSlotInputs( bufferTexture2, bufferTexture2 );      

      maxBloom.SetTextureSlotInputs( bufferTexture2, screenColorTexture );
      maxBloom.input.UseNearestSamplerEdgeClamped();

      maxBloom.AddSampledTextureSlotInput( outputFadeTexture );
      maxBloom.AddSampledTextureSlotInput( tintTexture );
      

      swapRepeat.imageProcessor = maxBlur;
      

      graph.SetProcessOrder(
        screenColorTexture, 
        bufferTexture,
        bufferTexture2,        
        inputFadeTexture,
        outputFadeTexture,
        tintTexture,

        fill,
        firstMaxBlur,
        swapRepeat,
        extractGlow,
        maxBloom
      );
      
    }

    [Export]
    public bool refresh = false;


    protected override void ForAllViews()
    {
      inputFadeTexture.SetImageTexture( inputFading );
      outputFadeTexture.SetImageTexture( outputFading );
      tintTexture.SetImageTexture( tint );

      fill.constants.Set( new Vector4( 0.0f, 0.0f, 0.0f, 1.0f ) );

      // context.messageLogLevel = verbose ? Messages.GetLevel( MessageType.Verbose ) : Messages.GetLevel( MessageType.Info );

      var isX = Direction.Horizontal == direction;
      
      var sizeX = context.internalSize.X;
      var sizeY = context.internalSize.Y;

      var edge = isX ? sizeY : sizeX;
      

      CreateReductionSteps( edge );
      
      var size = isX ? new Vector2I( sizeX, ReducedEdgeAtStep( 0 )  ):
                       new Vector2I( ReducedEdgeAtStep( 0 ), sizeY );
      
      var firstSize = size;
      var mode = isX ? 0 : 1;


      firstMaxBlur.SetCustomComputeSize( size  );
      firstMaxBlur.constants.Set(
        (float)size.X,
        (float)size.Y,
        (float)mode,
        (float)EdgeAtStep( 0 ),

        (float)(int)maxBlurMode,
        1f, 
        0f,
        0f
      );


      swapRepeat.repeats = numRepeats;


      if ( refresh || swapRepeat.repeats != _lastRepeats )
      {
        refresh = false;
        _lastRepeats = swapRepeat.repeats;

        if ( _lastRepeats % 2 == 1 )
        {
          maxBloom.input.ReplaceInput( bufferTexture, bufferTexture2 );
          extractGlow.input.ReplaceInput( bufferTexture, bufferTexture2 );
          extractGlow.output.ReplaceInput( bufferTexture, bufferTexture2 );

        }
        else
        {
          maxBloom.input.ReplaceInput( bufferTexture2, bufferTexture );
          extractGlow.input.ReplaceInput( bufferTexture2, bufferTexture );
          extractGlow.output.ReplaceInput( bufferTexture2, bufferTexture );

        }
      }

      
      swapRepeat.onPreProcess = ( int p ) => 
      {
        var stepIndex = p + 1;

        if ( isX )
        {
          size.Y = ReducedEdgeAtStep( stepIndex );
        }
        else
        {
          size.X = ReducedEdgeAtStep( stepIndex );
        }

        maxBlur.SetCustomComputeSize( size );


        maxBlur.constants.Set(
          (float)size.X,
          (float)size.Y,
          (float)mode,
          (float)EdgeAtStep( stepIndex ),

          (float)(int)maxBlurMode,
          0f, 
          0f,
          0f
        );
      };

      var lumaSize = isX ? new Vector2I( sizeX, 1 ) : new Vector2I( 1, sizeY ); 

      // this.LogInfo( lumaSize );
      extractGlow.SetCustomComputeSize( lumaSize );
      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,
        (float)lumaSize.X,
        (float)lumaSize.Y,
        0f
      );


      maxBloom.constants.Set(
        amount,
        1.0f + redShift,
        1.0f + greenShift,
        1.0f + blueShift,

        (float) smearingSteps,
        smearing,
        distortionAmount,
        blendMode,
        
        (float) mode,
        offset,
        tintOffset.X,
        tintOffset.Y,
        
        tintScaleXY * tintScale.X,
        tintScaleXY * tintScale.Y,
        0f,
        0f


      );    

     


    }

    
    int _lastRepeats = -1;

    int ToEven( int x )
    {
      if ( x % 2 == 0 )
      {
        return x;
      }

      return x + 1;
    }

    int _lastEdge = -1;
    List<int> reductionSteps = new List<int>();

    public int EdgeAtStep( int  i )
    {
      return reductionSteps[ i ];
    }

    public int ReducedEdgeAtStep( int i )
    {
      return reductionSteps[ i + 1 ];
    }   

    public int numSteps => reductionSteps.Count - 1;
    public int numRepeats => numSteps - 1;

    [Export]
    public int testEdge = 100;

    [ExportToolButton( "Log Steps")]
    public Callable logger => Callable.From(
      ( )=>
      {
        CreateReductionSteps( testEdge );

        this.LogInfo( reductionSteps );

        for ( int i = 0; i < reductionSteps.Count - 1; i++ )
        {
          this.LogInfo( "Input:" + EdgeAtStep( i ), "Output: " + ReducedEdgeAtStep( i )  );
        }    
      }
    );
    
    void CreateReductionSteps( int edge )
    {
      if ( _lastEdge == edge && reductionSteps.Count > 1 )
      {
        return;
      }

      reductionSteps = [];

      var it = edge;
      
      reductionSteps.Add( it );

      var b = it;
      it = it / 2; 

      if ( it * 2 <  b )
      {
        it ++;
      }

      while ( it > 1 )
      {
        reductionSteps.Add( it );

        var before = it;
        it = it / 2;

        if ( it * 2 < before )
        {
          it ++;
        }
      }

      reductionSteps.Add( 1 );

      _lastEdge = edge;
      
    }

   
  }
}