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

namespace Rokojori
{ 
  [Tool]
  [GlobalClass]
  public partial class LineFading:FadingModifier
  { 
    public enum Mode
    {
      World,
      WorldPoint_To_Camera,
      Screen
    }

    [Export]
    public Mode mode;

    public override bool IsSameType( FadingModifier modifier )
    {
      if ( modifier is LineFading lf )
      {
        return lf.mode == mode;
      }

      return false;
    }

    public override List<ShaderCode> GetFadingCode( ShaderGenerationContext context, int offsetIndex, AlphaFadeMode parentFadeMode )
    {
      var alphaFadeMode = GetFadeMode( parentFadeMode );

      var suffix = GetSuffix( offsetIndex );

      if ( context.isIncludesPhase )
      {
        return IncludeTransformLibrary().Concat( IncludeFromLibrary( "Line3" ) ).Concat( IncludeSDFLibrary() );
      }

      if ( context.isVariablesPhase )
      {
        if ( Mode.Screen == mode )
        {
          List<string> variables = 
          [
            Uniform( "screenLineDistanceFadeWorldPositionA" + suffix, Vector3.Zero ),
            Uniform( "screenLineDistanceFadeWorldPositionB" + suffix, Vector3.One ),
            VaryingVec3( "screenLineDistanceFadeScreenPositionA" + suffix ),
            VaryingVec3( "screenLineDistanceFadeScreenPositionB" + suffix ),
            Uniform( "screenLineDistanceFadeInnerRadius" + suffix, 0.1f ),
            Uniform( "screenLineDistanceFadeOuterRadius" + suffix, 0.15f ),
            VaryingVec2( "screenLineDistanceViewportRatio" + suffix )
          ];

          variables = variables.Map( v => v.LineBreak() );

          return ToCode( variables );
        }
        else if ( Mode.World == mode )
        {
          List<string> variables = 
          [
            Uniform( "worldLineDistanceFadeWorldPositionA" + suffix, Vector3.Zero ),
            Uniform( "worldLineDistanceFadeWorldPositionB" + suffix, Vector3.One ),
            VaryingVec3( "worldLineDistanceFadeViewPositionA" + suffix ),
            VaryingVec3( "worldLineDistanceFadeViewPositionB" + suffix ),
            Uniform( "worldLineDistanceFadeInnerRadius" + suffix, 0.1f ),
            Uniform( "worldLineDistanceFadeOuterRadius" + suffix, 0.15f )
          ];

          variables = variables.Map( v => v.LineBreak() );

          return ToCode( variables );
        }
        else if ( Mode.WorldPoint_To_Camera == mode )
        {
          List<string> variables = 
          [
            Uniform( "worldCameraLineDistanceFadeWorldPosition" + suffix, Vector3.Zero ),
            VaryingVec3( "worldCameraLineDistanceFadeViewPosition" + suffix ),
            Uniform( "worldCameraLineDistanceFadeInnerRadius" + suffix, 0.1f ),
            Uniform( "worldCameraLineDistanceFadeOuterRadius" + suffix, 0.15f )
          ];

          variables = variables.Map( v => v.LineBreak() );

          return ToCode( variables );
        }
      }

      if ( context.isVertexPhase )
      { 
        if ( Mode.Screen == mode )
        {
          var code = 
          $@"

          screenLineDistanceFadeScreenPositionA{suffix} = vec3( worldToScreen( screenLineDistanceFadeWorldPositionA{suffix}, VIEW_MATRIX, PROJECTION_MATRIX ), 0.0 );
          screenLineDistanceFadeScreenPositionB{suffix} = vec3( worldToScreen( screenLineDistanceFadeWorldPositionB{suffix}, VIEW_MATRIX, PROJECTION_MATRIX ), 0.0 );

          ";
          
          return ToCode( code.Indent( "  ") );
        }
        else if ( Mode.World == mode )
        {
          var code = 
          $@"

          worldLineDistanceFadeViewPositionA{suffix} = worldToView( worldLineDistanceFadeWorldPositionA{suffix}, VIEW_MATRIX );
          worldLineDistanceFadeViewPositionB{suffix} = worldToView( worldLineDistanceFadeWorldPositionB{suffix}, VIEW_MATRIX );

          ";
          
          return ToCode( code.Indent( "  ") );

        }
        else if ( Mode.WorldPoint_To_Camera == mode )
        {
          var code = 
          $@"

          worldCameraLineDistanceFadeViewPosition{suffix} = worldToView( worldCameraLineDistanceFadeWorldPosition{suffix}, VIEW_MATRIX );

          ";
          
          return ToCode( code.Indent( "  ") );

        }

        
      }

      if ( context.isFragmentPhase )
      {
        if ( Mode.Screen == mode )
        {
          var fadeVariable = "screenLineDistanceFadeAmount" + suffix;
          var fade = AlphaFade.Fragment( fadeVariable, alphaFadeMode );
          var code = 
          $@"

          {{

          vec2 screenRatioMultiply = VIEWPORT_SIZE;

          if ( screenRatioMultiply.x > screenRatioMultiply.y )
          {{
            screenRatioMultiply /= VIEWPORT_SIZE.x;
          }}
          else
          {{
            screenRatioMultiply /= VIEWPORT_SIZE.y;
          }}

          vec2 screenVertex = viewToScreen( VERTEX, PROJECTION_MATRIX ) * screenRatioMultiply;

         

          Line3 screenLineDistanceFadeLine3;
          screenLineDistanceFadeLine3.start = screenLineDistanceFadeScreenPositionA{suffix} * vec3( screenRatioMultiply, 1.0 );
          screenLineDistanceFadeLine3.end = screenLineDistanceFadeScreenPositionB{suffix} * vec3( screenRatioMultiply, 1.0 );

          float screenLineDistanceFadeDistance = Line3_getDistance( screenLineDistanceFadeLine3, vec3( screenVertex, 0.0 ) );

          float {fadeVariable} = clamp( smoothstep( screenLineDistanceFadeInnerRadius{suffix}, screenLineDistanceFadeOuterRadius{suffix}, screenLineDistanceFadeDistance ), 0.0, 1.0 );

          {fade}

          }}
          ";

          return ToCode( code.Indent( "  ") );
        }
        else if ( Mode.World == mode )
        {
          var fadeVariable = "worldLineDistanceFadeAmount" + suffix;
          var fade = AlphaFade.Fragment( fadeVariable, alphaFadeMode );
          var code = 
          $@"

          {{
         
          Line3 worldLineDistanceFadeLine3;
          worldLineDistanceFadeLine3.start = worldLineDistanceFadeViewPositionA{suffix};
          worldLineDistanceFadeLine3.end = worldLineDistanceFadeViewPositionB{suffix};

          float worldLineDistanceFadeDistance = Line3_getDistance( worldLineDistanceFadeLine3, VERTEX );

          float {fadeVariable} = clamp( smoothstep( worldLineDistanceFadeInnerRadius{suffix}, worldLineDistanceFadeOuterRadius{suffix}, worldLineDistanceFadeDistance ), 0.0, 1.0 );

          {fade}

          }}
          ";

          return ToCode( code.Indent( "  ") );
        }
        else if ( Mode.WorldPoint_To_Camera == mode )
        {
          var fadeVariable = "worldLineDistanceFadeAmount" + suffix;
          var fade = AlphaFade.Fragment( fadeVariable, alphaFadeMode );
          var code = 
          $@"

          {{
         
          float worldLineDistanceFadeDistance = sdRoundCone( VERTEX, vec3( 0.0, 0.0, 0.0), worldCameraLineDistanceFadeViewPosition{suffix}, 0, worldCameraLineDistanceFadeInnerRadius );


          float {fadeVariable} = clamp( smoothstep( 0, ( worldCameraLineDistanceFadeOuterRadius{suffix} - worldCameraLineDistanceFadeInnerRadius{suffix} ), worldLineDistanceFadeDistance ), 0.0, 1.0 );

          {fade}

          }}
          ";

          return ToCode( code.Indent( "  ") );
        }
      } 

      return [];
    }

  }
}