/*
 This file is part of GNUnet.
 (C) 2011, 2012 Christian Grothoff (and other contributing authors)

 GNUnet is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published
 by the Free Software Foundation; either version 3, or (at your
 option) any later version.

 GNUnet is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.util;


import com.google.common.base.Charsets;
import org.gnunet.construct.FixedSizeIntegerArray;
import org.gnunet.construct.Message;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;


/**
 * 512-bit hash code
 */
public class HashCode implements Message {

    @FixedSizeIntegerArray(length = 64, signed = false, bitSize = 8)
    public byte[] data; // should be immutable, final, can't be due to construct

    /**
     * Create a hash code initialized to zero.
     */
    public HashCode() {
        data = new byte[64];
    }

    /**
     * Create a HashCode from an existing SHA-512 hash code value.
     *
     * @param hash SHA-512 hash code value
     * @return a HashCode
     */
    public static HashCode fromHashCode(byte[] hash) {
        return new HashCode(hash);
    }

    /**
     * Create a HashCode from data to be hashed.
     *
     * @param data data to hash
     * @return a HashCode
     */
    public static HashCode hash(byte[] data) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("crypto algorithm 'SHA-512' required but not provided");
        }
        byte[] hb = digest.digest(data);
        HashCode h = new HashCode(hb);
        return h;
    }

    /**
     * Private constructor for HashCode from the hash code value.  Made
     * private because there are two ways to create hash codes from byte arrays.
     *
     * @param hash hash code value to store in the HashCode
     */
    private HashCode(byte[] hash) {
        if (hash.length != 64) {
            throw new AssertionError("HashCode has to have length 64");
        }
        data = Arrays.copyOf(hash, hash.length);
    }

    /**
     * Create the HashCode of an UTF-8 String using SHA-512.
     *
     * @param s the string to hash
     */
    public HashCode(String s) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("crypto algorithm required but not provided");
        }
        byte[] data = digest.digest(s.getBytes(Charsets.UTF_8));
        if (data.length != 64) {
            throw new RuntimeException("error in SHA512 algorithm");
        }
        this.data = data;
    }

    public boolean isAllZero() {
        for (byte aData : data) {
            if (aData != 0) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof HashCode)) {
            return false;
        }
        HashCode hashCode = (HashCode) other;
        return Arrays.equals(this.data, hashCode.data);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(this.data);
    }

    @Override
    public String toString() {
        return Strings.dataToString(data);
    }


    public static HashCode random() {
        HashCode hashCode = new HashCode();
        SecureRandom sr = new SecureRandom();
        sr.nextBytes(hashCode.data);
        return hashCode;
    }
}