package com.intellij.bash.parser;

import com.intellij.bash.BashTypes;
import com.intellij.bash.lexer.BashLexer;
import com.intellij.bash.lexer.BashTokenTypes;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.parser.GeneratedParserUtilBase;
import com.intellij.openapi.util.Key;
import com.intellij.psi.tree.IElementType;
import gnu.trove.TObjectLongHashMap;

public class BashParserUtil extends GeneratedParserUtilBase {
  private static final Key<TObjectLongHashMap<String>> MODES_KEY = Key.create("MODES_KEY");

  private static TObjectLongHashMap<String> getParsingModes(PsiBuilder b) {
    TObjectLongHashMap<String> flags = b.getUserData(MODES_KEY);
    if (flags == null) b.putUserData(MODES_KEY, flags = new TObjectLongHashMap<>());
    return flags;
  }

  static boolean isModeOn(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level, String mode) {
    return getParsingModes(b).get(mode) > 0;
  }

  public static boolean isModeOff(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level, String mode) {
    return getParsingModes(b).get(mode) == 0;
  }

  static boolean withOn(PsiBuilder b, int level_, String mode, Parser parser) {
    return withImpl(b, level_, mode, true, parser, parser);
  }

  public static boolean withCleared(PsiBuilder b, int level_, String mode, Parser whenOn, Parser whenOff) {
    return withImpl(b, level_, mode, false, whenOn, whenOff);
  }

  private static boolean withImpl(PsiBuilder b, int level_, String mode, boolean onOff, Parser whenOn, Parser whenOff) {
    TObjectLongHashMap<String> map = getParsingModes(b);
    long prev = map.get(mode);
    boolean change = ((prev & 1) == 0) == onOff;
    if (change) map.put(mode, prev << 1 | (onOff ? 1 : 0));
    boolean result = (change ? whenOn : whenOff).parse(b, level_);
    if (change) map.put(mode, prev);
    return result;
  }

  public static boolean enterMode(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level, String mode) {
    TObjectLongHashMap<String> flags = getParsingModes(b);
    if (!flags.increment(mode)) flags.put(mode, 1);
    return true;
  }

  public static boolean exitMode(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level, String mode) {
    TObjectLongHashMap<String> flags = getParsingModes(b);
    long count = flags.get(mode);
    if (count == 1) {
      flags.remove(mode);
    }
    else if (count > 1) {
      flags.put(mode, count - 1);
    }
    else {
      b.error("Could not exit inactive '" + mode + "' mode at offset " + b.getCurrentOffset());
    }
    return true;
  }

  static boolean condOp(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level) {
    return consumeTokenFast(b, BashTokenTypes.conditionalOperators);
  }

  static boolean paramExpansionOp(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level) {
    return consumeTokenFast(b, BashTokenTypes.paramExpansionOperators);
  }

  static boolean backslash(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level) {
    return consumeTokenFast(b, "\\\n");
  }

  static boolean notQuote(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level) {
    if (b.getTokenType() == BashTypes.QUOTE) return false;
    b.advanceLexer();
    return true;
  }

  static boolean parseUntilSpace(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level, Parser parser) {
    PsiBuilder.Marker mark = b.mark();
    while (true) {
      if (!parser.parse(b, level) || BashLexer.whitespaceTokens.contains(b.rawLookup(0)) || b.eof()) {
        mark.drop();
        return true;
      }
    }
  }

  static boolean keywordsRemapped(PsiBuilder b, @SuppressWarnings("UnusedParameters") int level) {
    IElementType type = b.getTokenType();
    if (BashTokenTypes.identifierKeywords.contains(type)) {
      PsiBuilder.Marker mark = b.mark();
      b.remapCurrentToken(BashTypes.WORD);
      b.advanceLexer();
      mark.done(BashTypes.SIMPLE_COMMAND_ELEMENT);
      return true;
    }
    return false;
  }
}

@@ -2705,32 +2705,138 @@
 tionalOperators)
+ %7C%7C%0A        consumeTokenFast(b, BashTokenTypes.arithmeticEqualityOps) %7C%7C consumeTokenFast(b, BashTypes.EQ)
 ;%0A  %7D%0A%0A  static 

support some conditionals