/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.avro.io.parsing;

import java.io.IOException;
import java.util.Arrays;

import org.apache.avro.AvroTypeException;

/**
 * Parser is the class that maintains the stack for parsing. This class
 * is used by encoders, which are not required to skip.
 */
public class Parser {
  /**
   * The parser knows how to handle the terminal and non-terminal
   * symbols. But it needs help from outside to handle implicit
   * and explicit actions. The clients implement this interface to
   * provide this help.
   */
  public interface ActionHandler {
    /**
     * Handle the action symbol <tt>top</tt> when the <tt>input</tt> is
     * sought to be taken off the stack.
     * @param input The input symbol from the caller of advance
     * @param top The symbol at the top the stack.
     * @return  <tt>null</tt> if advance() is to continue processing the
     * stack. If not <tt>null</tt> the return value will be returned
     * by advance().
     * @throws IOException
     */
    Symbol doAction(Symbol input, Symbol top) throws IOException;
  }

  protected final ActionHandler symbolHandler;
  protected Symbol[] stack;
  protected int pos;

  public Parser(Symbol root, ActionHandler symbolHandler)
    throws IOException {
    this.symbolHandler = symbolHandler;
    this.stack = new Symbol[5]; // Start small to make sure expansion code works
    this.stack[0] = root;
    this.pos = 1;
  }

  /**
   * If there is no sufficient room in the stack, use this expand it.
   */
  private void expandStack() {
    stack = Arrays.copyOf(stack, stack.length+Math.max(stack.length,1024));
  }

  /**
   * Recursively replaces the symbol at the top of the stack with its
   * production, until the top is a terminal. Then checks if the
   * top symbol matches the terminal symbol suppled <tt>terminal</tt>.
   * @param input The symbol to match against the terminal at the
   * top of the stack.
   * @return The terminal symbol at the top of the stack unless an
   * implicit action resulted in another symbol, in which case that
   * symbol is returned.
   */
  public final Symbol advance(Symbol input) throws IOException {
    for (; ;) {
      Symbol top = stack[--pos];
      if (top == input) {
        return top; // A common case
      }

      Symbol.Kind k = top.kind;
      if (k == Symbol.Kind.IMPLICIT_ACTION) {
        Symbol result = symbolHandler.doAction(input, top);
        if (result != null) {
          return result;
        }
      } else if (k == Symbol.Kind.TERMINAL) {
        throw new AvroTypeException("Attempt to process a "
                + input + " when a "
                + top + " was expected.");
      } else if (k == Symbol.Kind.REPEATER
          && input == ((Symbol.Repeater) top).end) {
        return input;
      } else {
        pushProduction(top);
      }
    }
  }

  /**
   * Performs any implicit actions at the top the stack, expanding any
   * production (other than the root) that may be encountered.
   * This method will fail if there are any repeaters on the stack.
   * @throws IOException
   */
  public final void processImplicitActions() throws IOException {
     while (pos > 1) {
      Symbol top = stack[pos - 1];
      if (top.kind == Symbol.Kind.IMPLICIT_ACTION) {
        pos--;
        symbolHandler.doAction(null, top);
      } else if (top.kind != Symbol.Kind.TERMINAL) {
        pos--;
        pushProduction(top);
      } else {
        break;
      }
    }
  }

  /**
   * Performs any "trailing" implicit actions at the top the stack.
   */
  public final void processTrailingImplicitActions() throws IOException {
    while (pos >= 1) {
      Symbol top = stack[pos - 1];
      if (top.kind == Symbol.Kind.IMPLICIT_ACTION
        && ((Symbol.ImplicitAction) top).isTrailing) {
        pos--;
        symbolHandler.doAction(null, top);
      } else {
        break;
      }
    }
  }

  /**
   * Pushes the production for the given symbol <tt>sym</tt>.
   * If <tt>sym</tt> is a repeater and <tt>input</tt> is either
   * {@link Symbol#ARRAY_END} or {@link Symbol#MAP_END} pushes nothing.
   * @param sym
   */
  public final void pushProduction(Symbol sym) {
    Symbol[] p = sym.production;
    while (pos + p.length > stack.length) {
      expandStack();
    }
    System.arraycopy(p, 0, stack, pos, p.length);
    pos += p.length;
  }

  /**
   * Pops and returns the top symbol from the stack.
   */
  public Symbol popSymbol() {
    return stack[--pos];
  }

  /**
   * Returns the top symbol from the stack.
   */
  public Symbol topSymbol() {
    return stack[pos - 1];
  }

  /**
   * Pushes <tt>sym</tt> on to the stack.
   */
  public void pushSymbol(Symbol sym) {
    if (pos == stack.length) {
      expandStack();
    }
    stack[pos++] = sym;
  }

  /**
   * Returns the depth of the stack.
   */
  public int depth() {
    return pos;
  }

  public void reset() {
    pos = 1;
  }
}

