/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer: Florent BENOIT
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.security.internal.realm.factory;

import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.management.MalformedObjectNameException;
import javax.naming.BinaryRefAddr;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;

import org.objectweb.util.monolog.api.BasicLevel;
import org.ow2.jonas.lib.util.JNDIUtils;
import org.ow2.jonas.security.internal.realm.lib.HashHelper;
import org.ow2.jonas.security.internal.realm.lib.XML;
import org.ow2.jonas.security.internal.realm.principal.Group;
import org.ow2.jonas.security.internal.realm.principal.Role;
import org.ow2.jonas.security.internal.realm.principal.User;
import org.ow2.jonas.security.realm.factory.JResourceException;
import org.ow2.jonas.security.realm.principal.JUser;

/**
 * This class extends the JResource class for the Memory implementation.
 * @author Florent Benoit
 */
public class JResourceMemory extends AbstractJResource implements JResourceMemoryMBean {

    /**
     * Groups.
     */
    private Hashtable<String, Group> groups = new Hashtable<String, Group>();

    /**
     * Roles.
     */
    private Hashtable<String, Role> roles = new Hashtable<String, Role>();

    /**
     * Type of the factory.
     */
    private static final String FACTORY_TYPE = "org.ow2.jonas.security.internal.realm.factory.JResourceMemory";

    /**
     * Name of the factory.
     */
    private static final String FACTORY_NAME = "org.ow2.jonas.security.internal.realm.factory.JResourceMemoryFactory";

    /**
     * Constructor . Use the super constructor
     * @throws Exception if super constructor fail
     */
    public JResourceMemory() throws Exception {
        super();
    }

    /**
     * Add a user to this resource.
     * @param user the user which need to be added.
     * @throws Exception if the user already exists
     */
    public void addUser(final User user) throws Exception {

        if (getUsers().get(user.getName()) != null) {
            throw new Exception("User " + user.getName() + " already exists.");
        }

        // Add group if the group was not present
        String[] userGroups = user.getArrayGroups();
        String groupName = null;
        for (int g = 0; g < userGroups.length; g++) {
            groupName = userGroups[g];
            if (!groups.containsKey(groupName)) {
                addGroup(new Group(groupName));
            }
        }

        // Add role if the role was not present
        String[] userRoles = user.getArrayRoles();
        String roleName = null;
        for (int g = 0; g < userRoles.length; g++) {
            roleName = userRoles[g];
            if (!roles.containsKey(roleName)) {
                addRole(new Role(roleName));
            }
        }

        // Add user
        getUsers().put(user.getName(), user);

        registerUserMBean(user);
    }

    /**
     * Add a group to this resource.
     * @param group the group which need to be added.
     * @throws Exception if the group already exists
     */
    public void addGroup(final Group group) throws Exception {

        if (groups.get(group.getName()) != null) {
            throw new Exception("Group " + group.getName() + " already exists.");
        }

        // Add role if the role was not present
        String[] groupRoles = group.getArrayRoles();
        String roleName = null;
        for (int g = 0; g < groupRoles.length; g++) {
            roleName = groupRoles[g];
            if (!roles.containsKey(roleName)) {
                addRole(new Role(roleName));
            }
        }

        // Add group
        groups.put(group.getName(), group);

        registerGroupMBean(group);
    }

    /**
     * Add a role to this resource.
     * @param role the role which need to be added.
     * @throws Exception if the role already exists
     */
    public void addRole(final Role role) throws Exception {

        if (roles.get(role.getName()) != null) {
            throw new Exception("Role " + role.getName() + " already exists.");
        }

        // Add role
        roles.put(role.getName(), role);

        registerRoleMBean(role);
    }

    /**
     * Check if a user is found and return it.
     * @param name the wanted user name
     * @return the user found or null
     * @throws JResourceException if there is an error during the search
     */
    public JUser findUser(final String name) throws JResourceException {
        if (name == null) {
            return null;
        }
        return ((User) getUsers().get(name));
    }

