/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.portal.security.pwd;

import com.liferay.portal.UserPasswordException;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.security.RandomUtil;
import com.liferay.portal.kernel.security.SecureRandom;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.model.PasswordPolicy;
import com.liferay.portal.model.User;
import com.liferay.portal.service.PasswordTrackerLocalServiceUtil;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.util.PropsValues;
import com.liferay.portal.words.WordsUtil;
import com.liferay.util.PwdGenerator;

import java.util.Arrays;
import java.util.Date;
import java.util.Random;

/**
 * @author Scott Lee
 * @author Mika Koivisto
 */
public class PasswordPolicyToolkit extends BasicToolkit {

	public PasswordPolicyToolkit() {
		_generatorLowerCaseCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_LOWERCASE);
		_generatorNumbersCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_NUMBERS);
		_generatorSymbolsCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_SYMBOLS);
		_generatorUpperCaseCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_UPPERCASE);

		_generatorAlphanumericCharsetArray = ArrayUtil.append(
			_generatorLowerCaseCharsetArray, _generatorUpperCaseCharsetArray,
			_generatorNumbersCharsetArray);

		Arrays.sort(_generatorAlphanumericCharsetArray);

		StringBundler sb = new StringBundler(4);

		sb.append(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_LOWERCASE);
		sb.append(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_NUMBERS);
		sb.append(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_SYMBOLS);
		sb.append(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_UPPERCASE);

		_generatorCompleteCharset = sb.toString();

		_validatorLowerCaseCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_LOWERCASE);
		_validatorNumbersCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_NUMBERS);
		_validatorSymbolsCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_SYMBOLS);
		_validatorUpperCaseCharsetArray = getSortedCharArray(
			PropsValues.
				PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_UPPERCASE);

		_validatorAlphanumericCharsetArray = ArrayUtil.append(
			_validatorLowerCaseCharsetArray, _validatorUpperCaseCharsetArray,
			_validatorNumbersCharsetArray);

		Arrays.sort(_validatorAlphanumericCharsetArray);
	}

	@Override
	public String generate(PasswordPolicy passwordPolicy) {
		if (PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR.equals(
				"static")) {

			return generateStatic(passwordPolicy);
		}
		else {
			return generateDynamic(passwordPolicy);
		}
	}

	@Override
	public void validate(
			long userId, String password1, String password2,
			PasswordPolicy passwordPolicy)
		throws PortalException, SystemException {

		if (passwordPolicy.isCheckSyntax()) {
			if (!passwordPolicy.isAllowDictionaryWords() &&
				WordsUtil.isDictionaryWord(password1)) {

				throw new UserPasswordException(
					UserPasswordException.PASSWORD_CONTAINS_TRIVIAL_WORDS);
			}

			if (password1.length() < passwordPolicy.getMinLength()) {
				throw new UserPasswordException(
					UserPasswordException.PASSWORD_LENGTH);
			}

			if ((getUsageCount(password1, _validatorAlphanumericCharsetArray) <
					passwordPolicy.getMinAlphanumeric()) ||
				(getUsageCount(password1, _validatorLowerCaseCharsetArray) <
					passwordPolicy.getMinLowerCase()) ||
				(getUsageCount(password1, _validatorNumbersCharsetArray) <
					passwordPolicy.getMinNumbers()) ||
				(getUsageCount(password1, _validatorSymbolsCharsetArray) <
					passwordPolicy.getMinSymbols()) ||
				(getUsageCount(password1, _validatorUpperCaseCharsetArray) <
					passwordPolicy.getMinUpperCase())) {

				throw new UserPasswordException(
					UserPasswordException.PASSWORD_TOO_TRIVIAL);
			}

			if (Validator.isNotNull(passwordPolicy.getRegex()) &&
				!password1.matches(passwordPolicy.getRegex())) {

				throw new UserPasswordException(
					UserPasswordException.PASSWORD_INVALID);
			}
		}

		if (!passwordPolicy.isChangeable() && (userId != 0)) {
			throw new UserPasswordException(
				UserPasswordException.PASSWORD_NOT_CHANGEABLE);
		}

		if (userId == 0) {
			return;
		}

		User user = UserLocalServiceUtil.getUserById(userId);

		Date passwordModfiedDate = user.getPasswordModifiedDate();

		if (passwordModfiedDate != null) {
			Date now = new Date();

			long passwordModificationElapsedTime =
				now.getTime() - passwordModfiedDate.getTime();

			long minAge = passwordPolicy.getMinAge() * 1000;

			if ((passwordModificationElapsedTime < minAge) &&
				!user.getPasswordReset()) {

				throw new UserPasswordException(
					UserPasswordException.PASSWORD_TOO_YOUNG);
			}
		}

		if (PasswordTrackerLocalServiceUtil.isSameAsCurrentPassword(
				userId, password1)) {

			throw new UserPasswordException(
				UserPasswordException.PASSWORD_SAME_AS_CURRENT);
		}
		else if (!PasswordTrackerLocalServiceUtil.isValidPassword(
					userId, password1)) {

			throw new UserPasswordException(
				UserPasswordException.PASSWORD_ALREADY_USED);
		}
	}

	protected String generateDynamic(PasswordPolicy passwordPolicy) {
		int alphanumericActualMinLength =
			passwordPolicy.getMinLowerCase() + passwordPolicy.getMinNumbers() +
				passwordPolicy.getMinUpperCase();

		int alphanumericMinLength = Math.max(
			passwordPolicy.getMinAlphanumeric(), alphanumericActualMinLength);
		int passwordMinLength = Math.max(
			passwordPolicy.getMinLength(),
			alphanumericMinLength + passwordPolicy.getMinSymbols());

		StringBundler sb = new StringBundler(6);

		if (passwordPolicy.getMinLowerCase() > 0) {
			sb.append(
				getRandomString(
					passwordPolicy.getMinLowerCase(),
					_generatorLowerCaseCharsetArray));
		}

		if (passwordPolicy.getMinNumbers() > 0) {
			sb.append(
				getRandomString(
					passwordPolicy.getMinNumbers(),
					_generatorNumbersCharsetArray));
		}

		if (passwordPolicy.getMinSymbols() > 0) {
			sb.append(
				getRandomString(
					passwordPolicy.getMinSymbols(),
					_generatorSymbolsCharsetArray));
		}

		if (passwordPolicy.getMinUpperCase() > 0) {
			sb.append(
				getRandomString(
					passwordPolicy.getMinUpperCase(),
					_generatorUpperCaseCharsetArray));
		}

		if (alphanumericMinLength > alphanumericActualMinLength) {
			int count = alphanumericMinLength - alphanumericActualMinLength;

			sb.append(
				getRandomString(count, _generatorAlphanumericCharsetArray));
		}

		if (passwordMinLength >
				(alphanumericMinLength + passwordPolicy.getMinSymbols())) {

			int count =
				passwordMinLength -
					(alphanumericMinLength + passwordPolicy.getMinSymbols());

			sb.append(
				PwdGenerator.getPassword(_generatorCompleteCharset, count));
		}

		if (sb.index() == 0) {
			sb.append(
				PwdGenerator.getPassword(
					_generatorCompleteCharset,
					PropsValues.PASSWORDS_DEFAULT_POLICY_MIN_LENGTH));
		}

		return RandomUtil.shuffle(new SecureRandom(), sb.toString());
	}

	protected String generateStatic(PasswordPolicy passwordPolicy) {
		return PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC;
	}

	protected String getRandomString(int count, char[] chars) {
		Random random = new SecureRandom();

		StringBundler sb = new StringBundler(count);

		for (int i = 0; i < count; i++) {
			int index = random.nextInt(chars.length);

			sb.append(chars[index]);
		}

		return sb.toString();
	}

	protected char[] getSortedCharArray(String s) {
		char[] chars = s.toCharArray();

		Arrays.sort(chars);

		return chars;
	}

	protected int getUsageCount(String s, char[] chars) {
		int count = 0;

		for (int i = 0; i < s.length(); i++) {
			if (Arrays.binarySearch(chars, s.charAt(i)) >= 0) {
				count++;
			}
		}

		return count;
	}

	private char[] _generatorAlphanumericCharsetArray;
	private String _generatorCompleteCharset;
	private char[] _generatorLowerCaseCharsetArray;
	private char[] _generatorNumbersCharsetArray;
	private char[] _generatorSymbolsCharsetArray;
	private char[] _generatorUpperCaseCharsetArray;
	private char[] _validatorAlphanumericCharsetArray;
	private char[] _validatorLowerCaseCharsetArray;
	private char[] _validatorNumbersCharsetArray;
	private char[] _validatorSymbolsCharsetArray;
	private char[] _validatorUpperCaseCharsetArray;

}