/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

====================================================================== */

package org.bodington.util;

import java.util.logging.*;



import org.bodington.util.*;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Vector;
import java.util.StringTokenizer;

public class ISSPasswordGenerator implements org.bodington.util.PasswordGenerator
	{
	private char[] allowed_chars;
	
	// modulo must be the largest prime number less than 
	// allowed_chars.size cubed
	int modulo;
	int base;
	
	public ISSPasswordGenerator()
		{
		base = 19;
		modulo = 6857;
		allowed_chars = new char[base];
		"bcdfghjkmnpqrstvwxz".getChars( 0, base, allowed_chars, 0 );
		}
	
	public void setAllowedCharacters( String str )
		throws PasswordGeneratorException
		{
		if ( allowed_chars == null )
			throw new PasswordGeneratorException( "Null string of allowed characters is not supported." );
		if ( allowed_chars.length != 19 )
			throw new PasswordGeneratorException( "A string of allowed characters that is not 19 characters in length is not supported." );
			
		str.getChars( 0, 19, allowed_chars, 0 );
		modulo = 6857;
		}
	
    public void setGlobalProperties(Properties props) throws PasswordGeneratorException
    	{
    	}
    	
    public Enumeration getGlobalPropertyNames()
    	{
    	Vector l = new Vector();
        return l.elements();
    	}

    public Enumeration getUserPropertyNames()
    	{
    	Vector l = new Vector();
    	l.addElement( "student_id" );
    	l.addElement( "date_of_birth" );
    	l.addElement( "academic_year" );
        return l.elements();
    	}

	private String charsToDigits( String id )
		{
		Logger.getLogger( "org.bodington" ).fine( "charsToDigits( \"" + id + "\")" );
		// irreversably transforms any string into a decimal number
		// of the same number of characters.
		// Multiple input strings will transform to the same output.
		// Typically the subsitution of one digit with a letter will
		// give the same output for two letters of the alphabet.
		// The pattern of students with the same transformed ID will
		// appear more or less randomly distributed through the student
		// population and it is very unlikely that two such students
		// will have the same date of birth and will try to log into
		// each other's accounts.
		StringBuffer output = new StringBuffer( id.length() );
		char c;
		for ( int i=0; i<id.length(); i++ )
			{
			c = id.charAt( i );
			
			if ( c >= '0' && c <= '9' )
				// decimal digits go through unchanged
				output.append( c );
			else
				{
				// make sure upper and lower case letters transform to
				// the same digit so password generation is insensitive
				// to case.
				c = Character.toLowerCase( c );
				// other characters change to decimal digit
				// characters that are evenly distributed according to their
				// unicode number by taking the base ten modulo and offsetting
				// to the code for digit zero.
				output.append( (char)('0' + (c % 10)) );
				}
			}
		
		// the output should be ready to parse into an integer variable
		Logger.getLogger( "org.bodington" ).fine( "return \"" + output.toString() + "\"" );
		return output.toString();
		}
		

    public String generate(Properties props) throws PasswordGeneratorException
    	{
    	String student_id, date_of_birth, academic_year;
		int m, n, sid, left, right, day, month, year, a_year;
		StringTokenizer tok;
		char[] password = new char[6];
    	
    	
    	if ( props == null )
    		throw new PasswordGeneratorException( "Null properties not allowed when generating a password." );
    		
    	student_id    = props.getProperty( "student_id"	   );
    	date_of_birth = props.getProperty( "date_of_birth" );
    	academic_year = props.getProperty( "academic_year" );
    	
    	if ( student_id ==null || date_of_birth==null || academic_year==null )
    		throw new PasswordGeneratorException( "Missing user property when attempting to generate a password." );
    		
    	tok = new StringTokenizer( date_of_birth, "/:,\\;:*#." );
    	if ( tok.countTokens()!=3 )
    		throw new PasswordGeneratorException( "Missing component of date of birth user property when attempting to generate a password." );
    		
    		
    	
    		
    	try
    		{
    		sid    = Integer.parseInt( charsToDigits( student_id )     );
    		day    = Integer.parseInt( tok.nextToken() );
    		month  = Integer.parseInt( tok.nextToken() );
    		year   = Integer.parseInt( tok.nextToken() );
    		a_year = Integer.parseInt( academic_year   );
    		}
    	catch ( NumberFormatException nfex )
    		{
    		throw new PasswordGeneratorException( "An invalid number was found in a user property." );
    		}
    			
    	year = year % 100;
    	a_year = a_year % 100;
    	left =	sid / 23719;
    	right = sid % 51383;
    		
    	// Think of password characters as digits in a base 19 number.
    	// Algorithm generates a 6 digit number.  I.e. between 0 and
    	// 19 to the power 6 minus 1.
    	
    	// original C
		//	x = ( 4229*left + 2137*right + 383*dy + 5431*mo + 1741*yr + 3571*acad ) % PWDMOD;
		//  y = ( 3271*left + 607*right + 4931*dy + 3803*mo + 1327*yr + 2309*acad ) % PWDMOD;
     
    	// create a random looking bit pattern in three base 19 digits.
    	// modulo is less than (but not too much less than) 19 to power three
    	n =   ( 	4229 * left    +						//  1000010000101
    			    2137 * right   +						//	 100001011001
    			     383 * day     +						//      101111111
    			    5431 * month   +						//  1010100110111
    			    1741 * year    +						//    11011001101
    			    3571 * a_year )    						//   110111110011
    			    					  % modulo;

    	// fill in another three base 19 digits.
    	m =  (  	3271 * left    +						//   110011000111
    				 607 * right   +						//     1001011111
    				4931 * day     +						//  1001101000011
    				3803 * month   +						//   111011011011
    				1327 * year    +						//    10100101111
    				2309 * a_year )	% modulo;				//   100100000101
    	
    	// now examine base 19 digits in turn to select character representation.
    	for ( int i=5; i>=3; i-- )
    		{
    		password[i] = allowed_chars[ m % base ];
    		m = m / base;
    		}
    	for ( int i=2; i>=0; i-- )
    		{
    		password[i] = allowed_chars[ n % base ];
    		n = n / base;
    		}
    	
    	return new String( password );
    	}


	public static void main( String[] params )
		{
		String pass;
		ISSPasswordGenerator gen = new ISSPasswordGenerator();
		
		Properties props = new Properties();
		
		props.put( "student_id",	"123456a89" 	);
		props.put( "date_of_birth", "31/12/1999"	);
		props.put( "academic_year", "2000"			);
		
		try
			{
			pass = gen.generate( props );
			}
		catch ( PasswordGeneratorException pgenex )
			{
			pgenex.printStackTrace();
			return;
			}
		
		Logger.getLogger( "org.bodington" ).info( pass );
		
		//Logger.getLogger( "org.bodington" ).info( genugpw( 999999999,31,12,1999,2000 ) );
		}
		
		
   private static final int PWDLEN = 6;
   private static final String PASSWDCHARS = "bcdfghjkmnpqrstvwxz";
   private static final int PWDMOD = 6857;
                        // This value assumes strlen(PASSWDCHARS) = 19.
                        // If PASSWDCHARS is changed, change PWDMOD to be the
                        // largest prime number less than (strlen(PASSWDCHARS))**3
                        // If the number of chars in a password needs to be
                        // changed from 6, consult RH

   private static String genugpw(int id, int dy, int mo, int yr, int acad)

   // id = student ID number.  This is currently 9 digits.
   // It must be <= 2147483647 or there will be overflow.

   // dy = day of month of date of birth, 1 <= dy <= 31
   // mo = month of birth, 1 <= mo <= 12
   // yr = year or birth.  Only the last 2 digits are used
   //      so it will give the same password regardless of
   //      whether the year is given as 75 or 1975

   // acad = the calendar year which contains the beginning
   //        of the academic year for which the password is
   //        to be valid.  Thus to generate a password to
   //        be used in the academic year 1994-95, acad
   //        should be 94 or 1994.  Only the last 2 digits of

   //        acad are used, so it will give the same password 
   //        regardless of which of these two forms is given.
   //        A separate simple routine acadyr() works out the
   //        value of acad from the current date

   // buff = pointer to a character array where passwd will
   //        be placed.  Must be at least 7 chars long,
   //        6 for the password and 1 for the terminating null


   { int[] sub = new int[PWDLEN];
     int left, right, x, y;
     int first3, last3, i;
     yr = yr % 100;

     left = id / 23719;
     right = id % 51383;

     x = ( 4229*left + 2137*right + 383*dy + 5431*mo + 1741*yr + 3571*acad ) % PWDMOD;
     y = ( 3271*left + 607*right + 4931*dy + 3803*mo + 1327*yr + 2309*acad ) % PWDMOD;

     first3 = x;
     last3 = y;

     sub[0] = first3 / 361;
     sub[1] = (first3 % 361) / 19;
     sub[2] = first3 % 19;
     sub[3] = last3 / 361;
     sub[4] = (last3 % 361) / 19;
     sub[5] = last3 % 19;

     char[] buff = new char[PWDLEN];
     for (i=0; i<PWDLEN; i++)
        buff[i] = PASSWDCHARS.charAt(sub[i]);
     return new String(buff);
   }		

	
	
	public boolean isOffLine()
		{
		return true;  // password IS stored off line - student uses mypasswd.
		}

	public String offLineAdvice()
		{
		return "The generated password has not be presented because the student should use the mypasswd program to find it out.";
		}
	}
