using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
using System.Linq;

namespace Rokojori
{  
  
  public enum HighlightActionType
  {
    Start,
    End  
  } 

  public class HighlightAnimation
  {
    public HighlightEffect effect;
    public Node3D[] targets = new Node3D[ 0 ];
    public int tweenID = -1; 

    public List<Material> materials = new List<Material>();  
    public float phase;

  }

  [Tool]
  [GlobalClass]
  public partial class HighlightEffect:Resource
  {    
    [Export]
    public TimeLine timeline;


    [ExportGroup( "Transition")]

    [Export]
    public float inDuration;

    [Export]
    public Curve inCurve = MathX.Curve( 0,  1 );

    [Export]
    public float outDuration;

    [Export]
    public Curve outCurve = MathX.Curve( 0,  1 );

    [ExportGroup( "Color")]
    [Export]
    public HDRColor color;

    [Export( PropertyHint.Range, "0,1")]
    public float opacityModulationStrength = 0.5f;

    [Export( PropertyHint.Range, "0,10")]
    public float opacityModulationDuration = 0.5f;

    [Export( PropertyHint.Range, "0,1")]
    public float opacityModulationTransition = 1f;

    public enum OutlineMaterialMode
    {
      Flat_Outline,
      Scanner_FancyOutline,
      Custom_Material
    }

    [ExportGroup( "Outline")]
    [Export]
    public OutlineMaterialMode outlineMaterialMode;

    [Export]
    public Material outlineCustomMaterial;

    [Export]
    public ColorPropertyName outlineCustomColorProperty;

    public enum OverlayMaterialMode
    {
      Flat_Overlay,
      Custom_Material
    }

    [ExportGroup( "Overlay")]
    [Export( PropertyHint.Range, "0,1")]
    public float overlayOpacity = 0.02f;

    [Export]
    public OverlayMaterialMode overlayMaterialMode;

    [Export]
    public Material overlayCustomMaterial;

    [Export]
    public ColorPropertyName overlayCustomColorProperty;

    List<HighlightAnimation> _active = new List<HighlightAnimation>();

    protected static Dictionary<Node3D,HighlightEffect> highlighted = new Dictionary<Node3D, HighlightEffect>();

    public void Highlight( HighlightActionType type, Node3D[] targets )
    {       
      if ( HighlightActionType.Start == type )
      {
        StartHighlight( targets );
      }
      else if ( HighlightActionType.End == type )
      {
        EndHighlight( targets );
      }
    }

    

    void ClearHighlight( HighlightAnimation animation )
    {
      for ( int i = 0; i < animation.targets.Length; i ++ )
      {
        Materials.RemoveOverlay( animation.targets[ i ], animation.materials[ i ] );
      }

      _active.Remove( animation );
      
      animation.targets.ForEach(
        ( t )=>
        {
          if ( highlighted.ContainsKey( t ) && highlighted[ t ] == this )
          {
            highlighted.Remove( t );
          }          
        }
      );
    }

    public static void CancelHighlight( Node3D[] targets )
    {
      RJLog.Log( "CancelHighlight", HierarchyName.Of( targets ) );

      targets.ForEach( t => CancelHighlight( t ) );
    }

    public static void CancelHighlight( Node3D node )
    {
      RJLog.Log( "CancelHighlight", HierarchyName.Of( node ) );

      if ( ! highlighted.ContainsKey( node ) )
      {
        RJLog.Log( "Not highlighted", node );
        return;
      }

      var highlightEffect = highlighted[ node ];

      if ( highlightEffect == null )
      {
        RJLog.Log( "Not highlighted", node );
        return;
      }

      var animations = highlightEffect._active;

      var removals = animations.Filter( a => a.targets.Contains( node ) );

      var numRemoved = 0;

      removals.ForEach(
        ( r )=>
        {
          RJLog.Log( "Removing animation", r );
          highlightEffect.ClearHighlight( r );
          
          numRemoved ++;
        }
      );
      
      RJLog.Log( "Removed animations", numRemoved );

      Lists.RemoveRange( animations, removals );     


    }

