/*
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.document.Document;
//import fr.gouv.culture.sdx.documentbase.DocumentBase;
import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
//import fr.gouv.culture.sdx.oai.DocumentBaseOAIHarvester;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.database.*;
import fr.gouv.culture.sdx.utils.save.SaveParameters;
//import fr.gouv.culture.sdx.utils.save.Saveable;

import org.apache.avalon.framework.parameters.Parameters;

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

/**
 * A database for keeping track of users and groups, and mainly of their membership relationships.
 *
 * <p>
 * This class is backed by a LuceneDatabase where groups and users information
 * are kept.
 * <p>
 * A user may be a member of some groups, and groups can be member of other groups.
 */
public class UserDatabase extends DatabaseBacked implements Database {

    /** The field for member relationships. */
    private static final String MEMBER_PROPERTY = "member"; //TODO : move to constants ? -pb

    /** The field for the encoded password */
    public static final String FIELD_PASSWORD = "password"; //TODO : move to constants ? -pb
    /** The field for the firstname */
    public static final String FIELD_FIRSTNAME = User.XCONF_FIRSTNAME;
    /** The field for the lastname */
    public static final String FIELD_LASTNAME = User.XCONF_LASTNAME;
    /** The field for the email */
    public static final String FIELD_EMAIL = User.XCONF_EMAIL;
    /** The field for the preferred locale */
    public static final String FIELD_LOCALE = "locale"; //TODO : move to constants ? -pb

    public UserDatabase(String id) throws SDXException {
        super.setId(id);
    }


