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

namespace Rokojori
{

  public class ShaderCode
  {    
    public string variantName = "";
    public ShaderPhase phase;
    public bool sortableCode = false;
    public List<ShaderCodeVariableOccurance> variableOccurances = new List<ShaderCodeVariableOccurance>();


    protected virtual string _GetCode( ShaderGenerationContext context )
    {
      return "";
    } 

    public string GetCode( ShaderGenerationContext context )
    {
      return _GetCode( context );
    }

    public void ResolveVariableOccurances( ShaderGenerationContext context )
    {
      var code = GetCode( context );
      var lexed = GDShaderLexer.Lex( code );
      var index = lexed.CreateIndex();

      var relevantVariables = ShaderCodeVariable.DefaultVariables.Map( v => v.variableName ).CreateSet();
      var foundVariables = lexed.Filter( le => le.Is( LexerMatcherLibrary.CwordMatcher ) && relevantVariables.Contains( le.match ) );
      
      var mapped = new Dictionary<ShaderCodeVariableOccurance,LexerEvent>();

      foundVariables.ForEach(
        ( w )=>
        {
          var nextTokenResult = LexerEvent.Find( lexed, index[ w ], 
            ( le ) =>
            {
              if ( le.Is( LexerMatcherLibrary.OperatorMatcher ) )
              {
                return LexerEvent.FindResultType.Found;
              }

              if ( le.IsAnyOf( LexerMatcherLibrary.Ignore ) )
              {
                return LexerEvent.FindResultType.KeepSearching;
              }

              return LexerEvent.FindResultType.NotFound;
              
            } 
          );

      
          if ( LexerEvent.FindResultType.NotFound == nextTokenResult.type )
          {
            variableOccurances.Add( ShaderCodeVariableOccurance.Read( w.match ) );
          }
          else
          { 
            var token = lexed[ nextTokenResult.index ];

            if ( token.match == "=" )
            {
              variableOccurances.Add( ShaderCodeVariableOccurance.Assignment( w.match ) );
            }
            else if ( token.MatchIsAny( "++", "--", "+=", "-=", "*=", "/=" ) )
            {
              variableOccurances.Add( ShaderCodeVariableOccurance.Modification( w.match ) );
            }
            else
            {
              variableOccurances.Add( ShaderCodeVariableOccurance.Read( w.match ) );
            }
          }

          mapped[ variableOccurances.Last() ] = w;
        }
      );

      variableOccurances.ForEach(
        ( vo )=>
        { 
          var lineInfo = mapped[ vo ].GetLineInfo( code );
          RJLog.Log( vo.type, ":", vo.variable.variableName, " >> ",  lineInfo );
        }
      );

    }


  }

  public class StringShaderCode:ShaderCode
  {
    public string code = "";

    public StringShaderCode( string code )
    {
      this.code = code;
    }
    
    protected override string _GetCode( ShaderGenerationContext context )
    {
      return code;
    } 

    public static StringShaderCode FromLines( List<string> code, bool addBreaks = true )
    {
      return new StringShaderCode( code.Join( addBreaks ? "\n" : "" ) );      
    }
  }

  public class BuiltInTextureShaderCode:ShaderCode
  {
    public enum TextureType
    {
      Screen,
      Depth,
      NormalRoughness
    }

    public TextureType textureType => _textureType;
    TextureType _textureType;

    public static BuiltInTextureShaderCode Screen
    {
      get
      {
        var sc = new BuiltInTextureShaderCode();
        sc._textureType = TextureType.Screen;
        return sc;
      }
    }

    public static BuiltInTextureShaderCode Depth
    {
      get
      {
        var sc = new BuiltInTextureShaderCode();
        sc._textureType = TextureType.Depth;
        return sc;
      }
    }

    public static BuiltInTextureShaderCode NormalRoughness
    {
      get
      {
        var sc = new BuiltInTextureShaderCode();
        sc._textureType = TextureType.NormalRoughness;
        return sc;
      }
    }

    public ShaderCode GetBuiltInCode()
    {
      if ( _textureType == TextureType.Screen )
      {
        return new StringShaderCode( ShaderGenerationModule.ScreenTextureUniform().LineBreak() );
      }

      if ( _textureType == TextureType.Depth )
      {
        return new StringShaderCode( ShaderGenerationModule.DepthTextureUniform().LineBreak() );
      }

      if ( _textureType == TextureType.NormalRoughness )
      {
        return new StringShaderCode( ShaderGenerationModule.NormalRoughnessTextureUniform().LineBreak() );
      }

      return null;
    }


  }

}