package users;

import java.io.*;

import java.util.*;

import users.events.*;
import users.swing.*;

import util.*;

/**
  * Manages users, their capabilities and their associations to other objects.
  *
  * <p>The UserManager provides possibilities to manage any number of users. Users can
  * be added, retrieved using their name to identify them, and removed from the system.
  * Additionally, users can be associated to, and disassociated from, any object. Their can be a maximum of
  * one user associated to any given object at any given time.</p>
  *
  * <p>You can provide a set of default capabilities, that every new user will be provided with.</p>
  *
  * @see User
  * @see #setDefaultCaps
  * @see sale.SalesPoint
  * @see #logOn
  *
  * @author Steffen Zschaler
  * @version 2.0 05/05/1999
  * @since v2.0
  */
public class UserManager extends Object implements Serializable {

  /**
    * The map used to store all the users managed by this UserManager.
    *
    * <p>The user's name is being used as the key. The user itself is being stored as
    * the value.</p>
    *
    * @see User
    *
    * @serial
    */
  private SortedMap m_mpUsers = new TreeMap();

  /**
    * The map used to store all the users currently associated to some object.
    *
    * <p>The key of the map is the object to which a given user is associated.
    * The user itself is being stored as the value.</p>
    *
    * @see User
    * @see #logOn
    *
    * @serial
    */
  private Map m_mpCurrentUsers = new HashMap();

  /**
    * The map of default capabilities to be associated with each new user.
    *
    * <p>The keys and values are being used in the same way as {@link User} uses them, i.e.
    * the capabilities names are used as keys and the capabilities themselves as
    * values.</p>
    *
    * @see #setDefaultCaps
    * @see Capability
    * @see User#setCapabilities
    *
    * @serial
    */
  private Map m_mpDefaultCaps = new HashMap();

  /**
    * The factory used to create new User objects.
    *
    * @see #createUser
    * @see #setUserCreator
    * @see User
    *
    * @serial
    */
  private UserCreator m_ucCreator;

  /**
    * The list of listeners registered with this UserManager.
    *
    * @serial See <a href="#util.ListenerHelper">ListenerHelper's serializable form</a> for more information on
    * what listeners get a chance to be serialized.
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * Create a new UserManager with an empty set of default capabilities, managing direct
    * instances of the User class.
    *
    * @see #setDefaultCaps
    * @see #createUser
    */
  public UserManager() {
    this (null, null);
  }

  /**
    * Create a new UserManager, using a specific set of default capabilities. This
    * UserManager will manage direct instances of User.
    *
    * @param mpDefaultCaps a map describing the default capabilities any new user should
    * have. The keys of this map should be the name of their associated capability. Specifying
    * <code>null</code> will result in there being no default capabilities.
    *
    * @see #setDefaultCaps
    * @see #createUser
    * @see Capability
    */
  public UserManager (Map mpDefaultCaps) {
    this (mpDefaultCaps, null);
  }

  /**
    * Create a new UserManager with an empty set of default capabilities. This UserManager
    * will create instances of a developer defined subclass of User.
    *
    * @param ucCreator the factory to be used to create new User objects. Specifying
    * <code>null</code> will result in a default implementation being used, which will
    * create direct instances of the User class.
    *
    * @see #setDefaultCaps
    * @see #createUser
    */
  public UserManager (UserCreator ucCreator) {
    this (null, ucCreator);
  }

  /**
    * Create a new UserManager providing both a set of default capabilities and a User
    * creation factory.
    *
    * @param mpDefaultCaps a map describing the default capabilities any new user should
    * have. The keys of this map should be the name of their associated capability. Specifying
    * <code>null</code> will result in there being no default capabilities.
    * @param ucCreator the factory to be used to create new User objects. Specifying
    * <code>null</code> will result in a default implementation being used, which will
    * create direct instances of the User class.
    *
    * @see #setDefaultCaps
    * @see Capability
    * @see #createUser
    * @see User
    */
  public UserManager (Map mpDefaultCaps,
                      UserCreator ucCreator) {
    super();

    setDefaultCaps (mpDefaultCaps);
    setUserCreator (ucCreator);
  }

  /**
    * Set the factory to be used when creating new User objects.
    *
    * @param ucCreator the factory to be used to create new User objects. Specifying
    * <code>null</code> will result in a default implementation being used, which will
    * create direct instances of the User class.
    *
    * @see #createUser
    * @see User
    *
    * @override Never
    */
  public synchronized void setUserCreator (UserCreator ucCreator) {
    if (ucCreator != null) {
      m_ucCreator = ucCreator;
    }
    else {
      m_ucCreator = new UserCreator();
    }
  }

  /**
    * Specify the set of default capabilities to be used when creating new User objects.
    *
    * <p>Calling this method will not influence the capabilities of users already created.</p>
    *
    * @param mpDefaultCaps a map describing the default capabilities any new user should
    * have. The keys of this map should be the name of their associated capability. Specifying
    * <code>null</code> will result in there being no default capabilities.
    *
    * @see #createUser
    * @see Capability
    *
    * @override Never
    */
  public synchronized void setDefaultCaps (Map mpDefaultCaps) {
    if (mpDefaultCaps != null) {
      m_mpDefaultCaps = new HashMap (mpDefaultCaps);
    }
    else {
      m_mpDefaultCaps = new HashMap();
    }
  }

