using Antlr4.Runtime;
using Antlr4.Runtime.Atn;
using Antlr4.Runtime.Tree;
using Rubberduck.Parsing.Exceptions;
using Rubberduck.Parsing.Grammar;
using Rubberduck.Parsing.TokenStreamProviders;

namespace Rubberduck.Parsing.Parsers;

public delegate IParseTree ParserStartRule(VBAParser parser);

/// <summary>
/// The class is meant to be used for where we need to do rewriting without actually
/// rewriting an existing module in a VBA project. The result is completely free-floating
/// and it is the caller's responsibility to persist the finalized results into one of
/// actual code. This is useful for e.g. creating a code preview for refactoring.
/// </summary>
public static class VBACodeStringParser
{
    //private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
    /// <summary>
    /// Parses the supplied code.
    /// </summary>
    /// <param name="code">The code to parse.</param>
    /// <param name="startRule">The parser rule to begin the process with.</param>
    /// <param name="mode">(Optional) The parser mode to run.</param>
    public static (IParseTree parseTree, TokenStreamRewriter rewriter) Parse(string code, ParserStartRule startRule, ParserMode mode = ParserMode.FallBackSllToLl)
    {
        var tokenStreamProvider = new StringTokenStreamProvider();
        var tokenStream = tokenStreamProvider.Tokens(code ?? string.Empty);
        
        var parser = new VBAParser(tokenStream);
        try
        {
            return ParseInternal(ParserMode.SllOnly, parser, tokenStream, startRule);
        }
        catch (SllPredictionFailException exception)
        {
            var actualMode = parser.Interpreter.PredictionMode.ToString()!.ToUpperInvariant();
            System.Diagnostics.Debug.Assert(actualMode == "SLL");

            var message = $"{actualMode} mode failed while parsing the code at symbol {exception.OffendingSymbol.Name} at L{exception.OffendingSymbol.Range.Start.Line}C{exception.OffendingSymbol.Range.Start.Character}. Retrying using LL.";
            LogAndReset(message, exception, parser, tokenStream);

            if (parser.Interpreter.PredictionMode == PredictionMode.Sll)
            {
                return ParseInternal(ParserMode.LlOnly, parser, tokenStream, startRule);
            }
        }
        catch (Exception exception)
        {
            var actualMode = parser.Interpreter.PredictionMode.ToString()!.ToUpperInvariant();
            System.Diagnostics.Debug.Assert(actualMode == "SLL");

            var message = $"{actualMode} mode threw an exception. Retrying LL mode.";
            LogAndReset(message, exception, parser, tokenStream);

            if (parser.Interpreter.PredictionMode == PredictionMode.Sll)
            {
                return ParseInternal(ParserMode.LlOnly, parser, tokenStream, startRule);
            }
        }

        return (null!, null!);
    }

    private static (IParseTree parseTree, TokenStreamRewriter rewriter) ParseInternal(ParserMode mode, VBAParser parser, CommonTokenStream tokenStream, ParserStartRule startRule)
    {
        if (mode == ParserMode.LlOnly)
        {
            parser.Interpreter.PredictionMode = PredictionMode.Ll;
        }
        else
        {
            parser.Interpreter.PredictionMode = PredictionMode.Sll;
        }
        var tree = startRule.Invoke(parser);
        return (tree, new TokenStreamRewriter(tokenStream));
    }

    private static void LogAndReset(string logWarnMessage, Exception exception, VBAParser parser, CommonTokenStream tokenStream)
    {
        tokenStream.Reset();
        parser.Reset();
    }
}
