package net.sourceforge.fenixedu.domain;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import net.sourceforge.fenixedu._development.PropertiesManager;
import net.sourceforge.fenixedu.domain.exceptions.DomainException;
import net.sourceforge.fenixedu.domain.organizationalStructure.Invitation;
import net.sourceforge.fenixedu.domain.person.RoleType;
import net.sourceforge.fenixedu.util.UsernameUtilsIscte;

import org.apache.commons.lang.StringUtils;
import org.joda.time.YearMonthDay;

import pt.iscte.ci.metadata.ISCTE;
import pt.ist.utl.fenix.utils.Pair;

public class Login extends Login_Base implements ModifyServiceEntriesMarker {

    final static String loginClass = PropertiesManager.getProperty("login.generator.class");

    public Login(User user) {
	super();
	checkIfUserAlreadyHaveLogin(user);
	setUser(user);
	setIsPassInKerberos(Boolean.FALSE);
	openLoginPeriod();
    }

    public void delete() {
	for (; !getAlias().isEmpty(); getAlias().get(0).delete())
	    ;
	for (; !getLoginPeriods().isEmpty(); getLoginPeriods().get(0).delete())
	    ;
	super.delete();
    }

    public LoginPeriod readLoginPeriodByTimeInterval(YearMonthDay begin, YearMonthDay end) {
	for (LoginPeriod loginPeriod : getLoginPeriodsSet()) {
	    if (loginPeriod.getBeginDate().equals(begin)
		    && ((loginPeriod.getEndDate() == null && end == null) || (loginPeriod.getEndDate().equals(end)))) {
		return loginPeriod;
	    }
	}
	return null;
    }

    public boolean isOpened() {
	YearMonthDay currentDate = new YearMonthDay();
	for (LoginPeriod loginPeriod : getLoginPeriodsSet()) {
	    if ((loginPeriod.getEndDate() == null || !loginPeriod.getEndDate().isBefore(currentDate))
		    && !loginPeriod.getBeginDate().isAfter(currentDate)) {
		return true;
	    }
	}
	return false;
    }

    public boolean isLogin() {
	return true;
    }

    public String getUsername() {
	String userUId = getUserUId();
	if (userUId == null) {
	    // <!--2009-01-16 removed because of restructuring of LoginAlias-->
	    // Role mostImportantRole =
	    // UsernameUtilsIscte.getMostImportantRole(getUser
	    // ().getPerson().getPersonRoles());
	    // if (mostImportantRole != null) {
	    // List<LoginAlias> loginAlias =
	    // getRoleLoginAlias(mostImportantRole.getRoleType());
	    // if (!loginAlias.isEmpty()) {
	    // return loginAlias.get(0).getAlias();
	    // }
	    // }
	    return (getAlias().isEmpty()) ? null : getMostImportantAlias();
	}
	return userUId;
    }

    public String getMostImportantAlias() {
	// List<Role> personRoles = getUser().getPerson().getPersonRoles();
	// Role mostImportantRole =
	// UsernameUtilsIscte.getMostImportantRole(personRoles);
	// if (mostImportantRole != null) {
	// RoleType roleType = mostImportantRole.getRoleType();
	// List<LoginAlias> loginAlias = getRoleLoginAlias(roleType);
	// if (!loginAlias.isEmpty()) {
	// return loginAlias.get(0).getAlias();
	// }
	// }
	if (getInstitutionalLoginAlias() != null) {
	    return getInstitutionalLoginAlias().getAlias();
	} else if (!getAllCustomLoginAlias().isEmpty()) {
	    return getAllCustomLoginAlias().get(0).getAlias();
	}
	return null;
    }

    @ISCTE(comment = "Changed the implementation so that the userUId in the user is returned", author = "Paulo Zenida")
    public String getUserUId() {
	return getUser() != null ? getUser().getUserUId() : null;
    }

