
using Godot;

using Rokojori;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Linq;

namespace Rokojori.DocGenerator
{  
  public class ShaderDocGenerator
  {   
    public static readonly string GDShaderExtension = ".gdshader";
    public static readonly string GDShaderIncludeExtension = ".gdshaderinc";

    ClassDocInfo info = new ClassDocInfo();

    List<LexerEvent> _lexerEvents;
    LexerList tokens;


    public ClassDocInfo Create( string filePath, string rootPath )
    { 
      var fileContent = FilesSync.LoadUTF8( filePath );
      _lexerEvents = GDShaderLexer.Lex( fileContent );
      tokens = LexerList.Create( _lexerEvents );

      if ( filePath.EndsWith( GDShaderIncludeExtension ) )
      {
        info.definitionType = "GDShaderInclude";
      }
      else
      {
        info.definitionType = "GDShader";
      }

      info.name = RegexUtility.TrimToLastPathFragment( filePath ).ReplaceEnd( GDShaderIncludeExtension, "" ).ReplaceEnd( GDShaderExtension, "" );
      info.sourcePath = FilePath.Join( "Runtime", filePath.ReplaceStart( rootPath ) );

      GetIncludes();
      GetUniforms();
      GetBlocks();


      return info;
    }

    void GetIncludes()
    {
      var includePrexix = "#include ";

      // RJLog.Log( "Processing includes:", tokens.GetAll( LexerMatcherLibrary.CInstructionMatcher ).events.Count );

      tokens.ForEach( LexerMatcherLibrary.CInstructionMatcher,
        ( t )=>
        {
         
          var match = t.match;

          // RJLog.Log( "Processing include:", match );

          if ( ! match.StartsWith( includePrexix ) )
          {
            //  RJLog.Log( "Processing include:", match );
            return;
          }

         
          var path = match.ReplaceStart( includePrexix, "" );
          path = path.Substring( 1, path.Length - 2 );
          var name = RegexUtility.TrimToLastPathFragment( path ).TrimSuffix( ".gdshaderinc");
          

          var memberInfo = MemberInfo.Create( "ShaderInclude" );
          memberInfo.name = name;
          memberInfo.path = path;
          memberInfo.dataType = "GDShaderInclude";

          info.memberInfos.Add( memberInfo );
        }
      );
    }

    void GetUniforms()
    {
      var sequences = tokens.FindSequences( 
        ( int tokenIndex, bool inSequence ) =>
        {
          var token = tokens.events[ tokenIndex ];

          if ( ! inSequence )
          {
            if ( token.Is( GDShaderLexer.UsageMatcher ) )
            {
              return Trillean.True;
            }

            return Trillean.Any;
          }
          else
          {
            return TrilleanLogic.FromBool( ! token.Is( LexerMatcherLibrary.OperatorMatcher, ";" ) );
          }

        }
      );

      // RJLog.Log( "Uniforms/Varyings:", sequences.Count );

      sequences.ForEach(
        ( s )=>
        {
          if ( s == null )
          {
            return;
          }


          var filtered = s.Filter( s => ! s.IsAnyOf( GDShaderLexer.ignore ) );

          // RJLog.Log( "'" + filtered.Map( s => s.match ).Join( "', '") + "'" );

          if ( filtered.Count < 3 )
          {
            return;
          }

          
          var first = filtered[ 0 ];
          var isUniform = first.MatchIs( "uniform" );
          var memberInfo = isUniform ? MemberInfo.CreateUniform() : MemberInfo.CreateVarying();

          memberInfo.dataType = filtered[ 1 ].match;
          memberInfo.name     = filtered[ 2 ].match;

          // LINE INFO

          info.memberInfos.Add( memberInfo );
        }
      );
    }

    void GetBlocks()
    {
      var blocks = tokens.GetBlocks();

      if ( blocks == null )
      {
        return;
      }

      // RJLog.Log( "Num blocks:", blocks.Count );
      blocks.ForEach( b => ProcessBlock( b ) );
    }

    void ProcessBlock( RangeI blockRange )
    {
      var blockStart = blockRange.min;
      
      var firstTokenBeforeResult = tokens.ReverseFind( blockStart - 1,
        ( t ) =>
        {
          // RJLog.Log( "Token type:", t );

          if ( t.IsAnyOf( GDShaderLexer.ignore ) )
          {
            return LexerEvent.FindResultType.KeepSearching;
          } 

          if ( ! 
            (
              t.Is( LexerMatcherLibrary.BracketMatcher, ")" ) ||
              t.Is( LexerMatcherLibrary.CwordMatcher ) 
            )          
          ) 
          {
            // RJLog.Log( "Invalid token type:", t );
            return LexerEvent.FindResultType.Error;
          }

          return LexerEvent.FindResultType.Found;
        }
      );

      if ( ! firstTokenBeforeResult.found )
      {
        // RJLog.Log( "No first token before block found!" );
        return;

      }

      var firstToken = _lexerEvents[ firstTokenBeforeResult.index ];
      var isMethod = firstToken.Is( LexerMatcherLibrary.BracketMatcher, ")" );

      

      if ( isMethod )
      {
        var closeBracketResult = firstTokenBeforeResult;
        var openBracketResult = tokens.ReverseFindOpeningBracket( closeBracketResult.index );

        if ( ! openBracketResult.found )
        {
          return;
        }
        
        var nameToken = tokens.ReverseFind( openBracketResult.index, 
          ( t ) => 
          { 
            return t.Is( LexerMatcherLibrary.CFunctionMatcher ) ? LexerEvent.FindResultType.Found : LexerEvent.FindResultType.KeepSearching;
          }
        );

        if ( ! nameToken.found )
        {
          return;
        }

        var typeToken = tokens.ReverseFind( nameToken.index - 1, 
          ( t ) => 
          { 
            return t.Is( LexerMatcherLibrary.CwordMatcher ) ? LexerEvent.FindResultType.Found : LexerEvent.FindResultType.KeepSearching;
          }
        );

        var memberInfo = MemberInfo.CreateMethod();
        memberInfo.name = tokens.events[ nameToken.index ].match;
        memberInfo.dataType = tokens.events[ typeToken.index ].match;

        var parameters = tokens.Seperate( openBracketResult.index + 1, closeBracketResult.index - 1, GDShaderLexer.ignore );

        parameters.ForEach(
          ( p )=>
          {
            var pValues = tokens.Range( p ).Filter( t => ! t.IsAnyOf( GDShaderLexer.ignore ) );

            var parameterType = new ParameterType();
            parameterType.name = pValues.events.ReverseAt( 0 ).match;
            parameterType.type = pValues.events.ReverseAt( 1 ).match;

            if ( pValues.events.Count > 2 )
            {
              parameterType.modifiers.Add( pValues.events.ReverseAt( 2 ).match );  
            }

            memberInfo.parameters.Add( parameterType );
            
          }
        );

        info.memberInfos.Add( memberInfo );

        // RJLog.Log( "Method found!", memberInfo.memberType, memberInfo.name );
       
      }
      else
      {
        // RJLog.Log( "No method found!", firstToken );
      }
    }
  }
}