package com.adi.plugin;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.ArrayList;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Wrapper for Android Keystore Functionality
 */
public final class KeyStorage
{
    private static final String AES_MODE = "AES/ECB/PKCS7Padding";
    private static final String UTF_8 = "UTF-8";
	private static final int AES_Key_Size = 128;

    //private final RSAPublicKey publicKey;
    //private final Key privateKey;

	private final Context keyContext;

    /**
     * Constructor - checks if the given alias already exists and creates it if not.
     * Initialises public and private keys generated by keystore
     *
     * @param context   Context used by the wrapper
     * @param alias     Keystore Alias to use
     */
    public KeyStorage(final Context context, final String alias, final boolean encryptionRequired) throws KeyStoreException, CertificateException,
            NoSuchAlgorithmException, IOException, NoSuchProviderException,
            InvalidAlgorithmParameterException, UnrecoverableEntryException
    {
    	keyContext = context;

        SharedPreferences pref = context.getSharedPreferences("keychaindetails", Context.MODE_PRIVATE);
        String enryptedKeyB64 = pref.getString("keychainKey", null);
        if (enryptedKeyB64 == null)
        {
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			kgen.init(AES_Key_Size);
			SecretKey key = kgen.generateKey();
			byte[] aesKey = key.getEncoded();

			enryptedKeyB64 = Base64.encodeToString(aesKey, Base64.DEFAULT);
			SharedPreferences.Editor edit = pref.edit();
			edit.putString("keychainKey", enryptedKeyB64);
			edit.apply();
        }
    }

    private Key getSecretKey(Context context) throws Exception
    {
        SharedPreferences pref = context.getSharedPreferences("keychaindetails", Context.MODE_PRIVATE);
        String enryptedKeyB64 = pref.getString("keychainKey", null);

        // need to check null, omitted here
        byte[] encryptedKey = Base64.decode(enryptedKeyB64, Base64.DEFAULT);
        return new SecretKeySpec(encryptedKey, "AES");
    }

    /**
     * Encrypt the given string value using the public key from the Keystore
     *
     * @param value     The string to encrypt
     * @return          The encrypted string
     */
    public String encryptString(final String value)
    {
        try
        {
            final Cipher inCipher = Cipher.getInstance(AES_MODE);
            inCipher.init(Cipher.ENCRYPT_MODE, getSecretKey(keyContext));

            byte[] cipherText = inCipher.doFinal(value.getBytes("UTF-8"));

            return Base64.encodeToString(cipherText, Base64.DEFAULT);
        }
        catch(NoSuchPaddingException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(InvalidKeyException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(IOException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(BadPaddingException e)
        {
        	throw new IllegalArgumentException(e);
        }
        catch(IllegalBlockSizeException e)
        {
        	throw new IllegalArgumentException(e);
        }
        catch(Exception e)
        {
        	throw new IllegalArgumentException(e);
        }
    }

    /**
     * Decrypt the given string using the private key from the Keystore
     *
     * @param cipherText    The Encrypted string
     * @return              The Decrypted string
     */
    public String decryptString(final String cipherText)
    {
        try
        {
            Cipher output = Cipher.getInstance(AES_MODE);
            output.init(Cipher.DECRYPT_MODE, getSecretKey(keyContext));

            CipherInputStream cipherInputStream = new CipherInputStream(
                    new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), output);
            ArrayList<Byte> values = new ArrayList<Byte>();
            int nextByte;
            while ((nextByte = cipherInputStream.read()) != -1)
            {
                values.add((byte) nextByte);
            }

            byte[] bytes = new byte[values.size()];
            for (int i = 0; i < bytes.length; i++)
            {
                bytes[i] = values.get(i);
            }

            return new String(bytes, 0, bytes.length, UTF_8);
        }
        catch(NoSuchPaddingException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(InvalidKeyException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(IOException e)
        {
            throw new IllegalArgumentException(e);
        }
        catch(Exception e)
        {
        	throw new IllegalArgumentException(e);
        }
    }
}
