
using Godot;

namespace Rokojori
{  
  public class RDTexture: RDObject
  {
    public RDTexture( RDContext context, Rid rid ):base( context, rid ){}

    public static RDTexture Create( RDContext context, Texture2D texture )
    {
      return new RDTexture( context, texture.GetRid() );
    }

    public static RDTexture Create( RDContext effect, RDTextureFormat format, RDTextureView view )
    {
      return new RDTexture( effect, effect.renderingDevice.TextureCreate( format, view ) );  
    }

    public static RDTexture Color( RDContext context )
    {
      // var rid = context.sceneBuffers.GetColorLayer( (uint) context.view );
      var rid = context.sceneBuffers.GetTextureSlice( "render_buffers", "color", (uint) context.view, 0, 1, 1 );
      return new RDTexture( context, rid );  
    }

    public static RDTexture Depth( RDContext context )
    {
      // var rid = context.sceneBuffers.GetDepthLayer( (uint) context.view );
      var rid = context.sceneBuffers.GetTextureSlice( "render_buffers", "depth", (uint) context.view, 0, 1, 1 );
      return new RDTexture( context, rid );  
    }

     public static RDTexture NormalRoughness( RDContext context )
    {
      // var rid = context.sceneBuffers.GetDepthLayer( (uint) context.view );
      var rid = context.sceneBuffers.GetTextureSlice( "forward_clustered", "normal_roughness", (uint) context.view, 0, 1, 1 );
      return new RDTexture( context, rid );  
    }
    

    public void SetData( byte[] data, int layer = 0 )
    {
      var error = context.renderingDevice.TextureUpdate( rid, (uint) layer, data );

      if ( error == Error.Ok )
      {
        context.Verbose( "Data was set" );
        return;

      }

      context.Error( error );
    }

    public void SetData( Viewport viewport )
    {
      SetData( viewport.GetTexture().GetImage() );
    }

    public void SetData( Image image )
    {
      var textureDataFormat = RDTextureFormats.ImageFormatToDataFormat( image.GetFormat() );

      if ( textureDataFormat != format.Format )
      {
        context.Error( "Incompatible image format:", textureDataFormat, " Expected:", format.Format );
        return;
      }
      
      SetData( image.GetData() );
    } 

    public int numPixels => (int) ( format.Width * format.Height );
    public int channelsPerPixel => RDTextureFormats.GetNumChannels( format.Format );

    public RDTextureFormats.PixelChannelEncoding pixelChannelEncoding => RDTextureFormats.GetPixelChannelEncoding( format.Format );

    public int bytesPerPixel => RDTextureFormats.NumBytesFor( pixelChannelEncoding ) * channelsPerPixel;

    public int GetNumBytes( int layer = 0 )
    {
      var bytes = numPixels * bytesPerPixel;
      
      while ( layer > 0 )
      {
        bytes /= 4;
        layer --;
      }

      return bytes;
    }

    public void SetData( Color color )
    {
      var fmt = format.Format;

      var colorBytes = RDTextureFormats.ColorToBytes( color, fmt );

      if ( colorBytes == null )
      {
        context.Error( "Unsupported texture format" );
        return;
      }

      context.Verbose( "ColorBytes:", colorBytes );

      var bytes = new byte[ GetNumBytes() ];

      context.Verbose( "NumPixels", numPixels, "BytesPerPixel", bytesPerPixel, "Num Bytes", GetNumBytes( 0 ) );

      for ( int i = 0; i < bytes.Length; i+= colorBytes.Length )
      {
        System.Array.Copy( colorBytes, 0, bytes, i, colorBytes.Length );
      }

      context.Verbose( "First Byte", bytes[ 0 ], bytes[ 1 ], bytes[ 2 ], bytes[ 3 ] );

      SetData( bytes );
    }

    public byte[] GetData( int layer = 0)
    {
      return context.renderingDevice.TextureGetData( rid, (uint) layer );
    }

    public int width => (int) format.Width;
    public int height => (int) format.Height;
    public int maxDimension => Mathf.RoundToInt( Mathf.Max( width, height ) ); 

    public RenderingDevice.DataFormat dataFormat => format.Format;
    public Image.Format imageFormat => RDTextureFormats.DataFormatToImageFormat( dataFormat );

