package gov.va.med.fw.security;

//Java imports
import java.math.BigInteger;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang.Validate;

import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.util.EncoderDecoderUtil;
import gov.va.med.fw.util.StringUtils;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * This is the API clients uses for getting secret key, encryption and
 * decryption. Encryption service to encrypt passwords using AES Symmetric algorithm as specified in the
 * configuration.
 *
 * @author Muddaiah Ranga
 * @version 3.0
 *
 * @author Sanjiv Surve
 * @02/26/2015
 * CodeCR12941 - Change encryption algorigthm from DES3 to AES.
 */
public class EncryptionServiceImpl extends AbstractComponent implements EncryptionService {

	public static final String AES_PREFIX = "{AES}";

	public static final String ENCRYPTION_ALOGORITHM_AES = "AES/ECB/PKCS5Padding";
    /**
	private static final byte[] key = {
            0x52, 0x32, 0x32, 0x24, 0x72, 0x56, 0x23, 0x02, 0x13, 0x76, 0x39, 0x18, 0x52, 0x64, 0x58, 0x16
    };
    */

	private String encryptionKey = null;
	private SecretKeySpec skeySpec = null;
	private String encryptionPrefix = AES_PREFIX;

	protected String algorithm;
	protected String keyAlgorithm;
	protected int keySize;
	protected boolean encryptionFlag;
	protected String defaultCharacterEncoding;
	protected KeyManager keyManager;
	private EncoderDecoderUtil encoderDecoder;

	// Load SunJCE provider
	static {
	}

	public EncryptionServiceImpl() {
	}


	/**
	 * Encrypt the given text using the given secret key. uses default crypto
	 * suffic for padding
	 *
	 * @param key
	 *            the secret key
	 * @param clearText
	 *            the test to be encrypted
	 * @param characterEncoding
	 *            the character encoding
	 * @return the encrypted text
	 * @throws EncryptionServiceException
	 *             thrown when there was an encryption error
	 */
	public String encrypt(SecretKey key, String characterEncoding, String clearText)
			throws EncryptionServiceException {
		return encrypt(key, characterEncoding, clearText, EncryptionService.CRYPTO_SUFFIX);
	}

	/**
	 *
	 * @param key
	 * @param characterEncoding
	 * @param clearText
	 * @param cryptoSuffix
	 * @return
	 * @throws EncryptionServiceException
	 */
	public String encrypt(SecretKey key, String characterEncoding, String clearText,
			String cryptoSuffix) throws EncryptionServiceException {
		String encrypted = clearText;
		if (key != null && (clearText != null && clearText.length() > 0)) {
			try {
				Cipher cipher = Cipher.getInstance(getAlgorithm());

				cipher.init(Cipher.ENCRYPT_MODE, key);

				if (!StringUtils.isEmpty(cryptoSuffix)) {
					clearText = clearText + cryptoSuffix;
				}

				if (StringUtils.isEmpty(characterEncoding)) {
					characterEncoding = defaultCharacterEncoding;
				}
				// Encode the string into bytes
				byte[] utf8 = clearText.getBytes(characterEncoding);

				// Encrypt
				byte[] enc = cipher.doFinal(utf8);

				// Encode bytes to base64 to get a string
				encrypted = new BASE64Encoder().encode(enc);
			} catch (Exception ex) {
				throw new EncryptionServiceException("error while encrypting" + ex);
			}
		}
		return encrypted;
	}

	/**
	 * default decrypt applies the default crypto suffix string
	 */
	public String decrypt(SecretKey key, String characterEncoding, String encryptedText)
			throws EncryptionServiceException {
		return decrypt(key, characterEncoding, encryptedText, EncryptionService.CRYPTO_SUFFIX);
	}

	/**
	 * Decrypt the given text using the given secret key.
	 *
	 * @param key
	 *            the secret key
	 * @param encryptedText
	 *            the encrypted text
	 * @param characterEncoding
	 *            the character encoding
	 * @return the decrypted text
	 * @throws DecryptionException
	 *             thrown when there was a decryption error
	 */
	public String decrypt(SecretKey key, String characterEncoding, String encryptedText,
			String cryptoSuffix) throws EncryptionServiceException {
		String decryptedText = encryptedText;
		if (key != null && (encryptedText != null && encryptedText.length() > 0)) {
			try {
				Cipher cipher = Cipher.getInstance(getAlgorithm());
				cipher.init(Cipher.DECRYPT_MODE, key);

				// Decode base64 to get bytes
				byte[] dec = new BASE64Decoder().decodeBuffer(encryptedText);
				// Decrypt
				byte[] utf8 = cipher.doFinal(dec);

				if (StringUtils.isEmpty(characterEncoding)) {
					characterEncoding = defaultCharacterEncoding;
				}
				// Decode bytes to the string
				String clearText = new String(utf8, characterEncoding);

				/*
                if (!StringUtils.isEmpty(cryptoSuffix)) {
                    if(!clearText.endsWith(cryptoSuffix))
                    {
                    	throw new EncryptionServiceException("decryption error.");
                    }

                    //Remove the suffix
                    decryptedText = clearText.substring(0,clearText.lastIndexOf(cryptoSuffix));
                }
                else {
                    decryptedText = clearText;
                }
                */
                decryptedText = clearText;

			} catch (Exception ex) {
				throw new EncryptionServiceException("error while decrypting the text", ex);
			}
		}
		return decryptedText;
	}

