/*
 * $Id: Command.java 5263 2009-03-09 13:36:43Z uckelman $
 *
 * Copyright (c) 2000-2003 by Rodney Kinney
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available 
 * at http://www.opensource.org.
 */
package VASSAL.command;

import java.util.LinkedList;
import java.util.ListIterator;

import VASSAL.tools.ErrorDialog;
import VASSAL.tools.ThrowableUtils;

/**
 * Command is an abstract class that does something. Any action that takes
 * place during a game should be encapsulated in a Command object. When
 * performing actions during a game, corresponding Commands will be logged
 * in the current logfile and/or sent to other players on the server.
 * 
 * Commands can be strung together into compound commands with the
 * {@link #append} method.
 * 
 * @see CommandEncoder
 */
public abstract class Command {
  private LinkedList<Command> seq = new LinkedList<Command>();
  private Command undo;

  public Command() {}

  public Command[] getSubCommands() {
    return seq.toArray(new Command[seq.size()]);
  }

  /**
   * Execute this command by first invoking {@link #executeCommand}, then
   * invoking {@link #execute} recursively on all subcommands.
   */
  public void execute() {
    try {
      executeCommand();
    }
    catch (Throwable t) {
      handleFailure(t);

      final LinkedList<Command> oldSeq = seq;
      stripSubCommands();
      seq = oldSeq;
    }

    for (Command cmd : seq) {
      try {
        cmd.execute();
      }
      catch (Throwable t) {
        handleFailure(t);
      }    
    }
  }

  private void handleFailure(Throwable t) {
    // find and rethrow causes which are not bugs
    ThrowableUtils.throwRecent(OutOfMemoryError.class, t);

    if (t instanceof Error) {
      // some unusual problem occurred    
      throw (Error) t;
    }
    else {
      // report the bug here
      ErrorDialog.bug(t);
    }
  }

  /**
   * Perform the action which this Command represents
   */
  protected abstract void executeCommand();

  /**
   * If the action can be undone, return a Command that performs the
   * inverse action. The Command returned should only undo
   * {@link #executeCommand}, not the actions of subcommands
   */
  protected abstract Command myUndoCommand();

  /**
   * Remove all subcommands.
   */
  public void stripSubCommands() {
    seq = new LinkedList<Command>();
  }

  /**
   * @return true if this command does nothing
   */
  public boolean isNull() {
    return false;
  }

  /**
   * 
   * @return true if this command should be stored in a logfile
   */
  public boolean isLoggable() {
    return !isNull();
  }

  /**
   * @deprecated Use {@link #isAtomic()}
   */
  @Deprecated protected boolean hasNullSubcommands() {
    return isAtomic();
  }

  /**
   * Return true if this command has no sub-commands attached to it
   * (other than null commands).
   * 
   * @return
   */
  protected boolean isAtomic() {
    for (Command c : seq) {
      if (!c.isNull()) {
        return false;
      }
    }
    return true;
  }

  public String toString() {
    final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
    final String details = getDetails();
    if (details != null) sb.append("[").append(details).append("]");

    for (Command c : seq) sb.append("+").append(c.toString());

    return sb.toString();
  }

  /** Detailed information for toString() */
  public String getDetails() {
    return null;
  }

  /**
   * Append a subcommand to this Command.
   */
  public Command append(Command c) {
    Command retval = this;
    if (c != null && !c.isNull()) {
      if (isNull()) {
        retval = c;
      }
      seq.add(c);
    }
    return retval;
  }

  /**
   * @return a Command that undoes not only this Command's action, but also
   * the actions of all its subcommands.
   */
  public Command getUndoCommand() {
    if (undo == null) {
      undo = new NullCommand();
      for (ListIterator<Command> i = seq.listIterator(seq.size());
           i.hasPrevious(); ) {
        undo = undo.append(i.previous().getUndoCommand());
      }
      undo = undo.append(myUndoCommand());
    }
    return undo;
  }
}
