/*
 * $Id: KeyBuffer.java 2982 2008-02-03 12:20:42Z 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.counters;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import VASSAL.command.Command;
import VASSAL.command.NullCommand;


public class KeyBuffer {
  private static KeyBuffer theBuffer;
  private List<GamePiece> pieces;
  private BoundsTracker bounds;
  private Comparator<GamePiece> pieceSorter = new PieceSorter();

  private KeyBuffer() {
    pieces = new ArrayList<GamePiece>();
    bounds = new BoundsTracker();
  }

  public static void init(KeyBuffer kb) {
    if (theBuffer == null)
      theBuffer = kb;
  }

  public static KeyBuffer getBuffer() {
    if (theBuffer == null) {
      theBuffer = new KeyBuffer();
    }
    return theBuffer;
  }

  public void add(GamePiece p) {
// FIXME: should we use a HashSet or LinkedHashSet instead to make contains()
// checks faster? Is insertion order important?
    if (p != null && !pieces.contains(p)) {
      pieces.add(p);
      p.setProperty(Properties.SELECTED, Boolean.TRUE);
    }
  }

  public void clear() {
    for (GamePiece p : pieces) {
      p.setProperty(Properties.SELECTED, null);
    }
    pieces.clear();
  }

  public void remove(GamePiece p) {
    if (p != null) {
      p.setProperty(Properties.SELECTED, null);
      pieces.remove(p);
    }
  }

  public boolean contains(GamePiece p) {
    if (p instanceof Stack) {
      for (Iterator<GamePiece> i = ((Stack) p).getPiecesIterator();
           i.hasNext();) {
        if (!pieces.contains(i.next())) {
          return false;
        }
      }
      return true;
    }
    else {
      return pieces.contains(p);
    }
  }

  public boolean isEmpty() {
    return pieces.isEmpty();
  }

  public Command keyCommand(javax.swing.KeyStroke stroke) {
    sort(pieceSorter);
    Command comm = new NullCommand();

    bounds.clear();

    // Copy contents into new list, because contents may change
    // as a result of key commands
    ArrayList<GamePiece> targets = new ArrayList<GamePiece>(pieces);
    // Reverse the order if this is a "Move Up" or "Move to Bottom" keystroke
    if (targets.size() > 0) {
      GamePiece top = targets.get(0);
      if (top.getMap() != null) {
        if (stroke.equals(top.getMap().getStackMetrics().getMoveBottomKey())
            || stroke.equals(top.getMap().getStackMetrics().getMoveUpKey())) {
          Collections.reverse(targets);
        }
      }
    }
    for (GamePiece p : targets) {
      bounds.addPiece(p);
      p.setProperty(Properties.SNAPSHOT, PieceCloner.getInstance().clonePiece(p)); // save state prior to command
      Command c2 = p.keyEvent(stroke);
      comm = comm.append(c2);
      bounds.addPiece(p);
    }
    bounds.repaint();
    return comm;
  }

  public Iterator<GamePiece> getPiecesIterator() {
    return pieces.iterator();
  }

  /** @deprecated Use {@link #getPiecesIterator()} instead. */
  @Deprecated
  public Enumeration<GamePiece> getPieces() {
    return Collections.enumeration(pieces);
  }

  public void sort(Comparator<GamePiece> comp) {
    Collections.sort(pieces, comp);
  }

  /**
   *
   * @param stack
   * @return true if a child of the specified Stack is selected
   */
  public boolean containsChild(Stack stack) {
    for (Iterator<GamePiece> i = stack.getPiecesIterator(); i.hasNext();) {
      if (contains(i.next())) {
        return true;
      }
    }
    return false;
  }
}
