/*
 * [FNV1a64Digester.java]
 *
 * Summary: Quick 64-bit hashing function with a digester.
 *
 * Copyright: (c) 2014 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.7+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2014-04-29 initial version.
 *  1.1 2014-08-01 make conform to the Checksum interface.
 */
package com.mindprod.common17;

import java.nio.charset.Charset;
import java.util.zip.Checksum;

/**
 * Quick 64-bit hashing function with a digester.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2014-04-07 initial version.
 * @see com.mindprod.common17.FNV1a64
 * @since 2014-04-07
 */
public class FNV1a64Digester implements Checksum
    {
    // declarations

    /**
     * magic 64 bit initial value for the hash = 14,695,981,039,346,656,037, needs to be unsigned to fit in long.
     * faked unsigned.
     */
    @SuppressWarnings( "NumericOverflow" )
    private static final long MAGIC_OFFSET = 14_695_981_039_346_656_03L * 10 + 7;

    /**
     * prime specified to use in 64 bit hashes
     * magic 64 bit FNV_prime = 240 + 28 + 0xb3 = 1,099,511,628,211
     */
    private static final long MAGIC_PRIME = 1_099_511_628_211L;

    /**
     * encoding for UTF-8
     */
    private static final Charset UTF8 = Charset.forName( "UTF-8" );

    /**
     * where we accumulate the hash
     */
    private long hash;
    // /declarations

    /**
     * constructor
     */
    public FNV1a64Digester()
        {
        hash = MAGIC_OFFSET;
        }
    // methods

    /**
     * Returns the current checksum value.
     *
     * @return the current checksum value
     */
    public long getValue()
        {
        return hash;
        }// /method

    /**
     * clear, ready to start another has computation
     */
    public void reset()
        {
        hash = MAGIC_OFFSET;
        }// /method

    /**
     * Updates the current checksum with the specified byte.
     *
     * @param bchar the byte to update the checksum with. Parm is int, only low 8 bits should be used.
     */
    public void update( int bchar )
        {
        hash ^= bchar;
        hash *= MAGIC_PRIME;
        }// /method

    /**
     * calculate 64 bit hash on byte array
     */
    public void update( byte[] ba )
        {
        for ( byte b : ba )
            {
            hash ^= b;
            hash *= MAGIC_PRIME;
            }
        }// /method

    /**
     * Updates the current checksum with the specified array of bytes.
     *
     * @param ba  the byte array to update the checksum with
     * @param off the start offset of the data
     * @param len the number of bytes to use for the update
     */
    public void update( byte[] ba, int off, int len )
        {
        final int to = off + len;
        for ( int i = off; i <= to; i++ )
            {
            hash ^= ba[ i ];
            hash *= MAGIC_PRIME;
            }
        }// /method

    /**
     * add int to info to be hashed
     */
    public void update( long n )
        {
        for ( int shift = 56; shift >= 0; shift -= 8 )
            {
            hash ^= ( int ) ( n >>> shift ) & 0xff;
            hash *= MAGIC_PRIME;
            }
        }// /method

    /**
     * calculate 64 bit hash on string
     */
    public void update( String s )
        {
        final byte[] theTextToDigestAsBytes = s.getBytes( UTF8 );
        for ( byte b : theTextToDigestAsBytes )
            {
            hash ^= b;
            hash *= MAGIC_PRIME;
            }
        }// /method

    /**
     * calculate 64 bit hash on array of strings
     */
    public void update( String[] strings )
        {
        for ( String s : strings )
            {
            final byte[] theTextToDigestAsBytes = s.getBytes( UTF8 );
            for ( byte b : theTextToDigestAsBytes )
                {
                hash ^= b;
                hash *= MAGIC_PRIME;
                }
            }
        }// /method

    /**
     * add int to info to be hashed
     */
    public void updateInt( int i )
        {
        for ( int shift = 24; shift >= 0; shift -= 8 )
            {
            hash ^= ( i >>> shift ) & 0xff;
            hash *= MAGIC_PRIME;
            }
        }// /method
    // /methods
    }
