/*
 * Copyright 2010 Silvio Meier 
 *
 * 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 com.blogspot.m3g4h4ard;

/**
 * This class provides a checking procedure for the Swiss Social Security number 
 * (SV no., previously called AHV no.).
 * 
 * @author Silvio Meier
 */
public class SSNNumberChecker {
	/**
	 * Indicates no error.
	 */
	public static int ERR_NO = 0;
	
	/**
	 * Indicates an error in the length of the number.
	 */
	public static int ERR_LENGTH = 1;
	
	/**
	 * Indicates an error in the checksum of the number.
	 */
	public static int ERR_WRONG_CHECKSUM = 2;
	
	/**
	 * Indicates a wrong country code.
	 */
	public static int ERR_WRONG_COUNTRY = 3;
	
	/**
	 * Indicates that the number contains non digits in the number blocks.
	 */
	public static int ERR_CONTAINS_NON_DIGITS = 4;
	
	
	/**
	 * Main method which checks the number for correctness.
	 * The returned integer indicates any occurred error. In order to 
	 * get not error, the number must consist of four blocks where the 
	 * first block has three, the second and the third block four and the
	 * fourth block two digits. The last digit is the EAN13 checksum.
	 * The blocks must be separated by dots. 
	 * 
	 * @param number The SSN as a string.
	 * @param countryCode The country code as string. If an empty string or null is given,
	 *        the number is not checked for the country.
	 * @return Returns a code indicating an error in the number. Possible values are
	 *         {@link #ERR_NO}, {@link #ERR_LENGTH}, {@link #ERR_CONTAINS_NON_DIGITS},
	 *         {@link #ERR_WRONG_COUNTRY} 
	 */
	public static int checkSSN(String number, String countryCode) {
		int retVal = ERR_NO;
		if (number != null){ 
			String[] blocks = getBlocks(number);
			if (!checkBlocks(blocks)) {
				retVal = ERR_LENGTH;
			} else if (countryCode != null && !countryCode.equals("") && !countryCode.equals(blocks[0])) {
				retVal = ERR_WRONG_COUNTRY;
			} else if (!checkNumbers(blocks)) {
				retVal = ERR_CONTAINS_NON_DIGITS;
			} else {
				// get checksum
				int checksum = getChecksum(getPreparedNo(blocks));
				String givenChksm = blocks[3].substring(1,2);
				if (!givenChksm.equals(Integer.toString(checksum))) {
					retVal = ERR_WRONG_CHECKSUM;
				}
			}
		}else {
			retVal = ERR_LENGTH;
		}
		return retVal;
	}
	
	/**
	 * Checks whether the blocks are correct in length or not.
	 * @param blocks The separated number blocks of the SV no.
	 * @return Returns true, if the blocks have a correct length.
	 */
	public static boolean checkBlocks(String[] blocks) {
		boolean retVal = true;
		if (blocks[0] == null) {
			retVal = false;
		} else if (blocks[1] == null) {
			retVal = false;
		} else if (blocks[2] == null) {
			retVal = false;
		} else if (blocks[3] == null) {
			retVal = false;
		} else if (blocks[0].length() != 3) {
			retVal = false;
		} else if (blocks[1].length() != 4) {
			retVal = false;
		} else if (blocks[2].length() != 4) {
			retVal = false;
		} else if (blocks[3].length() != 2) {
			retVal = false;
		}
		
		return retVal;		
	}
	
	/**
	 * Checks each number block whether it consists of digits or not.
	 * @param blocks The separated number blocks of the SV number.
	 * @return Returns false, if one or more number blocks do not 
	 *         contain digits.
	 */
	public static boolean checkNumbers(String[] blocks) {
		boolean retVal = true;
		for (int i = 0; i < blocks.length; i++) {
			for (int j = 0; j < blocks[i].length(); j++) {
				if (!"0123456789".contains(blocks[i].substring(j, j+ 1))) {
					retVal = false;
					break;
				}
			}
		}
		return retVal;
	}
	
	/**
	 * Returns a string of the first 12 digits in the SV number. This
	 * string can be used to calculate the check sum.
	 * @param blocks The number blocks of the SV number.
	 * @return Returns the prepared number as string which is used 
	 * to calculate the check sum.
	 */
	public static String getPreparedNo(String[] blocks) {
		String preparedNo = blocks[0] + blocks[1] + blocks[2] + blocks[3].substring(0, 1);		
		return preparedNo;		
	}
	
