package com.treelight.krnl;

import java.net.URL;
import java.util.*;

/**
 * Maintain and update a list of unstamped versions.
 * Note: All methods are static. This class cannot be
 * instantiated.
 * <p>
 * ___TODO:<br>
 * The methods in this class need to be thread-safe, so that
 * no new nodes are added while version-stamping is going on.
 *
 * @version 0.1
 * @author Eric Armstrong
 * @see ../NodesAndLists.html
 */
public class VersionControl {
  // U.S. Naval Observatory is the default time server
  //SNTP = Synchronous Network Time Protocol
  //  (keeps the time on remote systems reasonably synchronized) 
  private static final URL defaultServer = new URL("sntp://tick.usno.navy.mil"); 
  private static URL timeServer;

  private static UserID userID; // Identifies the author of the current version
  private static SystemID systemID; // Identifies the system the version was created on

  public String getUserID() { return userID; }
  public String getSystemID() { return systemID; }

  /**
   * This list provides a place to record all versionStamps that have
   * been created, but not time stamped.
   */
  static Set unstampedNodeSet = new HashSet();

  /**
   * The list of all unstamped versions. Versions are inserted at the
   * front of this list, so that the list iterator retreives the most
   * recent first.
   * <p>
   * Note that we don't want to use the AbstractList structure for this
   * purpose, since that class supports versioning! Here, we need normal
   * lists that we can manage without having to worry about recursively
   * versioning a version of the version list version -- or words to that
   * effect.
   */
  static LinkedList unstampedVersions = new LinkedList(); 

  /**
   * Return a list iterator on the set of unstamped versions.
   * The outer system can use that iterator to perform undo operations,
   * removing items from it and moving them to a redo list as the
   * user steps through the chain of undoable changes.
   */
  public ListIterator unstampedVersionsIterator() {
    return unstampedVersions.listIterator();
  }

  static {
    // ___TODO:
    // Determine the globally unique ID of the user's system.
    // this.systemID = ...;

    // ___TODO:
    // Set up the user ID, if not already stored in the system.
    // this.userID = new UserID(...);

    // ???????????????????  (may not be needed, given userID + systemID)
    // IF: we've previously stored the last node ID value
    // for the user's system, retrieve it now.
    // ___TODO___
    // ELSE: the value has not already been stored. Examine
    // all nodes in the system and find the last ID that
    // matches the user system's ID. Save the value in
    // persistent storage for faster startup next time.
    // ___TODO___
    // ???????????????????
  }

  /**
   * Configure the time server.
   */
  public static void configureServer(URL timeServer) {
    this.timeServer = timeServer;
  }

  /** 
   * Add an entry to the list of unstamped versions.
   * 
   * If the node already exists on the list, it does not
   * not need to be added again, because the node itself
   * contains a list of its previous versions. 
   * <p>
   * Synchronize on the set of unstamped nodes, to ensure
   * that some clever multitasking system isn't creating
   * new nodes and timestamping unstamped nodes at the
   * same time.
   */
  public static void addUnstampedVersion(VersionStamp versionStamp) {
    syncrhonize(unstampedNodeSet) { 
      // Add the node to a set, so it only exists once.
      this.unstampedNodeSet.add(versionStamp.getNode());
 
      // Insert the version node at the head of the unstamped list
      this.unstampedVersions.insert(0, versionStamp);
    }
  }

  /**
   * Use the currently configured timeServer, or the default server if none
   * is specified, to set the time. It is expected that offline systems
   * will create versionStamps without times, and keep a list of nodes that
   * have been created (in some atomic manner, of course). This function 
   * will then be invoked on each entry on that list when the user goes
   * online to synchronize with other systems.
   * <p>
   * Synchronize on the set of unstamped nodes, to ensure
   * that some clever multitasking system isn't creating
   * new nodes and timestamping unstamped nodes at the
   * same time.
   * <p>
   * ___TODO:
   * For each node on the unstampedVersions list, collapse the differences
   * down to a minimal set. For example, if one list item changed position
   * 3 times, then collapse those changes down to a single move, and
   * eliminate the intervening changes.
   */
  public static void stampVersionTimes() {
    syncrhonize(unstampedNodeSet) { 
      doVersionStamping();
    }
  }

  private static void doVersionStamping() {
    URL server = this.timeServer;
    if (server == null) {
      server = defaultServer;
    }
    // For now, set time = system's current time
    long time = System.currentTimeMillis(); 

    //___TODO: open a URLConnection to the server and revise the time

    // Reduce the time by the size of the list, so that each version
    // can get its own unique timestamp.
    time = time - unstampedVersions.size();

    //___TODO: Maintain an "all versions" list, as well as the undo
    // list (unstamped versions). When publishing, stamp version
    // times, and move the list of changes to a tree, where the
    // version identifer is identified by the user as "major, minor,
    // or micro".

    // Step through the list, adding one millisecond each time,
    // so that each version gets a unique stamp. Stamp the version
    // with that time and remove the version from the list.
    iterator it = unstampedNodeSet.iterator();
    while (lit.hasNext()) {
      AbstractNode node = (AbstractNode) it.next();
      //___TODO:___
      //As a result of UNDO operations, a node could be on the
      //list which has no changes. In that case, simply ignore it.

      // ___TODO:___
      // Cycle through all of the node's versions, collapse to a
      // minimal set of changes, and time stamp those. 
      //FOR EACH AS-YET UNSTAMPED VERSION OF A NODE:
      //{
        //___TODO??: Add the node to a list that can be returned
        //by this operation, so the using system can store that
        //list in a version tree
        VersionStamp versionStamp = node.getVersionStamp();
        versionStamp.setTime(++time);
      //}
    }

    // Reinitialize the data structures
    unstampedNodeSet = new HashMap();
    unstampedVesions = new LinkedList();
  }

  /**
   * A private constructor, so systems using this package
   * won't create VersionControl instances.
   */
  private VersionControl() {
  }

}//VersionControl
