package com.wix.rt.lang.parser;

import com.intellij.lang.PsiBuilder;
import com.intellij.lang.javascript.*;
import com.intellij.lang.javascript.parsing.*;
import com.intellij.psi.tree.IElementType;
import com.wix.rt.lang.lexer.RTTokenTypes;

/**
 * @author Dennis.Ushakov
 */
public class RTJSParser extends JavaScriptParser<RTJSParser.RTExpressionParser, StatementParser, FunctionParser, JSPsiTypeParser> {
    public RTJSParser(PsiBuilder builder) {
        super(JavaScriptSupportLoader.JAVASCRIPT_1_5, builder);
        myExpressionParser = new RTExpressionParser();
        myStatementParser = new StatementParser<RTJSParser>(this) {
            @Override
            protected void doParseStatement(boolean canHaveClasses) {
                final IElementType firstToken = builder.getTokenType();
                if (firstToken == JSTokenTypes.LBRACE) {
                    parseExpressionStatement();
                    checkForSemicolon();
                    return;
                }
                if (isIdentifierToken(firstToken)) {
                    final IElementType nextToken = builder.lookAhead(1);
                    if (nextToken == JSTokenTypes.IN_KEYWORD) {
                        parseInStatement();
                        return;
                    }
                }
                if (builder.getTokenType() == JSTokenTypes.LPAR) {
                    if (parseInStatement()) {
                        return;
                    }
                }
                super.doParseStatement(canHaveClasses);
            }

            private boolean parseInStatement() {
                PsiBuilder.Marker statement = builder.mark();
                if (!getExpressionParser().parseInExpression()) {
                    statement.drop();
                    return false;
                }
                statement.done(JSElementTypes.EXPRESSION_STATEMENT);
                return true;
            }
        };
    }

    public void parseRT(IElementType root) {
        final PsiBuilder.Marker rootMarker = builder.mark();
        while (!builder.eof()) {
            getStatementParser().parseStatement();
        }
        rootMarker.done(root);
    }

    protected class RTExpressionParser extends ExpressionParser<RTJSParser> {
        public RTExpressionParser() {
            super(RTJSParser.this);
        }

        @Override
        protected boolean parseUnaryExpression() {
            final IElementType tokenType = builder.getTokenType();
            if (tokenType == JSTokenTypes.OR) {
                builder.advanceLexer();
                if (!parseFilter()) {
                    builder.error("expected filter");
                }
                return true;
            }
            if (tokenType == RTTokenTypes.ONE_TIME_BINDING) {
                final PsiBuilder.Marker expr = builder.mark();
                builder.advanceLexer();
                if (!super.parseUnaryExpression()) {
                    builder.error(JSBundle.message("javascript.parser.message.expected.expression"));
                }
                expr.done(JSElementTypes.PREFIX_EXPRESSION);
                return true;
            }
            return super.parseUnaryExpression();
        }

        @Override
        public boolean parsePrimaryExpression() {
            final IElementType firstToken = builder.getTokenType();
            if (firstToken == JSTokenTypes.STRING_LITERAL) {
                return parseStringLiteral(firstToken);
            }
            if (firstToken == JSTokenTypes.IDENTIFIER && builder.lookAhead(1) == JSTokenTypes.AS_KEYWORD) {
                return parseAsExpression();
            }
            return super.parsePrimaryExpression();
        }

        private boolean parseAsExpression() {
            PsiBuilder.Marker expr = builder.mark();
            buildTokenElement(JSElementTypes.REFERENCE_EXPRESSION);
            builder.advanceLexer();
            parseExplicitIdentifierWithError();
            expr.done(RTElementTypes.AS_EXPRESSION);
            return true;
        }

        private void parseExplicitIdentifierWithError() {
            if (isIdentifierToken(builder.getTokenType())) {
                parseExplicitIdentifier();
            } else {
                builder.error(JSBundle.message("javascript.parser.message.expected.identifier"));
            }
        }

        @Override
        protected int getCurrentBinarySignPriority(boolean allowIn, boolean advance) {
            if (builder.getTokenType() == JSTokenTypes.OR) return 10;
            return super.getCurrentBinarySignPriority(allowIn, advance);
        }

        private boolean parseFilter() {
            final PsiBuilder.Marker mark = builder.mark();
            buildTokenElement(JSElementTypes.REFERENCE_EXPRESSION);
            PsiBuilder.Marker arguments = null;
            while (builder.getTokenType() == JSTokenTypes.COLON) {
                arguments = arguments == null ? builder.mark() : arguments;
                builder.advanceLexer();
                if (!super.parseUnaryExpression()) {
                    builder.error(JSBundle.message("javascript.parser.message.expected.expression"));
                }
            }
            if (arguments != null) {
                arguments.done(JSElementTypes.ARGUMENT_LIST);
            }
            mark.done(RTElementTypes.FILTER_EXPRESSION);
            return true;
        }

        private boolean parseStringLiteral(IElementType firstToken) {
            final PsiBuilder.Marker mark = builder.mark();
            IElementType currentToken = firstToken;
            StringBuilder literal = new StringBuilder();
            while (currentToken == JSTokenTypes.STRING_LITERAL ||
                    currentToken == RTTokenTypes.ESCAPE_SEQUENCE ||
                    currentToken == RTTokenTypes.INVALID_ESCAPE_SEQUENCE) {
                literal.append(builder.getTokenText());
                builder.advanceLexer();
                currentToken = builder.getTokenType();
            }
            mark.done(JSElementTypes.LITERAL_EXPRESSION);
            final String errorMessage = validateLiteralText(literal.toString());
            if (errorMessage != null) {
                builder.error(errorMessage);
            }
            return true;
        }

        public boolean parseInExpression() {
            final PsiBuilder.Marker expr = builder.mark();
            if (isIdentifierToken(builder.getTokenType())) {
                parseExplicitIdentifier();
            } else {
                final PsiBuilder.Marker keyValue = builder.mark();
                parseKeyValue();
                if (JSTokenTypes.IN_KEYWORD.equals(builder.getTokenType())) {
                    keyValue.done(JSElementTypes.PARENTHESIZED_EXPRESSION);
                } else {
                    expr.rollbackTo();
                    return false;
                }
            }
            builder.advanceLexer();
            parseExpression();
//            if (builder.getTokenType() == RTTokenTypes.TRACK_BY_KEYWORD) {
//                builder.advanceLexer();
//                parseExpression();
//            }
            expr.done(RTElementTypes.REPEAT_EXPRESSION);
            return true;
        }

        private void parseKeyValue() {
            builder.advanceLexer();
            final PsiBuilder.Marker comma = builder.mark();
            parseExplicitIdentifierWithError();
            if (builder.getTokenType() == JSTokenTypes.COMMA) {
                builder.advanceLexer();
            } else {
                builder.error(JSBundle.message("javascript.parser.message.expected.comma"));
            }
            parseExplicitIdentifierWithError();
            comma.done(JSElementTypes.COMMA_EXPRESSION);
            if (builder.getTokenType() == JSTokenTypes.RPAR) {
                builder.advanceLexer();
            } else {
                builder.error(JSBundle.message("javascript.parser.message.expected.rparen"));
            }
        }

        private void parseExplicitIdentifier() {
            final PsiBuilder.Marker def = builder.mark();
            buildTokenElement(JSElementTypes.REFERENCE_EXPRESSION);
            def.done(JSElementTypes.DEFINITION_EXPRESSION);
        }
    }
}