package nxm.sys.lib;

import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;

import nxm.sys.inc.InternalUseOnly;

/** Class for handling a stack of objects. This version of a stack has a special modes used to
    handle command line recall functions of the NeXtMidas shell.

    <pre>
      Terminology:
        Stack   - A LIFO (Last In, First Out) collection of objects.
        Push    - Add an item to top of the stack.
        Pop     - Remove the top item from the stack and return it.
        Peek    - Look at the top item in the stack, but don't
                  remove it.

      (See also <a href="http://www.nist.gov/dads/HTML/stack.html">http://www.nist.gov/dads/HTML/stack.html</a>)
    </pre>

    Note: An instance of the class is only Serializable if all the Objects pushed on the Stack are Serializable

    @author Jeff Schoen
 */
public class Stack implements java.io.Serializable {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2011031000291L; // for Serializable interface

  /** Maximum number of objects in array */
  private int maxObjects;

  /** the Object array */
  @SuppressWarnings("serial")
  private Object[] objects;

  /** The current position in the array */
  private int pushpos, peekpos, lastpeekpos;

  /** Message given when user tries to serialize a Stack that contains unserializble Objects */
  @InternalUseOnly
  public static final String UNSERIALIZABLE_STACK_OBJECT_MESSAGE =
      "nxm.sys.lib.Stack only Serializable if all objects added to stack are Serializable";

  /** Creates a new stack with the default stack-depth limit of 32. */
  public Stack () {
    this(32);
  }

  /** Creates a new stack from a previous Stack instance.
      @param stack previous stack instance
      @since NeXtMidas 3.3.1
   */
  public Stack (Stack stack) {
    maxObjects = stack.maxObjects;
    objects = stack.objects.clone();
    pushpos = stack.pushpos;
    peekpos = stack.peekpos;
    lastpeekpos = stack.lastpeekpos;
  }

  /** Creates a new stack with the given stack-depth limit.
      @param maximum The maximum number of elements on the stack.
   */
  public Stack (int maximum) {
    maxObjects = maximum;
    pushpos = peekpos = lastpeekpos = 0;
    objects = new Object[maxObjects];
  }

  /** Clears the Stack. */
  public void clear () {
    pushpos = peekpos = lastpeekpos = 0;
  }

  /** Pushes an object to the stack. If the stack is full, the oldest will be discarded.
      @param obj The object to push onto the top of the stack.
   */
  public void push (Object obj) {
    objects[pushpos] = obj;
    pushpos = (pushpos+1) % maxObjects;
    objects[pushpos] = null;  // boundary marker
    peekpos = pushpos;
  }

  /** Pops an object off of the stack.
      @return The top object in the stack.
      @see #peek()
   */
  public Object pop () {
    if (pushpos==0) return null;
    return objects[--pushpos];
  }

  /** Special peek method used for command-line recall. This method should only be used by the
      NeXtMidas library methods that manage command-line recall.
      @param code The operating code.
      @return The appropriate object.
   */
  public Object peek (int code) {
    switch (code) {
      case -1: // last
        if (peekpos==pushpos || objects[peekpos]!=null) peekpos--;
        break;
      case  1: // next
        if (peekpos!=pushpos) peekpos++;
        break;
      case -2: // last reference
        if (lastpeekpos==pushpos || objects[peekpos]!=null) peekpos = lastpeekpos-1;
        break;
      case  2: // next reference
        if (lastpeekpos!=pushpos) peekpos = lastpeekpos+1;
        break;
      default:
        peekpos = pushpos-1;
        break;
    }
    peekpos = (peekpos + maxObjects) % maxObjects;
    if (code!=0) lastpeekpos = peekpos;

    Object retval = objects[peekpos];
    if (retval == null) peekpos = pushpos; // reset peekpos, since hit boundary marker (BUG 1263)
    return retval;
  }

  /** Peeks at the top object in the stack, but does not remove it from the stack.
      @return The top object in the stack.
      @see #pop()
   */
  public Object peek () {
    peekpos = pushpos;
    int pos = (peekpos-1 + maxObjects) % maxObjects;
    return objects[pos];
  }

  /** Overwrite default serialization only to give more descriptive error
   *
   *  @param oos          Object Output Strean
   *  @throws IOException Exception thrown by writeObject
   */
  private void writeObject (ObjectOutputStream oos) throws IOException {
    try {
      // default serialization
      oos.defaultWriteObject();
    }
    catch (NotSerializableException nse) {
      throw new UnsupportedOperationException(UNSERIALIZABLE_STACK_OBJECT_MESSAGE, nse);
    }
  }

}