    /**
     * Adds a member to a group. The member should be in the database.
     *
     * @param   group       The group.
     * @param   id          Id of an identity to add as a member.
     */
    public void addMember(Group group, String id) throws SDXException {
        if (group == null || group.getId() == null || id == null || "".equals(id)) return;
        // Get the relevant database entity for the member and add the relationship
        DatabaseEntity de = super._database.getEntity(id);
        if (de != null) {
            de.addProperty(MEMBER_PROPERTY, group.getId());
            super._database.update(de);
        } else {
            //member doesn't exist in database
            String[] args = new String[1];
            args[0] = id;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_NO_USER_EXISTS, args, null);
        }
    }

    /**Deletes all references to this group in members
     *
     * @param group     The group
     * @throws SDXException
     */
    public void deleteMembers(Group group) throws SDXException {

        if (group == null) return;

        // Keep the group name
        String groupName = group.getId();
        if (groupName == null) return;

        // Remove all the references to this group

        // We must first find all entites (groups or users) with this group as a member
        Parameters params = new Parameters();
        params.setParameter(MEMBER_PROPERTY, groupName);
        String[] users = super._database.search(params);
        if (users == null) return;
        DatabaseEntity[] updatedUsers = new DatabaseEntity[users.length];
        // Now, 'users' contains all the users that are members of the group. We must
        // then remove the reference to this group

        // Loop over the entities
        for (int i = 0; i < users.length; i++) {
            // Build a new entity object with the same id
            DatabaseEntity updatedUser = new DatabaseEntity(users[i]);

            DatabaseEntity oldUser = getEntity(users[i]);
            // Loop over the properties
            Property[] oldProps = oldUser.getProperties();
            for (int j = 0; j < oldProps.length; j++) {

                if (oldProps[j].getName().equals(MEMBER_PROPERTY)) {
                    // For the member field, we add it only if it's not the group name
                    String oldValue = oldProps[j].getValue();
                    if (oldValue != null) {
                        if (!oldValue.equals(groupName)) updatedUser.addProperty(MEMBER_PROPERTY, oldValue);
                    }
                } else {
                    // For other fields, we simply copy it
                    updatedUser.addProperties(oldProps[j].getName(), oldProps[j].getValues());
                }
            }
            // Our new entity is now OK : let's keep it for now
            updatedUsers[i] = updatedUser;
        }

        // We have all our new database entities : let's save them.
        for (int j = 0; j < updatedUsers.length; j++) super._database.update(updatedUsers[j]);

    }

    /**
     * Removes an identity from the database.
     *
     * <p>
     * For groups, all identities member of the
     * group will have this membership removed also.
     *
     * @param   identity    The identity to delete.
     */
    public void delete(Identity identity) throws SDXException {

        if (identity != null && identity.getId() != null) {

            // In all cases, we must remove the corresponding database entity
            super._database.delete(new DatabaseEntity(getId(identity)));

            // For groups, we must also remove links to the group
            if (identity.getDocType() == Document.DOCTYPE_GROUP) deleteMembers((Group) identity);
        }
    }

    /**
     * Adds an identity to this database.
     *
     * @param   identity       The identity to add.
     * @param   password       The encoded password associated to this identity (may be null).
     */
    public void add(Identity identity, String password) throws SDXException {
        if (identity == null && identity.getId() == null) return; // do nothing
        // if identity exists we must preserve his membership
        DatabaseEntity de = super._database.getEntity(getId(identity));
        // else, create one
        if (de == null) de = new DatabaseEntity(getId(identity));
        // For a user, we will also store the password and the locale here
        if (identity instanceof User) {
            // something like that could be logged ? dont't know how to get a log from here
            // security against commodity, should we show the pass to administrator of the logs ?
            // System.out.println(getId(identity)+" created with pass "+password);
            de.deleteProperty(FIELD_PASSWORD);
            if (password != null) de.addProperty(FIELD_PASSWORD, password);
            //System.out.println("identity : " + de.getId() + " ; " + de.getProperty(FIELD_PASSWORD) + "--" +  password);
            Locale locale = ((User) identity).getPreferredLocale();
            if (locale != null) {
                de.deleteProperty(FIELD_LOCALE);
                de.addProperty(FIELD_LOCALE, locale.toString());
            }
            //TODO FIX ME: This is a quick bug fix by MS ; not very efficient...
            de.deleteProperty(FIELD_FIRSTNAME);
            de.addProperty(FIELD_FIRSTNAME, ((User) identity).getFirstname());
            de.deleteProperty(FIELD_LASTNAME);
            de.addProperty(FIELD_LASTNAME, ((User) identity).getLastname());
            de.deleteProperty(FIELD_EMAIL);
            de.addProperty(FIELD_EMAIL, ((User) identity).getEmail());
        }
        super._database.update(de);
    }

    /**Gets an array of member ids for a group
     *
     * @param groupName The name of the group for which the list is desired
     * @throws SDXException
     */
    public String[] getMembers(String groupName) throws SDXException {

        if (!Utilities.checkString(groupName)) return null;

        Parameters params = new Parameters();
        params.setParameter(MEMBER_PROPERTY, groupName);
        String[] users = super._database.search(params);
        if (users == null) return null;
        return users;
    }

    /**
     * Returns all the groups an identity belongs to.
     *
     * @param   id        Name of the identity (group or user).
     */
    public Hashtable getParents(String id) throws SDXException {
        Hashtable table = new Hashtable();
        getParents(id, table);
        return table;
    }

    /**Returns the groups to which an identity belongs to.
     *
     * @param id    Name of the identity (group or user).
     * @param table The Hashtable to populate
     * @throws SDXException
     */
    private void getParents(String id, Hashtable table) throws SDXException {
        DatabaseEntity de = super._database.getEntity(id);
        if (de == null) return;
        String[] groups = de.getPropertyValues(MEMBER_PROPERTY);
        if (groups == null) return;
        for (int i = 0; i < groups.length; i++)
            if (!table.containsKey(groups[i])) {
                table.put(groups[i], new Group(groups[i]));
                getParents(groups[i], table);
            }
    }

    /**
     * Tests whether a database entity indicates membership of a group.
     *
     * @param   identity    a group or a user to check
     * @param   groupName   the groupName to test membership in
     */
    public boolean isMember(Identity identity, String groupName) throws SDXException {
        if (identity == null || identity.getId() == null || groupName == null) return false;
        DatabaseEntity de = new DatabaseEntity(identity.getId());
        String[] groups = de.getPropertyValues(MEMBER_PROPERTY);
        if (groups != null)
            for (int j = 0; j < groups.length; j++)
                if (groups[j].equals(groupName)) return true;
        return false;
    }

    /**
     * Returns the ID used in the database for an identity.
     *
     * @param   identity        The identity.
     */
    private String getId(Identity identity) {
        if (identity != null)

            return identity.getId();
        else
            return null;
    }


    /**
     * Checks if user login as "id" have the encoded password "password".
     *
     * @param   id              id of user (if null returns false).
     * @param   password        The password (may be null).
     */
    public boolean checkPassword(String id, String password) throws SDXException {
        if (id == null) return false;
        DatabaseEntity de = super._database.getEntity(id);
        if (de == null) return false;
        String pwd = de.getProperty(FIELD_PASSWORD); // FG
        // something like that could be logged ? dont't know how to get a log from here
        // security against commodity, should we show the pass to administrator of the logs ?
        //System.out.println(id + " try to pass with " + password + " against " + pwd);
        if (pwd == null) {
            if (password == null)
                return true;
            else
                return false;
        } else {
            if (password == null)
                return false;
            else
                return password.equals(pwd);
        }
    }

    /**
     * Checks if user login as "id" have the encoded password "password".
     *
     * @param   id              id of user (if null returns false).
     * @param   oldPass  The password (may be null).
     * @param   newPass  The password (may NOT be null).

     */
    public boolean changePassword(String id, String oldPass, String newPass) throws SDXException {
        if (!checkPassword(id, oldPass))
            return false;
        else {
            if (Utilities.checkString(newPass)) {
                DatabaseEntity de = super._database.getEntity(id);
                if (de == null) return false;
                de.deleteProperty(FIELD_PASSWORD);
                de.addProperty(FIELD_PASSWORD, newPass);
                super._database.update(de);
                return true;
            } else
                return false;
        }
    }

    /**
     * Returns basic information about a user.
     *
     * @param   application           id of the application where the user should be registered
     * @param   username              id of a user (if <code>null</code>, anonymous user is returned).
     * @param   locale          The default locale if the user's locale is not set.
     */
    public UserInformation getUserInformation(String application, String username, Locale locale, String adminGroup) throws SDXException {
        if (username == null || username.equals(UserInformation.ANONYMOUS_USERNAME)) {
            AnonymousUserInformation aui = new AnonymousUserInformation(locale);
            aui.enableLogging(super.getLog());
            return aui;
        }
        DatabaseEntity de = super._database.getEntity(username);
        if (de == null) return null;
        Locale preferredLocale = locale;
        String sLocale = de.getProperty(FIELD_LOCALE);
        if (sLocale != null) preferredLocale = parseLocaleString(sLocale);
        User usr = new User(username);
        usr.enableLogging(super.getLog());
        UserInformation ui = new UserInformation();
        ui.enableLogging(super.getLog());
        ui.setUp(application, username, getParents(username), preferredLocale, de.getProperty(FIELD_FIRSTNAME), de.getProperty(FIELD_LASTNAME), de.getProperty(FIELD_EMAIL), adminGroup);
        return ui;
    }

    /* Returns whether a user exists or not */
    public boolean exists(String id) throws SDXException {
        DatabaseEntity de = super._database.getEntity(id);
        if (de == null)
            return false;
        else
            return true;
    }

    /**
     * Parses a string (returned by the Locale.toString() method) and returns a Locale object.
     *
     * @param   locale        The string to parse (may not be null).
     */
    // TODO : is it the right place for such a method ? Move to utils ? -pb
    private Locale parseLocaleString(String locale) {
        if (!Utilities.checkString(locale)) return null;
        String lang = "";
        String country = "";
        String variant = "";
        // A locale string either begins by a language or an underscore.
//        String rest = "";
        if (locale.substring(0, 1).equals("_")) {
            // We begin with a country
            country = locale.substring(1, 2);
            if (country.length() > 4) variant = locale.substring(4);
        } else {
            // We begin with a language
            lang = locale.substring(0, 2);
            if (locale.length() > 4) {
                // We may have a country : if so, the fourth character is not "_"
                if (locale.substring(3, 4).equals("_")) {
                    // We only have a variant
                    if (locale.length() > 4) variant = locale.substring(4);
                } else {
                    // We have a country first
                    country = locale.substring(3, 5);
                    if (locale.length() > 6) variant = locale.substring(6);
                }
            }
        }
        // We should have at least a non empty language or country, but let's make a last check
        if (lang.equals("") && country.equals("")) return null;
        return new Locale(lang, country, variant);
    }

    public DatabaseEntity getEntity(String id) throws SDXException {
        return super._database.getEntity(id);
    }

    public DatabaseEntity[] getEntities() throws SDXException {
        return super._database.getEntities();
    }

    public String getPropertyValue(String entityId, String name) throws SDXException {
        return super._database.getPropertyValue(entityId, name);
    }

    public String[] getPropertyValues(String entityId, String propertyName) throws SDXException {
        return super._database.getPropertyValues(entityId, propertyName);
    }

    public Property[] getProperties(String entityId) throws SDXException {
        return super._database.getProperties(entityId);
    }

    public void save(DatabaseEntity ent) throws SDXException {
        super._database.save(ent);
    }

    public void delete(DatabaseEntity ent) throws SDXException {
        super._database.delete(ent);
    }

    public void update(DatabaseEntity ent) throws SDXException {
        super._database.update(ent);
    }

    public long size() {
        return super._database.size();
    }

    public void empty() throws SDXException {
        super._database.empty();
    }

    public boolean entityExists(String id) {
        return super._database.entityExists(id);
    }

    public String[] search(Parameters params) throws SDXException {
        return super._database.search(params);
    }

    public String[] search(Parameters params, int mode) throws SDXException {
        return super._database.search(params, mode);
    }

    public void optimize() throws SDXException {
        super._database.optimize();
    }

    public String getDatabaseDirectoryName() {
        return super._database.getDatabaseDirectoryName();
    }

    public DatabaseConnection getConnection() throws SDXException {
        return super._database.getConnection();
    }

    public void releaseConnection(DatabaseConnection conn) throws SDXException {
        super._database.releaseConnection(conn);
    }

    public void addProperty(String entityId, String propertyName, String propertyValue) throws SDXException {
        super._database.addProperty(entityId, propertyName, propertyValue);
    }

    public void removeProperty(String entityId, String propertyName, String propertyValue) throws SDXException {
        super._database.removeProperty(entityId, propertyName, propertyValue);

    }

    public void removeProperty(String propertyName, String propertyValue) throws SDXException {
        super._database.removeProperty(propertyName, propertyValue);
    }

    protected String getClassNameSuffix() {
        return Database.CLASS_NAME_SUFFIX;
    }

    public String getWildcardSearchToken() {
        return super._database.getWildcardSearchToken();
    }

    protected boolean initToSax(){
    	if(!super.initToSax())
    		return false;
    	else{
    		try{
    			this._xmlizable_objects.put(UserDatabase.MEMBER_PROPERTY,this.getEntities()[0].getProperty(UserDatabase.MEMBER_PROPERTY));
    			
    		}catch(SDXException e){
    		}
    		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() {
		super.initVolatileObjectsToSax();
		try{
			this._xmlizable_objects.put(UserDatabase.MEMBER_PROPERTY,this.getEntities()[0].getProperty(UserDatabase.MEMBER_PROPERTY));
		}catch(SDXException e){
		}
	}
	
	/** Save the DocumentBase data objects
	 * @see fr.gouv.culture.sdx.utils.save.Saveable#backup(fr.gouv.culture.sdx.utils.save.SaveParameters)
	 */
	public void backup(SaveParameters save_config) throws SDXException {
		if(save_config != null)
			if(save_config.isAllElementSelected())
			{
				save_config.setAttribute(Node.Name.ID,this.getId());
				save_config.setAttribute(Node.Name.TYPE,"user");
				this._database.backup(save_config);
				
			}
	}
	/** restore the DocumentBase data objects
	 * @see fr.gouv.culture.sdx.utils.save.Saveable#restore(fr.gouv.culture.sdx.utils.save.SaveParameters)
	 */
	public void restore(SaveParameters save_config) throws SDXException {
		

	}

}
