package no.brodwall.insanejava.crypto;

import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import org.springframework.util.ObjectUtils;

public class EncryptedStringUserType implements UserType {

    private final static String KEY_ALGORITHM = "PBEWithMD5AndDES";

    private final static String CIPHER_ALGORITHM = "PBEWithMD5AndDES/CBC/PKCS5Padding";

    private final static byte[] SALT = "aadvslaa".getBytes();

    private final PBEParameterSpec paramSpec = new PBEParameterSpec(SALT, 5);


    private static SecretKey masterKey;

    public final void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.VARBINARY);
        } else {
            noNullSet(st, value, index);
        }
    }

    protected void noNullSet(PreparedStatement st, Object value, int index) throws SQLException {
        byte[] clearText = ((String)value).getBytes();

        try {
            Cipher encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
            encryptCipher.init(Cipher.ENCRYPT_MODE, masterKey, paramSpec);
            st.setBytes(index, encryptCipher.doFinal(clearText));
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("should never happen", e);
        }
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
        byte[] bytes = rs.getBytes(names[0]);
        try {
            Cipher decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
            decryptCipher.init(Cipher.DECRYPT_MODE, masterKey, paramSpec);
            return new String(decryptCipher.doFinal(bytes));
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("Whoa! Master password wrong");
        }
    }


    public static void setKey(char[] masterKeyText) {
        try {
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
            PBEKeySpec keySpec = new PBEKeySpec(masterKeyText);
            masterKey = secretKeyFactory.generateSecret(keySpec);
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("should never happen", e);
        }
    }



    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        return ObjectUtils.nullSafeEquals(x, y);
    }

    public int hashCode(Object x) throws HibernateException {
        return ObjectUtils.nullSafeHashCode(x);
    }

    public boolean isMutable() {
        return false;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return target;
    }

    public final int[] sqlTypes() {
        return new int[] { Types.LONGVARBINARY };
    }

    public Class returnedClass() {
        return String.class;
    }



}
