
using Godot;
using System.Collections.Generic;

namespace Rokojori
{  
  [Tool]
  [GlobalClass]
  public partial class RokojoriCompositorEffect:CompositorEffect
  {
    protected RenderingDevice _rd;
    public RenderingDevice rd => _rd;       

    
    protected List<RenderingObject> _cleanUps = new List<RenderingObject>();
    protected List<string> _cleanUpInfo = new List<string>();
    
    EditableRokojoriCompositorContext _context;
    protected bool _hasContext = false;
    public RokojoriCompositorContext context => _hasContext ? _context : null;

    protected List<Message> _messages = new List<Message>();
    public List<Message> messages => _messages;
    public bool logMessages = true;
    public int messageLogLevel = Messages.GetLevel( MessageType.Verbose );

    protected virtual void OnConfigure(){}
    protected virtual void OnInitialize(){}
    protected virtual void ForAllViews(){}    
    protected virtual void RenderView(){}

    public RokojoriCompositorEffect():base()
    {
      RenderingServer.CallOnRenderThread( Callable.From( _InitializeCompositorEffect ) );    
    }

    protected void _InitializeCompositorEffect()
    {            
      OnConfigure();      

      _rd = RenderingServer.Singleton.GetRenderingDevice();
      
      if ( _rd == null )
      {
        Error( "Found no rendering device" );
        return;
      }

      _context = new EditableRokojoriCompositorContext();
      _context.SetEffect( this );

      _hasContext = true;
      OnInitialize();    
      _hasContext = false;  

    }

    public override void _RenderCallback( int effectCallbackType, RenderData renderData )
    {  
           
      _hasContext = false;

      if ( rd == null )
      {
        Error( "No render device" );
        return;
      }
      
      var sceneBuffers = ( RenderSceneBuffersRD ) renderData.GetRenderSceneBuffers();
      var sceneData =    ( RenderSceneDataRD ) renderData.GetRenderSceneData();
     
      if ( sceneBuffers == null && sceneData == null )
      {
        Error( "sceneBuffers == null && sceneData == null" );
        return;
      }
      
     

      var size = sceneBuffers.GetInternalSize();

      if ( size.X == 0 || size.Y == 0 )
      {
        Warning( "InternalSize.X == 0 || InternalSize.Y == 0" );
        return;
      }
      
      _context.SetRenderData( renderData, sceneBuffers, sceneData );
      _context.SetView( -1 );

      _context.SetGroups( new Vector3I( 1, 1, 1 ) );

     

      _hasContext = true;
      // var groups = ComputeGroups();
      // _context.SetGroups( groups );

      ForAllViews();

      

      int viewCount = ( int ) sceneBuffers.GetViewCount();

      for ( int i = 0; i < viewCount; i++ )
      {
        _context.SetView( i );
        RenderView();
      }

      _hasContext = false;
    }
    
    


    public override void _Notification( int what )
    {  
      var _shader = context.shader;

      Verbose( "Got notification: ", what ); 

      if ( what != NotificationPredelete || ( _shader == null || !_shader.valid ) || rd == null )
      {
        Verbose( 
          "what != NotificationPredelete", what != NotificationPredelete,
          "( _shader == null || !_shader.valid )", ( _shader == null || !_shader.valid ),
          "rd == null", rd == null 
        );
        return;
      }

      CleanUp( _shader, "Shader" );

      var index = 0;

      _cleanUps.ForEach( 
        c => 
        {
          CleanUp( c, "_cleanUps[" + index + "]");
          index ++;
        }
      );

      _cleanUps.Clear();
      
    }

    public void AddToCleanUp( RenderingObject ro, string info = null )
    {
      var _shader = context.shader;

      if ( _cleanUps.Contains( ro ) || _shader == ro )
      {
        return;
      }

      _cleanUps.Add( ro );
      _cleanUpInfo.Add( info );
    }

    public void AddToCleanUp( List<RenderingObject> ro, string info = null )
    {
      var index = 0;
      info = info == null ? "" : info;

      ro.ForEach( 
        r =>       
        { 
          AddToCleanUp( r, info + "["+ index + "]" ); 
          index ++; 
        } 
      );
    }

    void CleanUp( RenderingObject ro, string info )
    {
      if ( ro == null )
      {
        Warning( "ro == null, couldn't clean up: ", info );
        return;
      }
      
      Verbose( "Cleaning up: ", info, ro.rid  );
      rd.FreeRid( ro.rid );
    }


    
    // ---------------------------------------------
    // ---------------------------------------------
    // CONVINIENCE
    // ---------------------------------------------
    // ---------------------------------------------
    
    public RDSampler Sampler( RDSamplerState state = null)
    {
      if ( state == null )
      {
        state = new RDSamplerState();
        state.MinFilter = RenderingDevice.SamplerFilter.Linear;
        state.MagFilter = RenderingDevice.SamplerFilter.Linear;
        state.RepeatU = RenderingDevice.SamplerRepeatMode.Repeat;
        state.RepeatV = RenderingDevice.SamplerRepeatMode.Repeat;
      } 

      var sampler = RDSampler.Create( this, state );
      return sampler;
    }

    public RDSampler Sampler( RenderingDevice.SamplerFilter filter, RenderingDevice.SamplerRepeatMode repeatMode)
    {
      var state = new RDSamplerState();

      state.MinFilter = filter;
      state.MagFilter = filter;

      state.RepeatU = repeatMode;
      state.RepeatV = repeatMode;

      return Sampler( state );
    }



    // ---------------------------------------------
    // ---------------------------------------------
    // MESSAGES
    // ---------------------------------------------
    // ---------------------------------------------

    
    public void Error( params object[] messages )
    {
      var message = RJLog.GetLogString( messages );

      Messages.Error( _messages, message );

      if ( logMessages )
      {
        this.LogError( message );
      }
    }

    public void Warning( params object[] messages )
    {
      var message = RJLog.GetLogString( messages );

      Messages.Warning( _messages, message );

      if ( logMessages && Messages.GetLevel( MessageType.Warning ) >= messageLogLevel )
      {
        this.LogInfo( message );
      }
    }

    public void Info( params object[] messages )
    {
      var message = RJLog.GetLogString( messages );

      Messages.Info( _messages, message );

      if ( logMessages && Messages.GetLevel( MessageType.Info ) >= messageLogLevel )
      {
        this.LogInfo( message );
      }
    }

    public void Verbose( params object[] messages )
    {
      var message = RJLog.GetLogString( messages );

      Messages.Verbose( _messages, message );

      if ( logMessages && Messages.GetLevel( MessageType.Verbose ) >= messageLogLevel )
      {
        this.LogInfo( message );
      }
    }
    

    
  }
}