/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.security;

import java.security.MessageDigest;
import java.util.*;

public class Generator
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  public static final int CONFIRM_MIN = 1000000;
  public static final int CONFIRM_MAX = 9999999;

  private static final String AUTH_ALPHABET =
      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
  private static final int AUTH_STRING_LEN = 32;

  private static final String[] syllabary = {
    "ba", "be", "bi", "bo", "bu",
    "da", "de", "di", "do", "du",
    "cha", "chi", "cho", "chu",
    "fa", "fe", "fi", "fo", "fu",
    "ga", "ge", "gi", "go", "gu",
    "ha", "he", "hi", "ho", "hu",
    "ja", "je", "ji", "jo", "ju",
    "ka", "ke", "ki", "ko", "ku",
    "la", "le", "li", "lo", "lu",
    "ma", "me", "mi", "mo", "mu",
    "na", "ne", "ni", "no", "nu",
    "pa", "pe", "pi", "po", "pu",
    "ra", "re", "ri", "ro", "ru",
    "sa", "se", "si", "so", "su",
    "sha", "she", "sho", "shu",
    "ta", "te", "ti", "to", "tu",
    "va", "ve", "vi", "vo", "vu",
    "wa", "we", "wi", "wo", "wu",
    "ya", "ye", "yi", "yo", "yu",
    "za", "ze", "zi", "zo", "zu"
  };  // 98 syllables

  private static final char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

  private static Generator self = null;  // my own personal instance

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private Random rng;             // my random number generator

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  private Generator()
  {
    // Initialize the random number generator.
    java.util.Date seed_date = new java.util.Date();
    rng = new Random(seed_date.getTime());

  } // end constructor

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  public final String generateRandomAuthString()
  {
    StringBuffer buf = new StringBuffer(AUTH_STRING_LEN);
    for (int i=0; i<AUTH_STRING_LEN; i++)
      buf.append(AUTH_ALPHABET.charAt(rng.nextInt(AUTH_ALPHABET.length())));
    return buf.toString();

  } // end generateRandomAuthString

  public final int getNewConfirmationNumber()
  {
    return rng.nextInt(CONFIRM_MAX - CONFIRM_MIN + 1) + CONFIRM_MIN;

  } // end getNewConfirmationNumber

  public final int getAuthorizationNumber()
  {
    return rng.nextInt(Integer.MAX_VALUE);

  } // end getAuthorizationNumber

  public final String generateRandomPassword()
  {
    StringBuffer buf = new StringBuffer();
    int i;
    for (i=0; i<4; i++)   // 4 random syllables
      buf.append(syllabary[rng.nextInt(syllabary.length)]);
    for (i=0; i<3; i++)   // 3 random digits
      buf.append(digits[rng.nextInt(digits.length)]);
    return buf.toString();
    // total possible number of passwords = 92,236,816,000

  } // end generateRandomPassword

  /*--------------------------------------------------------------------------------
   * External static operations
   *--------------------------------------------------------------------------------
   */

  public static final boolean isValidRandomAuthString(String s)
  {
    if (s.length()!=AUTH_STRING_LEN)
      return false;
    for (int i=0; i<AUTH_STRING_LEN; i++)
    { // verify each authentication character in turn
      if (AUTH_ALPHABET.indexOf(s.charAt(i))<0)
	return false;

    } // end for

    return true;  // all tests passed - ship it!

  } // end isValidRandomAuthString

  public static final String hashPassword(String password)
  {
    if ((password!=null) && (password.length()>0))
    { // hash the password and save the hash value
      MessageDigest hasher;

      try
      { // get a hasher implementing the Secure Hashing Algorithm
	hasher = MessageDigest.getInstance("SHA");

      } // end try
      catch (java.security.NoSuchAlgorithmException e)
      { // SHA should be a standard algorithm...if it isn't, we're h0sed
	throw new RuntimeException("HOSED JRE - SHA should be a standard algorithm");

      } // end catch

      try
      { // update the hasher with the UTF-8 bytes of the password
	hasher.update(password.getBytes("UTF8"));

      } // end try
      catch (java.io.UnsupportedEncodingException e)
      { // WTF? How can the JRE NOT know about UTF-8?  HOW?!?
	throw new RuntimeException("HOSED JRE - UTF-8 encoding should be supported");

      } // end catch

      // Retrieve the raw hash value (should be 160 bits, or 20 bytes)
      byte[] raw_hash = hasher.digest();

      // Convert the hash value to a hexadecimal string (40 chars in length)
      StringBuffer hash_buf = new StringBuffer(raw_hash.length * 2);
      StringBuffer tmp_buf = new StringBuffer();
      String tmp;
      for (int i=0; i<raw_hash.length; i++)
      { // N.B.: Integer.toHexString does not zero-pad on the left, so that's why this is
	// a little complex
	tmp_buf.setLength(0);
	tmp_buf.append("00").append(Integer.toHexString(raw_hash[i]).trim());
	tmp = tmp_buf.toString();
	hash_buf.append(tmp.substring(tmp.length()-2));

      } // end for

      // finally, save off the password hash value
      return hash_buf.toString().toUpperCase();

    } // end if
    else  // no password
      return "";

  } // end hashPassword

  public static final Generator get()
  {
    synchronized (Generator.class)
    { // create me if I don't exist
      if (self==null)
	self = new Generator();

    } // end synchronized block

    return self;

  } // end get

} // end class Generator