    void StartHighlight( Node3D[] targets )
    {
      var animation = _active.Find( a => Arrays.AreEntriesEqual( a.targets, targets ) );

      targets.ForEach(
        ( t )=>
        {
          highlighted[ t ] = this;
        }
      );

      var hdrColor = color.GetHDRColor();
      var colorTransparent = ColorX.Fade( hdrColor, 0 );
      
      
      if ( animation == null )
      {
        animation = new HighlightAnimation();
        animation.targets = targets;

        _active.Add( animation );

        Material material = null;        

        if ( OutlineMaterialMode.Flat_Outline == outlineMaterialMode )
        {
          var outlineMaterial = new OutlineMaterial();
          outlineMaterial.albedo.Set( colorTransparent );
          material = outlineMaterial;
          outlineMaterial.size.Set( 2 );

          outlineMaterial.opacityModulationDuration.Set( opacityModulationDuration );
          outlineMaterial.opacityModulationStrength.Set( opacityModulationStrength );
        }
        else if ( OutlineMaterialMode.Scanner_FancyOutline == outlineMaterialMode )
        {
          var outlineMaterial = new FancyOutlineMaterial();
          outlineMaterial.albedo.Set( colorTransparent );
          material = outlineMaterial;

          outlineMaterial.opacityModulationDuration.Set( opacityModulationDuration );
          outlineMaterial.opacityModulationStrength.Set( opacityModulationStrength );
        }
        else if ( OutlineMaterialMode.Custom_Material == outlineMaterialMode )
        {
          material = (Material) outlineCustomMaterial.Duplicate();
          outlineCustomColorProperty.Set( material, colorTransparent );
        }

        
        if ( OverlayMaterialMode.Flat_Overlay == overlayMaterialMode )
        {
          var overlay = new OverlayMaterial();
          overlay.albedo.Set( ColorX.SetAlpha( hdrColor, 0 ) );
          material.NextPass = overlay; 

          overlay.opacityModulationDuration.Set( opacityModulationDuration );
          overlay.opacityModulationStrength.Set( opacityModulationStrength );
        }
        else if ( OverlayMaterialMode.Custom_Material == overlayMaterialMode )
        {
          var overlay = (Material) overlayCustomMaterial.Duplicate();
          overlayCustomColorProperty.Set( overlay, colorTransparent );
          material.NextPass = overlay; 
        }



        Arrays.ForEach( targets, 
          t =>
          {
            var m = (Material) material.Duplicate( true );

            animation.materials.Add( m );
            Materials.AddOverlay( t, m ); 
          }
        );

      }

      var startPhase = animation.phase;

      animation.tweenID = TimeLineManager.ScheduleSpanIn( timeline, 0, inDuration, 
        ( span, type )=>
        {
          
          if ( animation.tweenID != span.id )
          {
            return;
          }

          if ( ! _active.Contains( animation ) )
          {
            return;
          }

          var phase = span.phase;
          
          
          animation.phase = MathX.Map( phase, 0, 1, startPhase, 1 );  
          
          var p = animation.phase;

          if ( inCurve != null )
          {
            p = inCurve.Sample( p );
          }

          var transitionModulationStrength = Mathf.Lerp( 
            opacityModulationStrength, opacityModulationStrength * p,
            opacityModulationTransition );


          var tweenColor = ColorX.Fade( hdrColor, p );

          if ( TimeLineSpanUpdateType.End == type )
          {
            tweenColor = hdrColor;
            animation.phase = 1;
          }

          animation.materials.ForEach(
            ( m )=>
            {
              if ( OutlineMaterialMode.Flat_Outline == outlineMaterialMode )
              {
                var outlineMaterial = ( OutlineMaterial ) m;
                outlineMaterial.albedo.Set( tweenColor );
                outlineMaterial.opacityModulationStrength.Set( transitionModulationStrength );
              }
              else if ( OutlineMaterialMode.Scanner_FancyOutline == outlineMaterialMode )
              {
                var outlineMaterial = ( FancyOutlineMaterial ) m;
                outlineMaterial.albedo.Set( tweenColor ); 
                outlineMaterial.opacityModulationStrength.Set( transitionModulationStrength );
              }
              else if ( OutlineMaterialMode.Custom_Material == outlineMaterialMode )
              {
                outlineCustomColorProperty.Set( m, tweenColor);
              }

              if ( OverlayMaterialMode.Flat_Overlay == overlayMaterialMode )
              {
                var overlay = (OverlayMaterial) m.NextPass;
                overlay.albedo.Set( ColorX.Fade( tweenColor, overlayOpacity ) );
                overlay.opacityModulationStrength.Set( transitionModulationStrength );
              }
              else if ( OutlineMaterialMode.Custom_Material == outlineMaterialMode )
              {
                var material = (OverlayMaterial) m.NextPass;
                outlineCustomColorProperty.Set( m, tweenColor);
              }



            }
          );
        }
      ).id;
    }

