
using Godot;
using System.Collections.Generic;

namespace Rokojori
{  
  public partial class RDContext
  {
    
    protected RenderingDevice _renderingDevice;
    public RenderingDevice renderingDevice => _renderingDevice;

    protected bool _localRenderingDevice;
    public bool isLocalRenderingDevice => _localRenderingDevice;

    protected RDShader _shader;
    public RDShader shader => _shader;

    protected RDPipeline _pipeline;
    public RDPipeline pipeline => _pipeline;

    public static RDContext Local()
    {
      var ctx = new RDContext();
      ctx.Initialize( true );
      return ctx;
    }

    public static RDContext Current()
    {
      var ctx = new RDContext();
      ctx.Initialize( false );
      return ctx;
    }

    public void Initialize( bool local = false)
    {
      _localRenderingDevice = local;
      _renderingDevice = local ? RenderingServer.Singleton.CreateLocalRenderingDevice():
                                 RenderingServer.Singleton.GetRenderingDevice();

      if ( _renderingDevice == null )
      {
        Error( "Could not initialize rendering device" );
      }
    }


    protected int _view = -1;
    public int view => _view;

    protected int _effectCallbackType;
    public int effectCallbackType => _effectCallbackType;

    protected RenderData _renderData;
    public RenderData renderData => _renderData;

    protected RenderSceneBuffersRD _sceneBuffers;
    public RenderSceneBuffersRD sceneBuffers => _sceneBuffers;

    protected RenderSceneDataRD _sceneData;
    public RenderSceneDataRD sceneData => _sceneData;

    protected Vector3I _groups;
    public Vector3I groups => _groups;

    public Vector2I internalSize => _sceneBuffers.GetInternalSize();

    public Projection GetCameraProjection()
    {
      return sceneData.GetCamProjection();
    }

    public Transform3D GetCameraTransform()
    {
      return sceneData.GetCamTransform();
    }

    public RDTexture GetScreenColorTexture()
    {
      return RDTexture.Color( this );
    }

    public RDTexture GetScreenDepthTexture()
    {
      return RDTexture.Depth( this );
    }

    public RDTexture GetScreenNormalRoughnessTexture()
    {
      return RDTexture.NormalRoughness( this );
    }


    public void SetProgram( RDProgram p )
    {
      SetShaderAndPipeline( p.shader, p.pipeline );
    }

    public void SetProgramFromPath( string path )
    {
      Verbose( "Creating program:", path );
      var program = RDProgram.ComputeFromPath( this, path );
      SetProgram( program );
    }

    public void SetShaderAndPipeline( RDShader shader, RDPipeline pipeline )
    {
      if ( shader == null || pipeline == null )
      {
        _shader = null;
        _pipeline = null;
        Error( "Shader Pipeline is null", shader, pipeline );
        return; 
      }

      Verbose( "Set Shader Pipeline", shader, pipeline );
      _shader = shader;
      _pipeline = pipeline;

    }

    public void AssignScreenColorTexture( RDSampler sampler = null, int setIndex = -1 )
    {
      AssignTexture( GetScreenColorTexture(), sampler, setIndex );
    }

    public void AssignScreenDepthTexture( RDSampler sampler = null, int setIndex = -1 )
    {
      AssignTexture( GetScreenDepthTexture(), sampler, setIndex );
    } 

    public void AssignScreenNormalRoughnessTexture( RDSampler sampler = null, int setIndex = -1 )
    {
      AssignTexture( GetScreenNormalRoughnessTexture(), sampler, setIndex );
    } 

    RDTexture _missingTexture = null;
    Vector2I _missingTextureSize = new Vector2I( 64, 64 );

    public void AssignMissingTexture( RDSampler sampler = null, int setIndex = -1 )
    { 
      if ( _missingTexture == null )
      {
        _missingTexture = RDTexture.Create( this, _missingTextureSize, RenderingDevice.DataFormat.R16G16B16A16Sfloat );
        _missingTexture.SetData( new Color( 1, 0, 1, 1 ) );
      }

      AssignTexture( _missingTexture, sampler, setIndex );
    }

    public void AssignTexture( RDTexture texture, RDSampler sampler = null, int setIndex = -1 )
    {
      // effect.Verbose( "Incoming Uniform Index", setIndex );

      if ( setIndex == -1 )
      {
        setIndex = _uniformSets.Count;
      }

      // effect.Verbose( "Set Uniform Index", setIndex );

      if ( sampler == null )
      {
        // RJLog.Log( "Adding Image" );
        AddUniformSet( RDUniformSet.Image( this, texture, setIndex ) );
      }
      else
      {
        // RJLog.Log( "Adding Sampler" );
        AddUniformSet( RDUniformSet.Sampler( this, sampler,texture, setIndex ) );
      }
    }