	/********************************************************************************/
	/************************ Private/protected Methods *****************************/
	/********************************************************************************/

	/**
	 * Gets/creates the secret key. If the new key created, it will be added to
	 * the key cache.
	 *
	 * @param keyCache
	 *            the key cache.
	 * @param createSecretKey
	 *            a flag to indicate the creation of the new key. If the this
	 *            flag is <code>true</code> and key is not present in the key
	 *            cache a new key will be created. If this flag is
	 *            <code>false</code> and key is not present in the key cache
	 *            null will be returned.
	 *
	 * @return the key from the key cache or a newl key.
	 * @throws EncryptionServiceException
	 *             when there is an error while creating a new key or the key
	 *             manager is null
	 */
	protected SecretKey getSecretKey(KeyCache keyCache, boolean createSecretKey)
			throws EncryptionServiceException {
		if (keyManager == null) {
			throw new EncryptionServiceException("keyManager is null");
		}
		SecretKey secretKey = keyManager.getKey(keyCache);
		if (secretKey == null && createSecretKey == true) {
			secretKey = getSkeySpec();
			keyCache.setKey(secretKey);
		}
		return secretKey;
	}

	public EncoderDecoderUtil getEncoderDecoder() {
		return encoderDecoder;
	}

	public void setEncoderDecoder(EncoderDecoderUtil encoderDecoder) {
		this.encoderDecoder = encoderDecoder;
	}

	/**
	 * Gets the encryption algorithm.
	 */
	public String getAlgorithm() {
		return algorithm;
	}

	/**
	 * Sets the encryption algorithm.
	 */
	public void setAlgorithm(String algorithm) {
		this.algorithm = algorithm;
	}

	/**
	 * Gets the key algorithm.
	 */
	public String getKeyAlgorithm() {
		return keyAlgorithm;
	}

	/**
	 * Sets the key algorithm.
	 */
	public void setKeyAlgorithm(String keyAlgorithm) {
		this.keyAlgorithm = keyAlgorithm;
	}
	/**
	 * Gets the key size from the configuration file.
	 */
	public int getKeySize() {
		return keySize;
	}

	/**
	 * Gets the key size from the configuration file.
	 */
	public void setKeySize(int keySize) {
		this.keySize = keySize;
	}

	/**
	 * Checks to see the encryption is enabled or not.
	 */
	public boolean isEncryptionEnabled() {
		return encryptionFlag;
	}

	/**
	 * Sets the encryption flag.
	 */
	public void setEncryptionFlag(boolean encryptionFlag) {
		this.encryptionFlag = encryptionFlag;
	}

	/**
	 * Gets the default Character Encoding.
	 */
	public String getDefaultCharacterEncoding() {
		return defaultCharacterEncoding;
	}

	/**
	 * Sets the default Character Encoding.
	 */
	public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
		this.defaultCharacterEncoding = defaultCharacterEncoding;
	}

	/**
	 * Gets the KeyManager object.
	 */
	public KeyManager getKeyManager() {
		return keyManager;
	}

	/**
	 * Sets the KeyManager object.
	 */
	public void setKeyManager(KeyManager keyManager) {
		this.keyManager = keyManager;
	}

	public String getEncryptionKey() {
		return encryptionKey;
	}

	public void setEncryptionKey(String encryptionKey) {
		this.encryptionKey = encryptionKey;
	}

	public SecretKeySpec getSkeySpec() {
		return skeySpec;
	}

	public void setSkeySpec(SecretKeySpec skeySpec) {
		this.skeySpec = skeySpec;
	}

	public String getEncryptionPrefix() {
		return encryptionPrefix;
	}

	public void setEncryptionPrefix(String encryptionPrefix) {
		this.encryptionPrefix = encryptionPrefix;
	}

	/**
	 * create the Secret Key from the specified encryption key and encryption
	 * method
	 */
	public void afterPropertiesSet() throws Exception {
		if (StringUtils.isEmpty(algorithm)) {
			throw new EncryptionServiceException("encryption algorithm is not set.");
		}

		if (keyManager == null) {
			throw new EncryptionServiceException("key manager is not set.");
		}

		if (StringUtils.isEmpty(defaultCharacterEncoding)) {
			throw new EncryptionServiceException("default character encoding is not set.");
		}

		Validate.notNull(encoderDecoder, "encoderDecoder is null");

		// create key specification - encryption key is stored as hexa decimal
		// number
		//byte[] bytearray = new BigInteger(getEncryptionKey(), getKeySize()).toByteArray();
		String encKey = getEncryptionKey();
		if (StringUtils.isNotEmpty(encKey)) {
			String[] v = encKey.split(",");
		    byte[] key = new byte[v.length];
		    for(int i = 0; i < v.length; i++) {
		        key[i] =  Integer.decode("0x" + v[i]).byteValue();
	
		    }
		    
			setSkeySpec(new SecretKeySpec(key, getKeyAlgorithm()));
			if (getAlgorithm().startsWith(ENCRYPTION_ALOGORITHM_AES)) {
				encryptionPrefix = AES_PREFIX;
			}
		}
	}

}
