
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System;
using Godot;
using System.Linq;


namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/VirtualCameraManager.svg") ]
  public partial class CameraManager:NetworkNode
  { 
    [Export]
    public Camera3D camera;

    [Export]
    public WorldEnvironment worldEnvironment;

    [Export]
    public VirtualCamera debugCamera;

    [Export]
    public Sensor debugCameraToggle;

    [Export]
    public bool debugCameraActive = false;

    [Export]
    public bool refreshSlots = false;

    [Export]
    public bool active = true; 

    [Export]
    public bool postProcess = true;

    [Export]
    public CompositorEffectLayout compositorLayout = new CompositorEffectLayout();
    
    [Export]
    public PostProcessVolume[] postProcessVolumes = [];

    public static CameraManager Get()
    {
      return Unique<CameraManager>.Get();
    }

    public CameraSlot activeSlot 
    {
      get
      {
        return Lists.GetWithHighestValue( _cameraSlots, c => c.priority );
      }
    }

    [ExportGroup("Read Only" )]
    [Export]
    public bool lerpingCameras;
    [Export]
    public bool lerpingPostProcessing;

    bool _lerpingCameras = false;
    const int minimalLerpFrames = 100;
    int _cameraLerpCounter = 0;
    int _postProcessLerpCounter = 0;


    bool _lerpingPostProcessVolumes = false;


    public void StartLerping()
    {
      _lerpingCameras = true;
      lerpingCameras = _lerpingCameras;
      _cameraLerpCounter = minimalLerpFrames;

      // this.LogInfo( "Started Lerping" );
    }

    List<float> prios = new List<float>();

    public override void _Process( double delta ) 
    {     
      if ( ! active )
      {
        return;
      }

      
      ProcessCameras( delta );

      ProcessDebugCamera();
      
      ProcessPostProcess( delta );



      
    }

    void ProcessDebugCamera()
    {
      if ( Sensors.IsDown( debugCameraToggle ) )
      {
        debugCameraActive = ! debugCameraActive;
        
        if ( debugCameraActive )
        {
          Input.MouseMode = Input.MouseModeEnum.Visible;
        }
        
        this.LogInfo( debugCameraActive );
      }

      if ( debugCameraActive && debugCamera != null )
      {
        SetDebugCamera( debugCamera );
        return;
      }

    }


    PostProcessEffectProcessor<GlowEffect> glowEffectProcessor = new PostProcessEffectProcessor<GlowEffect>();
    PostProcessEffectProcessor<FogEffect> fogEffectProcessor = new PostProcessEffectProcessor<FogEffect>();
    PostProcessEffectProcessor<_XX_DepthOfFieldEffect> depthOfFieldProcessor = new PostProcessEffectProcessor<_XX_DepthOfFieldEffect>();

    void SetAllPostProcessors()
    {
      _allPostProcessors =new List<IPostProcessEffectProcessor>()
      {
        glowEffectProcessor,
        fogEffectProcessor,
        depthOfFieldProcessor
      };
    }

    List<IPostProcessEffectProcessor> _allPostProcessors;

    bool updatePostEffectStack = true;

    [Export]
    public bool postStackUpdate = false;

    void ProcessPostProcess( double delta )
    {
      if ( ! postProcess || postProcessVolumes == null )
      {
        return;
      } 

      compositorLayout.Update();
      
      postProcessVolumes.ForEach( 
        v => 
        { 
          if ( v == null )
          { return; }
          
          v.UpdateEffects( this );
        }
      );

      if ( postStackUpdate )
      {
        updatePostEffectStack =true;
        postStackUpdate = false;
      }

      UpdatePostEffectStack();

      

      _allPostProcessors.ForEach( p => p.Apply( worldEnvironment ) );

    }

    int _postEffectsSize = -1;



    void UpdatePostEffectStack()
    {
      if ( _allPostProcessors == null || _allPostProcessors.Count == 0 )
      {
        SetAllPostProcessors();
      }

      if ( _postEffectsSize != postProcessVolumes.Length )
      {
        updatePostEffectStack = true;
      }

      if ( ! updatePostEffectStack )
      {
        return;
      }

      _postEffectsSize = postProcessVolumes.Length;

      _allPostProcessors.ForEach( p => p.ClearForStackUpdate() );

      postProcessVolumes.ForEach( 
        volume => 
        {           
          var allEffects = volume.effects.ToList();

          _allPostProcessors.ForEach( p => p.OnVolumeUpdate( volume, allEffects ) );
        }
      );
    }

    void ProcessCameras( double delta )
    {
     

      if ( ! _lerpingCameras )
      {
        RefreshSlots();

        var cam = activeSlot;

        if ( camera == null || cam == null || cam.camera == null )
        {
          return;
        }

        SetSingleCamera( cam, delta );
        
      }
      else
      { 
        LerpCameras( delta );
      }
    }

    void SetDebugCamera( VirtualCamera c )
    {
      var rotation = c.GetCameraRotation(); 

      var vUp      = rotation * Vector3.Up;
      var vForward = rotation * Vector3.Forward;

      var position = c.GetCameraPosition();
      var up       = vUp;
      var forward  = vForward;      
      var fov      = c.GetCameraFOV();

      if ( forward.LengthSquared() == 0 )
      {
        forward = camera.Basis.Z; 
      }
      else
      {
        forward = forward.Normalized();
      }

      if ( up.LengthSquared() == 0 )
      {
        up = camera.Basis.Y; 
      }
      else
      {
        up = up.Normalized();
      }      

      // RJLog.Log( "Set Cam", position );

    

      camera.GlobalPosition = position;
      camera.LookAt( position - forward, up );
      camera.Fov = fov;
    }
    
    void SetSingleCamera( CameraSlot c, double delta )
    {
      c.Update( delta, this );

      var rotation = c.GetCameraRotation(); 

      if ( ! rotation.IsFinite() || rotation.Length() == 0 )
      {
        rotation = new Quaternion();
        rotation.X = 0;
        rotation.Y = 0;
        rotation.Z = 0;
        rotation.W = 1;
        rotation = rotation.Normalized();
      }
    

      var vUp      = rotation * Vector3.Up;
      var vForward = rotation * Vector3.Forward;

      var position = c.GetCameraPosition();
      var up       = vUp;
      var forward  = vForward;      
      var fov      = c.GetCameraFOV();

      if ( forward.LengthSquared() == 0 )
      {
        forward = camera.Basis.Z; 
      }
      else
      {
        forward = forward.Normalized();
      }

      if ( up.LengthSquared() == 0 )
      {
        up = camera.Basis.Y; 
      }
      else
      {
        up = up.Normalized();
      }      

      // RJLog.Log( "Set Cam", position );

    

      camera.GlobalPosition = position;
      camera.LookAt( position - forward, up );
      camera.Fov = fov;
    }


    [Export]
    public float CameraPrioritySmoothingCoefficient = 0.1f;

    [Export]
    public float CameraPrioritySmoothingStepFPS = 120;

    public float smoothStepDelta => 1f / CameraPrioritySmoothingStepFPS;
    public float safeSmoothing => Mathf.Max( 0, CameraPrioritySmoothingCoefficient );

    List<CameraSlot> _cameraSlots = new List<CameraSlot>();

    public CameraSlot GetSlot( int index )
    {
      return _cameraSlots[ index ];
    }

    public CameraSlot GetSlot( VirtualCamera virtualCamera3D )
    {
      return _cameraSlots.Find( s => s.camera == virtualCamera3D );
    }

    public CameraSlot GetSlot( SelectorFlag[] flags )
    {
      return _cameraSlots.Find( s => Arrays.ContainsAll( s.flags, flags ) );
    }

    public void SetActiveSlot( CameraSlot slot )
    {
      _cameraSlots.ForEach( c => c.priority = ( c == slot ? 1 : 0 ) );
      StartLerping();
    }

    public void RefreshSlots( bool forceUpdate = false )
    {
  
      var needsUpdate = forceUpdate || refreshSlots ||  _cameraSlots == null || _cameraSlots.Count == 0;

      if ( ! needsUpdate )
      {
        return;
      }

      refreshSlots = false;
      _cameraSlots = Nodes.GetDirectChildren<CameraSlot>( this ); 
      StartLerping();
    }

    

    public void RemoveSlot( CameraSlot slot )
    {
      if ( slot == null || slot.GetParent() != this )
      {
        return;
      }

      Nodes.RemoveAndDelete( slot );

      RefreshSlots( true );
    }

    void LerpCameras( double delta )
    {
      RefreshSlots();
      

      var sumPriority = 0f;

      if ( prios.Count != _cameraSlots.Count )
      {
        prios = _cameraSlots.Map( c => c.smoothedPriority );
      }

      var numWithNonZero = 0;


      _cameraSlots.ForEach( 
        c => 
        { 
          c.Update( delta, this );
          sumPriority += MathF.Max( 0, c.smoothedPriority );

          if ( c.smoothedPriority > 0.001f )
          { 
            numWithNonZero ++;
          }
        }
      );

      if ( _cameraLerpCounter > 0 )
      {
        _cameraLerpCounter --;
      }

      if ( _cameraLerpCounter == 0 && numWithNonZero == 1 )
      {         
        _lerpingCameras = false;
        lerpingCameras = _lerpingCameras;

        // this.LogInfo( "Stopped Lerping" );
        // return;
      }
      

      if ( sumPriority == 0 )
      {
        return;
      }

      var position = new Vector3();
      var up       = new Vector3();
      var forward  = new Vector3();
      var fov = 0f;

      _cameraSlots.ForEach( 
        c => 
        {
          var priority = MathF.Max( 0, c.smoothedPriority );
          var rotation = c.GetCameraRotation(); 

          if ( ! rotation.IsFinite() || rotation.Length() == 0 )
          {
            rotation = new Quaternion();
            rotation.X = 0;
            rotation.Y = 0;
            rotation.Z = 0;
            rotation.W = 1;
            rotation = rotation.Normalized();
          }
        

          var vUp      = rotation * Vector3.Up;
          var vForward = rotation * Vector3.Forward;

          position += priority * c.GetCameraPosition();
          up       += priority * vUp;
          forward  += priority * vForward;      
          fov      += priority * c.GetCameraFOV();
        } 
      );

      position /= sumPriority;
      fov /= sumPriority;

      if ( forward.LengthSquared() == 0 )
      {
        forward = camera.Basis.Z; 
      }
      else
      {
        forward = forward.Normalized();
      }

      if ( up.LengthSquared() == 0 )
      {
        up = camera.Basis.Y; 
      }
      else
      {
        up = up.Normalized();
      }      

      // RJLog.Log( "Set Cam", position );

    

      camera.GlobalPosition = position;
      camera.LookAt( position - forward, up );
      camera.Fov = fov;

    }

    public VirtualCamera GetCamera( int index )
    {
      return _cameraSlots[ index ].camera;
    }

    public int GetCameraIndex( VirtualCamera camera3D )
    {
      return _cameraSlots.FindIndex( c => c.camera == camera3D );
    }

    public float GetCameraPriority( int index )
    {
      return _cameraSlots[ index ].priority;
    }

    public void SetCameraPriority( int index, float priority )
    {
      _cameraSlots[ index ].priority = priority;
    }

    

  }
}