package p.studio.compiler.pbs.parser;

import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsToken;
import p.studio.compiler.pbs.lexer.PbsTokenKind;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;

/**
 * High-level manual parser facade for PBS source files.
 */
public final class PbsParser {
    public enum ParseMode {
        ORDINARY,
        INTERFACE_MODULE
    }

    private final PbsParserContext context;
    private final PbsTokenCursor cursor;
    private final PbsTopLevelParser topLevelParser;
    private final PbsBlockParser blockParser;

    private PbsParser(
            final ReadOnlyList<PbsToken> tokens,
            final FileId fileId,
            final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics,
            final ParseMode parseMode) {
        this.context = new PbsParserContext(new PbsTokenCursor(tokens), fileId, diagnostics, parseMode);
        this.cursor = context.cursor();

        final var exprParser = new PbsExprParser(context, this::parseExpressionSurfaceBlock);
        final var attributeParser = new PbsAttributeParser(context, this::isTopLevelRestartToken);
        final var typeParser = new PbsTypeParser(context);

        this.blockParser = new PbsBlockParser(context, exprParser, typeParser);

        final var declarationParser = new PbsDeclarationParser(
                context,
                exprParser,
                attributeParser,
                typeParser,
                blockParser::parseBlock,
                this::consumeDeclarationTerminator,
                this::synchronizeTopLevel);
        this.topLevelParser = new PbsTopLevelParser(context, attributeParser, declarationParser);
    }

    public static PbsAst.File parse(
            final ReadOnlyList<PbsToken> tokens,
            final FileId fileId,
            final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics) {
        return parse(tokens, fileId, diagnostics, ParseMode.ORDINARY);
    }

    public static PbsAst.File parse(
            final ReadOnlyList<PbsToken> tokens,
            final FileId fileId,
            final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics,
            final ParseMode parseMode) {
        return new PbsParser(tokens, fileId, diagnostics, parseMode).parseFile();
    }

    private PbsAst.File parseFile() {
        return topLevelParser.parseFile();
    }

    private PbsAst.Block parseExpressionSurfaceBlock(final String message) {
        return blockParser.parseBlock(message);
    }

    private long consumeDeclarationTerminator() {
        var end = cursor.previous().end();
        var parenDepth = 0;
        while (!cursor.isAtEnd()) {
            final var token = cursor.peek();
            if (parenDepth == 0 && token.kind() == PbsTokenKind.SEMICOLON) {
                return cursor.advance().end();
            }
            if (parenDepth == 0 && token.kind() == PbsTokenKind.LEFT_BRACE) {
                return consumeBalancedBraces(cursor.advance());
            }

            final var consumed = cursor.advance();
            end = consumed.end();
            if (consumed.kind() == PbsTokenKind.LEFT_PAREN) {
                parenDepth++;
            } else if (consumed.kind() == PbsTokenKind.RIGHT_PAREN) {
                parenDepth = Math.max(0, parenDepth - 1);
            }
        }
        return end;
    }

    private long consumeBalancedBraces(final PbsToken leftBrace) {
        var depth = 1;
        var end = leftBrace.end();
        while (!cursor.isAtEnd() && depth > 0) {
            final var token = cursor.advance();
            end = token.end();
            if (token.kind() == PbsTokenKind.LEFT_BRACE) {
                depth++;
            } else if (token.kind() == PbsTokenKind.RIGHT_BRACE) {
                depth--;
            }
        }
        if (depth > 0) {
            context.report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected '}' to close declaration body");
        }
        return end;
    }

    private void synchronizeTopLevel() {
        while (!cursor.isAtEnd()) {
            if (isTopLevelRestartToken(cursor.peek().kind())) {
                return;
            }
            if (cursor.match(PbsTokenKind.SEMICOLON)) {
                return;
            }
            cursor.advance();
        }
    }

    private boolean isTopLevelRestartToken(final PbsTokenKind kind) {
        return kind == PbsTokenKind.FN
                || kind == PbsTokenKind.IMPORT
                || kind == PbsTokenKind.DECLARE
                || kind == PbsTokenKind.IMPLEMENTS
                || kind == PbsTokenKind.LEFT_BRACKET;
    }
}
