/**
 * MVEL 2.0
 * Copyright (C) 2007 The Codehaus
 * Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
 *
 * 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.mvel2;

import org.mvel2.compiler.AbstractParser;
import org.mvel2.util.StringAppender;

import java.util.Map;

import static org.mvel2.util.ParseTools.*;

/**
 * A simple, fast, macro processor.  This processor works by simply replacing a matched identifier with a set of code.
 */
public class MacroProcessor extends AbstractParser implements PreProcessor {
  private Map<String, Macro> macros;

  public MacroProcessor() {
  }

  public MacroProcessor(Map<String, Macro> macros) {
    this.macros = macros;
  }

  public char[] parse(char[] input) {
    setExpression(input);

    StringAppender appender = new StringAppender();

    int start;
    boolean macroArmed = true;
    String token;

    for (; cursor < length; cursor++) {
      start = cursor;
      while (cursor < length && isIdentifierPart(expr[cursor])) cursor++;
      if (cursor > start) {
        if (macros.containsKey(token = new String(expr, start, cursor - start)) && macroArmed) {
          appender.append(macros.get(token).doMacro());
        }
        else {
          appender.append(token);
        }
      }

      if (cursor < length) {
        switch (expr[cursor]) {
          case '\\':
            cursor++;
            break;
          case '/':
            start = cursor;

            if (cursor + 1 != length) {
              switch (expr[cursor + 1]) {
                case '/':
                  while (cursor != length && expr[cursor] != '\n') cursor++;
                  break;
                case '*':
                  int len = length - 1;
                  while (cursor != len && !(expr[cursor] == '*' && expr[cursor + 1] == '/')) cursor++;
                  cursor += 2;
                  break;
              }
            }

            if (cursor < length) cursor++;

            appender.append(new String(expr, start, cursor - start));

            if (cursor < length) cursor--;
            break;

          case '"':
          case '\'':
            appender.append(new String(expr, (start = cursor),
                (cursor = captureStringLiteral(expr[cursor], expr, cursor, length)) - start));

            if (cursor >= length) break;
            else if (isIdentifierPart(expr[cursor])) cursor--;

          default:
            switch (expr[cursor]) {
              case '.':
                macroArmed = false;
                break;
              case ';':
              case '{':
              case '(':
                macroArmed = true;
                break;
            }

            appender.append(expr[cursor]);
        }
      }
    }

    return appender.toChars();
  }

  public String parse(String input) {
    return new String(parse(input.toCharArray()));
  }

  public Map<String, Macro> getMacros() {
    return macros;
  }

  public void setMacros(Map<String, Macro> macros) {
    this.macros = macros;
  }

  public void captureToWhitespace() {
    while (cursor < length && !isWhitespace(expr[cursor])) cursor++;
  }
}