    void EndHighlight( Node3D[] targets )
    {
      var animation = _active.Find( a => Arrays.AreEntriesEqual( a.targets, targets ) );

      if ( animation == null )
      {
        return;
      } 


      var startPhase = animation.phase;
      var hdrColor = ColorX.Fade( color.GetHDRColor(), startPhase );
      var colorTransparent = ColorX.Fade( hdrColor, 0 );

      animation.tweenID = TimeLineManager.ScheduleSpanIn( timeline, 0, outDuration, 
        ( span, type )=>
        {
          
          if ( animation.tweenID != span.id )
          {
            return;
          }

          if ( ! _active.Contains( animation ) )
          {
            return;
          }

          animation.phase = MathX.Map( span.phase, 0, 1, startPhase, 0 );

          var p = animation.phase;

          if ( outCurve != null )
          {
            p = outCurve.Sample( p );
          }

          var transitionModulationStrength = Mathf.Lerp( 
            opacityModulationStrength, opacityModulationStrength * p,
            opacityModulationTransition );

          var tweenColor = ColorX.Fade( hdrColor, p );

          if ( TimeLineSpanUpdateType.End == type )
          {
            tweenColor = colorTransparent;
            animation.phase = 0;

            ClearHighlight( animation );
          }
          else
          {
            animation.materials.ForEach(
              ( m )=>
              {
                if ( OutlineMaterialMode.Flat_Outline == outlineMaterialMode )
                {
                  var outlineMaterial = ( OutlineMaterial ) m;
                  outlineMaterial.albedo.Set( tweenColor );
                  outlineMaterial.opacityModulationStrength.Set( transitionModulationStrength );
                }
                else if ( OutlineMaterialMode.Scanner_FancyOutline == outlineMaterialMode )
                {
                  var outlineMaterial = ( FancyOutlineMaterial ) m;
                  outlineMaterial.albedo.Set( tweenColor ); 
                  outlineMaterial.opacityModulationStrength.Set( transitionModulationStrength );
                }
                else if ( OutlineMaterialMode.Custom_Material == outlineMaterialMode )
                {
                  outlineCustomColorProperty.Set( m, tweenColor );
                }

                if ( OverlayMaterialMode.Flat_Overlay == overlayMaterialMode )
                {
                  var overlay = (OverlayMaterial) m.NextPass;
                  overlay.albedo.Set( ColorX.Fade( tweenColor, overlayOpacity ) );
                  overlay.opacityModulationStrength.Set( transitionModulationStrength );
                }
                else if ( OutlineMaterialMode.Custom_Material == outlineMaterialMode )
                {
                  var material = (OverlayMaterial) m.NextPass;
                  outlineCustomColorProperty.Set( m, tweenColor);
                }

              }
            );
          }
          
        }
      ).id;


    }

  }
}