  /**
    * Set a capability to be used as a default capability henceforward.
    *
    * <p>Calling this method will not influence the capabilities of users already created.</p>
    *
    * <p>If a default capability of the same name as the one given did already exist, it
    * will be replaced by the new capability.</p>
    *
    * @param cap the capability to be set as a default capability.
    *
    * @see #createUser
    *
    * @override Never
    */
  public synchronized void setDefaultCapability (Capability cap) {
    m_mpDefaultCaps.put (cap.getName(), cap);
  }

  /**
    * Create a new user to be managed by this UserManager.
    *
    * <p>This method uses the defined {@link UserCreator} (see {@link #setUserCreator}) to create the
    * new <code>User</code> object. The new user will later be accessible using its name.</p>
    *
    * <p>The newly created user will get all the default capabilities defined at the time
    * this method is called.</p>
    *
    * <p>A <code>userAdded</code> event will be received by any {@link UserDataListener} that
    * registered an interest in this <code>UserManager</code>.</p>
    *
    * @param sName the name of the new user. This must be unique, i.e. there must not be
    * a user with the same name already managed by this UserManager.
    *
    * @return the newly created user.
    *
    * @exception DuplicateUserException if there already was a user with the given name.
    *
    * @see #setDefaultCaps
    * @see #setUserCreator
    * @see User
    * @see UserCreator#createUser
    * @see users.events.UserDataListener#userAdded
    *
    * @override Never Rather than overriding this method, you should provide a new {@link UserCreator}.
    */
  public synchronized User createUser (String sName) {

    if (m_mpUsers.containsKey (sName)) {
      throw new DuplicateUserException ("User \"" + sName + "\" already exists ! Cannot have two users with the same user name.");
    }

    User usr = m_ucCreator.createUser (sName);

    usr.setCapabilities (m_mpDefaultCaps);

    m_mpUsers.put (sName, usr);

    fireUserAdded (usr);

    return usr;
  }

  /**
    * Add a user to the UserManager.
    *
    * <p>A <code>userAdded</code> event will be received by any {@link UserDataListener} that
    * registered an interest in this UserManager.</p>
    *
    * @param usr the user to be added.
    *
    * @exception DuplicateUserException if there already is a user of the same name.
    *
    * @see users.events.UserDataListener#userAdded
    *
    * @override Never
    */
  public synchronized void addUser (User usr) {

    if (m_mpUsers.containsKey (usr.getName())) {
      throw new DuplicateUserException ("User \"" + usr.getName() + "\" already exists ! Cannot have two users with the same user name.");
    }

    m_mpUsers.put (usr.getName(), usr);

    fireUserAdded (usr);
  }

  /**
    * Retrieve a user by name.
    *
    * <p>If no user with the given name exists, this method will return <code>null</code>.</p>
    *
    * @param sName the name of the user looked for.
    *
    * @return the user corresponding to the given name, if any. <code>null</code> will
    * be returned if no such user can be found.
    *
    * @see #createUser
    *
    * @override Never
    */
  public synchronized User getUser (String sName) {
    return (User) m_mpUsers.get (sName);
  }

  /**
    * Return all user names registered with this UserManager.
    *
    * <p>The returned set is backed by the UserManager, i.e. it will reflect changes
    * made through <code>createUser()</code> or <code>removeUser()</code>. The set itself
    * is unmodifiable and ordered alphabetically.</p>
    *
    * @return an unmodifiable, ordered set of all user names in this UserManager.
    *
    * @see #createUser
    * @see #deleteUser
    *
    * @override Never
    */
  public synchronized Set getUserNames() {
    return Collections.unmodifiableSet (m_mpUsers.keySet());
  }

  /**
    * Return all users registered with this UserManager.
    *
    * <p>The returned collection is backed by the UserManager, i.e. it will reflect
    * changes made through <code>createUser()</code> or <code>removeUser()</code>. The
    * collection itself is unmodifiable and ordered alphabetically by the users' names.</p>
    *
    * @return an unmodifiable, ordered set of all users in this UserManager.
    *
    * @see #createUser
    * @see #deleteUser
    *
    * @override Never
    */
  public synchronized Collection getUsers() {
    return Collections.unmodifiableCollection (m_mpUsers.values());
  }

  /**
    * Delete a user from this UserManager.
    *
    * <p>The user will be removed from the UserManager and will no longer be available
    * via {@link #getUser}. A <code>userDeleted</code> event will be received by all
    * {@link UserDataListener UserDataListeners} registered with this UserManager if a user was removed. If no user
    * with the given name existed no exception will be thrown and no event will be fired.</p>
    *
    * <p>If the user is currently associated to some object, it will stay so until
    * it is disassociated explicitly. It will not have the possibility to log in again,
    * though.</p>
    *
    * @param sName the name of the user to be removed
    *
    * @return the user that was just removed or <code>null</code> if none.
    *
    * @see users.events.UserDataListener#userDeleted
    *
    * @override Never
    */
  public synchronized User deleteUser (String sName) {
    User usrReturn = (User) m_mpUsers.remove (sName);

    if (usrReturn != null) {
      fireUserDeleted (usrReturn);
    }

    return usrReturn;
  }

