package gov.va.med.authentication.kernel.sspi.authentication.manageable;

import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 * Represents a principal (that is, user or group) in the database.
 * Contains behaviors common to users and groups.
 * The principal is cached in memory.
 *
 * 
 * @author VHIT Security and Other Common Services (S&OCS)
 * @version 1.1.0.002
 */

/*package*/ abstract class KaajeePrincipalEntry
{
  private KaajeeUserGroupDatabase database; // the user/group database

  // All database entries have a key.  The key has two
  // parts: a prefix containing the type of entry (user or group)
  // a suffix containing the principal's name.
  private String prefix; // the principal's prefix, that is, type
  private String name;   // the principal's name

  // Principals can be members of groups.
  // The names of the groups immediately containing this principal:
  private HashSet groups = new HashSet();

  /**
   * The constructor.
   *
   * @param prefix a String containing the principal's prefix
   * (that is, type).
   *
   * @param name a String containing the principal's name
   *
   * @param database a UserGroupDatabase containing the
   * users and groups for the manageable sample authenticator.

   * @throws IllegalArgumentException if the name is
   * invalid (eg. starts with whitespace).
   */
  protected KaajeePrincipalEntry(String prefix, String name, KaajeeUserGroupDatabase database)
  {
    Utils.checkVal(name);
    this.database = database;
    this.name = name;
    this.prefix = prefix;
  }

  /**
   * The derived classes for user and group entries need
   * to fill in the behavior that writes the user or group
   * to the database.
   */
  protected abstract void update();

  /**
   * In the database, a principal's groups are stored as a comma separated
   * list of group names.  Convert the list into a hash set.
   *
   * @param groupsString a String containing a comma separated list of
   * the names of the groups immediately containing this principal as a member.
   * Pass in null or an empty string if there are no groups.
   */
  protected void initializeGroups(String groupsString)
  {
    if (groupsString == null) return;
    for (StringTokenizer i = new StringTokenizer(groupsString, ","); i.hasMoreTokens();) {
      groups.add(i.nextToken().trim());
    }
	//System.out.println("principalentry  initializeGroups   groups    "+  groups);
  }

  /**
   * Convert the list of the groups immediately containing this
   * principal as a member into a comma separated list of group names.
   *
   * @return a String containing a comma separated list of group names.
   * Returns an empty string if there are no groups.
   */
  protected String getGroupsString()
  {
    String groupsString = "";
    for (Iterator i = getImmediateParentGroups(); i.hasNext();) {
      String group = (String)i.next();
      if (groupsString.length() > 0) {
        groupsString = groupsString + ",";
      }
      groupsString = groupsString + group;
    }
    return groupsString;      
  }

  /**
   * Get the name of this principal.
   *
   * @return a String containing this principal's name
   */
  protected String getName()
  {
    return name;
  }

  /**
   * A utility to get the database key for this principal.
   * The key is a prefix (type) followed by the principal's name.
   *
   * @return a String containing this principal's database key.
   */
  private String getKey()
  {
    return prefix + name;
  }

  /**
   * Get this principal's persistent state from the database.
   * The derived user / group class is responsible for defining
   * the format of its persistent state.
   *
   * @return a String containing the principal's persistent state.
   */
  protected String getValue()
  {
    return database.get(getKey());
  }

  /**
   * Does the principal exist in the database?
   *
   * @return a boolean indicating if the principal exists.
   */
  /*package*/ boolean exists()
  {
    return database.exists(getKey());
  }

  /**
   * Set this principal's persistent state in the database.
   *
   * Creates the principal if it doesn't already exist in
   * the database.  Otherwise, updates the principal's
   * persistent state.
   *
   * The derived user / group class is responsible for
   * defining the format of its persistent state.
   *
   * @param value a String containing the principal's
   * persistent state.
   */
  /*package*/ void setValue(String value)
  {
    database.set(getKey(), value);
  }

  /**
   * Removes this principal from the database.
   * Ignored if the principal doesn't exist.
   */
  /*package*/ void remove()
  {
    database.remove(getKey());
  }

  /**
   * Determines if this principal is immediately a member of a group.
   *
   * eg. if group2 contains group1 and group1 contains user1, then user1
   * is immediately a member of group1 but not immediately a member
   * of group2.
   *
   * @param group a String containing the group's name.
   *
   * @return a boolean indicating if this principal is immediately
   * a member of the group.
   */
  /*package*/ boolean immediateMemberOf(String group)
  {
    return groups.contains(group);
  }

  /**
   * Determines if this principal is a member of a group either
   * immediately or indirectly.
   *
   * eg. if group2 contains group1 and group1 contains user1,
   * then user1 is recursively a member of both group1 and group2.
   *
   * @param group a String containing the group's name.
   *
   * @return a boolean indicating if this principal is
   * recursively a member of the group.
   */
  /*package*/ boolean recursiveMemberOf(String group)
  {
    if (immediateMemberOf(group)) return true;
    for (Iterator i = getImmediateParentGroups(); i.hasNext();) {
      String parentGroupName = (String)i.next();
      KaajeeGroupEntry parentGroup = database.getGroup(parentGroupName);
      if (parentGroup.recursiveMemberOf(group)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Return the list of groups immediately containing this
   * principal as a member.
   *
   * @return an Iterator of String containing the groups' names.
   */
  /*package*/ Iterator getImmediateParentGroups()
  {
	 return groups.iterator();
  }

  /**
   * Return the list of all groups that immediately or indirectly
   * contain this principal as a member.
   *
   * @return an Iterator of String containing the groups' names.
   */
  /*package*/ Iterator getAllParentGroups()
  {
    HashSet allGroups = new HashSet();
    getGroupsRecursive(getImmediateParentGroups(), allGroups);
    return allGroups.iterator();
  }

  /**
   * A recursive utility to help find all the groups containing a principal.
   *
   * @param currentGroups an Iterator of String containing the
   * names of immediate groups at a level of the recursion.
   *
   * @param allGroups a HashSet of String containing the names of
   * all the groups that have been found thus far.
   */
  private void getGroupsRecursive(Iterator currentGroups, HashSet allGroups)
  {
    while (currentGroups.hasNext()) {
      String currentGroupName = (String)currentGroups.next();
      allGroups.add(currentGroupName);
	  KaajeeGroupEntry currentGroup = database.getGroup(currentGroupName);
      getGroupsRecursive(currentGroup.getImmediateParentGroups(), allGroups);
    }
  }

  /**
   * Make this principal immediately a member of a group.
   *
   * It is OK if the principal is already a member of the group.
   *
   * This method calls "update" which causes the derived
   * user/group class to write the principal's persistent state
   * out to the database.
   *
   * @param parentGroup a String containing the name of the
   * group that should immediately contain this principal.
   */
  /*package*/ void addToGroup(String parentGroup)
  {
    groups.add(parentGroup);
    update();
  }

  /**
   * Make sure this principal is NOT immediately a member of a group.
   *
   * It is OK if the principal is already not a member of the group.
   *
   * This method calls "update" which causes the derived
   * user/group class to write the principal's persistent state
   * out to the database.
   *
   * @param parentGroup a String containing the name of the
   * group that should immediately contain this principal.
   */
  /*package*/ void removeFromGroup(String parentGroup)
  {
    groups.remove(parentGroup);
    update();
  }
}
