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



namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class TextureDilate:SequenceAction
  { 
    [Export]
    public SubViewport viewport;

    [Export]
    public Texture2D target;

    [Export( PropertyHint.Range, "0,15" ) ]
    public int steps = 0;

    [Export]
    public bool useSRGB = true;

    [ExportToolButton( "Grab Texture")]
    public Callable GrabButton => Callable.From( 
      () => 
      {
        Grab( useSRGB, true );
      } 
    );

    public void SetDilationRadius( int pixelSize )
    {
      steps = ( Mathf.RoundToInt( MathX.Exponent( 2, pixelSize ) ) - 1 );
    }

    public void SetDilationRadius( Vector2 size )
    {
      SetDilationRadius( Mathf.RoundToInt( Mathf.Max( size.X, size.Y ) ) );
    } 

    public bool skipMode = false;

    public async Task<Texture2D> Grab( bool srgb, bool alpha, bool mipmaps = false )
    {   

      this.LogInfo( "Current Render Device Memory", RDContext.Current().GetMemoryInfo() );


      var updateMode = viewport.RenderTargetUpdateMode;

      if ( updateMode != SubViewport.UpdateMode.Always )
      {
        viewport.RenderTargetUpdateMode = SubViewport.UpdateMode.Always;
        await this.RequestNextFrame();
        viewport.RenderTargetUpdateMode = updateMode;
      }

      if ( skipMode )
      {
        this.LogInfo( "Skipping Creation" );
        var skipImage = viewport.GetTexture().GetImage();
        var skipBuffer = TextureCombinerBuffer.From( ImageTexture.CreateFromImage( skipImage ), -1 );
        var skipTexture = skipBuffer.CreateImageTexture( alpha, mipmaps );
        return skipTexture;
      }


      var viewPortFormat = RDTextureFormats.GetDataFormat( viewport );
      var viewPortData = viewport.GetTexture().GetImage().GetData();
      var viewPortSize = viewport.Size;


      // this.LogInfo( "Grabbed viewport context", viewPortFormat, viewPortSize );

      // this.LogInfo( "Creating context" );
      var ctx = RDContext.Local();
      ctx.computeSize = viewPortSize;
      // ctx.messageLogLevel = Messages.GetLevel( MessageType.Verbose );
      
      
      // this.LogInfo( "Creating textures" );
      
      var bufferTexture  = RDTexture.Create( ctx, viewport.Size, viewPortFormat );
      var bufferTexture2 = RDTexture.Create( ctx, viewport.Size, viewPortFormat );
      var resultTexture  = RDTexture.Create( ctx, viewport.Size, viewPortFormat ); 
      var initTexture    = RDTexture.Create( ctx, viewport.Size, viewPortFormat ); 
     

      var inputTexture = RDTexture.Create( ctx, viewport.Size, viewPortFormat );
      inputTexture.SetData( viewPortData );

      // this.LogInfo( "Creating graph" );

      var graph = CreateGraph( ctx, inputTexture, initTexture, bufferTexture, bufferTexture2, resultTexture );

      // this.LogInfo( "Process graph" );
      graph.ProcessForView();
     
      ctx.SubmitAndSync();

      var exportTexture = resultTexture;
      var width = exportTexture.width;
      var height = exportTexture.height;
      var format = exportTexture.imageFormat; 

      var data = exportTexture.GetData();

      ctx.SubmitAndSync();
      await this.RequestNextFrame();


      // this.LogInfo( "Copying texture:", format );

      var image = Image.CreateFromData( width, height, false, format, data );

      var gammaConversion = 0;

      if ( RenderingDevice.DataFormat.R16G16B16A16Sfloat == viewPortFormat && srgb )
      {
        gammaConversion = -1;
      }

      var buffer = TextureCombinerBuffer.From( ImageTexture.CreateFromImage( image ), gammaConversion );
      var texture = buffer.CreateImageTexture( alpha, mipmaps );
      


      target = texture;

      // ctx.messageLogLevel = Messages.GetLevel( MessageType.Verbose );
      // ctx.CleanUp();
      // ctx.messageLogLevel = Messages.GetLevel( MessageType.Info );

      ctx.CleanUpAndFree();
      
      GC.Collect();
      GC.WaitForPendingFinalizers();


      return texture;
    
    }

    RDGraph CreateGraph( RDContext ctx, RDTexture it, RDTexture ii, RDTexture b1, RDTexture b2, RDTexture rt )
    {
      var graph = new RDGraph( ctx );

      var viewTexture    = RG_BufferTexture.From( graph, it );
      var initTexture    = RG_BufferTexture.From( graph, ii );
      var bufferTexture  = RG_BufferTexture.From( graph, b1 );    
      var bufferTexture2 = RG_BufferTexture.From( graph, b2 ); 
      var resultTexture  = RG_BufferTexture.From( graph, rt ); 

      ctx.Verbose( "Init Texture:", initTexture.GetTexture() );

      var jfaInititialize = new RG_JFAInitialize( graph );
      jfaInititialize.constants.Set( 0.95f );

      var copy = new CEG_Copy( graph );
      var jfaIterate = new RG_JFAIterate( graph );
      var jfaAssign  = new RG_JFAAssign( graph );
      var repeat = new RG_SwapRepeat( graph );
      
      var numSteps = steps == 0 ? 
                       ( Mathf.RoundToInt( MathX.Exponent( 2, it.maxDimension ) ) - 1 ) : 
                       steps;

      // RJLog.Log( "Steps >>", numSteps, Mathf.Pow( 2, numSteps ) );

      repeat.repeats = numSteps;
      repeat.imageProcessor = jfaIterate;

      repeat.onPreProcess =
        ( index ) =>
        {
          var stepIndex = numSteps - ( index + 1 );
          var jump = Mathf.RoundToInt( Mathf.Pow( 2, stepIndex ) );

          jfaIterate.constants.Set(
            jump
          );
        };
      
      

      graph.InitializeNodes();

      
      jfaInititialize.SetTextureSlotInputs( viewTexture, initTexture );
      copy.SetTextureSlotInputs( initTexture, bufferTexture );
      repeat.SetTextureSlotInputs( bufferTexture, bufferTexture2 );

      var assignTexture = steps % 2 == 0 ? bufferTexture : bufferTexture2;
      jfaAssign.SetTextureSlotInputs( assignTexture, resultTexture );
      jfaAssign.AddTextureSlotInput( viewTexture );

      graph.SetProcessOrder( 
        viewTexture, 
        bufferTexture, 
        bufferTexture2,
        initTexture,
        resultTexture,
        jfaInititialize, 
        copy,
        repeat,
        jfaAssign
      );

      

      return graph;
    }

  
  } 
}