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



namespace Rokojori
{
  public class TextureCombinerBuffer
  {
    int _width;
    public int width => _width;

    int _height;
    public int height => _height;

    Color[] _pixels;    

    public int numPixels => _pixels.Length;

    public static TextureCombinerBuffer Create( int w, int h )
    {
      var tb = new TextureCombinerBuffer();
      tb._height = h;
      tb._width = w;
      tb._pixels = new Color[ w * h ];

      return tb;
    }

    public void UnblendBlack( float treshold = 10f/255f )
    {
      for ( int i = 0; i < _pixels.Length; i++ )
      {
        _pixels[ i ] = _pixels[ i ].UnblendBlack( treshold ); 
      }
    }

    public void Fill( Color color, int x, int y, int w, int h )
    {
      for ( int i = 0; i < w; i++ )
      {
        for ( int j = 0; j < h; j++ )
        {
          SetAt( i + x, j + y, color ); 
        } 
      }
    }

    public void Fill( Color color )
    {
      Fill( color, 0, 0, width, height );
    }

    public static TextureCombinerBuffer From( Texture2D texture, int srgbConversion = 0 )
    {
      var buffer = Create( texture.GetWidth(), texture.GetHeight() );
      buffer.CopyFrom( texture.GetImage(), 0, 0, 0, 0, texture.GetWidth(), texture.GetHeight(), srgbConversion );

      return buffer;
    } 

    public static TextureCombinerBuffer FromPath( string path, int srgbConversion = 0 )
    {
      var texture = ResourceLoader.Load<Texture2D>( path );
      var sourceBuffer = TextureCombinerBuffer.From( texture, srgbConversion );

      return sourceBuffer;
    }

    public void CopyFromSampled( TextureCombinerBuffer source, int x, int y, int w, int h )
    {
      // RJLog.Log( "Copy Sampled", x,y, w, h );

      for ( int i = 0; i < w; i ++ )
      {
        for ( int j = 0; j < h; j++ )
        {
          float u = i / (float)( w );
          float v = j / (float)( h );

          var sourceX = Mathf.RoundToInt( u * source.width );
          var sourceY = Mathf.RoundToInt( v * source.height );

          var color = source.GetAt( sourceX, sourceY );
          SetAt( x + i, y + j, color );
        }
      }
    }

    public static Texture2D GridMerge( int w, int h, Color background, bool alpha, bool mipMaps, 
                                       Vector2I alignment,      
                                   List<Texture2D> textures, List<Color> nullTextureColors )
    {

      var positions = new List<Vector2I>();
      var sizes = new List<Vector2I>();

      var mergeSize = new Vector2( w, h );
      var gridSize = (Vector2I)( mergeSize / alignment );

      // RJLog.Log( "GridMerge" );

      for ( int i = 0; i < textures.Count; i++ )
      {
        var box = TextureMerger.GetUVAlignmentBoxFor( alignment, i );
        var position = (Vector2I) ( box.min * mergeSize );
        positions.Add( position );
        sizes.Add( gridSize );

        // RJLog.Log( i, position, gridSize );
      }



      return Merge( w, h, background, alpha, mipMaps, textures, nullTextureColors, positions, sizes );
    }

    public static Texture2D Merge( int w, int h, Color background, bool alpha, bool mipMaps, 
                                   List<Texture2D> textures, List<Color> nullTextureColors, List<Vector2I> positions, List<Vector2I> sizes )
    {
      var buffer = Create( w, h );

      buffer.Fill( background );

      for ( int i = 0; i < textures.Count; i++ )
      {
        var position = positions[ i ];
        var size = sizes[ i ];

        if ( textures[ i ] == null )
        {
          buffer.Fill( nullTextureColors[ i ], position.X, position.Y, size.X, size.Y );
          continue;
        }

        var imageBuffer = From( textures[ i ] );       
        buffer.CopyFromSampled( imageBuffer, position.X, position.Y, size.X, size.Y );
      }

      return buffer.CreateImageTexture( alpha, mipMaps );
    }

    public ImageTexture CreateImageTexture( bool alpha = true, bool generateMipmaps = false )
    {
      var image = Image.CreateEmpty( width, height, generateMipmaps, alpha ? Image.Format.Rgba8 : Image.Format.Rgb8);
      CopyTo( 0, 0, 0, 0, width, height, image, alpha );
      
      if ( generateMipmaps )
      {
        image.GenerateMipmaps();
      }

      return ImageTexture.CreateFromImage( image );
    }

    public ImageTexture CreateImageTexture3D( int depth, bool alpha = true, bool generateMipmaps = false )
    {
      var image = Image.CreateEmpty( width, height, generateMipmaps, alpha ? Image.Format.Rgba8 : Image.Format.Rgb8);
      CopyTo( 0, 0, 0, 0, width, height, image, alpha );
      
      if ( generateMipmaps )
      {
        image.GenerateMipmaps();
      }

      return ImageTexture.CreateFromImage( image );
    }

    public void UpdateTexture( ImageTexture imageTexture, bool hasAlpha = true )
    {
      var image = imageTexture.GetImage();
      CopyTo( 0, 0, 0, 0, width, height, image, hasAlpha ); 
      imageTexture.Update( image );
    }

