#!/usr/bin/env python
# Lots of calls to ANTLRv4Parser etc., which are autogenerated and untyped
# so we ignore these errors for the entire file
# mypy: disable-error-code=no-untyped-call

import sys
from typing import Any

from antlr4.CommonTokenStream import CommonTokenStream
from antlr4.FileStream import FileStream
from antlr4.tree.Tree import TerminalNode

from fandango.converters.FandangoConverter import FandangoConverter
from fandango.converters.antlr.ANTLRv4Lexer import ANTLRv4Lexer
from fandango.converters.antlr.ANTLRv4Parser import ANTLRv4Parser
from fandango.converters.antlr.ANTLRv4ParserVisitor import ANTLRv4ParserVisitor
from fandango.language.parse.parser_error_listeners import PythonAntlrErrorListener


class ANTLRFandangoConverterVisitor(ANTLRv4ParserVisitor):
    def strip_quotes(self, s: str) -> str:
        """Strip quotes from a string."""
        if s.startswith('"') and s.endswith('"'):
            return s[1:-1]
        elif s.startswith("'") and s.endswith("'"):
            return s[1:-1]
        return s

    def invert_range(self, range_str: str) -> str:
        """Invert a character range string."""
        if range_str.startswith("[") and range_str.endswith("]"):
            content = range_str[1:-1]
            if content.startswith("^"):
                content = content[1:]  # Remove the negation
            else:
                content = "^" + content  # Add negation
            return f"[{content}]"

        self.addNote(f"cannot invert {range_str}")
        return "not " + range_str  # Cannot handle

    def quote(self, s: str) -> str:
        """Quote a string if it is not already quoted."""
        if "'" in s and '"' in s:
            return f'"""{s}"""'
        if "'" in s:
            return f'"{s}"'
        return f"'{s}'"

    def rquote(self, s: str) -> str:
        return "r" + self.quote(s)

    def addNote(self, message: str) -> None:
        """Add a note to the current rule"""
        if getattr(self, "notes", None) is None:
            self.notes = []
        self.notes.append(message)

    def getNotes(self) -> str:
        """Retrieve notes for current rule"""
        if getattr(self, "notes", None) is None:
            self.notes = []
        if not self.notes:
            return ""
        s = "  # NOTE: " + "; ".join(self.notes)
        self.notes = []
        return s

    def visitChildren(self, ctx: Any, sep: str = "", altEmpty: str = "") -> str:
        """Visit all children of a context `ctx`. Separate them with `sep`. If they evaluate to an empty string, use `altEmpty` instead."""
        if ctx is None or ctx.children is None:
            return ""

        children_s = []
        for child in ctx.children or []:
            s = self.visit(child)
            if s == "":
                s = altEmpty
            if s:
                children_s.append(s)
        return sep.join(children_s)

    def visitGrammarDecl(self, ctx: ANTLRv4Parser.GrammarDeclContext) -> str:
        return f"# {ctx.identifier().getText()}\n"

    def visitParserRuleSpec(self, ctx: ANTLRv4Parser.ParserRuleSpecContext) -> str:
        if ctx.ruleModifiers():
            self.addNote(f"had modifier(s) '{ctx.ruleModifiers().getText()}'")
        if ctx.argActionBlock():
            self.addNote(f"had action block {ctx.argActionBlock().getText()}")
        if ctx.ruleReturns():
            self.addNote(f"had 'returns' clause {ctx.ruleReturns().getText()}")
        if ctx.throwsSpec():
            self.addNote(f"had 'throws' clause {ctx.throwsSpec().getText()}")
        if ctx.rulePrequel():
            self.addNote(f"had rule prequel '{ctx.rulePrequel().getText()}'")
        if ctx.exceptionGroup() and ctx.exceptionGroup().getText():
            self.addNote(f"had exception group '{ctx.exceptionGroup().getText()}'")

        nonterminal = ctx.RULE_REF().getText()
        return (
            f"<{nonterminal}> ::= " + self.visitChildren(ctx) + self.getNotes() + "\n"
        )

    def visitRuleAltList(self, ctx: ANTLRv4Parser.RuleAltListContext) -> str:
        return self.visitChildren(ctx, sep=" | ", altEmpty="''")

    def visitAltList(self, ctx: ANTLRv4Parser.AltListContext) -> str:
        return self.visitChildren(ctx, sep=" | ", altEmpty="''")

    def visitAlternative(self, ctx: ANTLRv4Parser.AlternativeContext) -> str:
        return self.visitChildren(ctx, sep=" ")

    def visitLexerAltList(self, ctx: ANTLRv4Parser.LexerAltListContext) -> str:
        return self.visitChildren(ctx, sep=" | ")

    def visitEbnf(self, ctx: ANTLRv4Parser.EbnfContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        if ctx.blockSuffix():
            return f"{self.visit(ctx.block())}{self.visit(ctx.blockSuffix())}"
        return super().visitEbnf(ctx)

    def visitElement(self, ctx: ANTLRv4Parser.ElementContext) -> str:
        s = ""
        if ctx.labeledElement():
            s = self.visit(ctx.labeledElement())
        if ctx.atom():
            s = self.visit(ctx.atom())
        if ctx.ebnf():
            s = self.visit(ctx.ebnf())
        if ctx.actionBlock():
            s = self.visit(ctx.actionBlock())
        if ctx.ebnfSuffix():
            s += self.visit(ctx.ebnfSuffix())
        return s

    def visitWildcard(self, ctx: ANTLRv4Parser.WildcardContext) -> str:
        return self.rquote(".")

    def visitEbnfSuffix(self, ctx: ANTLRv4Parser.EbnfSuffixContext) -> str:
        suffix = ctx.getText() if ctx else ""
        if len(suffix) > 1:
            # ANTLR has non-greedy suffixes such as *? and +?
            self.addNote(f"was '{suffix}'")
            suffix = suffix[0]
        return suffix

    def visitLexerRuleSpec(self, ctx: ANTLRv4Parser.LexerRuleSpecContext) -> str:
        # This method can be customized to handle rule specifications
        nonterminal = ctx.TOKEN_REF().getText()
        return (
            f"<{nonterminal}> ::= " + self.visitChildren(ctx) + self.getNotes() + "\n"
        )

    def visitRuleref(self, ctx: ANTLRv4Parser.RulerefContext) -> str:
        rule_name = ctx.RULE_REF().getText()
        return f"<{rule_name}>"

    def visitTerminalDef(self, ctx: ANTLRv4Parser.TerminalDefContext) -> str:
        if ctx.STRING_LITERAL():
            terminal = ctx.STRING_LITERAL().getText()
            return f"{terminal}"
        elif ctx.TOKEN_REF():
            token = ctx.TOKEN_REF().getText()
            return f"<{token}>"
        # elif ctx.LEXER_CHAR_SET():
        #     char_set = ctx.LEXER_CHAR_SET().getText()
        #     return f"{char_set}"
        else:
            elem = ctx.getText()
            return f"{elem}"

    def visitLexerElement(self, ctx: ANTLRv4Parser.LexerElementContext) -> str:
        if ctx.actionBlock():
            self.visit(ctx.actionBlock())

        s = ""
        if ctx.lexerAtom():
            s = self.visit(ctx.lexerAtom())
        if ctx.lexerBlock():
            s = self.visit(ctx.lexerBlock())
        if ctx.ebnfSuffix():
            s += self.visit(ctx.ebnfSuffix())
        return s

    def visitActionBlock(self, ctx: ANTLRv4Parser.ActionBlockContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        self.addNote(f"was {ctx.getText()}")
        return super().visitActionBlock(ctx)

    def visitLexerCommand(self, ctx: ANTLRv4Parser.LexerCommandContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        self.addNote(f"was '-> {ctx.getText()}'")
        return super().visitLexerCommand(ctx)

    def visitPredicateOption(self, ctx: ANTLRv4Parser.PredicateOptionContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        self.addNote(f"was '{ctx.getText()}'")
        return super().visitPredicateOption(ctx)

    def visitElementOption(self, ctx: ANTLRv4Parser.ElementOptionContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        self.addNote(f"was '{ctx.getText()}'")
        return super().visitElementOption(ctx)

    def visitLexerElements(self, ctx: ANTLRv4Parser.LexerElementsContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        return self.visitChildren(ctx, sep=" ")

    def visitLexerBlock(self, ctx: ANTLRv4Parser.LexerBlockContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        return "(" + super().visitLexerBlock(ctx) + ")"

    def visitBlockSet(self, ctx: ANTLRv4Parser.BlockSetContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        return "(" + super().visitBlockSet(ctx) + ")"

    def visitBlock(self, ctx: ANTLRv4Parser.BlockContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        if ctx.ruleAction():
            self.addNote(f"action was {ctx.ruleAction().getText()}")
        return "(" + super().visitBlock(ctx) + ")"

    def visitLexerAtom(self, ctx: ANTLRv4Parser.LexerAtomContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        if ctx.LEXER_CHAR_SET():
            return self.rquote(ctx.LEXER_CHAR_SET().getText())
        return super().visitLexerAtom(ctx)

    def visitCharacterRange(self, ctx: ANTLRv4Parser.CharacterRangeContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        range_start = self.strip_quotes(ctx.STRING_LITERAL(0).getText())
        range_end = self.strip_quotes(ctx.STRING_LITERAL(1).getText())
        return self.rquote(f"[{range_start}-{range_end}]")

    def visitNotSet(self, ctx: ANTLRv4Parser.NotSetContext):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        if ctx.setElement():
            return self.rquote(self.invert_range(ctx.setElement().getText()))
        if ctx.blockSet():
            return self.rquote(self.invert_range(ctx.blockSet().getText()))
        return super().visitChildren(ctx)

    def visitTerminal(self, node: TerminalNode):  # type: ignore[no-untyped-def] # antlr doesn't provide return types
        return super().visitTerminal(node)


class ANTLRFandangoConverter(FandangoConverter):
    """Convert ANTLR4 grammar to Fandango format."""

    def __init__(self, filename: str):
        """Initialize with given grammar file"""
        super().__init__(filename)

    def to_fan(self) -> str:
        """Convert the grammar spec to Fandango format"""
        # Read the grammar spec
        input_stream = FileStream(self.filename)
        error_listener = PythonAntlrErrorListener(self.filename)

        lexer = ANTLRv4Lexer(input_stream)
        lexer.removeErrorListeners()
        lexer.addErrorListener(error_listener)

        stream = CommonTokenStream(lexer)
        parser = ANTLRv4Parser(stream)
        parser.removeErrorListeners()
        parser.addErrorListener(error_listener)

        # Start parsing at the 'grammarSpec' rule
        tree = parser.grammarSpec()

        # Create a visitor and evaluate the expression
        converter = ANTLRFandangoConverterVisitor()
        spec = converter.visit(tree)

        header = f"""# Automatically generated from {self.filename!r}.
#
"""
        return f"{header}{spec}"


if __name__ == "__main__":
    for filename in sys.argv[1:]:
        converter = ANTLRFandangoConverter(filename)
        print(converter.to_fan(), end="")
