/*
 * $Id: UniqueIdManager.java 2962 2008-02-01 20:43:58Z 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.tools;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import VASSAL.build.Buildable;
import VASSAL.configure.ConfigureTree;
import VASSAL.configure.ValidationReport;
import VASSAL.configure.ValidityChecker;

/**
 * A class for assigning unique identifiers to objects.  Identifiers will be
 * of the form prefix#, where prefix is specified at initialization and the #
 * is an increasing digit. Components will have the same ID provided they
 * are loaded in the same order.
 *
 * Unfortunately, this approach is flawed. If a module is edited, saved games
 * from previous versions can become broken. Worse, two players with different
 * extensions loaded could have incompatible behavior.
 *
 * The preferred way to have unique identifiers is to allow the user to provide
 * names and use a {@link VASSAL.configure.ValidityChecker} to ensure that the
 * names are unique.  This class provides some support for using this approach
 * while providing backward compatibility with old saved games and modules.
 *
 * Usage:  an {@link Identifyable} instance invokes {@link #add}, typically
 * during the {@link Buildable#build} method.  Classes can use the
 * {@link #getIdentifier} method to look up an identifier for that instance,
 * and can use {@link #findInstance} to look up a component by id.
 */
public class UniqueIdManager implements ValidityChecker {
  private List<Identifyable> instances = new ArrayList<Identifyable>();
  private String prefix;

  public UniqueIdManager(String prefix) {
    this.prefix = prefix;
  }

  public void add(Identifyable i) {
    i.setId(prefix + instances.size());
    instances.add(i);
  }

  public void remove(Identifyable i) {
    int index = instances.indexOf(i);
    if (index >= 0) {
      for (int j = index + 1, n = instances.size(); j < n; ++j) {
        instances.get(j).setId(prefix + (j - 1));
      }
      instances.remove(index);
    }
  }

  /**
   * Make a best guess for a unique identifier for the target.
   * Use {@link Identifyable#getConfigureName if non-null, otherwise
   * use {@link Identifyable#getId
   * @param target
   * @return
   */
  public static String getIdentifier(Identifyable target) {
    String id = target.getConfigureName();
    if (id == null || id.length() == 0) {
      id = target.getId();
    }
    return id;
  }

  public Iterator<Identifyable> getAllInstances() {
    return instances.iterator();
  }

  /**
   * Return the first instance whose name or id matches the argument
   * @param id
   * @return
   */
  public Identifyable findInstance(String id) {
    if (id != null) {
      for (Identifyable i : instances) {
        if (id.equals(i.getConfigureName()) || id.equals(i.getId())) {
          return i;
        }
      }
    }
    return null;
  }

  /** Ensures that no other instance of the same class has the same name */
  public void validate(Buildable target, ValidationReport report) {
    if (target instanceof Identifyable) {
      Identifyable iTarget = (Identifyable) target;
      if (iTarget.getConfigureName() == null ||
          iTarget.getConfigureName().length() == 0) {
        report.addWarning("A " +
                          ConfigureTree.getConfigureName(target.getClass()) +
                          " has not been given a name");
      }
      else if (instances.contains(iTarget)) {
        Identifyable compare = null;
        for (Iterator<Identifyable> i = instances.iterator();
             i.hasNext() && compare != iTarget; ) {
          compare = i.next();
          if (compare != iTarget &&
              iTarget.getConfigureName().equals(compare.getConfigureName())) {
            report.addWarning("More than one " +
                              ConfigureTree.getConfigureName(target.getClass())
                              + " named " + iTarget.getConfigureName());
            break;
          }
        }
      }
    }
  }

  /**
   * An object with an identifier that can be manipulated by a
   * {@link UniqueIdManager}
   */
  public static interface Identifyable {
    void setId(String id);

    String getId();

    String getConfigureName(); // User-assigned name
  }
}
