/*
 * $Id: TurnTracker.java 5315 2009-03-15 20:06:10Z uckelman $
 *
 * Copyright (c) 2005 by Rodney Kinney, Brent Easton
 *
 * 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.build.module.turn;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;

import VASSAL.build.AutoConfigurable;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.build.module.Chatter;
import VASSAL.build.module.GameComponent;
import VASSAL.build.module.GlobalOptions;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.properties.MutablePropertiesContainer;
import VASSAL.build.module.properties.MutableProperty;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.configure.BooleanConfigurer;
import VASSAL.configure.Configurer;
import VASSAL.configure.ConfigurerFactory;
import VASSAL.configure.FormattedStringConfigurer;
import VASSAL.configure.HotKeyConfigurer;
import VASSAL.configure.IconConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.PlayerIdFormattedStringConfigurer;
import VASSAL.configure.StringEnum;
import VASSAL.configure.StringEnumConfigurer;
import VASSAL.configure.VisibilityCondition;
import VASSAL.i18n.Resources;
import VASSAL.i18n.TranslatableConfigurerFactory;
import VASSAL.tools.FormattedString;
import VASSAL.tools.IconButton;
import VASSAL.tools.KeyStrokeListener;
import VASSAL.tools.LaunchButton;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.UniqueIdManager;

/**
 * Generic Turn Counter
 */
public class TurnTracker extends TurnComponent implements CommandEncoder, GameComponent, ActionListener, UniqueIdManager.Identifyable {

  protected static UniqueIdManager idMgr = new UniqueIdManager("TurnTracker"); //$NON-NLS-1$
  
  protected static final String COMMAND_PREFIX = "TURN"; //$NON-NLS-1$

  public static final String NAME = "name"; //$NON-NLS-1$
  public static final String HOT_KEY = "hotkey"; //$NON-NLS-1$
  public static final String NEXT_HOT_KEY = "nexthotkey"; //$NON-NLS-1$
  public static final String PREV_HOT_KEY = "prevhotkey"; //$NON-NLS-1$
  public static final String ICON = "icon"; //$NON-NLS-1$
  public static final String BUTTON_TEXT = "buttonText"; //$NON-NLS-1$
  public static final String BUTTON_TOOLTIP = "buttonTooltip"; //$NON-NLS-1$
  public static final String TURN_FORMAT = "turnFormat"; //$NON-NLS-1$
  public static final String REPORT_FORMAT = "reportFormat"; //$NON-NLS-1$
  public static final String TOOLTIP = "tooltip"; //$NON-NLS-1$
  public static final String LENGTH = "length"; //$NON-NLS-1$
  public static final String LENGTH_STYLE = "lengthStyle"; //$NON-NLS-1$
  
  protected static final String FONT_SIZE = "turnFontSize"; //$NON-NLS-1$
  protected static final String FONT_BOLD = "turnFontBold"; //$NON-NLS-1$
  protected static final String DOCKED = "turnDocked"; //$NON-NLS-1$

  /** Variable name for reporting format */
  protected static final String OLD_TURN = "oldTurn"; //$NON-NLS-1$
  protected static final String NEW_TURN = "newTurn"; //$NON-NLS-1$
  protected static final String LEVEL = "level"; //$NON-NLS-1$
  
  protected static final String TURN_FONT = "Dialog";
  protected static String SET_COMMAND;
  protected static String DOCK_COMMAND;
  protected static String UNDOCK_COMMAND;
  
  protected static final String NEXT = "Next";
  protected static final String PREV = "Prev";
  protected static final String SET = "Set";
  
  protected static final String PROP_VALUE = "_value"; //$NON-NLS-1$
  protected static final String PROP_COMMAND = "_command"; //$NON-NLS-1$
  
