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


namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class TextureCombiner:Resource
  {
    [Export]
    public Texture2D textureOutput;

    [Export]
    public bool createMipmaps = true;

    [Export]
    public TextureCombinerMaterialOutput materialOutput;

    [Export]
    public bool create 
    {
      get => false;
      set { if ( ! value ) return; CreateTexture(); }
    }
    
    [Export]
    public bool autoCreate = false;

    [Export]
    public TextureCombinerStack layerStack;

    [Export]
    public int width = 1024;
    
    [Export]
    public int height = 1024;

    [ExportGroup( "Processing Settings")]
    [Export]
    public bool _creatingTexture = false;

    [Export]
    int _blockSize = 512;

    public void UpdateProgressTexture()
    {
      var size = 64;
      var hRatio = width / (float)height;
      var oProgress = Mathf.Min( 1, _progress * _progress + 0.2f );
      var waitImage = Image.CreateEmpty( size, Mathf.Max( 1, Mathf.RoundToInt( size / hRatio ) ), false, Image.Format.Rgba8 );
      waitImage.Fill( new HSLColor( oProgress * 120, 1f, 0.3f ) );

      var progressI = size * _progress;

      for ( int i = 0; i < progressI; i++ )
      {
        waitImage.SetPixel( i, waitImage.GetHeight() / 2 - 2,  new Color( 1, 1, 1 ) );
        waitImage.SetPixel( ( size - 1 ) - i, waitImage.GetHeight() / 2 + 2,  new Color( 1, 1, 1 ) );
      }
    

      textureOutput = ImageTexture.CreateFromImage( waitImage );
    }

    float _progress = 0;

    async void CreateTexture()
    {
      if ( _creatingTexture )
      {
        return;
      }

      /*var hRatio = width / (float)height;
      var waitImage = Image.CreateEmpty( 32, Mathf.Max( 1, Mathf.RoundToInt( 32 / hRatio ) ), false, Image.Format.Rgba8 );
      waitImage.Fill( new Color( 1, 0, 1 ));

      textureOutput = ImageTexture.CreateFromImage( waitImage );
      */

      UpdateProgressTexture();

    

      _creatingTexture = true;

      var context = new TextureCombinerContext();
      context.combiner = this;
      context.imageWidth = width;
      context.imageHeight = height;

      outputBuffer = TextureCombinerBuffer.Create( width, height ); 
      inputBuffer  = TextureCombinerBuffer.Create( width, height ); 

      var first = true;

      var pNormalizer = 1f / ( layerStack.layers.Length - 2 );

      for ( int i = 0; i < layerStack.layers.Length; i++ )
      {
        var layerIndex = ( layerStack.layers.Length - 1 ) - i;
        var layer = layerStack.layers[ layerIndex ];

        if ( ! layer.visible )
        {
          continue;
        }

        _progress = Mathf.Min( 1, i * pNormalizer );
        UpdateProgressTexture();

        await ProcessLayer( layer, context );

        
        _progress = Mathf.Min( 1, ( i + 0.5f ) * pNormalizer );
        UpdateProgressTexture();

        if ( first )
        {
          var b = inputBuffer;
          inputBuffer = outputBuffer;
          outputBuffer = b;
          
          first = false;
        }
        else
        {
          await BlendLayer( layer, context );
        }
        
        
      }
      

      var image = Image.CreateEmpty( width, height, createMipmaps, Image.Format.Rgba8 );
      inputBuffer.CopyTo( 0, 0, 0, 0, width, height, image );
      image.GenerateMipmaps();

      textureOutput = ImageTexture.CreateFromImage( image );
     
      if ( materialOutput != null && 
           materialOutput.material != null &&
           materialOutput.textureName != null
      )
      {
        materialOutput.textureName.Set( materialOutput.material, textureOutput );
      }

      _creatingTexture = false;
    }

    async Task ProcessLayer( TextureCombinerLayer layer, TextureCombinerContext context )
    {
      var blockSize = _blockSize;
      var blocksX = Mathf.CeilToInt( width / (float) blockSize );
      var blocksY = Mathf.CeilToInt( height / (float) blockSize );
      
      var time = Async.StartTimer();

      for ( int i = 0; i < blocksX; i++ )
      {
        var startX = i * blockSize;
        var endX   = Mathf.Min( startX + blockSize, width );

        for ( int j = 0; j < blocksY; j++ )
        {
          var startY = j * blockSize;
          var endY   = Mathf.Min( startY + blockSize, height );

          var rect = new TextureCombinerProcessingRect();
      
          rect.processingX = startX;
          rect.processingY = startY;
          rect.processingWidth = endX - startX;
          rect.processingHeight = endY - startY;
          rect.context = context; 
          rect.inputBuffer = inputBuffer;
          rect.outputBuffer = outputBuffer;

          await layer.Process( rect );

          time = await Async.WaitIfExceeded( time );
            
        }
      }
    }

    async Task BlendLayer( TextureCombinerLayer layer, TextureCombinerContext context )
    {
      var blockSize = _blockSize;
      var blocksX = Mathf.CeilToInt( width / (float) blockSize );
      var blocksY = Mathf.CeilToInt( height / (float) blockSize );

      var time = Async.StartTimer();

      TextureCombinerBuffer maskBuffer = null;

      if ( layer.opacityMask != null )
      {
        maskBuffer = TextureCombinerBuffer.From( layer.opacityMask ).Resize( context.imageWidth, context.imageHeight );      
      }

      for ( int i = 0; i < blocksX; i++ )
      {
        var startX = i * blockSize;
        var endX   = Mathf.Min( startX + blockSize, width );

        for ( int j = 0; j < blocksY; j++ )
        {
          var startY = j * blockSize;
          var endY   = Mathf.Min( startY + blockSize, width );

          var rect = new TextureCombinerProcessingRect();

          if ( layer.opacityMask == null )
          {
            TextureCombinerBlendModeAlgorithm.Blend( layer.blendMode, 
              startX, startY, endX - startX, endY - startY, layer.opacity,
              inputBuffer, outputBuffer, inputBuffer 
            );
          }
          else
          {
             TextureCombinerBlendModeAlgorithm.BlendMasked( layer.blendMode, 
              startX, startY, endX - startX, endY - startY, layer.opacity, maskBuffer,
              inputBuffer, outputBuffer, inputBuffer 
            );
          }
         

          time = await Async.WaitIfExceeded( time );
            
        }
      }
    }


    TextureCombinerBuffer outputBuffer;
    TextureCombinerBuffer inputBuffer;

   


  }
}