    public Image GetImage()
    {      
      var fmt = format;
      var imgF = RDTextureFormats.DataFormatToImageFormat( format.Format );

      
      var data = GetData();

      var output = "";
      for ( int i = 0; i < 20; i++ )
      {
        if ( i != 0 ){ output += ", "; }
        output += data[ i ] + "";
      }

      context.Verbose( "Converting:", fmt.Format, ">>", imgF, "output:", output );
      return Image.CreateFromData( (int) fmt.Width, (int)fmt.Height, false, imgF, data );
    }

    public Texture2D GetTexture2D()
    {
      return ImageTexture.CreateFromImage( GetImage() );
    }

    public RDTextureFormat format 
    {
      get
      {
        return context.renderingDevice.TextureGetFormat( rid );
      }
    }

    public Vector2I size 
    {
      get 
      {
        var textureFormat = format;
        return new Vector2I( (int) textureFormat.Width, (int) textureFormat.Height ); 
      }
    }

    public static RDTexture EnsureScreenSizeTexture( RDTexture texture, RDContext context, bool cleanUp = true )
    {
      var size = context.internalSize;

      if ( texture == null || texture.size != size )
      {
        if ( cleanUp && texture != null )
        {
          context.Free( texture, "Old Screen Size Texture" );
        }

        texture = Create( context, size );
      }

      return texture;
    }

    public static RDTexture Create( RDContext context, RDTextureFormat format )
    {
      var view = new RDTextureView();
      var rid = context.renderingDevice.TextureCreate( format, view ); 
      return new RDTexture( context, rid );  
    }

    public static RDTextureFormat DefaultFormat( int w, int h,  RenderingDevice.DataFormat dataFormat = RenderingDevice.DataFormat.R16G16B16A16Sfloat)
    {
      var format = new RDTextureFormat();

      format.Width  = (uint) w;
      format.Height = (uint) h;
      format.Format = dataFormat;
      format.UsageBits = RenderingDevice.TextureUsageBits.StorageBit | 
                         RenderingDevice.TextureUsageBits.SamplingBit |
                         RenderingDevice.TextureUsageBits.CanCopyFromBit | 
                         RenderingDevice.TextureUsageBits.CanUpdateBit |
                         RenderingDevice.TextureUsageBits.CanCopyToBit |
                         RenderingDevice.TextureUsageBits.CpuReadBit
                        ;

      return format;
    }

    public static RDTexture Create( RDContext context, Vector2I size, RenderingDevice.DataFormat dataFormat = RenderingDevice.DataFormat.R16G16B16A16Sfloat )
    {
      return Create( context, size.X, size.Y, dataFormat );
    }    


    public static RDTexture Create( RDContext context, int width, int height, 
                                    RenderingDevice.DataFormat dataFormat = RenderingDevice.DataFormat.R16G16B16A16Sfloat,
                                    RenderingDevice.TextureUsageBits textureUsageBits = RDTextureFormats.Usage_Default )
    {
      var view   = new RDTextureView();
      var format = RDTextureFormats.DefaultFormat( width, height, dataFormat );
      format.UsageBits = textureUsageBits;

      context.Verbose( "Format:", format, "DataFormat",  dataFormat);

      var rid = context.renderingDevice.TextureCreate( format, view ); 
      return new RDTexture( context, rid );  
    }

    
    public static RDTexture CreateCopyFrom( RDContext context, SubViewport viewport )
    {
      var dataFormat = RDTextureFormats.GetDataFormat( viewport );
      var bytes = RDTextureFormats.GetNumBytes( viewport );
      var image = viewport.GetTexture().GetImage();
      var viewPortImageFormat = image.GetFormat();
      var rdFormat = RDTextureFormats.ImageFormatToDataFormat( viewPortImageFormat );
      var data = image.GetData();

      RJLog.Log( "Data", data.Length, Lists.SubList( data, 0, 100 ) );

      RJLog.Log( "Copying Texture From", viewport.Size, viewPortImageFormat, rdFormat, "Bytes", bytes );
      var texture = Create( context, viewport.Size, dataFormat );
  
      texture.SetData( data );
      return texture;
    }
  }
}