    // <!--2009-01-16 removed because of restructuring of LoginAlias-->
    // public void setUsername(RoleType roleType) {
    // final String newUsername =
    // UsernameUtilsIscte.generateNewUsername(roleType, getUser().getPerson());
    // setUsername(roleType, newUsername);
    // }
    //
    //
    // public void setUsername(final RoleType roleType, final String
    // newUsername) {
    //
    // // removeAliasWithoutCloseLogin(roleType);
    // if (!StringUtils.isEmpty(newUsername)) {
    // if (!getUser().getPerson().hasUsername(newUsername)) {
    // LoginAlias.createNewRoleLoginAlias(this, newUsername, roleType);
    // openLoginIfNecessary(roleType);
    // }
    // }
    // }
    //
    // FIXME: This kind of methods should not exist... A person should have only
    // one login.
    // TODO delete old login
    public void setUsername(String username) {
	if (!StringUtils.isEmpty(username)) {
	    if (getUserUId() != null && !username.equals(getUserUId())) {
		LoginAlias.createNewLoginAlias(this, username);
		// setUserUID(username);
	    }
	}
    }

    @ISCTE(comment = "Added the set to the isUniversalUserUId property on the user", author = "Paulo Zenida")
    public void setUserUID() {
	final LoginAlias loginAlias = getInstitutionalLoginAlias();
	final User user = getUser();
	final Pair<Boolean, String> pairIsUserUIdUniversalAndUserUId = UsernameUtilsIscte.updateIstUsername(user.getPerson());
	final String userUId = pairIsUserUIdUniversalAndUserUId.getSecond();
	if (loginAlias == null) {
	    if (!StringUtils.isEmpty(userUId)) {
		LoginAlias.createNewLoginAlias(this, userUId);
		user.setUserUId(userUId);
		user.setIsUniversalUserUId(pairIsUserUIdUniversalAndUserUId.getFirst());
	    }
	} else if (user.getUserUId() != null && !user.getUserUId().equals(userUId) && user.getIsUniversalUserUId() != null
		&& !user.getIsUniversalUserUId()) {
	    user.setUserUId(userUId);
	    user.setIsUniversalUserUId(pairIsUserUIdUniversalAndUserUId.getFirst());
	    LoginAlias.createNewLoginAlias(loginAlias.getLogin(), userUId);
	}
    }

    public void setUserUID(final String userUId) {
	final LoginAlias loginAlias = getInstitutionalLoginAlias();
	final User user = getUser();
	if (loginAlias == null) {
	    if (!StringUtils.isEmpty(userUId)) {
		LoginAlias.createNewLoginAlias(this, userUId);
		user.setUserUId(userUId);
	    }
	} else {
	    user.setUserUId(userUId);
	    loginAlias.setAlias(userUId);
	}
    }

    public LoginAlias readLoginAliasByAlias(String alias) {
	if (alias != null) {
	    for (LoginAlias loginAlias : getAlias()) {
		if (loginAlias.getAlias().equalsIgnoreCase(alias)) {
		    return loginAlias;
		}
	    }
	}
	return null;
    }

    public LoginAlias getInstitutionalLoginAlias() {
	for (LoginAlias loginAlias : getAlias()) {
	    if (loginAlias.isInstitutionalAlias()) {
		return loginAlias;
	    }
	}
	return null;
    }

    public List<LoginAlias> getAllCustomLoginAlias() {
	List<LoginAlias> result = new ArrayList<LoginAlias>();
	for (LoginAlias loginAlias : getAlias()) {
	    if (!loginAlias.isInstitutionalAlias()) {
		result.add(loginAlias);
	    }
	}
	return result.size() > 0 ? result : null;
    }

    public List<LoginAlias> getAllCustomLoginAliasOrEmptyList() {
	List<LoginAlias> result = new ArrayList<LoginAlias>();
	for (LoginAlias loginAlias : getAlias()) {
	    if (!loginAlias.isInstitutionalAlias()) {
		result.add(loginAlias);
	    }
	}
	return result;
    }

    public Set<LoginAlias> getLoginAliasOrderByImportance() {
	Set<LoginAlias> result = new TreeSet<LoginAlias>((LoginAlias.COMPARATOR_BY_ALIAS));
	result.addAll(getAlias());
	return result;
    }

    // <!--2009-01-16 removed because of restructuring of LoginAlias-->
    // public void removeAlias(RoleType roleType) {
    // // removeAliasWithoutCloseLogin(roleType);
    // closeLoginIfNecessary();
    // }