    /**
     * Check if the given credential is the right credential for the given user.
     * @param user user to check its credentials
     * @param credentials the given credentials
     * @return true if the credential is valid for this user
     */
    public boolean isValidUser(final JUser user, final String credentials) {

        boolean validated = false;

        //Get algorithm and hashpassword
        String pass = user.getHashPassword().getPassword();
        String algo = user.getHashPassword().getAlgorithm();

        // Crypt password ?
        if (algo != null) {
            try {
                validated = HashHelper.hashPassword(credentials, algo).equalsIgnoreCase(pass);
            } catch (NoSuchAlgorithmException nsae) {
                getLogger().log(BasicLevel.ERROR, "Can't make a password with the algorithm " + algo + ". "
                        + nsae.getMessage());
            }
        } else {
            // clear
            validated = credentials.equals(pass);
        }
        return validated;
    }

    /**
     * Return all the groups.
     * @return the groups
     */
    public Hashtable<String, Group> getGroups() {
        return groups;
    }

    /**
     * Return all the roles.
     * @return the roles
     */
    public Hashtable<String, Role> getRoles() {
        return roles;
    }

    /**
     * Get all the roles (from the roles and from the groups) of the given user.
     * @param user the given user
     * @return the array list of all the roles for a given user
     * @throws JResourceException if it fails
     */
    public ArrayList<String> getArrayListCombinedRoles(final JUser user) throws JResourceException {
        ArrayList<String> allCombinedRoles = new ArrayList<String>();

        // Return empty array if user null
        if (user == null) {
            return allCombinedRoles;
        }

        // Add all user roles
        String[] userRoles = user.getArrayRoles();
        for (int r = 0; r < userRoles.length; r++) {
            String roleName = userRoles[r];
            if (!allCombinedRoles.contains(roleName)) {
                allCombinedRoles.add(roleName);
            }
        }

        // Add roles of each group
        String[] userGroups = user.getArrayGroups();
        for (int g = 0; g < userGroups.length; g++) {
            String groupName = userGroups[g];

            // For each roles of the given group
            Group group = groups.get(groupName);
            if (group == null) {
                continue;
            }

            String[] groupRoles = group.getArrayRoles();
            for (int gr = 0; gr < groupRoles.length; gr++) {
                String roleName = groupRoles[gr];
                if (!allCombinedRoles.contains(roleName)) {
                    allCombinedRoles.add(roleName);
                }
            }
        }

        return allCombinedRoles;
    }

    /**
     * Set the groups.
     * @param groups the groups of this resource
     */
    public void setGroups(final Hashtable<String, Group> groups) {
        this.groups = groups;
    }

    /**
     * Set the roles.
     * @param roles the roles of this resource
     */
    public void setRoles(final Hashtable<String, Role> roles) {
        this.roles = roles;
    }

    /**
     * Add a user with a given principal and credential.
     * @param username the name of the user
     * @param password password of the user
     * @throws Exception if the user already exists
     */
    public void addUser(final String username, final String password) throws Exception {
        addUser(new User(username, password));
    }

    /**
     * Add a group with a given name?
     * @param groupname the name of the group
     * @throws Exception if the group already exists
     */
    public void addGroup(final String groupname) throws Exception {
        addGroup(new Group(groupname));
    }

    /**
     * Add a role with a given name?
     * @param rolename the name of the role
     * @throws Exception if the role already exists
     */
    public void addRole(final String rolename) throws Exception {
        addRole(new Role(rolename));
    }

    /**
     * Remove a user with a given principal?
     * @param username the name of the user
     * @throws Exception if the user was not found
     */
    public void removeUser(final String username) throws Exception {
        if (getUsers().get(username) == null) {
            throw new Exception("Can not remove user " + username + ". This user doesn't exist");
        }
        getUsers().remove(username);

        // Remove associated MBean
        unregisterUserMBean(getName(), username);
    }

    /**
     * Remove a group with a given name?
     * @param groupname the name of the group
     * @throws Exception if the group was not found
     */
    public void removeGroup(final String groupname) throws Exception {
        if (groups.get(groupname) == null) {
            throw new Exception("Can not remove group " + groupname + ". This group doesn't exist");
        }

        Enumeration users = getUsers().elements();
        while (users.hasMoreElements()) {
            User user = (User) users.nextElement();
            user.removeGroup(groupname);
        }

        groups.remove(groupname);
        // Remove associated MBean
        unregisterGroupMBean(getName(), groupname);
    }