  /**
    * Add a UserDataListener. UserDataListeners will receive an event whenever a user
    * was created or removed.
    *
    * @param udl the UserDataListener to add.
    *
    * @override Never
    */
  public void addUserDataListener (UserDataListener udl) {
    m_lhListeners.add (UserDataListener.class, udl);
  }

  /**
    * Remove a UserDataListener.
    *
    * @param udl the UserDataListener to remove.
    *
    * @override Never
    */
  public void removeUserDataListener (UserDataListener udl) {
    m_lhListeners.remove (UserDataListener.class, udl);
  }

  /**
    * Fire a <code>userAdded</code> event to all interested listeners.
    *
    * @param usr the user that was added.
    *
    * @override Never
    */
  protected void fireUserAdded (User usr) {
    UserDataEvent ude = null;

    // Guaranteed to return a non-null array
    Object[] listeners = m_lhListeners.getListenerList();

    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==UserDataListener.class) {
        // Lazily create the event:
        if (ude == null)
          ude = new UserDataEvent (this, usr);

        ((UserDataListener) listeners[i+1]).userAdded (ude);
      }
    }
  }

  /**
    * Fire a <code>userDeleted</code> event to all interested listeners.
    *
    * @param usr the user that was deleted.
    *
    * @override Never
    */
  protected void fireUserDeleted (User usr) {
    UserDataEvent ude = null;

    // Guaranteed to return a non-null array
    Object[] listeners = m_lhListeners.getListenerList();

    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==UserDataListener.class) {
        // Lazily create the event:
        if (ude == null)
          ude = new UserDataEvent (this, usr);

        ((UserDataListener) listeners[i+1]).userDeleted (ude);
      }
    }
  }

  /**
    * Associate a user with an object.
    *
    * <p>Only one user at a time can be associated with one Object. If there is already
    * another user associated with the given Object, its association is undone and the
    * user is returned.</p>
    *
    * <p>Only users that are actually managed by this UserManager can be logged in. An
    * exception will be thrown if you try to log in a user that is unknown to this
    * UserManager. Especially, this can happen if you try to log in a user that was
    * previously removed using {@link #deleteUser}.</p>
    *
    * @param o the Object with which to associate the user.
    * @param u the user to associate with the Object
    *
    * @return the user that was previously associated with the Object or
    * <code>null</code> if none.
    *
    * @exception UnknownUserException if the user to log in is not known at this
    * UserManager. A user is known at a UserManager, if the User object is registered,
    * i.e. no <code>equals()</code> method of any kind is called.
    *
    * @see User#loggedOn
    *
    * @override Never
    */
  public synchronized User logOn (Object o, User u) {

    User usrTemp = getUser (u.getName());

    if ((usrTemp == null) ||
        (usrTemp != u)) {
      throw new UnknownUserException (u.getName());
    }

    User usrReturn = logOff (o);

    if (u != null) {
      m_mpCurrentUsers.put (o, u);

      u.loggedOn (o);
    }

    return usrReturn;
  }

  /**
    * Disassociate the current user from an Object.
    *
    * @param o the Object from which to disassociate a user.
    *
    * @return the user just logged off or <code>null</code> if none.
    *
    * @see User#loggedOff
    *
    * @override Never
    */
  public synchronized User logOff (Object o) {
    User u = (User) m_mpCurrentUsers.remove (o);

    if (u != null) {
      u.loggedOff (o);
    }

    return u;
  }

  /**
    * Retrieve the user currently associated with some Object.
    *
    * @param o the Object with which the searched user must be associated.
    *
    * @return the user associated with the given Object or <code>null</code> if none.
    *
    * @override Never
    */
  public synchronized User getCurrentUser (Object o) {
    return (User) m_mpCurrentUsers.get (o);
  }

  ////////////////////////////////////////////////////////////////////////////////////////////
  // STATIC PART
  ////////////////////////////////////////////////////////////////////////////////////////////

  /**
    * The global UserManager.
    */
  private static UserManager s_umGlobal = new UserManager();

  /**
    * Get the global UserManager.
    *
    * <p>The global UserManager can be used as a centralized instance to manage all the
    * users in an application.</p>
    *
    * @return the global UserManager.
    */
  public static synchronized UserManager getGlobalUM() {
    return s_umGlobal;
  }

  /**
    * Set a new UserManager to be the global UserManager from now on.
    *
    * @param umNew the new global UserManager.
    *
    * @return the previous UserManager.
    */
  public static synchronized UserManager setGlobalUM (UserManager umNew) {
    UserManager umReturn = s_umGlobal;

    s_umGlobal = umNew;

    return s_umGlobal;
  }
}