  protected static final String[] FONT_FAMILYS = new String[] { "Dialog", "DialogInput", "Monospaced", "SanSerif", "Serif"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$

  protected static final String LENGTH_VARIABLE = "Variable";
  protected static final String LENGTH_MAXIMUM = "Maximum";  
  protected static final String LENGTH_FIXED = "Fixed";
  
  protected FormattedString turnFormat = new FormattedString("$"+LEVEL+"1$ $"+LEVEL+"2$ $"+LEVEL+"3$ $"+LEVEL+"4$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$

  protected FormattedString reportFormat = new FormattedString("* <$" + GlobalOptions.PLAYER_ID //$NON-NLS-1$
      + "$> Turn Updated from $"+OLD_TURN+"$ to $"+NEW_TURN+"$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

  protected TurnWindow turnWindow;
  protected TurnWidget turnWidget;
  protected JPanel launchWidget;
  protected SetDialog setDialog;
  protected LaunchButton launch;
  protected KeyStrokeListener nextListener;
  protected KeyStrokeListener prevListener;
  
  protected String savedState = ""; //$NON-NLS-1$
  protected String savedSetState = ""; //$NON-NLS-1$
  protected String savedTurn = ""; //$NON-NLS-1$
  protected JPopupMenu popup;
  
  protected int currentLevel = 0;
  protected String id;
  protected int width = -1;
  protected String lengthStyle = LENGTH_MAXIMUM;
  
  protected MutableProperty.Impl lastCommand = new MutableProperty.Impl(SET,this);
  protected MutableProperty.Impl lastTurn = new MutableProperty.Impl("",this);

  public TurnTracker() {
    
    ActionListener al = new ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
        if (!isDocked()) {
          turnWindow.setControls();
          turnWindow.setVisible(!turnWindow.isShowing());
          turnWindow.setFocusable(true);
        }
      }
    };
    setConfigureName(Resources.getString("TurnTracker.turn")); //$NON-NLS-1$
    launch = new LaunchButton(Resources.getString("TurnTracker.turn"), BUTTON_TOOLTIP, BUTTON_TEXT, HOT_KEY, ICON, al); //$NON-NLS-1$
    launch.setToolTipText(Resources.getString("TurnTracker.turn_tracker")); //$NON-NLS-1$
    
    SET_COMMAND = Resources.getString("TurnTracker.set_turn"); //$NON-NLS-1$
    DOCK_COMMAND = Resources.getString("General.dock"); //$NON-NLS-1$
    UNDOCK_COMMAND = Resources.getString("General.undock"); //$NON-NLS-1$

    // Create preferences
    final IntConfigurer size = new IntConfigurer(FONT_SIZE, Resources.getString("TurnTracker.size_pref"), new Integer(14)); //$NON-NLS-1$
    final BooleanConfigurer bold = new BooleanConfigurer(FONT_BOLD,  Resources.getString("TurnTracker.bold_pref"), Boolean.FALSE); //$NON-NLS-1$
    final BooleanConfigurer docked = new BooleanConfigurer(DOCKED,  Resources.getString("TurnTracker.docked_pref"), Boolean.FALSE); //$NON-NLS-1$
    
    String prefTab = Resources.getString("TurnTracker.turn_counter"); //$NON-NLS-1$
    GameModule.getGameModule().getPrefs().addOption(prefTab, size);
    GameModule.getGameModule().getPrefs().addOption(prefTab, bold);
    GameModule.getGameModule().getPrefs().addOption(prefTab, docked);
    
    size.addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        setDisplayFont();     
      }});   
    
    bold.addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        setDisplayFont();         
      }}); 
    
    docked.addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        setDocked(isDocked());         
      }}); 
    
    // Set up listeners for prev/next hotkeys
    nextListener = new KeyStrokeListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        turnWidget.doNext();
      }
    });
    GameModule.getGameModule().addKeyStrokeListener(nextListener);

    prevListener = new KeyStrokeListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        turnWidget.doPrev();
      }
    });
    GameModule.getGameModule().addKeyStrokeListener(prevListener);
    
    // Create the displayable widget
    turnWidget = new TurnWidget();
  }
  
  
  public String getState() {
    final SequenceEncoder se = new SequenceEncoder('|');
    se.append(currentLevel);
    final Iterator<TurnLevel> i = getTurnLevels();
    while (i.hasNext()) {
      final TurnLevel level = i.next();
      se.append(level.getState());
    }
    return se.getValue();
  }

  public void setState(String newState) {
    final SequenceEncoder.Decoder sd =
      new SequenceEncoder.Decoder(newState, '|');
    currentLevel = sd.nextInt(0);
    final Iterator<TurnLevel> i = getTurnLevels();
    while (i.hasNext()) {
      final TurnLevel level = i.next();
      level.setState(sd.nextToken("")); //$NON-NLS-1$
    }
    
    setLaunchToolTip();
    updateTurnDisplay(SET);
  }
  
  protected void setLaunchToolTip() {
    launch.setToolTipText(getTurnString());
  }
  
  /*
   * Module level Configuration stuff
   */
  public String[] getAttributeNames() {
    return new String[] { NAME, BUTTON_TEXT, ICON, BUTTON_TOOLTIP, HOT_KEY, NEXT_HOT_KEY, PREV_HOT_KEY, TURN_FORMAT, REPORT_FORMAT, TOOLTIP, LENGTH_STYLE, LENGTH };
  }

  public void setAttribute(String key, Object value) {
    if (NAME.equals(key)) {
      clearGlobalProperties();
      setConfigureName((String) value);
      lastCommand.setPropertyName(getConfigureName()+PROP_COMMAND);
      lastTurn.setPropertyName(getConfigureName()+PROP_VALUE);
    }
    else if (REPORT_FORMAT.equals(key)) {
      reportFormat.setFormat((String) value);
    }
    else if (TURN_FORMAT.equals(key)) {
      turnFormat.setFormat((String) value);
    }
    else if (TOOLTIP.equals(key)) {
      turnWidget.setLabelToolTipText((String) value);
    }
    else if (LENGTH.equals(key)) {
      if (value instanceof String) {
        value = new Integer((String) value);
      }
      width = ((Integer) value).intValue();
    }
    else if (LENGTH_STYLE.equals(key)) {
      lengthStyle = (String) value;
      if (LENGTH_VARIABLE.equals(lengthStyle)) {
        width = 0;
      }
      else if (LENGTH_MAXIMUM.equals(lengthStyle)) {
        width = -1;
      }
    }
    else if (NEXT_HOT_KEY.equals(key)) {
      if (value instanceof String) {
        value = HotKeyConfigurer.decode((String) value);
      }
      nextListener.setKeyStroke((KeyStroke) value);
      turnWidget.setNextStroke((KeyStroke) value);
    }
    else if (PREV_HOT_KEY.equals(key)) {
      if (value instanceof String) {
        value = HotKeyConfigurer.decode((String) value);
      }
      prevListener.setKeyStroke((KeyStroke) value);
      turnWidget.setPrevStroke((KeyStroke) value);
    }
    else {
      launch.setAttribute(key, value);
    }
  }

  protected void setWidgetWidth() {
    if (LENGTH_FIXED.equals(lengthStyle)) {
      turnWidget.setWidth(width);
    }
    else if (LENGTH_MAXIMUM.equals(lengthStyle)) {
      turnWidget.setWidth(getMaximumWidth());
    }
    else {
      turnWidget.setWidth(0);
    }
  }
  
  /**
   * Calculate the maximum width for the turnWidget to display
   * any item.
   * 
   * First calculate the maximum string that can be displayed, then
   * convert this to a width based on the display font.
   * 
   * @return Maximum Width
   */
  protected int getMaximumWidth() {
    String maxString = getMaximumTurnString();
    int max = turnWidget.getWidth(maxString);
    return max+2;
  }
  
  protected void setDisplayFont() {
    turnWidget.setLabelFont(getDisplayFont());
    if (!isDocked() && turnWindow != null) {
      turnWindow.pack();
    }
  }
  
  protected Font getDisplayFont() {
    int style = getFontStyle();
    int size = getFontSize();
    return new Font(TURN_FONT, style, size);
  }
 
  protected void setFontSize() {
    setDisplayFont();
  }
  
  protected int getFontSize() {
    return ((Integer) GameModule.getGameModule().getPrefs().getValue(FONT_SIZE)).intValue();
  }
  
  protected int getFontStyle() {
    return ((Boolean) GameModule.getGameModule().getPrefs().getValue(FONT_BOLD)).booleanValue() ? 1 : 0;
  }
  
  protected boolean isDocked() {
    return ((Boolean) GameModule.getGameModule().getPrefs().getValue(DOCKED)).booleanValue();
  }
  
  protected void setDocked(boolean dock) {
    GameModule.getGameModule()
              .getPrefs()
              .setValue(DOCKED, Boolean.valueOf(dock));
    launch.setVisible(!dock);
    if (dock) {
      turnWindow.setWidget(null);
      turnWindow.setVisible(false);
      launchWidget.add(turnWidget, BorderLayout.CENTER);
      launchWidget.setVisible(GameModule.getGameModule().getGameState().isGameStarted());
    }
    else {
      launchWidget.setVisible(false);
      launchWidget.remove(turnWidget);
      turnWindow.setWidget(turnWidget);
      turnWindow.setVisible(GameModule.getGameModule().getGameState().isGameStarted());
      turnWindow.setFocusable(true);
    }
  }
  
  public String getAttributeValueString(String key) {
    if (NAME.equals(key)) {
      return getConfigureName() + ""; //$NON-NLS-1$
    }
    else if (REPORT_FORMAT.equals(key)) {
      return reportFormat.getFormat();
    }
    else if (TURN_FORMAT.equals(key)) {
      return turnFormat.getFormat();
    }
    else if (TOOLTIP.equals(key)) {
      return turnWidget.getLabelToolTipText();
    }
    else if (LENGTH.equals(key)) {
      return String.valueOf(width);
    }
    else if (LENGTH_STYLE.equals(key)) {
      return lengthStyle;
    }
    else if (NEXT_HOT_KEY.equals(key)) {
      return HotKeyConfigurer.encode(nextListener.getKeyStroke());
    }
    else if (PREV_HOT_KEY.equals(key)) {
      return HotKeyConfigurer.encode(prevListener.getKeyStroke());
    }
    else {
      return launch.getAttributeValueString(key);
    }
  }
  
  public String[] getAttributeDescriptions() {
    return new String[] {
      "Name:  ",
      "Button text:  ",
      "Button Icon:  ",
      "Button Tooltip:  ",
      "Show/hide Hotkey:  ",
      "Next Turn Hotkey:  ",
      "Previous Turn Hotkey:  ",
      "Turn Name Format:  ",
      "Report Format:  ",
      "Turn Label Tooltip Text:  ",
      "Turn Label Length:  ",
      "Turn label Display length:  "
    };
  }

  public Class<?>[] getAttributeTypes() {
    return new Class<?>[] {
      String.class,
      String.class,
      IconConfig.class,
      String.class,
      KeyStroke.class,
      KeyStroke.class,
      KeyStroke.class, 
      TurnFormatConfig.class,
      ReportFormatConfig.class,
      String.class,
      LengthStyleConfig.class,
      Integer.class
    };
  }

  public static class IconConfig implements ConfigurerFactory {
    public Configurer getConfigurer(AutoConfigurable c, String key, String name) {
      return new IconConfigurer(key, name, ((TurnTracker) c).launch.getAttributeValueString(ICON));
    }
  }

  public static class TurnFormatConfig implements TranslatableConfigurerFactory {
    public Configurer getConfigurer(AutoConfigurable c, String key, String name) {
      TurnTracker t = (TurnTracker) c;
      String s[] = new String[t.getLevelCount()];
      for (int i = 0; i < s.length; i++) {
        s[i] = LEVEL+(i+1);
      }
      return new FormattedStringConfigurer(key, name, s);
    }
  }
  
  public static class ReportFormatConfig implements TranslatableConfigurerFactory {
    public Configurer getConfigurer(AutoConfigurable c, String key, String name) {
      return new PlayerIdFormattedStringConfigurer(key, name, new String[] {OLD_TURN, NEW_TURN } );
    }
  }

  public static class LengthStyleConfig extends StringEnum {
    public String[] getValidValues(AutoConfigurable target) {
      return new String[]{LENGTH_VARIABLE, LENGTH_FIXED, LENGTH_MAXIMUM};
    }
  }

  public VisibilityCondition getAttributeVisibility(String name) {
    if (LENGTH.equals(name)) {
      return new VisibilityCondition() {
        public boolean shouldBeVisible() {
          return LENGTH_FIXED.equals(lengthStyle);
        }
      };
    }
    else {
      return null;
    }
  }
  
  public Class<?>[] getAllowableConfigureComponents() {
    return new Class[] { CounterTurnLevel.class, ListTurnLevel.class, TurnGlobalHotkey.class };
  }

  public static String getConfigureTypeName() {
    return "Turn Counter";
  }

  public void addTo(Buildable b) {
    //Create the turn window  
    turnWindow = new TurnWindow();
    turnWindow.pack(); 
    turnWindow.setVisible(false);
    
    launchWidget = new JPanel();
    launchWidget.setLayout(new BorderLayout());
    launchWidget.setBorder(BorderFactory.createEtchedBorder());
    GameModule.getGameModule().getToolBar().add(launchWidget);
    launchWidget.setAlignmentY(0.0F);
    launchWidget.setVisible(false);
    
    GameModule.getGameModule().getToolBar().add(launch);
    launch.setAlignmentY(0.0F);
    launch.setEnabled(false);
    
    setDocked(isDocked());
    
    GameModule.getGameModule().addCommandEncoder(this);
    GameModule.getGameModule().getGameState().addGameComponent(this);
    idMgr.add(this);
     
    //Global Property support
    lastCommand.addTo((MutablePropertiesContainer) b);
    lastTurn.addTo((MutablePropertiesContainer) b);
   
  }

  public void removeFrom(Buildable b) {
    GameModule.getGameModule().getToolBar().remove(launch);
    GameModule.getGameModule().getToolBar().remove(launchWidget);
    GameModule.getGameModule().removeCommandEncoder(this);
    GameModule.getGameModule().getGameState().removeGameComponent(this);
    lastCommand.removeFromContainer();
    lastTurn.removeFromContainer();
    clearGlobalProperties();
  }

  public HelpFile getHelpFile() {
    return HelpFile.getReferenceManualPage("TurnTracker.htm"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getId() {
    return id;
  }
  
  protected void captureState() {
    savedState = getState();
    savedTurn = getTurnString();
  }

  protected void save() {

    if (!savedState.equals(getState())) {
      
      reportFormat.setProperty(OLD_TURN, savedTurn);
      reportFormat.setProperty(NEW_TURN, getTurnString());

      String s = updateString(reportFormat.getText(), new String[] { "\\n", "\\t" }, new String[] { " - ", " " }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
      Command c = new Chatter.DisplayText(GameModule.getGameModule().getChatter(), "* "+s);
      c.execute();
      c.append(new SetTurn(this, savedState));

      GameModule.getGameModule().sendAndLog(c);
      
      setLaunchToolTip();
    }
    
    captureState();
  }
  
  /** 
   * Calculate the maximum sized turn string that can be generated
   * by any turn combination. 
   * 
   * @return maximum turn string
   */
  protected String getMaximumTurnString() {
    List<String> levels = new ArrayList<String>();
    for (Buildable b : getBuildables()) {
      if (b instanceof TurnLevel) {
        ((TurnLevel) b).findMaximumStrings(levels, 0);
      }
    }
    turnFormat.clearProperties();
    for (int i = 0; i < levels.size(); i++) {
        turnFormat.setProperty(LEVEL+(i+1), levels.get(i));
      }
    return turnFormat.getText(GameModule.getGameModule());
  }
  
  /**
   * Build the turn string to be displayed from the currently
   * active Child TurnLevel's
   * @return Turn String
   */
  protected String getTurnString() {
    turnFormat.clearProperties();
    List<TurnLevel> turnDesc = getActiveChildLevels();
    for (int i = 0; i < 15; i++) {
      turnFormat.setProperty(LEVEL+(i+1), i < turnDesc.size() ? turnDesc.get(i).getTurnString() : "");
    }
    return turnFormat.getText(GameModule.getGameModule());
  }
  
  /**
   * A list of all active TurnLevels within the TurnTracker
   * @return
   */
  protected List<TurnLevel> getActiveChildLevels() {
    ArrayList<TurnLevel> levels = new ArrayList<TurnLevel>();
    TurnLevel level = getTurnLevel(currentLevel);
    if (level != null) {
      levels.add(level);
      levels.addAll(level.getActiveChildLevels());
    }
    return levels;
  }
  
  protected int getLevelCount() {
    return getActiveChildLevels().size();
  }

  protected void next() {

    if (getTurnLevelCount() == 0) {
      return;
    }

    TurnLevel level = getTurnLevel(currentLevel);
    level.advance();
    if (level.hasRolledOver()) {
      currentLevel++;
      if (currentLevel >= getTurnLevelCount()) {
        currentLevel = 0;
      }
      getTurnLevel(currentLevel).setLow();
    }
    
    updateTurnDisplay(NEXT);
    doGlobalkeys();
  }
  
  protected void prev() {

    if (getTurnLevelCount() == 0) {
      return;
    }

    TurnLevel level = getTurnLevel(currentLevel);
    level.retreat();
    if (level.hasRolledOver()) {
      currentLevel--;
      if (currentLevel < 0) {
        currentLevel = getTurnLevelCount()-1;
      }
      getTurnLevel(currentLevel).setHigh();
    }

    updateTurnDisplay(PREV);
    doGlobalkeys();
  }

  protected void doGlobalkeys() {
    for (TurnGlobalHotkey key : getComponentsOf(TurnGlobalHotkey.class)) {
      key.apply();
    }
  }

  public void actionPerformed(ActionEvent e) {
    String command = e.getActionCommand();
    if (command.equals(SET_COMMAND)) {
      set();
    }
    else if (command.equals(DOCK_COMMAND)) {
      setDocked(true);
    }
    else if (command.equals(UNDOCK_COMMAND)) {
      setDocked(false);
    }
  }
  
  protected void set() {
    savedSetState = getState();
    if (setDialog == null) {
      setDialog = new SetDialog();
      setDialog.setTitle(Resources.getString("TurnTracker.set_turn2", getConfigureName())); //$NON-NLS-1$
    }
    setDialog.setControls(this);
    setDialog.setVisible(true);
  }

  protected void updateTurnDisplay(String command) {
    lastCommand.setPropertyValue(command);
    lastTurn.setPropertyValue(getTurnString());
    turnWidget.setControls();
    turnWidget.repaint();
    turnWindow.pack();
    turnWindow.setFocusable(true);
    turnWindow.requestFocus();
  }

  protected void clearGlobalProperties() {
    lastCommand.setPropertyValue(null);
    lastTurn.setPropertyValue(null);
  }
  
  public Command decode(String command) {
    Command comm = null;
    if (command.startsWith(COMMAND_PREFIX+getId())) {
      SequenceEncoder.Decoder sd = new SequenceEncoder.Decoder(command, '\t');
      sd.nextToken(""); //$NON-NLS-1$
     comm = new SetTurn(sd.nextToken(""), this); //$NON-NLS-1$
    }
    return comm;
  }

  public String encode(Command c) {
    String s = null;
    if (c instanceof SetTurn) {
      SetTurn com = (SetTurn) c;
      SequenceEncoder se = new SequenceEncoder('\t');
      se.append(COMMAND_PREFIX + com.getTurn().getId());
      se.append(com.newState);
      return se.getValue();
    }
    return s;
  }
  
  public void setup(boolean gameStarting) {
    launch.setEnabled(gameStarting);
    turnWindow.setVisible(false);
    launchWidget.setVisible(isDocked() && gameStarting);
    if (gameStarting) {
      lastCommand.setPropertyValue(SET);
      lastTurn.setPropertyValue("");
      turnWidget.setControls();
      setWidgetWidth();
    }
    else {
      reset();
    }
  }

  protected void reset() {
    for (int i = 0; i < getTurnLevelCount(); i++) {
      (getTurnLevel(i)).reset();
    }
    currentLevel = 0;
    setLaunchToolTip();
    clearGlobalProperties();
  }
  
  public String updateString(String str, String[] from, String[] to) {
    final StringBuilder s = new StringBuilder(str);
  
    for (int i = 0; i < from.length; i++) {
      replace(s, from[i], to[i]);
    }
    
    return s.toString();
  }
  
  public void replace(StringBuilder s, String from, String to) {
    int i = s.indexOf(from);
    while (i >= 0) {
      s = s.replace(i, i+2, to);
      i = s.indexOf(from);
    }
  }

  /** @deprecated Use {@link #replace(StringBuilder,String,String)} instead. */
  @Deprecated
  public void replace(StringBuffer s, String from, String to) {
    int i = s.indexOf(from);
    while (i >= 0) {
      s = s.replace(i, i+2, to);
      i = s.indexOf(from);
    }
  }
  
  public Command getRestoreCommand() {
    return new SetTurn(getState(), this);
  }

  protected class TurnWindow extends JDialog {
    private static final long serialVersionUID = 1L;
    protected TurnWidget widget;
    
    protected TurnWindow() {
      super(GameModule.getGameModule().getFrame());
      setTitle(getConfigureName()); 
      pack();
      setLocation(100, 100);
      setFocusable(true);
    }

    protected void setWidget(TurnWidget t) {
      if (t == null) {
        if (widget != null) {
          remove(widget);
        }
      }
      else {
        add(t);
      }
      pack();
    }
    
    protected void setControls() {
      if (widget != null) {
        widget.setControls();
      }
      pack();
    } 
    
  }
  
  protected class TurnWidget extends JPanel implements MouseListener {

  private static final long serialVersionUID = 1L;
  private IconButton nextButton;
  private IconButton prevButton;
  
  protected final int BUTTON_SIZE = 22;

    protected JLabel turnLabel = new JLabel();

    protected TurnWidget() {
      super();
      initComponents();
    }

    public void setLabelFont(Font displayFont) {
      turnLabel.setFont(displayFont);      
    }

    public void setWidth(int length) {
      if (length > 0) {
        turnLabel.setMinimumSize(new Dimension(length, BUTTON_SIZE));
        turnLabel.setPreferredSize(new Dimension(length, BUTTON_SIZE));
      }
      else {
        turnLabel.setMinimumSize(null);
        turnLabel.setPreferredSize(null);
      }      
    }

    public void setLabelToolTipText(String tooltip) {
      turnLabel.setToolTipText(tooltip);      
    }

    public String getLabelToolTipText() {
      return turnLabel.getToolTipText();
    }
    
    public Color getColor() {
      return turnLabel.getBackground();
    }

    public int getWidth(String text) {
      return turnLabel.getGraphics().getFontMetrics().stringWidth(text);
    }
    
    protected void doNext() {
      captureState();
      next();
      save();
    }
    
    protected void doPrev() {
      captureState();
      prev();
      save();
    }
    
    protected void initComponents() {

      setLayout(new BorderLayout(5, 5)); 
  
      nextButton = new IconButton(IconButton.PLUS_ICON, BUTTON_SIZE);
      setNextStroke(nextListener.getKeyStroke());
      nextButton.setAlignmentY(Component.TOP_ALIGNMENT);
      nextButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          doNext();
        }});

      prevButton = new IconButton(IconButton.MINUS_ICON, BUTTON_SIZE);
      setPrevStroke(prevListener.getKeyStroke());
      prevButton.setAlignmentY(Component.TOP_ALIGNMENT);
      prevButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          doPrev();
        }});
      
      // Next, the Label containing the Turn Text
      turnLabel.setFont(getDisplayFont());
      turnLabel.setFocusable(false);
      turnLabel.setHorizontalTextPosition(JLabel.CENTER);
      turnLabel.setHorizontalAlignment(SwingConstants.CENTER);
      turnLabel.addMouseListener(this);
      turnLabel.setBackground(Color.WHITE);
      turnLabel.setToolTipText(Resources.getString("TurnTracker.click_to_configure")); //$NON-NLS-1$
      
 
      add(prevButton, BorderLayout.LINE_START);
      add(turnLabel, BorderLayout.CENTER);
      add(nextButton, BorderLayout.LINE_END);
      
      addMouseListener(this);
    }

    public void setNextStroke(KeyStroke key) {
      final String tooltip = Resources.getString("TurnTracker.next_turn") + 
        (key == null ? "" : " [" + HotKeyConfigurer.getString(key) + "]");
      nextButton.setToolTipText(tooltip);
    }

    public void setPrevStroke(KeyStroke key) {
      final String tooltip = Resources.getString("TurnTracker.prev_turn") +  //$NON-NLS-1$
      (key == null ? "" : " [" + HotKeyConfigurer.getString(key) + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      prevButton.setToolTipText(tooltip);
    }
    
    public void setControls() {
      String s = updateString(getTurnString(), new String[] { "\\n", "\\t" }, new String[] { "\n", "    " }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$   
      turnLabel.setText(s);
    }
           
    public void mouseClicked(MouseEvent e) {
      if (e.isMetaDown()) {
        doPopup(e.getPoint());
      }
    }

    public void mouseEntered(MouseEvent e) {
      
    }

    public void mouseExited(MouseEvent e) {
      
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }
    
    public void doPopup(Point p) {
      buildPopup();
      popup.show(this, p.x, p.y);
    }
    
  }
  
  protected void buildPopup() {
    
    popup = new JPopupMenu();
    popup.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
      public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
        turnWidget.repaint();
      }

      public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) {
        turnWidget.repaint();
      }

      public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {
      }
    });
    
    JMenuItem item;
    
    // Dock/Undock
    if (isDocked()) {
      item = new JMenuItem(UNDOCK_COMMAND);
    }
    else {
      item = new JMenuItem(DOCK_COMMAND);
    }
    item.addActionListener(this);
    popup.add(item);
    
    // Set Current Turn directly
    item = new JMenuItem(SET_COMMAND);
    item.addActionListener(this);
    popup.add(item);
    
    // Configure List Items
    JMenu config = new JMenu(Resources.getString("TurnTracker.configure")); //$NON-NLS-1$
    
    for (int i = 0; i < getTurnLevelCount(); i++) {
      getTurnLevel(i).buildConfigMenu(config);
    }
    
    if (config.getItemCount() > 0) {
      popup.add(config);
    }
  }

  protected void addItem(JMenu menu, String command) {
    JMenuItem item = new JMenuItem(command);
    item.addActionListener(this);
    menu.add(item);
  }
    
  private static final Dimension FILLER = new Dimension(0, 3);
  
  protected class SetDialog extends JDialog {

    private static final long serialVersionUID = 1L;
    
    protected JPanel panel;
    protected JPanel controls = null;
    protected JPanel levelControls = null;
    protected Component childControls = null;
    protected TurnTracker turn;
    protected JDialog me;

    protected SetDialog() {
      super(GameModule.getGameModule().getFrame());
      initComponents();
      setLocation(100, 100);
      me = this;
    }
    
    protected void initComponents() {
      setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
      setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
      addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          cancelSet();
          setVisible(false);
        }
      });

      panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
      add(panel);

      JPanel p = new JPanel();

      JButton saveButton = new JButton(Resources.getString(Resources.SAVE));
      saveButton.setToolTipText(Resources.getString("TurnTracker.save_changes")); //$NON-NLS-1$ 
      p.add(saveButton);
      saveButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          saveSet();
          setVisible(false);
        }
      });

      JButton cancelButton = new JButton(Resources.getString(Resources.CANCEL));
      cancelButton.setToolTipText(Resources.getString("TurnTracker.discard_changes")); //$NON-NLS-1$ 
      cancelButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          cancelSet();
          setVisible(false);
        }
      });
      p.add(cancelButton);
      
      add(p);
    }

    public void setControls(TurnTracker turn) {
      
      this.turn = turn;
      
      if (controls != null) {
        panel.remove(controls);
      }

      controls = new JPanel();
      controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));

      levelControls = new JPanel();
      levelControls.setLayout(new BoxLayout(levelControls, BoxLayout.Y_AXIS));
      
      if (getTurnLevelCount() > 1) {
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        p.setBorder(BorderFactory.createLineBorder(Color.black));
        
        String s[] = new String[getTurnLevelCount()];
        for (int i = 0; i < s.length; i++) {
          s[i] = getTurnLevel(i).getConfigureName();
        }    
        StringEnumConfigurer e = new StringEnumConfigurer(null, Resources.getString("TurnTracker.select"), s); //$NON-NLS-1$
        e.setValue(getTurnLevel(currentLevel).getConfigureName());
        e.addPropertyChangeListener(new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent e) {
            String option = ((StringEnumConfigurer) e.getSource()).getValueString();
            for (int i = 0; i < getTurnLevelCount(); i++) {
              if (option.equals(getTurnLevel(i).getConfigureName())) {
                  currentLevel = i;
                  updateTurnDisplay(SET);
                  addChildControls();
              }
            }
          }});
        
        p.add(Box.createRigidArea(FILLER));
        p.add(e.getControls());
        p.add(Box.createRigidArea(FILLER));
        levelControls.add(p);
        levelControls.add(Box.createRigidArea(FILLER));
        
      }
      
      addChildControls();
      
      controls.add(levelControls);

      panel.add(controls);
      pack();
    }
    
    protected void addChildControls () {
      if (childControls != null) {
        levelControls.remove(childControls);
      }
      childControls = getTurnLevel(currentLevel).getSetControls(me, turn);
      levelControls.add(childControls);
      pack();
    }
     
  }
  
  protected void cancelSet() {
    setState(savedSetState);
    turnWindow.setVisible(true);
    turnWindow.setFocusable(true);
  }
  
  protected void saveSet() {
    save();
    updateTurnDisplay(SET);
    doGlobalkeys();
  }

  public static class SetTurn extends Command {
    private String oldState;
    private String newState;
    private TurnTracker turn;

    public SetTurn(String newState, TurnTracker t) {
      this.newState = newState;
      oldState = t.getState();
      turn = t;
    }
    
    public SetTurn(TurnTracker t, String oldState) {
      newState = t.getState();
      this.oldState = oldState;
      turn = t;
    }

    public TurnTracker getTurn() {
      return turn;
    }
    
    protected void executeCommand() {
      turn.setState(newState);
    }

    protected Command myUndoCommand() {
      return new SetTurn(oldState, turn);
    }
  }
}
