package com.tvd12.ezyfox.sercurity;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.crypto.Cipher;

import com.tvd12.ezyfox.function.EzyBytesFunction;

public class EzyAsyCrypt {

	protected Cipher cipher;
	protected byte[] publicKey;
	protected byte[] privateKey;
	protected KeyFactory keyFactory;
	
	@SuppressWarnings("rawtypes")
	protected static final Map<Class, EzyBytesFunction> BYTES_CONVERTERS = defaultBytesConverters();
	
	protected EzyAsyCrypt(Builder<?> builder) {
		try {
			init(builder);
		}
		catch(Exception e) {
			throw new IllegalStateException("init asymmetric encryption error", e);
		}
	}
	
	protected void init(Builder<?> builder) throws Exception {
		this.cipher = builder.newCipher();
		this.publicKey = builder.getPublicKey();
		this.privateKey = builder.getPrivateKey();
		this.keyFactory = builder.newKeyFactory();
	}
	
	public byte[] encrypt(byte[] data) throws Exception {
		cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
		return cipher.doFinal(data);
    }
	
	public byte[] decrypt(byte[] data) throws Exception {
		cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
		return cipher.doFinal(data);
    }
	
	public <T> T encrypt(byte[] data, Class<T> outType) throws Exception {
		byte[] bytes = encrypt(data);
		return convertBytes(bytes, outType);
	}
	
	public <T> T decrypt(byte[] data, Class<T> outType) throws Exception {
		byte[] bytes = decrypt(data);
		return convertBytes(bytes, outType);
	}
	
	public byte[] encrypt(String data) throws Exception {
		return encrypt(data.getBytes("UTF-8"));
    }
	
	public byte[] decrypt(String data) throws Exception {
		return decrypt(EzyBase64.decode(data));
    }
	
	public <T> T encrypt(String data, Class<T> outType) throws Exception {
		return encrypt(data.getBytes("UTF-8"), outType);
	}
	
	public <T> T decrypt(String data, Class<T> outType) throws Exception {
		return decrypt(EzyBase64.decode(data), outType);
	}
	
	@SuppressWarnings("unchecked")
	protected <T> T convertBytes(byte[] bytes, Class<T> outType) {
		return (T) getBytesConverters().get(outType).apply(bytes);
	}
	
	@SuppressWarnings("rawtypes")
	protected Map<Class, EzyBytesFunction> getBytesConverters() {
		return BYTES_CONVERTERS;
	}

	/**
	 * https://docs.oracle.com/javase/8/docs/api/java/security/spec/PKCS8EncodedKeySpec.html
	 * 
	 * @return the private key object
	 * @throws Exception when get any errors
	 */
	protected PrivateKey getPrivateKey() throws Exception {
		return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
	}

	/**
	 * https://docs.oracle.com/javase/8/docs/api/java/security/spec/X509EncodedKeySpec.html
	 * 
	 * @return the public key
	 * @throws Exception when get any error
	 */
	protected PublicKey getPublicKey() throws Exception {
		return keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
	}
	
	@SuppressWarnings("rawtypes")
	private static Map<Class, EzyBytesFunction> defaultBytesConverters() {
		Map<Class, EzyBytesFunction> answer = new ConcurrentHashMap<>();
		answer.put(byte[].class, (bytes) -> bytes);
		answer.put(String.class, (bytes) -> EzyBase64.encode2utf((byte[])bytes));
		return answer;
	}
	
	public static Builder<?> builder() {
		return new Builder<>();
	}
	
	@SuppressWarnings("unchecked")
	public static class Builder<B extends Builder<B>> {
		
		protected String algorithm;
		protected byte[] publicKey;
		protected byte[] privateKey;
		
		public B algorithm(String algorithm) {
			this.algorithm = algorithm;
			return (B)this;
		}
		
		public B publicKey(byte[] publicKey) {
			this.publicKey = publicKey;
			return (B)this;
		}
		
		public B privateKey(byte[] privateKey) {
			this.privateKey = privateKey;
			return (B)this;
		}
		
		protected byte[] getPublicKey() {
			return publicKey;
		}
		
		protected byte[] getPrivateKey() {
			return privateKey;
		}
		
		public EzyAsyCrypt build() {
			return new EzyAsyCrypt(this);
		}
		
		protected Cipher newCipher() throws Exception {
			return Cipher.getInstance(algorithm);
		}
		
		protected KeyFactory newKeyFactory() throws Exception {
			return KeyFactory.getInstance(algorithm);
		}
	}

}

@@ -407,24 +407,30 @@
 %0A%09protected 
+final 
 Cipher ciphe
@@ -435,32 +435,38 @@
 her;%0A%09protected 
+final 
 byte%5B%5D publicKey
@@ -470,32 +470,38 @@
 Key;%0A%09protected 
+final 
 byte%5B%5D privateKe
@@ -506,32 +506,38 @@
 Key;%0A%09protected 
+final 
 KeyFactory keyFa
@@ -545,16 +545,73 @@
 tory;%0A%09%0A
+%09public static final String DEFAULT_ALGORITHM = %22RSA%22;%0A%09%0A
 %09@Suppre
@@ -791,196 +791,8 @@
  %7B%0A%09
-%09%09init(builder);%0A%09%09%7D%0A%09%09catch(Exception e) %7B%0A%09%09%09throw new IllegalStateException(%22init asymmetric encryption error%22, e);%0A%09%09%7D%0A%09%7D%0A%09%0A%09protected void init(Builder%3C?%3E builder) throws Exception %7B%0A
 %09%09th
@@ -820,24 +820,25 @@
 ewCipher();%0A
+%09
 %09%09this.publi
@@ -866,24 +866,25 @@
 licKey();%0A%09%09
+%09
 this.private
@@ -916,16 +916,17 @@
 ey();%0A%09%09
+%09
 this.key
@@ -956,24 +956,130 @@
 yFactory();%0A
+%09%09%7D%0A%09%09catch(Exception e) %7B%0A%09%09%09throw new IllegalStateException(%22init asymmetric encryption error%22, e);%0A%09%09%7D%0A
 %09%7D%0A%09%0A%09public
@@ -3508,38 +3508,8 @@
 %0A%09%09%0A
-%09%09protected String algorithm;%0A
 %09%09pr
@@ -3565,16 +3565,66 @@
 ateKey;%0A
+%09%09protected String algorithm = DEFAULT_ALGORITHM;%0A
 %09%09%0A%09%09pub
@@ -4327,9 +4327,8 @@
 %09%7D%0A%09%7D%0A%0A%7D
-%0A

update EzyAsyCrypt