
using Godot;
using System.Collections.Generic;

namespace Rokojori
{  
  public class RokojoriCompositorContext
  {
    protected RokojoriCompositorEffect _effect;
    public RokojoriCompositorEffect effect => _effect;

    protected RDShader _shader;
    public RDShader shader => _shader;

    protected RDPipeline _pipeline;
    public RDPipeline pipeline => _pipeline;

    public void SetShaderAndPipeline( RDShader shader, RDPipeline pipeline )
    {
      effect.Verbose( "Set Shader Pipeline", shader, pipeline );
      _shader = shader;
      _pipeline = pipeline;

    }

    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 RDTexture GetScreenColorTexture()
    {
      return RDTexture.Color( this );
    }

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

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

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

    public void AssignUniformSet_Texture( 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 )
      {
        // effect.Verbose( "Adding Image" );
        AddUniformSet( RDUniformSet.Image( _effect, texture, setIndex ) );
      }
      else
      {
        // effect.Verbose( "Adding Sampler" );
        AddUniformSet( RDUniformSet.Sampler( _effect, sampler,texture, setIndex ) );
      }
    }

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

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

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

    public void SetGroups( Vector3I groups )
    {
      this._groups = groups;
    }
    
    public void ComputeGroups( int groupSize )
    {
      ComputeGroups( new Vector3I( groupSize, groupSize, 0 ) );
    }

    public void ComputeGroups( Vector3I groupSize )
    {
      var size = sceneBuffers.GetInternalSize();

      var xGroups = Mathf.CeilToInt( size.X / (float) groupSize.X );
      var yGroups = groupSize.Y == 0 ? 1 : Mathf.CeilToInt( size.Y / (float) groupSize.Y );
      var zGroups = groupSize.Z == 0 ? 1 : Mathf.CeilToInt( size.Y / (float) groupSize.Y );

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

    public void Render()
    {
      try
      {
      
        var computeList = RDComputeList.Begin( _effect.rd );
        computeList.BindPipeline( pipeline );      
        

        _uniformSets.ForEach(
          ( u )=>
          {
            computeList.BindUniformSet( u, u.setIndex );
          }
        );
        

        if ( pushConstants != null )
        {
          computeList.SetPushConstants( pushConstants );
        }
        
      

        computeList.Dispatch( groups );
        

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

      Clear();
    }

  }

  public class EditableRokojoriCompositorContext:RokojoriCompositorContext
  {
    public void SetEffect( RokojoriCompositorEffect effect )
    {
      this._effect = effect;
    }

    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;
    }

    
  }
}