package au.com.acegi.hashbench.hashers;

import java.nio.ByteBuffer;
import java.util.Map;

import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.GOST3411Digest;
import org.bouncycastle.crypto.digests.MD2Digest;
import org.bouncycastle.crypto.digests.MD4Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.RIPEMD128Digest;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.bouncycastle.crypto.digests.RIPEMD256Digest;
import org.bouncycastle.crypto.digests.RIPEMD320Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA224Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.digests.SHA512tDigest;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.digests.SkeinDigest;
import org.bouncycastle.crypto.digests.TigerDigest;
import org.bouncycastle.crypto.digests.WhirlpoolDigest;

import com.google.common.primitives.Longs;

public class BouncyCastleHasher implements Hasher {
  public static final String GOST = "gost-bc";
  public static final String MD2 = "md2-bc";
  public static final String MD4 = "md4-bc";
  public static final String MD5 = "md5-bc";
  public static final String RIPEMD128 = "ripemd128-bc";
  public static final String RIPEMD160 = "ripemd160-bc";
  public static final String RIPEMD256 = "ripemd256-bc";
  public static final String RIPEMD320 = "ripemd320-bc";
  public static final String SHA1 = "sha1-bc";
  public static final String SHA224 = "sha224-bc";
  public static final String SHA256 = "sha256-bc";
  public static final String SHA3 = "sha3-bc";
  public static final String SHA384 = "sha384-bc";
  public static final String SHA512 = "sha512-bc";
  public static final String SHA512_T = "sha512-bc-t";
  public static final String SKEIN1024 = "skein1024-bc";
  public static final String SKEIN256 = "skein256-bc";
  public static final String SKEIN512 = "skein512-bc";
  public static final String SM3 = "sm3-bc";
  public static final String TIGER = "tiger-bc";
  public static final String WHIRLPOOL2 = "whirlpool2-bc";

  public static final void register(final Map<String, Hasher> hashers) {
    hashers.put(BouncyCastleHasher.GOST,
            new BouncyCastleHasher(new GOST3411Digest()));
    hashers.put(BouncyCastleHasher.MD2,
            new BouncyCastleHasher(new MD2Digest()));
    hashers.put(BouncyCastleHasher.MD4,
            new BouncyCastleHasher(new MD4Digest()));
    hashers.put(BouncyCastleHasher.MD5,
            new BouncyCastleHasher(new MD5Digest()));
    hashers.put(BouncyCastleHasher.RIPEMD128,
            new BouncyCastleHasher(new RIPEMD128Digest()));
    hashers.put(BouncyCastleHasher.RIPEMD160,
            new BouncyCastleHasher(new RIPEMD160Digest()));
    hashers.put(BouncyCastleHasher.RIPEMD256,
            new BouncyCastleHasher(new RIPEMD256Digest()));
    hashers.put(BouncyCastleHasher.RIPEMD320,
            new BouncyCastleHasher(new RIPEMD320Digest()));
    hashers.put(BouncyCastleHasher.SHA1,
            new BouncyCastleHasher(new SHA1Digest()));
    hashers.put(BouncyCastleHasher.SHA224,
            new BouncyCastleHasher(new SHA224Digest()));
    hashers.put(BouncyCastleHasher.SHA256,
            new BouncyCastleHasher(new SHA256Digest()));
    hashers.put(BouncyCastleHasher.SHA3,
            new BouncyCastleHasher(new SHA3Digest()));
    hashers.put(BouncyCastleHasher.SHA384,
            new BouncyCastleHasher(new SHA384Digest()));
    hashers.put(BouncyCastleHasher.SHA512,
            new BouncyCastleHasher(new SHA512Digest()));
    hashers.put(BouncyCastleHasher.SHA512_T,
            new BouncyCastleHasher(new SHA512tDigest(7 * 8)));
    hashers.put(BouncyCastleHasher.SKEIN1024, new BouncyCastleHasher(
            new SkeinDigest(SkeinDigest.SKEIN_1024, Long.BYTES * 8)));
    hashers.put(BouncyCastleHasher.SKEIN256, new BouncyCastleHasher(
            new SkeinDigest(SkeinDigest.SKEIN_256, Long.BYTES * 8)));
    hashers.put(BouncyCastleHasher.SKEIN512, new BouncyCastleHasher(
            new SkeinDigest(SkeinDigest.SKEIN_512, Long.BYTES * 8)));
    hashers.put(BouncyCastleHasher.SM3,
            new BouncyCastleHasher(new SM3Digest()));
    hashers.put(BouncyCastleHasher.TIGER,
            new BouncyCastleHasher(new TigerDigest()));
    hashers.put(BouncyCastleHasher.WHIRLPOOL2,
            new BouncyCastleHasher(new WhirlpoolDigest()));
  }

  private final Digest delegate;
  private final byte[] digest;

  private BouncyCastleHasher(final Digest delegate) {
    this.delegate = delegate;
    this.digest = new byte[(this.delegate.getDigestSize() < Long.BYTES
            ? Long.BYTES : this.delegate.getDigestSize())];
  }

  @Override
  public long hash(final byte[] in, final int off, final int len) {
    this.delegate.reset();
    this.delegate.update(in, off, len);
    this.delegate.doFinal(this.digest, 0);
    return Longs.fromByteArray(this.digest);
  }

  @Override
  public long hash(final ByteBuffer bb, final int off, final int len) {
    this.delegate.reset();

    if (bb.hasArray()) {
      this.delegate.update(bb.array(), off, len);
      this.delegate.doFinal(this.digest, 0);
      return Longs.fromByteArray(this.digest);
    }

    final ByteBuffer view = bb.duplicate();
    view.position(off);
    view.limit(off + len);
    final byte[] data = new byte[len];
    view.get(data, 0, len);

    this.delegate.update(data, 0, len);
    this.delegate.doFinal(this.digest, 0);
    return Longs.fromByteArray(this.digest);
  }
}