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

import org.bouncycastle.crypto.Digest;
import org.cryptacular.spec.Spec;
import org.cryptacular.util.HashUtil;

/**
 * Abstract base class for all hash beans.
 *
 * @author  Middleware Services
 */
public abstract class AbstractHashBean
{

  /** Digest specification. */
  private Spec<Digest> digestSpec;

  /** Number of hash rounds. */
  private int iterations = 1;


  /** Creates a new instance. */
  public AbstractHashBean() {}


  /**
   * Creates a new instance by specifying all properties.
   *
   * @param  digestSpec  Digest specification.
   * @param  iterations  Number of hash rounds.
   */
  public AbstractHashBean(final Spec<Digest> digestSpec, final int iterations)
  {
    setDigestSpec(digestSpec);
    setIterations(iterations);
  }


  /** @return  Digest specification that determines the instance of {@link Digest} used to compute the hash. */
  public Spec<Digest> getDigestSpec()
  {
    return digestSpec;
  }


  /**
   * Sets the digest specification that determines the instance of {@link Digest} used to compute the hash.
   *
   * @param  digestSpec  Digest algorithm specification.
   */
  public void setDigestSpec(final Spec<Digest> digestSpec)
  {
    this.digestSpec = digestSpec;
  }


  /** @return  Number of iterations the digest function is applied to the input data. */
  public int getIterations()
  {
    return iterations;
  }


  /**
   * Sets the number of iterations the digest function is applied to the input data.
   *
   * @param  iterations  Number of hash rounds. Default value is 1.
   */
  public void setIterations(final int iterations)
  {
    if (iterations < 1) {
      throw new IllegalArgumentException("Iterations must be positive");
    }
    this.iterations = iterations;
  }


  /**
   * Hashes the given data.
   *
   * @param  data  Data to hash.
   *
   * @return  Digest output.
   */
  protected byte[] hashInternal(final Object... data)
  {
    return HashUtil.hash(digestSpec.newInstance(), iterations, data);
  }


  /**
   * Compares the hash of the given data against a known hash output.
   *
   * @param  hash  Known hash value. If the length of the array is greater than the length of the digest output,
   *               anything beyond the digest length is considered salt data that is hashed <strong>after</strong> the
   *               input data.
   * @param  data  Data to hash.
   *
   * @return  True if hashed data equals known hash output, false otherwise.
   */
  protected boolean compareInternal(final byte[] hash, final Object... data)
  {
    return HashUtil.compareHash(digestSpec.newInstance(), hash, iterations, data);
  }
}
