/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.spec;

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.TigerDigest;
import org.bouncycastle.crypto.digests.WhirlpoolDigest;
import org.cryptacular.CryptUtil;

/**
 * Describes a message digest function by name and provides a means to create a new instance of the digest via the
 * {@link #newInstance()} method.
 *
 * @author  Middleware Services
 */
public class DigestSpec implements Spec<Digest>
{

  /** Digest algorithm name. */
  private final String algorithm;

  /** Requested size of variable-size hash algorithms, e.g. SHA-3. -1 for hashes with fixed size outputs. */
  private final int size;


  /**
   * Creates a new instance from the given algorithm name.
   *
   * @param  algName  Digest algorithm name.
   */
  public DigestSpec(final String algName)
  {
    this.algorithm = CryptUtil.assertNotNullArg(algName, "Algorithm name cannot be null.");
    this.size = -1;
  }


  /**
   * Constructor for digests that have variable output size, e.g. SHA3.
   *
   * @param  algName  Digest algorithm name.
   * @param  digestSize  Size of resultant digest in bits.
   */
  public DigestSpec(final String algName, final int digestSize)
  {
    this.algorithm = CryptUtil.assertNotNullArg(algName, "Algorithm name cannot be null.");
    if (digestSize < 0) {
      throw new IllegalArgumentException("Digest size must be positive.");
    }
    this.size = digestSize;
  }


  @Override
  public String getAlgorithm()
  {
    return algorithm;
  }


  /** @return  Size of digest output in bytes, or -1 if the digest does not support variable size output. */
  public int getSize()
  {
    return size;
  }


  /**
   * Creates a new digest instance.
   *
   * @return  Digest instance.
   */
  @Override
  public Digest newInstance()
  {
    final Digest digest;
    if ("GOST3411".equalsIgnoreCase(algorithm)) {
      digest = new GOST3411Digest();
    } else if ("MD2".equalsIgnoreCase(algorithm)) {
      digest = new MD2Digest();
    } else if ("MD4".equalsIgnoreCase(algorithm)) {
      digest = new MD4Digest();
    } else if ("MD5".equalsIgnoreCase(algorithm)) {
      digest = new MD5Digest();
    } else if ("RIPEMD128".equalsIgnoreCase(algorithm) || "RIPEMD-128".equalsIgnoreCase(algorithm)) {
      digest = new RIPEMD128Digest();
    } else if ("RIPEMD160".equalsIgnoreCase(algorithm) || "RIPEMD-160".equalsIgnoreCase(algorithm)) {
      digest = new RIPEMD160Digest();
    } else if ("RIPEMD256".equalsIgnoreCase(algorithm) || "RIPEMD-256".equalsIgnoreCase(algorithm)) {
      digest = new RIPEMD256Digest();
    } else if ("RIPEMD320".equalsIgnoreCase(algorithm) || "RIPEMD-320".equalsIgnoreCase(algorithm)) {
      digest = new RIPEMD320Digest();
    } else if ("SHA1".equalsIgnoreCase(algorithm) || "SHA-1".equalsIgnoreCase(algorithm)) {
      digest = new SHA1Digest();
    } else if ("SHA224".equalsIgnoreCase(algorithm) || "SHA-224".equalsIgnoreCase(algorithm)) {
      digest = new SHA224Digest();
    } else if ("SHA256".equalsIgnoreCase(algorithm) || "SHA-256".equalsIgnoreCase(algorithm)) {
      digest = new SHA256Digest();
    } else if ("SHA384".equalsIgnoreCase(algorithm) || "SHA-384".equalsIgnoreCase(algorithm)) {
      digest = new SHA384Digest();
    } else if ("SHA512".equalsIgnoreCase(algorithm) || "SHA-512".equalsIgnoreCase(algorithm)) {
      digest = new SHA512Digest();
    } else if ("SHA3".equalsIgnoreCase(algorithm) || "SHA-3".equalsIgnoreCase(algorithm)) {
      digest = new SHA3Digest(size);
    } else if ("Tiger".equalsIgnoreCase(algorithm)) {
      digest = new TigerDigest();
    } else if ("Whirlpool".equalsIgnoreCase(algorithm)) {
      digest = new WhirlpoolDigest();
    } else {
      throw new IllegalStateException("Unsupported digest algorithm " + algorithm);
    }
    return digest;
  }


  @Override
  public String toString()
  {
    return algorithm;
  }
}
