/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

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

package org.bodington.util;

import org.apache.log4j.Logger;

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 static Logger log = Logger.getLogger(ISSPasswordGenerator.class);
    
	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 )
		{
		log.debug( "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
		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;
			}
		
		log.info( pass );
		
		}
		
		
   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.";
		}
	}
