package routines;

import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;

//import sun.misc.BASE64Decoder;
//import sun.misc.BASE64Encoder;
import java.util.Base64;

public class PasswordUtil {

	public static void uncryptContextPwds(java.util.Properties c) {
		java.util.Enumeration<?> en = c.propertyNames();
		String salt = c.getProperty("conf_rootDataDirectory");
		String cryptedPwd = "";
		while (en.hasMoreElements()) {
			String key = (String) en.nextElement();
			if (key.contains("Password")) {
				// c.setProperty(key,PasswordUtil.unCrypt(c.getProperty(key)));
				cryptedPwd = c.getProperty(key);
				cryptedPwd = cryptedPwd.replace("@", "=");
				c.setProperty(key, decrypt(cryptedPwd, salt));

			}
		}
		publishContext(c);
	}

	private static void publishContext(java.util.Properties context) {
		java.lang.reflect.Field[] declaredFields = context.getClass()
				.getDeclaredFields();
		for (java.lang.reflect.Field contextField : declaredFields) {
			for (java.util.Map.Entry<?, ?> propertyEntry : context.entrySet()) {
				if (isSecuredProperty(contextField.getName()) &&
				    contextField.getName().equals(propertyEntry.getKey())) {
					try {
						contextField.set(context, propertyEntry.getValue());
					} catch (IllegalArgumentException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}

	// crypt modul
    private static String getSalt(String saltstr){
    	return (saltstr+"919ebba032181dacaa1849192").substring(0, 16);
    }

	public static String decrypt(String text, String saltstr) {
		return decrypt("##nopropertyname##", text, saltstr);
	}

    
	public static String decrypt(String propertyName, String text, String saltstr) {
		String result = null;
		saltstr=getSalt(saltstr);
		try {
			// zufaelligen Schluessel erzeugen
			KeyGenerator keygen = KeyGenerator.getInstance("AES");
			keygen.init(128);
			// SecretKey aesKey = keygen.generateKey();
			byte[] salt = saltstr.getBytes();
			Key aesKey = new SecretKeySpec(salt, 0, 16, "AES");

			// Klasse erzeugen
			EasyCrypt ec = new EasyCrypt(aesKey, "AES");

			// Text verschlüsseln
			result = ec.decrypt(text);
		} catch (Exception e) {
			// e.printStackTrace();
			// Im Fehlerfall soll keine Aenderung des Eingabestrings erfolgen und wir melden das  an die Konsole
			result = text;
			if(!"##nopropertyname##".equals(propertyName)) System.err.println("Unsecured property found:'"+propertyName+"'");
		}
		return result;
	}

	public static String encrypt(String text, String saltstr) {
		String result = null;
		saltstr=getSalt(saltstr);
		try {
			// zufaelligen Schluessel erzeugen
			KeyGenerator keygen = KeyGenerator.getInstance("AES");
			keygen.init(128);
			// SecretKey aesKey = keygen.generateKey();
			byte[] salt = saltstr.getBytes();
			Key aesKey = new SecretKeySpec(salt, 0, 16, "AES");
			// Klasse erzeugen
			EasyCrypt ec = new EasyCrypt(aesKey, "AES");

			// Text verschlüsseln
			result = ec.encrypt(text);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public static boolean isSecuredProperty(String key) {  //Extend this method to add more Patterns for Password properties
		return key.contains("Password");
	}
	

	
	private static class EasyCrypt {
	
		private Key key = null;
		private String verfahren = null;
	
		/**
		 * @param Key
		 *            verwendeter Schluessel
		 * @param verfahren
		 *            bestimmt das verwendete Verschluesselungsverfahren "RSA",
		 *            "AES", ....
		 * @throws Exception
		 */
		public EasyCrypt(Key k, String verfahren) throws Exception {
			this.key = k;
			this.verfahren = verfahren;
		}
	
		/**
		 * Verschluesselt einen Outputstream
		 * 
		 * @param os
		 *            Klartext-Outputstream
		 * @return verschluesselter Outputstream
		 * @throws Exception
		 */
		public OutputStream encryptOutputStream(OutputStream os) throws Exception {
			// integritaet pruefen
			valid();
	
			// eigentliche Nachricht mit RSA verschluesseln
			Cipher cipher = Cipher.getInstance(verfahren);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			os = new CipherOutputStream(os, cipher);
	
			return os;
		}
	
		/**
		 * Entschluesselt einen Inputstream
		 * 
		 * @param is
		 *            verschluesselter Inputstream
		 * @return Klartext-Inputstream
		 * @throws Exception
		 */
		public InputStream decryptInputStream(InputStream is) throws Exception {
			// integritaet pruefen
			valid();
	
			// Daten mit AES entschluesseln
			Cipher cipher = Cipher.getInstance(verfahren);
			cipher.init(Cipher.DECRYPT_MODE, key);
			is = new CipherInputStream(is, cipher);
	
			return is;
		}
	
		/**
		 * Verschluesselt einen Text in BASE64
		 * 
		 * @param text
		 *            Klartext
		 * @return BASE64 String
		 * @throws Exception
		 */
		public String encrypt(String text) throws Exception {
			// integritaet pruefen
			valid();
	
			// Verschluesseln
			Cipher cipher = Cipher.getInstance(verfahren);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			byte[] encrypted = cipher.doFinal(text.getBytes());
	
			// bytes zu Base64-String konvertieren
			String geheim = Base64.getEncoder().encodeToString(encrypted);
			//java.util.Base64.Encoder myEncoder = new java.util.Base64.Encoder();
			//String geheim = myEncoder.encode(encrypted);
	
			return geheim;
		}
	
		/**
		 * Entschluesselt einen BASE64 kodierten Text
		 * 
		 * @param geheim
		 *            BASE64 kodierter Text
		 * @return Klartext
		 * @throws Exception
		 */
		public String decrypt(String geheim) throws Exception {
			// integritaet pruefen
			valid();
	
			// BASE64 String zu Byte-Array
			//BASE64Decoder myDecoder = new BASE64Decoder();
			//byte[] crypted = myDecoder.decodeBuffer(geheim);
			byte[] crypted = Base64.getDecoder().decode(geheim);
	
			// entschluesseln
			Cipher cipher = Cipher.getInstance(verfahren);
			cipher.init(Cipher.DECRYPT_MODE, key);
			byte[] cipherData = cipher.doFinal(crypted);
			return new String(cipherData);
		}
	
		// ++++++++++++++++++++++++++++++
		// Validierung
		// ++++++++++++++++++++++++++++++
	
		private boolean valid() throws Exception {
			if (verfahren == null) {
				throw new NullPointerException("Kein Verfahren angegeben!");
			}
	
			if (key == null) {
				throw new NullPointerException("Keinen Key angegeben!");
			}
	
			if (verfahren.isEmpty()) {
				throw new NullPointerException("Kein Verfahren angegeben!");
			}
	
			return true;
		}
	
		// ++++++++++++++++++++++++++++++
		// Getter und Setter
		// ++++++++++++++++++++++++++++++
	
		public Key getKey() {
			return key;
		}
	
		public void setKey(Key key) {
			this.key = key;
		}
	
		public String getVerfahren() {
			return verfahren;
		}
	
		public void setVerfahren(String verfahren) {
			this.verfahren = verfahren;
		}
	}
	
}