    public TextureCombinerBuffer Resize( int w, int h )
    {
      var buffer = Create( w, h );

      for ( int i = 0; i < w; i++ )
      {
        for ( int j = 0; j < h; j++ )
        {
          var uv = new Vector2( i / (float) w, j / (float) h );
          var pixel = SampleBilinearUV( uv );

          buffer.SetAt( i, j, pixel );
        }
      }

      return buffer;

    }

    public void CopyFrom( Image image, int sourceX, int sourceY, int ownX, int ownY, int w, int h, int srgbConversion = 0 )
    {
      if ( srgbConversion != 0 )
      {
        if ( srgbConversion == 1 )
        { 
          for ( int i = 0; i < w; i++ )
          {
            for ( int j = 0; j < h; j++ )
            {
              var sourcePixel = image == null ? new Color( 0, 0, 0 ) : image.GetPixel( sourceX + i, sourceY + j );
              sourcePixel = sourcePixel.SRGBtoLinear();
              SetAt( ownX + i, ownY + j, sourcePixel );

            }
          }
        }
        else if ( srgbConversion == -1 )
        {
          for ( int i = 0; i < w; i++ )
          {
            for ( int j = 0; j < h; j++ )
            {
              var sourcePixel = image == null ? new Color( 0, 0, 0 ) : image.GetPixel( sourceX + i, sourceY + j );
              sourcePixel = sourcePixel.LinearToSRGB();
              SetAt( ownX + i, ownY + j, sourcePixel );

            }
          }
        }
       
      }
      else
      {
      
        for ( int i = 0; i < w; i++ )
        {
          for ( int j = 0; j < h; j++ )
          {
            var sourcePixel = image == null ? new Color( 0, 0, 0 ) : image.GetPixel( sourceX + i, sourceY + j );
            SetAt( ownX + i, ownY + j, sourcePixel );

          }
        }
      }
    }


    public void CopyTo( int ownX, int ownY, int targetX, int targetY, int w, int h, TextureCombinerBuffer output )
    {
      for ( int i = 0; i < w; i++ )
      {
        for ( int j = 0; j < h; j++ )
        {
          var ownPixel = GetAt( ownX + i, ownY + j );

          output.SetAt( targetX + i, targetY + j, ownPixel );

        }
      }
    }

    public void CopyTo( int ownX, int ownY, int targetX, int targetY, int w, int h, Image image, bool alpha = true )
    {
      if ( ! alpha )
      {
        for ( int i = 0; i < w; i++ )
        {
          for ( int j = 0; j < h; j++ )
          {
            var ownPixel = GetAt( ownX + i, ownY + j ); 
            ownPixel.A = 1;
            image.SetPixel( targetX + i, targetY + j, ownPixel );
          }
        }
      }   
      else
      {
        for ( int i = 0; i < w; i++ )
        {
          for ( int j = 0; j < h; j++ )
          {
            var ownPixel = GetAt( ownX + i, ownY + j );
            image.SetPixel( targetX + i, targetY + j, ownPixel );

          }
        }
      }
    }

    public int ComputeIndexFromPosition( int x, int y )
    {
      return x + y * _width;
    }

    public Vector2I ComputePositionFromIndex( int index )
    {
      var x = index % width;
      var y = index / width;

      return new Vector2I( x, y );
    }

    public void SetAt( int x, int y, Color value )
    {
      SetIndexed( ComputeIndexFromPosition( x, y ), value );
    }
    
    public void SetIndexed( int index, Color value )
    {
      _pixels[ index ] = value;
    }

    public Color GetAt( int x, int y )
    {
      return GetIndexed( ComputeIndexFromPosition( x, y ) );
    }

    public Color GetIndexed( int index )
    {
      return _pixels[ index ];
    } 

    public Color SampleNearestUV( Vector2 uv )
    {
      return SampleNearestImage( uv.X * width, uv.Y * height );
    }

    public Color SampleNearestImage( float imageDimensionsX, float imageDimensionsY )
    {
      return GetAt( Mathf.RoundToInt( imageDimensionsX ), Mathf.RoundToInt( imageDimensionsY ) ); 
    }    

    public Color SampleBilinearUV( Vector2 uv )
    {
      return SampleBilinearImage( uv.X * width, uv.Y * height );
    }

    public Color SampleBilinearImage( float imageDimensionsX, float imageDimensionsY )
    {
      var lowX  = Mathf.FloorToInt( imageDimensionsX );
      var highX = Mathf.Min( lowX + 1, width - 1 );
      var lerpX = imageDimensionsX - lowX;

      
    
      var lowY  = Mathf.FloorToInt( imageDimensionsY );
      var highY = Mathf.Min( lowY + 1, height - 1 );
      var lerpY = imageDimensionsY - lowY;


      var ll = GetAt( lowX, lowY );
      var hl = GetAt( highX, lowY );

      var lh = GetAt( lowX, highY );
      var hh = GetAt( highX, highY );

      var upper = ll.Lerp( hl, lerpX );
      var lower = lh.Lerp( hh, lerpX );

      return upper.Lerp( lower, lerpY ); 
    }
  }
}