/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.generic;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import net.ranides.assira.collection.arrays.NativeArray;
import net.ranides.assira.collection.lists.NativeArrayList;
import net.ranides.assira.text.Charsets;

@SuppressWarnings({
    "PMD.AvoidReassigningParameters"
})
public final class HashUtils {

    private HashUtils() {
        // utility class
    }
    
    /**
     * Avalanches the bits of an integer by applying the finalisation step of
     * MurmurHash3.
     *
     * <p>This function implements the finalisation step of Austin Appleby's <a
     * href="http://sites.google.com/site/murmurhash/">MurmurHash3</a>. Its
     * purpose is to avalanche the bits of the argument to within 0.25% bias. It
     * is used, among other things, to scramble quickly (but deeply) the hash
     * values returned by {@link Object#hashCode()}.
     *
     * @param x an integer.
     * @return a hash value with good avalanching properties.
     */
    public static int murmurHash3(int x) {
        x ^= x >>> 16;
        x *= 0x85ebca6b;
        x ^= x >>> 13;
        x *= 0xc2b2ae35;
        x ^= x >>> 16;
        
        return x;
    }

    /**
     * Avalanches the bits of a long integer by applying the finalisation step
     * of MurmurHash3.
     *
     * <p>This function implements the finalisation step of Austin Appleby's <a
     * href="http://sites.google.com/site/murmurhash/">MurmurHash3</a>. Its
     * purpose is to avalanche the bits of the argument to within 0.25% bias. It
     * is used, among other things, to scramble quickly (but deeply) the hash
     * values returned by {@link Object#hashCode()}.
     *
     * @param x a long integer.
     * @return a hash value with good avalanching properties.
     */
    public static long murmurHash3(long x) {
        x ^= x >>> 33;
        x *= 0xff51afd7ed558ccdL;
        x ^= x >>> 33;
        x *= 0xc4ceb9fe1a85ec53L;
        x ^= x >>> 33;

        return x;
    }

    // @todo (assira #3) murmurHash3 (values...)
    
    public static int hash(Object value) {
        if(null == value) {
            return 0;
        }
        if(value.getClass().isArray()) {
            return NativeArray.wrap(value).hashCode();
        }
        return value.hashCode();
    }
    
    /**
     * Calculates hash value compatible with code automatically generated by NetBeans IDE.
     * @param initial 
     * @param multiplier
     * @param values
     * @return 
     */
    public static int hashValues(int initial, int multiplier, Object... values) {
        return hashIterable(initial, multiplier, Arrays.asList(values));
    }
    
    public static int hashPair(Object value1, Object value2) {
        int a = value1 == null ? 0 : value1.hashCode();
        int b = value2 == null ? 0 : value2.hashCode();
        return murmurHash3(a) ^ murmurHash3(b);
    }
    
    /**
     * Calculates hash value compatible with code automatically generated by NetBeans IDE.
     * @param initial 
     * @param multiplier
     * @param values
     * @return 
     */
    public static int hashIterable(int initial, int multiplier, Iterable<?> values) {
        int hash = initial;
        for(Object value: values) {
            hash *= multiplier;
            if(null == value) {
                continue;
            }
            if( value.getClass().isArray() ) {
                hash += NativeArrayList.wrap(value).hashCode();
            } else {
                hash += value.hashCode();
            }
        }
        return hash;
    }
    
    /**
     * Calculates hash value of text using SHA-512
     * @param value
     * @return
     */
    public static String digest(String value) {
        return digest(value, "SHA-512");
    }
    
    /**
     * Calculates hash value of text using specified JRE built-in algorithm.
     * {@see MessageDigest}.
     * @param value
     * @param method
     * @return
     */
    public static String digest(String value, String method) {
        if( (null== value) || value.isEmpty() ) { 
            return null; 
        }
        try {
            MessageDigest mdt = MessageDigest.getInstance(method);
            byte[] digest = mdt.digest(value.getBytes(Charsets.UTF8) );
            BigInteger number = new BigInteger(1, digest);
            return number.toString(16);
        } catch (NoSuchAlgorithmException cause) {
            throw new UnsupportedOperationException(cause);
        }
    }
}