    /**
     * Remove a role with a given name.
     * @param rolename the name of the role
     * @throws Exception if the role was not found
     */
    public void removeRole(final String rolename) throws Exception {
        if (roles.get(rolename) == null) {
            throw new Exception("Can not remove role " + rolename + ". This role doesn't exist");
        }

        Enumeration<Group> groups = getGroups().elements();
        while (groups.hasMoreElements()) {
            Group group = groups.nextElement();
            group.removeRole(rolename);
        }

        Enumeration users = getUsers().elements();
        while (users.hasMoreElements()) {
            User user = (User) users.nextElement();
            user.removeRole(rolename);
        }

        roles.remove(rolename);

        // Remove Mbean
        unregisterRoleMBean(getName(), rolename);
    }

    /**
     * String representation of the MemoryRealm.
     * @return the xml representation of the MemoryRealm
     */
    public String toXML() {
        StringBuffer xml = new StringBuffer("    <memoryrealm name=\"");
        xml.append(getName());
        xml.append("\">\n");

        // Roles
        xml.append("      <roles>\n");
        XML.xmlHashtable(xml, getRoles(), "        ");
        xml.append("      </roles>\n");

        // Groups
        xml.append("      <groups>\n");
        XML.xmlHashtable(xml, getGroups(), "        ");
        xml.append("      </groups>\n");

        // Users
        xml.append("      <users>\n");
        XML.xmlHashtable(xml, getUsers(), "        ");
        xml.append("      </users>\n");

        xml.append("    </memoryrealm>");
        return xml.toString();
    }

    /**
     * The string representation of this realm is the XML.
     * @return XML representation
     */
    @Override
    public String toString() {
        return this.toXML();
    }

    /**
     * Retrieves the Reference of the object. The Reference contains the factory
     * used to create this object and the optional parameters used to configure
     * the factory.
     * @return the non-null Reference of the object.
     * @throws NamingException if a naming exception was encountered while
     *         retrieving the reference.
     */
    public Reference getReference() throws NamingException {

        // Build the reference to the factory FACTORY_TYPE
        Reference reference = new Reference(FACTORY_TYPE, FACTORY_NAME, null);

        // Add name
        reference.add(new StringRefAddr("name", getName()));

        // Add users
        byte[] bytes = JNDIUtils.getBytesFromObject(getUsers(), getLogger());
        if (bytes != null) {
            reference.add(new BinaryRefAddr("users", bytes));
        }

        // Add groups
        bytes = JNDIUtils.getBytesFromObject(groups, getLogger());
        if (bytes != null) {
            reference.add(new BinaryRefAddr("groups", bytes));
        }

        // Add roles
        bytes = JNDIUtils.getBytesFromObject(roles, getLogger());
        if (bytes != null) {
            reference.add(new BinaryRefAddr("roles", bytes));
        }

        return reference;

    }

    /**
     * Get the roles.
     * @return the array of the roles
     */
    public String[] listRoles() {
        String[] s = new String[roles.size()];
        int i = 0;
        for (Enumeration<String> e = roles.keys(); e.hasMoreElements(); i++) {
            s[i] = e.nextElement();
        }
        return s;
    }

    /**
     * Get the groups.
     * @return the array of the groups
     */
    public String[] listGroups() {
        String[] s = new String[groups.size()];
        int i = 0;
        for (Enumeration<String> e = groups.keys(); e.hasMoreElements(); i++) {
            s[i] = e.nextElement();
        }
        return s;
    }

    /**
     * Remove all the MBeans used by this resource.
     * @throws JResourceException if the MBeans can not be removed
     */
    public void removeMBeans() throws JResourceException {

        boolean error = false;
        // Remove user MBeans
        for (Enumeration e = getUsers().elements(); e.hasMoreElements();) {
            User u = (User) e.nextElement();
            try {
                unregisterUserMBean(getName(), u.getName());
            } catch (MalformedObjectNameException e1) {
                e1.printStackTrace();
                error = true;
            }
        }

        // Remove role MBeans
        for (Enumeration<Role> e = roles.elements(); e.hasMoreElements();) {
            Role r = e.nextElement();
            try {
                unregisterRoleMBean(getName(), r.getName());
            } catch (MalformedObjectNameException e1) {
                e1.printStackTrace();
                error = true;
            }
        }

        // Remove group MBeans
        for (Enumeration<Group> e = groups.elements(); e.hasMoreElements();) {
            Group g = e.nextElement();
            try {
                unregisterGroupMBean(getName(), g.getName());
            } catch (MalformedObjectNameException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                error = true;
            }
        }

        if (error) {
            throw new JResourceException(
                    "There were errors during the remove of the MBeans resource " + getName());
        }

    }

}