/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.user;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.utils.AbstractSdxObject;
import fr.gouv.culture.sdx.utils.constants.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;

/**
 * Basic user information used in sessions.
 *
 * <p>
 * This class handles the username, the groups the user belongs to,
 * and the preferred locale. If the locale is not set for a user,
 * the user information object will refer to the application's
 * default locale.
 */
public class UserInformation extends AbstractSdxObject {

    public static final String CLASS_NAME_SUFFIX = "UserInformation";
    // identities could be mixed between same appId in 2 frameworks under a same server

    /** The id of the application from which is this user */
    private String appId;
    /** The firstname. */
    private String firstname;
    /** The lastname. */
    private String lastname;
    /**The email address*/
    private String email;
    /** belongs to admin group */
    private boolean isAdmin;
    /** The list of groups it belongs to. */
    private Hashtable groups;
    /** Possible remote IP externally setted to inform toSAX() */
    protected String ip;
    /** Possible remote host externally setted to inform toSAX() */
    protected String host;

    /** The list of applications this user may admin. */
    //TODO:FG how a user from one application is able to be admin on a other ?
    // private Hashtable admins;

    /** The session property name for user information. */
    public static final String SESSION_PROPERTY = "sdxUserInformation"; //TODO : move to constants ? -pb

    /** The anonymous user name*/
    public static final String ANONYMOUS_USERNAME = "sdxAnonymous"; //TODO : move to constants ? -pb

    private final static String ATTRIBUTE_NAME_FIRSTNAME = UserDatabase.FIELD_FIRSTNAME;
    private final static String ATTRIBUTE_NAME_LASTNAME = UserDatabase.FIELD_LASTNAME;
    private final static String ATTRIBUTE_NAME_EMAIL = UserDatabase.FIELD_EMAIL;


    /**Creates the object
     *
     *<p>
     * A super.getLog() must be set and then this object must be setUp.
     *
     * @see #enableLogging
     * @see #setUp
     */
    public UserInformation() {
    }

    /**
     * Creates user information.
     *
     * @param   id              The username.
     * @param   groups          The groups it belongs to (may be null or empty).
     * @param   locale          Thhye user's preferred locale.
     *
     * If logging is desired the super.getLog() should be set after creation.
     * @see #enableLogging
     *
     * @throws SDXException  An exception if the username or the locale are null.
     */

    public void setUp(String appId, String id, Hashtable groups, Locale locale, String firstname, String lastname, String email, String adminGroup) throws SDXException {
        this.appId = appId;
        super.setId(id);
        this.firstname = firstname;
        this.lastname = lastname;
        this.email = email;
        if (groups != null)
            setGroups(groups);
        else
            setGroups(new Hashtable());
        this.isAdmin = isMember(adminGroup);
        setPreferredLocale(locale);
    }

    /**
     * Returns the list of groups this user belongs to.
     */
    public Hashtable getGroups() {
        return this.groups;
    }

