/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.mice.voila.runtime.security;

import it.mice.voila.runtime.util.StringUtils;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;


/**
 * Models core user information retieved by an {@link org.acegisecurity.userdetails.UserDetailsService}.<p>Implemented with value object
 * semantics (immutable after construction, like a <code>String</code>). Developers may use this class directly,
 * subclass it, or write their own {@link org.acegisecurity.userdetails.UserDetails} implementation from scratch.</p>
 *
 * @author Ben Alex
 * @version $Id: UserDetailImpl.java,v 1.2 2007/02/21 15:17:50 zzy9v4 Exp $
 */
public class UserDetailImpl implements UserDetails {
    //~ Instance fields ================================================================================================
    private String password;
    private String username;
    private String firstName;
    private String middleName;
    private String lastName;
    private Collection<GrantedAuthority> authorities;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;
    private String userProperties;
    private Properties userChainProperties = null;
    private String rolePrefix = null;
    private Object securityUser = null;

    private static final long serialVersionUID = -7940129298244451321L;

    //~ Constructors ===================================================================================================

    /**
     * Construct the <code>User</code> with the details required by {@link org.acegisecurity.providers.dao.DaoAuthenticationProvider}.
     *
     * @param username              the username presented to the
     *                              <code>DaoAuthenticationProvider</code>
     * @param password              the password that should be presented to the
     *                              <code>DaoAuthenticationProvider</code>
     * @param enabled               set to <code>true</code> if the user is enabled
     * @param accountNonExpired     set to <code>true</code> if the account has not
     *                              expired
     * @param credentialsNonExpired set to <code>true</code> if the credentials
     *                              have not expired
     * @param accountNonLocked      set to <code>true</code> if the account is not
     *                              locked
     * @param authorities           the authorities that should be granted to the caller
     *                              if they presented the correct username and password and the user
     *                              is enabled
     * @throws IllegalArgumentException if a <code>null</code> value was passed
     *                                  either as a parameter or as an element in the
     *                                  <code>GrantedAuthority[]</code> array
     */
    public UserDetailImpl(String username, String password, boolean enabled, boolean accountNonExpired,
                          boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities, String userProp)
            throws IllegalArgumentException {
        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.userProperties = StringUtils.convertToProperties(userProp);
        setAuthorities(authorities);
    }
    //~ Methods ========================================================================================================

    public UserDetailImpl() {
    }

    public boolean equals(Object rhs) {
        if (!(rhs instanceof it.mice.voila.runtime.security.UserDetailImpl) || (rhs == null)) {
            return false;
        }

        UserDetailImpl user = (UserDetailImpl) rhs;

        // We rely on constructor to guarantee any User has non-null and >0
        // authorities
        if (user.getAuthorities().size() != this.getAuthorities().size()) {
            return false;
        }

        Iterator<GrantedAuthority> thisIter = this.getAuthorities().iterator();
        for (Iterator<GrantedAuthority> iterator = user.getAuthorities().iterator(); iterator.hasNext();) {
			GrantedAuthority thisGrantedAuthority = thisIter.next();
			GrantedAuthority grantedAuthority = iterator.next();
            if (!grantedAuthority.equals(thisGrantedAuthority)) {
                return false;
            }
			
		}

        // We rely on constructor to guarantee non-null username and password
        return (this.getPassword().equals(user.getPassword()) && this.getUsername().equals(user.getUsername())
                && (this.isAccountNonExpired() == user.isAccountNonExpired())
                && (this.isAccountNonLocked() == user.isAccountNonLocked())
                && (this.isCredentialsNonExpired() == user.isCredentialsNonExpired()) && (this.isEnabled() == user.isEnabled()));
    }

    public Collection<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public Object getSecurityUser() {
		return securityUser;
	}

	public void setSecurityUser(Object securityUser) {
		this.securityUser = securityUser;
	}

