
using System;
using Godot;


namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Tween.svg")]
  public partial class TweenMaterial : SequenceAction, Animator
  {
    public void OnAnimatorStart(){}
    public void OnAnimatorEnd(){}
    public void OnAnimatorCancel(){}

    // [Export]
    // public Material fromMaterial;

    [Export]
    public Material toMaterial;
    
    [Export]
    public TweenType tweenType = new TweenTimeCurve();

    [Export]
    public TimeLine timeLine;

    ColorProperty[] colors = [];

    FloatProperty[] floats = [];

    public enum MaterialAssignmentMode
    {
      No_Assignment,
      Assignment_Once,
      Unique_Once,
      Temporary_Duplicate_For_Each_Tween
    }

    
    // [Export]
    // public 
    MaterialAssignmentMode assignmentMode = MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween;

    [ExportGroup( "Target")]
    [Export]
    public Node3D target;   

    [Export]
    public MaterialSlot slot = MaterialSlot.None;

    [Export]
    public int index = 0;

    // [ExportToolButton( "Assign From-Material")]
    // public Callable AssignFromMaterialButton => Callable.From( 
    //   () => 
    //   { 
    //     MaterialSurfaceContainer.SetMaterialInSlot( target, index, slot, fromMaterial );
    //   } 
    // );

    [ExportToolButton( "Assign To-Material")]
    public Callable AssignToMaterialButton => Callable.From( 
      () => 
      { 
        MaterialSurfaceContainer.SetMaterialInSlot( target, index, slot, toMaterial );
      } 
    );

     [ExportToolButton( "Log Material Delta")]
    public Callable GetMaterialDeltaButton => Callable.From( 
      () => 
      { 
        var from = MaterialSurfaceContainer.GetMaterialInSlot<Material>( target, index, slot );
        this.LogInfo( "Delta", MaterialDelta.Create( from, toMaterial ) );
      } 
    );

    Material _assignedMaterial = null;
  
    protected override void _OnTrigger()
    {
      if ( toMaterial == null && this._assignedMaterial == null || target == null )
      {
        return;
      }

      var fromMaterial = MaterialSurfaceContainer.From( target, this.index ).GetMaterialInSlot<Material>( slot );

      SetAssignedMaterial( fromMaterial );

      var tl = TimeLineManager.Ensure( timeLine );

      var start = tl.position;

      

      var delta = MaterialDelta.Create( fromMaterial, toMaterial );

      // this.LogInfo( "Delta:", delta );


      colors = delta.colorProperties.ToArray();
      floats = delta.floatProperties.ToArray();


      var startColors = new Color[ colors.Length ];
      var startFloats = new float[ floats.Length ];

      var elementIndex = 0;

      foreach ( var c in colors )
      {
        AnimationManager.StartAnimation( this, _assignedMaterial, c.propertyName );
        startColors[ elementIndex ] = c.propertyName.Get( fromMaterial );

        elementIndex ++;
      }

      elementIndex = 0;
      foreach ( var f in floats )
      {
        AnimationManager.StartAnimation( this, _assignedMaterial, f.propertyName );
        startFloats[ elementIndex ] = f.propertyName.Get( fromMaterial );
        elementIndex ++;
      }

      
      var sequenceID = DispatchStart(); 

      var tweenType = this.tweenType;

      if ( tweenType == null )
      {
        tweenType = TweenTimeCurve.defaultCurve;
      }

      TimeLineManager.ScheduleSpanIn( tl, 0, tweenType.GetTweenDuration(), 
        ( span, type )=>
        {         
          var timeNow = tl.position;
          var elapsed = timeNow - start;

          var elementIndex = 0;
          var state = tweenType.GetTweenPhaseForPhase( span.phase );

          foreach ( var c in colors )
          {
            if ( AnimationManager.IsAnimating( this, _assignedMaterial, c.propertyName ) )
            {
              c.ApplyLerped( _assignedMaterial, startColors[ elementIndex ], state );
            }

            elementIndex ++;
          }

          elementIndex = 0;

          foreach ( var f in floats )
          {
            if ( AnimationManager.IsAnimating( this, _assignedMaterial, f.propertyName ) )
            {
              f.ApplyLerped( _assignedMaterial, startFloats[ elementIndex ], state );
            }

            elementIndex ++;
          }

          if ( type == TimeLineSpanUpdateType.End )
          {
            foreach ( var c in colors )
            {
              AnimationManager.EndAnimation( this, _assignedMaterial, c.propertyName );
            }

            foreach ( var f in floats )
            {
              AnimationManager.EndAnimation( this, _assignedMaterial, f.propertyName );
            }

            if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
            {
              _assignedMaterial = null;
              var msc = MaterialSurfaceContainer.From( target, index );
              msc.SetMaterialInSlot( slot, toMaterial );     
            }
          
            DispatchEnd( sequenceID );
          }
        },
        this
      );
    

    }

    void SetAssignedMaterial( Material material )
    {
      if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
      {
        _assignedMaterial = (Material)material.Duplicate();        
        var tmsc = MaterialSurfaceContainer.From( target, index );
        tmsc.SetMaterialInSlot( slot, _assignedMaterial );      
        return;
      }

      if ( _assignedMaterial != null )
      {
        return;
      }

      if ( MaterialAssignmentMode.No_Assignment == assignmentMode )
      {
        _assignedMaterial = toMaterial;
        return;
      }

      _assignedMaterial = toMaterial;

      if ( MaterialAssignmentMode.Unique_Once == assignmentMode )
      {
        _assignedMaterial = (Material)toMaterial.Duplicate();
      }
      
      var msc = MaterialSurfaceContainer.From( target, index );

      msc.SetMaterialInSlot( slot, _assignedMaterial );       
      
    }
  }
}