    public boolean hasUsername(String username) {
	return readLoginAliasByAlias(username) != null;
    }

    public Set<LoginPeriod> getLoginPeriodsWithoutInvitationPeriods() {
	Person person = getUser().getPerson();
	Login login = person.getLoginIdentification();
	Set<LoginPeriod> loginPeriods = new TreeSet<LoginPeriod>(LoginPeriod.COMPARATOR_BY_BEGIN_DATE);
	loginPeriods.addAll(login.getLoginPeriods());
	for (Invitation invitation : person.getInvitationsOrderByDate()) {
	    LoginPeriod period = login.readLoginPeriodByTimeInterval(invitation.getBeginDate(), invitation.getEndDate());
	    if (period != null) {
		loginPeriods.remove(period);
	    }
	}
	return loginPeriods;
    }

    private static final Map<String, SoftReference<LoginAlias>> loginMap = new Hashtable<String, SoftReference<LoginAlias>>();

    public static Login readLoginByUsername(String username) {
	final LoginAlias loginAlias = LoginAlias.readLoginByUsername(username);
	return loginAlias == null ? null : loginAlias.getLogin();
    }

    public void closeLoginIfNecessary() {
	Person person = getUser().getPerson();

	if (!person.hasRole(RoleType.TEACHER) && !person.hasRole(RoleType.EMPLOYEE) && !person.hasRole(RoleType.STUDENT)
		&& !person.hasRole(RoleType.ALUMNI) && !person.hasRole(RoleType.CANDIDATE)
		&& !person.hasRole(RoleType.INSTITUCIONAL_PROJECTS_MANAGER) && !person.hasRole(RoleType.PROJECTS_MANAGER)
		&& !person.hasRole(RoleType.MANAGER)) {

	    // minusDays(1) -> This is for person dont make login today
	    YearMonthDay currentDate = new YearMonthDay().minusDays(1);
	    for (LoginPeriod loginPeriod : getLoginPeriodsSet()) {
		if (loginPeriod.getEndDate() == null) {
		    loginPeriod.setEndDate(currentDate);
		}
	    }
	}
    }

    public void openLoginPeriod() {
	for (LoginPeriod loginPeriod : getLoginPeriodsSet()) {
	    if (loginPeriod.getEndDate() == null) {
		return;
	    }
	}
	// minusDays(2) -> This is for prevent errors in
	// closeLoginIfNecessary (setEndDate line).
	addLoginPeriods(new LoginPeriod(new YearMonthDay().minusDays(2), this));
    }

    public void openLoginIfNecessary(RoleType roleType) {
	switch (roleType) {
	case MANAGER:
	case TEACHER:
	case EMPLOYEE:
	case STUDENT:
	case ALUMNI:
	case CANDIDATE:
	case INSTITUCIONAL_PROJECTS_MANAGER:
	case PROJECTS_MANAGER:
	    for (LoginPeriod loginPeriod : getLoginPeriodsSet()) {
		if (loginPeriod.getEndDate() == null) {
		    return;
		}
	    }
	    // minusDays(2) -> This is for prevent errors in
	    // closeLoginIfNecessary (setEndDate line).
	    addLoginPeriods(new LoginPeriod(new YearMonthDay().minusDays(2), this));
	    break;

	default:
	    break;
	}
    }

    // <!--2009-01-16 removed because of restructuring of LoginAlias-->
    // private void removeAliasWithoutCloseLogin(RoleType roleType) {
    // if (roleType != null) {
    // for (final Iterator<LoginAlias> iter = getAlias().iterator();
    // iter.hasNext();) {
    // final LoginAlias loginAlias = iter.next();
    // if (loginAlias.getRoleType() != null &&
    // loginAlias.getRoleType().equals(roleType)) {
    // iter.remove();
    // loginAlias.delete();
    // }
    // }
    // }
    // }

    private void checkIfUserAlreadyHaveLogin(User user) {
	if (user != null) {
	    Login login = user.readUserLoginIdentification();
	    if (login != null && !login.equals(this)) {
		throw new DomainException("error.user.login.already.exists");
	    }
	}
    }
}