    List<RDUniformSet> _uniformSets = new List<RDUniformSet>();
    public RDPushConstants pushConstants;

    public void AddUniformSet( RDUniformSet uniformSet )
    {
      _uniformSets.Add( uniformSet );
    }

    public void CreateUniformSet( params RDUniform[] uniforms )
    {
      var setIndex = _uniformSets.Count;
     _CreateUniformSetRid( setIndex, uniforms );
    }

    public Rid _CreateUniformSetRid( int index, params RDUniform[] uniforms )
    {
      var array = new Godot.Collections.Array<RDUniform>();
      array.AddRange( uniforms );

      if ( ! isLocalRenderingDevice )
      {
        return UniformSetCacheRD.GetCache( shader.rid, (uint) index, array );
      }
      else
      {
        RJLog.Log( "Creating uncached uniform", array );
        return renderingDevice.UniformSetCreate( array, shader.rid, (uint) index );
      }

    }

    public void Clear()
    {
       _uniformSets.Clear();
      pushConstants = null;
    }

    public Vector2I computeSize = new Vector2I( 512, 512 );

    public bool useFixedComputeSize = false;

    

    public Vector2I GetComputeSize()
    {
      if ( useFixedComputeSize )
      {
        return computeSize;
      }

      if ( _sceneBuffers != null )
      {
        return _sceneBuffers.GetInternalSize();
      }

      return computeSize;
    }

    public void SetComputeGroups( Vector3I groups )
    {
      this._groups = groups;
    }
    
    public void CalculateSceneComputeGroups( int groupSize )
    {
      var size = GetComputeSize();
      CalculateComputeGroups( new Vector3I( groupSize, groupSize, 0 ), size );
    }

    public void CalculateSceneComputeGroups( Vector3I groupSize )
    {
      var size = GetComputeSize();
      CalculateComputeGroups( groupSize, size );
    }

    public void CalculateComputeGroups( int groupSize, Vector2I size )
    {
      CalculateComputeGroups( new Vector3I( groupSize, groupSize, 0 ), size );
    }

    public void CalculateComputeGroups( Vector3I groupSize, Vector2I size )
    {
      var xGroups = Mathf.CeilToInt( size.X / (float) groupSize.X );
      var yGroups = groupSize.Y == 0 ? 1 : Mathf.CeilToInt( size.Y / (float) groupSize.Y );
      var zGroups = 1;

      SetComputeGroups( new Vector3I( xGroups, yGroups, zGroups ) );
    }

    public void Submit()
    {
      if ( ! isLocalRenderingDevice )
      {
        Error( "You can only submit or sync in non-local rendering devices" );
        return;
      }

      renderingDevice.Submit();
    }

    public void Sync()
    {
      if ( ! isLocalRenderingDevice )
      {
        Error( "You can only submit or sync in non-local rendering devices" );
        return;
      }

      renderingDevice.Sync();
    }

    public void SubmitAndSync()
    {
      if ( ! isLocalRenderingDevice )
      {
        Error( "You can only submit or sync in non-local rendering devices" );
        return;
      }

      Submit();
      Sync();
    }

    public void ProcessComputeProgram()
    {
      try
      {
      
        var computeList = RDComputeList.Begin( _renderingDevice );
        computeList.BindPipeline( pipeline );      
        

        _uniformSets.ForEach(
          ( u )=>
          {
            // RJLog.Log( "Binding Uniform Set:", u.rid );
            computeList.BindUniformSet( u, u.setIndex );
          }
        );
        

        if ( pushConstants != null )
        {
          Verbose( "Set constants", pushConstants.size );
          computeList.SetPushConstants( pushConstants );
        }
        
      

        computeList.Dispatch( groups );
        

        computeList.End();
      }
      catch( System.Exception e )
      { 
        Error( e ); 
      }

      Clear();
    }

    public void SetRenderData( RenderData renderData, RenderSceneBuffersRD buffers, RenderSceneDataRD sceneData )
    {
      this._renderData = renderData;
      this._sceneBuffers = buffers;
      this._sceneData = sceneData;
    }

    public void SetView( int view )
    {
      this._view = view;
    }

  }
}