/*
 * Copyright 2007 Sascha Weinreuter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.intellij.plugins.relaxNG.compact.parser;

import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.intellij.plugins.relaxNG.compact.RncElementTypes;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import static org.intellij.plugins.relaxNG.compact.RncTokenTypes.*;

public abstract class AbstractParsing {
  protected static final TokenSet LA_INCLUDE_CONTENT = TokenSet.orSet(TokenSet.create(
          KEYWORD_DIV, KEYWORD_START), IDENTIFIERS);

  protected static final TokenSet LA_GRAMMAR_CONTENT = TokenSet.orSet(TokenSet.create(
          KEYWORD_INCLUDE), LA_INCLUDE_CONTENT);

  protected static final TokenSet ATTR_OR_ELEMENT = TokenSet.create(
          KEYWORD_ATTRIBUTE, KEYWORD_ElEMENT);

  protected static final TokenSet CONTENT = TokenSet.create(
          KEYWORD_LIST, KEYWORD_MIXED);

  protected static final TokenSet LA_DATATYPE = TokenSet.create(
          KEYWORD_STRING, KEYWORD_TOKEN);

  protected static final TokenSet LA_DECL = TokenSet.create(
          KEYWORD_DATATYPES, KEYWORD_NAMESPACE, KEYWORD_DEFAULT);

  protected static final TokenSet NS_URI_LITERAL = TokenSet.create(KEYWORD_INHERIT, LITERAL);
  protected static final TokenSet ASSIGN_METHOD = TokenSet.create(EQ, CHOICE_EQ, INTERLEAVE_EQ);
  protected final PsiBuilder myBuilder;

  public AbstractParsing(PsiBuilder builder) {
    myBuilder = builder;
  }

  protected final PsiBuilder.Marker begin() {
    final PsiBuilder.Marker marker = myBuilder.mark();
    advance();
    return marker;
  }

  protected final boolean matches(TokenSet set) {
    if (set.contains(currentToken())) {
      advance();
      return true;
    }
    return false;
  }

  protected final boolean matches(IElementType t) {
    if (t == currentToken()) {
      advance();
      return true;
    }
    return false;
  }

  protected final void match(IElementType token, @NotNull @Nls String msg) {
    if (!matches(token)) {
      error(msg);
      advance();
    }
  }

  protected final void match(TokenSet set, @NotNull @Nls String msg) {
    if (!matches(set)) {
      error(msg);
      advance();
    }
  }

  protected final void error(@NotNull @Nls String s) {
    myBuilder.error(s);
  }

  protected final void advance() {
    myBuilder.advanceLexer();
  }

  protected final IElementType currentToken() {
    final IElementType token = myBuilder.getTokenType();
    if (isName(token)) {
      final PsiBuilder.Marker marker = myBuilder.mark();
      makeName();
      if (myBuilder.getTokenType() == LBRACKET) {
        skipAnnotation(marker, RncElementTypes.ANNOTATION_ELEMENT);
        return currentToken();
      } else {
        marker.rollbackTo();
      }
    } else if (token == LBRACKET) {
      final PsiBuilder.Marker marker = myBuilder.mark();
      skipAnnotation(marker, RncElementTypes.ANNOTATION);
      return currentToken();
    } else if (token == GTGT) {
      final PsiBuilder.Marker marker = myBuilder.mark();
      advance();
      if (isName(myBuilder.getTokenType())) {
        makeName();
        if (myBuilder.getTokenType() == LBRACKET) {
          skipAnnotation(marker, RncElementTypes.FORWARD_ANNOTATION);
          return currentToken();
        }
      }
      marker.done(RncElementTypes.FORWARD_ANNOTATION);
      return currentToken();
    }
    return token;
  }

  protected static boolean isName(IElementType token) {
    return IDENTIFIER_OR_KEYWORD.contains(token) || token == PREFIXED_NAME;
  }

  private void skipAnnotation(PsiBuilder.Marker marker, IElementType annotationType) {
    final boolean b = myBuilder.getTokenType() == LBRACKET;
    advance();
    assert b;

    while (!myBuilder.eof() && currentToken() != RBRACKET) {
      advance();
    }
    if (myBuilder.getTokenType() == RBRACKET) {
      advance();
    }
    marker.done(annotationType);
  }

  protected final void makeName() {
    final PsiBuilder.Marker name = myBuilder.mark();
    advance();
    name.done(RncElementTypes.NAME);
  }
}
