package gov.va.med.fw.security;

//JCE imports
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

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

//Framework imports
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.util.StringUtils;

/**
 * This is the API clients uses for getting secret key, encrypting and decrypting.
 * Encryption service to encrypt passwords using AES Symmetric algorithm as specified in the
 * configuration. 
 *
 * @author Muddaiah Ranga
 * @version 3.0
 */
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;

    //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;
                }
            }
            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;
    }
	
	/**
	 * 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.");
		}
		
		// 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;
			}
		}
	}    
}