    /**
     * Sets the groups the user belongs to.
     *
     * @param   groups      The list of group names (may not be <code>null</code>).
     */
    public void setGroups(Hashtable groups) throws SDXException {
        if (groups == null)
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_GROUPS_NULL, null, null);
        else
            this.groups = groups;
    }

    /**
     * Returns isAdmin.
     */
    public boolean isAdmin() {
        return this.isAdmin;
    }

    /**
     * Returns the firstname.
     */
    public String getFirstname() {
        return this.firstname;
    }

    /**
     * Sets the firstname (may be <code>null</code>).
     */
    public void setFirstname(String firstname) throws SDXException {
        // if (!Utilities.checkString(firstname))
        //    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_INVALID_NAME, null, null);
        // else
        this.firstname = firstname;
    }

    /**
     * Returns the lastname.
     */
    public String getLastname() {
        return this.lastname;
    }

    /**
     * Sets the firstname (may be <code>null</code>).
     */
    public void setLastname(String lastname) throws SDXException {
        // if (!Utilities.checkString(lastname))
        //    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_INVALID_NAME, null, null);
        // else
        this.lastname = lastname;
    }

    /**
     * Returns the preferred locale.
     */
    public Locale getPreferredLocale() {
        return super.getLocale();
    }

    /**
     * Sets the preferred locale (may not be <code>null</code>).
     *
     * @param preferredLocale   The locale
     */
    public void setPreferredLocale(Locale preferredLocale) throws SDXException {
        // TODO:FG why not a null locale ? the default admin user has none; would be better to check at indexation whith only a warn in log not a fatal exception
        // if (preferredLocale == null)
        //     throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOCALE_NULL, null, null);
        // else
        super.setLocale(preferredLocale);
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAppId() {
        return this.appId;
    }

    /**
     * Test whether this user is member of some groups.
     *
     * @param   groupnames      The list of groups.
     * @param   all             If <code>true</code>, the user must be member of each groups ; otherwise of at least one of them.
     */
    public boolean isMember(String[] groupnames, boolean all) {
        if (groupnames == null) return false;
        int nbGroups = 0;
        for (int i = 0; i < groupnames.length; i++) {
            String group = groupnames[i];
            if (group != null) {
                if (groups.containsKey(group)) {
                    nbGroups++;
                    if (!all) return true;
                } else if (all) return false;
            }
        }
        // If we are here, it's because either :
        //      a) all was false and the user is not a member (should return false)
        //      b) all was true and the user is member of all of them (should return true)
        // Situation a) is when nbGroups = 0
        // Situation b) is when nbGroups > 0;
        if (nbGroups > 0)
            return true;
        else
            return false;
    }

    /**
     * Returns whether this user is member of at least one of the groups. This method
     * is a shortcut for isMember(groupnames, false).
     *
     * @param   groupnames      The list of group names.
     */
    public boolean isMember(String[] groupnames) {
        return isMember(groupnames, false);
    }

    /**
     * Returns whether this user is member of a group.
     *
     * @param   groupname       The name of the group.
     */
    public boolean isMember(String groupname) {
        //System.out.println("trying user :" + this.id + " is member of: " + groupname);
        if (groupname == null) return false;
        if (this.groups == null) return false;
        //System.out.println("ismem" + this.getId() + " - " + groupname + " " + groups.containsKey(groupname));
        return groups.containsKey(groupname);
    }

    /**
     * Set an IP externally, probably from acceptRequest
     *
     * @param    ip
     */
    public void setIp(String ip) {
        this.ip = ip;
    }

    /**
     * Get an IP if setted
     */
    public String getIp() {
        return this.ip;
    }

    /**
     * Set a remote Host externally, probably from acceptRequest
     *
     * @param    host
     */
    public void setHost(String host) {
        this.host = host;
    }

    /**
     * Get an host if setted
     */
    public String getHost() {
        return this.host;
    }

    /**
     * Sends information about this anonymous user.
     *
     * <p>
     * The information sent is basically a user definition
     * with nested groups
     *
     * @param   handler
     *
     *
     *   The handler where to send events.
     */


    public void toSAX(ContentHandler handler) throws SAXException {

    	AttributesImpl atts = new AttributesImpl();
        if (getId() != null) atts.addAttribute("", Node.Name.ID, Node.Name.ID, Node.Type.CDATA, getId());
        if (this.firstname != null) atts.addAttribute("", ATTRIBUTE_NAME_FIRSTNAME, ATTRIBUTE_NAME_FIRSTNAME, Node.Type.CDATA, this.firstname);
        if (this.lastname != null) atts.addAttribute("", ATTRIBUTE_NAME_LASTNAME, ATTRIBUTE_NAME_LASTNAME, Node.Type.CDATA, this.lastname);
        if (isAdmin) atts.addAttribute("", Node.Name.ADMIN, Node.Name.ADMIN, Node.Type.CDATA, "true");
        if (this.getEmail() != null) atts.addAttribute("", ATTRIBUTE_NAME_EMAIL, ATTRIBUTE_NAME_EMAIL, Node.Type.CDATA, this.getEmail());
        if (this.appId != null) atts.addAttribute("", Node.Name.APP, Node.Name.APP, Node.Type.CDATA, this.appId);
        if (this.ip != null) atts.addAttribute("", Node.Name.IP, Node.Name.IP, Node.Type.CDATA, this.ip);
        if (this.host != null) atts.addAttribute("", Node.Name.HOST, Node.Name.HOST, Node.Type.CDATA, this.host);
        if (this.isSuperuser()) atts.addAttribute("", Node.Name.SUPERUSER, Node.Name.SUPERUSER, Node.Type.CDATA, "true");
        String lang = this.getPreferredLocale().getLanguage();
        if (!"".equals(lang) && !"".equals(this.getPreferredLocale().getCountry())) lang += "-" + this.getPreferredLocale().getCountry();
        if (lang != null && !"".equals(lang))
            atts.addAttribute(Framework.XMLNamespaceURI, Node.Name.LANG, Node.Name.XML_LANG, Node.Type.CDATA, lang);
        handler.startElement(Framework.SDXNamespaceURI, Node.Name.USER, Framework.SDXNamespacePrefix + ":" + Node.Name.USER, atts);
        if (getGroups() != null)
            for (Enumeration e = getGroups().keys(); e.hasMoreElements();) {
                atts = new AttributesImpl();
                atts.addAttribute("", Node.Name.ID, Node.Name.ID, Node.Type.CDATA, String.valueOf(e.nextElement()));
                handler.startElement(Framework.SDXNamespaceURI, Node.Name.GROUP, Framework.SDXNamespacePrefix + ":" + Node.Name.GROUP, atts);
                handler.endElement(Framework.SDXNamespaceURI, Node.Name.GROUP, Framework.SDXNamespacePrefix + ":" + Node.Name.GROUP);
            }
        handler.endElement(Framework.SDXNamespaceURI, Node.Name.USER, Framework.SDXNamespacePrefix + ":" + Node.Name.USER);
    }

    /**
     * Returns <code>true</code> if this user is an SDX superuser.
     *
     * <p>This class always return <code>false</code>. Subclasses such as SuperuserInformation
     * may return <code>true</code>.
     */
    public boolean isSuperuser() {
        return false;
    }

    // TODO:FG  don't use that, only check if a user is member of Application.defaultUserAdminGroup
    /**
     * Returns true if this user may admin an application.
     *
     * @param   app     The application ID.
     */
/*
    public boolean isAdmin(String app) {
        if (app == null || admins == null) return false;
        return (admins.get(app) != null);
    }
*/
    /**
     * Tells whether this user may admin an application.
     *
     * @param   app     The application ID.
     * @param   admin   If true, user may admin.
     */
/*
    public void setAdmin(String app, boolean admin) {
        if (app == null) return;
        if (admins == null) admins = new Hashtable();
        if (admin)
            admins.put(app, app);
        else if (admins.get(app) != null) admins.remove(app);
    }
*/
    protected String getClassNameSuffix() {
        return UserInformation.CLASS_NAME_SUFFIX;
    }

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.utils.AbstractSdxObject#initToSax()
	 */
	protected boolean initToSax() {
		
		this._xmlizable_objects.put("Application_ID", this.getAppId());
		this._xmlizable_objects.put("ID", this.getId());
		this._xmlizable_objects.put("Encoding", this.getEncoding());
		this._xmlizable_objects.put("XML-Lang", this.getXmlLang());
		this._xmlizable_objects.put("First_Name", this.getFirstname());
		this._xmlizable_objects.put("Last_Name", this.getLastname());
		this._xmlizable_objects.put("Email", this.getEmail());
		this._xmlizable_objects.put("Host", this.getHost());
		this._xmlizable_objects.put("IP", this.getIp());
		this._xmlizable_objects.put("Groups", this.getGroups());
		if(this.getLocale() != null)
			this._xmlizable_objects.put("Locale", this.getLocale().toString());
		if(this.getPreferredLocale() != null)
			this._xmlizable_objects.put("Prefered_Locale", this.getPreferredLocale().toString());
		this._xmlizable_objects.put("Is_Admin", String.valueOf(this.isAdmin()));
		this._xmlizable_objects.put("Is_Superuser", String.valueOf(this.isSuperuser()));
		return true;
	}

	/**Init the LinkedHashMap _xmlizable_volatile_objects with the objects in order to describ them in XML
	 * Some objects need to be refresh each time a toSAX is called*/
	protected void initVolatileObjectsToSax() {
		if(this.getLocale() != null)
			this._xmlizable_objects.put("Locale", this.getLocale().toString());
		if(this.getPreferredLocale() != null)
			this._xmlizable_objects.put("Prefered_Locale", this.getPreferredLocale().toString());
		this._xmlizable_objects.put("Is_Admin", String.valueOf(this.isAdmin()));
		this._xmlizable_objects.put("Is_Superuser", String.valueOf(this.isSuperuser()));
		
	}

}