	public int hashCode() {
        int code = 9792;

        if (this.getAuthorities() != null) {
            for (Iterator<GrantedAuthority> iterator = this.getAuthorities().iterator(); iterator.hasNext();) {
    			GrantedAuthority grantedAuthority = iterator.next();
                code = code * (grantedAuthority.hashCode() % 7);
    		}
        }

        if (this.getPassword() != null) {
            code = code * (this.getPassword().hashCode() % 7);
        }

        if (this.getUsername() != null) {
            code = code * (this.getUsername().hashCode() % 7);
        }

        if (this.isAccountNonExpired()) {
            code = code * -2;
        }

        if (this.isAccountNonLocked()) {
            code = code * -3;
        }

        if (this.isCredentialsNonExpired()) {
            code = code * -5;
        }

        if (this.isEnabled()) {
            code = code * -7;
        }

        return code;
    }

    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getUserDescription() {
        StringBuffer sb = new StringBuffer();
    	if (getFirstName()!=null) {
    		sb.append(getFirstName()).append(" ");
    	}
    	if (getMiddleName()!=null) {
    		sb.append(getMiddleName()).append(" ");
    	}
    	if (getLastName()!=null) {
    		sb.append(getLastName());
    	}   	
        return sb.toString();
    }
    
    public void setAuthorities(Collection<GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority array");

        for (Iterator<GrantedAuthority> iterator = authorities.iterator(); iterator.hasNext();) {
        	GrantedAuthority grantedAuthority = iterator.next();
            Assert.notNull(grantedAuthority,
                    "Granted authority element is null - GrantedAuthority[] cannot contain any null elements");
		}

        this.authorities = authorities;
    }

    public String getUserProperties() {
        return userProperties;
    }

    public void setUserProperties(String userProperties) {
        this.userProperties = StringUtils.convertToProperties(userProperties);
    }

	public String getRolePrefix() {
		return rolePrefix;
	}

	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}

	public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(super.toString()).append(": ");
        sb.append("Username: ").append(this.username).append("; ");
        sb.append("Password: [PROTECTED]; ");
        sb.append("Enabled: ").append(this.enabled).append("; ");
        sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
        sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
        sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");

        if (this.getAuthorities() != null) {
            sb.append("Granted Authorities: ");
            sb.append(this.getAuthorities());
        } else {
            sb.append("Not granted any authorities");
        }

        return sb.toString();
    }

	public Properties getUserChainProperties() {
		if (userChainProperties == null) {
			//build user chain properties
			userChainProperties = new Properties();
			addUserProperties(userChainProperties, getUserProperties());
	        for (Iterator<GrantedAuthority> iterator = this.getAuthorities().iterator(); iterator.hasNext();) {
	        	GrantedAuthority grantedAuthority = iterator.next();
				addUserProperties(userChainProperties, ((UserProfileGrantedAuthorityImpl)grantedAuthority).getProfileProps());
				addUserProperties(userChainProperties, ((UserProfileGrantedAuthorityImpl)grantedAuthority).getProfilePerUserProps());
			}
		}
		return userChainProperties;
	}
	
	private void addUserProperties(Properties userChainProperties, String PropertyPart) {
		StringTokenizer st = new StringTokenizer(PropertyPart, ";\n\r");
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			int startPos = token.indexOf("$");
			int endPos = token.indexOf("=");
			String propName = token.substring(startPos + 1, endPos);
			int endPos2 = token.indexOf(")");
			String propValue = token.substring(endPos + 1, endPos2);
			userChainProperties.put(propName.trim(), propValue.trim());
		}
	}
	
	public boolean hasProfile(String profileName) {
		String roleName = (getRolePrefix() != null) ? getRolePrefix() + profileName : profileName;
        for (Iterator<GrantedAuthority> iterator = this.getAuthorities().iterator(); iterator.hasNext();) {
        	GrantedAuthority grantedAuthority = iterator.next();
			if (grantedAuthority.getAuthority().equalsIgnoreCase(roleName)) {
				return true;
			}
		}
		return false;
	}
	
    public Set<String> getAclQueryText(String queryId) {
    	Set<String> result = new HashSet<String>();
        for (Iterator<GrantedAuthority> iterator = this.getAuthorities().iterator(); iterator.hasNext();) {
        	GrantedAuthority authority = iterator.next();
        	if (authority instanceof UserProfileGrantedAuthorityImpl) {
            	UserProfileGrantedAuthorityImpl grantedAuthority = (UserProfileGrantedAuthorityImpl)authority;
            	String aclQuery = grantedAuthority.getAclQueryText(queryId); 
    			if (aclQuery != null) {
    				result.add(aclQuery);
    			}
        	}
		}
    	return result;
    }

}
