/*
 * $Id: UsePrototype.java 5913 2009-08-09 20:00:28Z uckelman $
 *
 * Copyright (c) 2004 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.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;

import javax.swing.KeyStroke;

import VASSAL.build.module.PrototypeDefinition;
import VASSAL.build.module.PrototypesContainer;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.properties.PropertySource;
import VASSAL.command.Command;
import VASSAL.configure.StringConfigurer;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.RecursionLimitException;
import VASSAL.tools.RecursionLimiter;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.RecursionLimiter.Loopable;

/**
 * This trait is a placeholder for a pre-defined series of traits specified in a
 * {@link VASSAL.build.module.PrototypeDefinition} object. When a piece that uses a prototype is defined in a module, it
 * is simply assigned the name of a particular prototype definition. When that piece is during a game, the UsePrototype
 * trait is substituted for the list of traits in the prototype definition. From that point on, the piece has no record
 * that those traits were defined in a prototype instead of assigned to piece directly. This is necessary so that
 * subsequent changes to a prototype definition don't invalidate games that were saved using previous versions of the
 * module.
 * 
 */
public class UsePrototype extends Decorator implements EditablePiece, Loopable {
  public static final String ID = "prototype;";
  private String prototypeName;
  private String lastCachedPrototype;
  private GamePiece prototype;
  private PropertySource properties;
  private String type;

  public UsePrototype() {
    this(ID, null);
  }

  public UsePrototype(String type, GamePiece inner) {
    mySetType(type);
    setInner(inner);
  }

  public String getDescription() {
    return prototypeName != null && prototypeName.length() > 0 ? "Prototype - " + prototypeName : "Prototype";
  }

  public HelpFile getHelpFile() {
    return HelpFile.getReferenceManualPage("UsePrototype.htm");
  }

  public void mySetType(String type) {
    this.type = type;
    SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type.substring(ID.length()), ';');
    prototypeName = st.nextToken("");
    if (st.hasMoreTokens()) {
      final java.util.Properties p = new java.util.Properties();
      SequenceEncoder.Decoder st2 = new SequenceEncoder.Decoder(st.nextToken(), ',');
      while (st2.hasMoreTokens()) {
        SequenceEncoder.Decoder st3 = new SequenceEncoder.Decoder(st2.nextToken(), '=');
        if (st3.hasMoreTokens()) {
          String key = st3.nextToken();
          if (st3.hasMoreTokens()) {
            String value = st3.nextToken();
            p.setProperty(key, value);
          }
        } 
      }
      properties = new PropertySource() {
        public Object getProperty(Object key) {
          return p.getProperty(String.valueOf(key));
        }

        public Object getLocalizedProperty(Object key) {
          return getProperty(key);
        }
      };
    }
    lastCachedPrototype = null;
  }

  protected KeyCommand[] myGetKeyCommands() {
    return new KeyCommand[0];
  }

  protected KeyCommand[] getKeyCommands() {
    return (KeyCommand[]) getExpandedInner().getProperty(Properties.KEY_COMMANDS);
  }

  public void setInner(GamePiece p) {
    super.setInner(p);
    lastCachedPrototype = null;
  }

  protected void buildPrototype() {
    final PrototypeDefinition def =
      PrototypesContainer.getPrototype(prototypeName);
    if (def != null) {
      final GamePiece expandedPrototype = def.getPiece(properties);

      // Check to see if prototype definition has changed
      final String type = expandedPrototype.getType();
      if (!type.equals(lastCachedPrototype)) {
        lastCachedPrototype = type;
        try {
          RecursionLimiter.startExecution(this);

          prototype = PieceCloner.getInstance().clonePiece(expandedPrototype);
          final Decorator outer = (Decorator)
            Decorator.getInnermost(prototype).getProperty(Properties.OUTER);
          if (outer != null) { // Will be null for an empty prototype
            outer.setInner(piece);
            prototype.setProperty(Properties.OUTER, this);
          }
          else {
            prototype = null;
          }
        }
        catch (RecursionLimitException e) {
          ErrorDialog.infiniteLoop(e);
          prototype = null;
        }
        finally {
          RecursionLimiter.endExecution();
        }
      }
    }
    else {
      prototype = null;
    }
  }

  /**
   * Build a new GamePiece instance based on the traits in the referenced {@link PrototypeDefinition}. Substitute the
   * new instance for {@link #getInner} and return it. If the referenced definition does not exist, return the default
   * inner piece.
   * 
   * @return the new instance
   */
  public GamePiece getExpandedInner() {
    buildPrototype();
    return prototype != null ? prototype : piece;
  }

  public String myGetState() {
    return "";
  }

  public String myGetType() {
    return type;
  }

  public Command keyEvent(KeyStroke stroke) {
    return getExpandedInner().keyEvent(stroke);
  }

  public Command myKeyEvent(KeyStroke stroke) {
    return null;
  }

  public void mySetState(String newState) {
  }

  public Rectangle boundingBox() {
    return getExpandedInner().boundingBox();
  }

  public void draw(Graphics g, int x, int y, Component obs, double zoom) {
    getExpandedInner().draw(g, x, y, obs, zoom);
  }

  public String getName() {
    return getExpandedInner().getName();
  }

  public Shape getShape() {
    return getExpandedInner().getShape();
  }

  public String getPrototypeName() {
    return prototypeName;
  }

  public PieceEditor getEditor() {
    return new Editor(this);
  }
  public static class Editor implements PieceEditor {
    private StringConfigurer nameConfig;

    public Editor(UsePrototype up) {
      nameConfig = new StringConfigurer(null, "Prototype name:  ", up.type.substring(ID.length()));
    }

    public Component getControls() {
      return nameConfig.getControls();
    }

    public String getState() {
      return "";
    }

    public String getType() {
      return ID + nameConfig.getValueString();
    }
  }
  
  // Implement Loopable
  public String getComponentName() {
    return getDescription();
  }

  public String getComponentTypeName() {
    return "Prototype";
  }
}