	/**
	 * Divides the SV no. string into for distinct blocks of digits.
	 * The blocks are returned as array of string consisting of four elements.
	 * Note blocks that are not well-formed (properly separated with dots from
	 * the other elements) are not contained in the array. Instead a null value is 
	 * given.
	 * 
	 * @param number The Swiss SSN (SV no.) as String. 
	 * @return Returns the given SV number as blocks of Strings.
	 */
	public static String[] getBlocks(String number) {
		String[] retVal = new String[4];
		
		if (number.lastIndexOf('.') >0) {
			if (number.charAt(3) == '.') {
				retVal[0] = number.substring(0,3);
			} 
			if (number.length() > 9 && number.charAt(8)=='.') {
				retVal[1] = number.substring(4,8);
			} 
			if (number.length() > 13 && number.charAt(13)=='.') {
				retVal[2] = number.substring(9, 13);
				retVal[3] = number.substring(14, number.length());
			}
		} 
		return retVal;		
	}
	
	/**
	 * Returns the EAN13 checksum of the preprocessed number string.
	 * @param preprocessedNumber The preprocessed number string which contains the first
	 * 12 digits of the SV number without dots.
	 * @return Returns an integer between 0 and 9 which is the computed EAN13 checksum.
	 */
	public static int getChecksum(String preprocessedNumber) {
		int retVal = 0;
		int sum = 0;
		int inverseIndex=0;
		for (int i = preprocessedNumber.length() - 1; i >= 0; i--) {
			int no = Integer.parseInt(preprocessedNumber.substring(i, i+ 1)) * ((inverseIndex % 2==0)?3:1);
			sum = sum + no;
			inverseIndex++;
		}
		
		// in the case the sum is divisible without remainder by ten, the sum is also equals to the nextTen
		int nextTen = sum;
		
		// if the sum is not an integer divisible by ten without remainder
		if (sum / 10 * 10 != sum) {
			// compute the nextTen, i.e. the next higher number from sum which is 
			// integer divisible by ten without remainder 
			nextTen = ((sum / 10) + 1) * 10;
		}
		
		retVal = nextTen - sum;
		return retVal;
	}
	
	/**
	 * Test cases.
	 * 
	 * @param args The arguments.
	 */
	public static void main(String[] args) {
		System.out.println(getChecksum("756888899992"));
		System.out.println(getChecksum("756123456789"));
		System.out.println(getChecksum("756987654321"));
		System.out.println(getChecksum("756333344442"));
		System.out.println(getChecksum("756111122223"));
		System.out.println(getChecksum("756888894837"));
		System.out.println(getChecksum("756743628393"));
		System.out.println(getChecksum("756736438249"));
		System.out.println(getChecksum("756095857734"));
		System.out.println(getChecksum("756123746585"));
		System.out.println(getChecksum("756985743623"));
		System.out.println(getChecksum("756665324244"));
		System.out.println(getChecksum("756009548736"));
		System.out.println(getChecksum("756945847736"));
		System.out.println(getChecksum("756132436478"));
		System.out.println(getChecksum("756847362652"));
		System.out.println("***");
		System.out.println(checkSSN("666.9217.0769.83", ""));
		System.out.println(checkSSN("666.9217.0769.83", null));
		System.out.println(checkSSN("666.9217.0769.83", "756"));
		System.out.println(checkSSN("666.9217.0769.83", "666"));
		System.out.println(checkSSN("756.9217.0769.85", null));
		System.out.println(checkSSN("756.6993.1624.68", null));
		System.out.println(checkSSN("756.8217.0769.85", null));
		System.out.println(checkSSN("756.6893.1624.68", null));
		System.out.println(checkSSN("756.9217.0769.85", null));
		System.out.println(checkSSN("75a.6993.1624.68", null));
		System.out.println(checkSSN("756.9217", null));
		System.out.println(checkSSN("", null));
		System.out.println(checkSSN("aaaaaaaaaaaaaaaa", null));
		System.out.println("****************");
		System.out.println(checkSSN("756.8888.9999.20","756"));
		System.out.println(checkSSN("756.1234.5678.97","756"));
		System.out.println(checkSSN("756.9876.5432.17","756"));
		System.out.println(checkSSN("756.3333.4444.20","756"));
		System.out.println(checkSSN("756.1111.2222.39","756"));
		System.out.println(checkSSN("756.8888.9483.79","756"));
		System.out.println(checkSSN("756.7436.2839.31","756"));
		System.out.println(checkSSN("756.7364.3824.92","756"));
		System.out.println(checkSSN("756.0958.5773.42","756"));
		System.out.println(checkSSN("756.1237.4658.55","756"));
		System.out.println(checkSSN("756.9857.4362.31","756"));
		System.out.println(checkSSN("756.6653.2424.48","756"));
		System.out.println(checkSSN("756.0095.4873.68","756"));
		System.out.println(checkSSN("756.9458.4773.67","756"));
		System.out.println(checkSSN("756.1324.3647.88","756"));
		System.out.println(checkSSN("756.8473.6265.21","756"));
		
	}
}

