package org.mvel2.util;

import org.mvel2.CompileException;
import org.mvel2.ParserContext;
import org.mvel2.ast.EndOfStatement;
import org.mvel2.ast.Function;
import org.mvel2.compiler.AbstractParser;

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

public class FunctionParser {
  private String name;

  private int cursor;
  private int start;
  private int length;

  private int fields;
  private char[] expr;
  private ParserContext pCtx;

  private ExecutionStack splitAccumulator;

  public FunctionParser(String functionName, int cursor, int endOffset, char[] expr, int fields, ParserContext pCtx, ExecutionStack splitAccumulator) {
    this.name = functionName;
    this.cursor = this.start = cursor;
    this.length = endOffset;

    this.expr = expr;
    this.fields = fields;
    this.pCtx = pCtx;
    this.splitAccumulator = splitAccumulator;
  }

  public Function parse() {
    int start = cursor;

    int startCond = 0;
    int endCond = 0;

    int blockStart;
    int blockEnd;

    int end = cursor + length;

    cursor = ParseTools.captureToNextTokenJunction(expr, cursor, end, pCtx);

    if (expr[cursor = ParseTools.nextNonBlank(expr, cursor)] == '(') {
      /**
       * If we discover an opening bracket after the function name, we check to see
       * if this function accepts parameters.
       */
      endCond = cursor = balancedCaptureWithLineAccounting(expr, startCond = cursor, end, '(', pCtx);
      startCond++;
      cursor++;

      cursor = ParseTools.skipWhitespace(expr, cursor);

      if (cursor >= end) {
        throw new CompileException("incomplete statement", expr, cursor);
      }
      else if (expr[cursor] == '{') {
        blockEnd = cursor = balancedCaptureWithLineAccounting(expr, blockStart = cursor, end, '{', pCtx);
      }
      else {
        blockStart = cursor - 1;
        cursor = ParseTools.captureToEOS(expr, cursor, end, pCtx);
        blockEnd = cursor;
      }
    }
    else {
      /**
       * This function has not parameters.
       */
      if (expr[cursor] == '{') {
        /**
         * This function is bracketed.  We capture the entire range in the brackets.
         */
        blockEnd = cursor = balancedCaptureWithLineAccounting(expr, blockStart = cursor, end, '{', pCtx);
      }
      else {
        /**
         * This is a single statement function declaration.  We only capture the statement.
         */
        blockStart = cursor - 1;
        cursor = ParseTools.captureToEOS(expr, cursor, end, pCtx);
        blockEnd = cursor;
      }
    }

    /**
     * Trim any whitespace from the captured block range.
     */
    blockStart = ParseTools.trimRight(expr, start, blockStart + 1);
    blockEnd = ParseTools.trimLeft(expr, start, blockEnd);

    cursor++;

    /**
     * Check if the function is manually terminated.
     */
    if (splitAccumulator != null && ParseTools.isStatementNotManuallyTerminated(expr, cursor)) {
      /**
       * Add an EndOfStatement to the split accumulator in the parser.
       */
      splitAccumulator.add(new EndOfStatement());
    }

    /**
     * Produce the funciton node.
     */
    return new Function(name, expr, startCond, endCond - startCond, blockStart, blockEnd - blockStart, fields,
        pCtx == null ? pCtx = AbstractParser.getCurrentThreadParserContext() : pCtx);
  }

  public String getName() {
    return name;
  }

  public int getCursor() {
    return cursor;
  }
}
