/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * 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.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.patient.impl;

import org.openmrs.patient.IdentifierValidator;
import org.openmrs.patient.UnallowedIdentifierException;

/**
 * An abstract class for identifier validators for identifiers that have a hyphen before a single
 * check digit. Identifiers can not be null, must have at least one character before the check
 * digit, and can not contain white space. Integers 0-9 or characters A-J are allowed for the check
 * digit. A character is used by default.
 */
public abstract class BaseHyphenatedIdentifierValidator implements IdentifierValidator {
	
	protected abstract int getCheckDigit(String undecoratedIdentifier);
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getAllowedCharacters()
	 */
	public abstract String getAllowedCharacters();
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getName()
	 */
	public abstract String getName();
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getValidIdentifier(java.lang.String)
	 */
	public String getValidIdentifier(String undecoratedIdentifier) throws UnallowedIdentifierException {
		
		checkAllowedIdentifier(undecoratedIdentifier);
		
		char checkLetter = convertCheckDigitToChar(getCheckDigit(undecoratedIdentifier));
		
		String result = undecoratedIdentifier + "-" + checkLetter;
		return result;
	}
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#isValid(java.lang.String)
	 */
	public boolean isValid(String identifier) throws UnallowedIdentifierException {
		
		if (identifier.indexOf("-") < 1) {
			throw new UnallowedIdentifierException("Identifier must contain something besides the check digit.");
		}
		
		String idWithoutCheckDigit = identifier.substring(0, identifier.indexOf("-"));
		
		checkAllowedIdentifier(idWithoutCheckDigit);
		
		int computedCheckDigit = getCheckDigit(idWithoutCheckDigit);
		
		String checkDigit = identifier.substring(identifier.indexOf("-") + 1, identifier.length());
		
		if (checkDigit.length() != 1)
			throw new UnallowedIdentifierException("Identifier must have a check digit of length 1.");
		
		if (checkDigit.equalsIgnoreCase("A"))
			checkDigit = "0";
		if (checkDigit.equalsIgnoreCase("B"))
			checkDigit = "1";
		if (checkDigit.equalsIgnoreCase("C"))
			checkDigit = "2";
		if (checkDigit.equalsIgnoreCase("D"))
			checkDigit = "3";
		if (checkDigit.equalsIgnoreCase("E"))
			checkDigit = "4";
		if (checkDigit.equalsIgnoreCase("F"))
			checkDigit = "5";
		if (checkDigit.equalsIgnoreCase("G"))
			checkDigit = "6";
		if (checkDigit.equalsIgnoreCase("H"))
			checkDigit = "7";
		if (checkDigit.equalsIgnoreCase("I"))
			checkDigit = "8";
		if (checkDigit.equalsIgnoreCase("J"))
			checkDigit = "9";
		
		int givenCheckDigit = 10;
		
		try {
			givenCheckDigit = Integer.valueOf(checkDigit);
		}
		catch (NumberFormatException e) {
			throw new UnallowedIdentifierException(
			        "Check digit must either be a character from A to J or a single digit integer.");
		}
		
		return (computedCheckDigit == givenCheckDigit);
	}
	
	/**
	 * @param undecoratedIdentifier
	 * @throws UnallowedIdentifierException if identifier contains unallowed characters or is
	 *             otherwise invalid.
	 */
	protected void checkAllowedIdentifier(String undecoratedIdentifier) throws UnallowedIdentifierException {
		if (undecoratedIdentifier == null)
			throw new UnallowedIdentifierException("Identifier can not be null.");
		if (undecoratedIdentifier.length() == 0)
			throw new UnallowedIdentifierException("Identifier must contain at least one character.");
		if (undecoratedIdentifier.contains(" "))
			throw new UnallowedIdentifierException("Identifier may not contain white space.");
		for (int i = 0; i < undecoratedIdentifier.length(); i++) {
			if (getAllowedCharacters().indexOf(undecoratedIdentifier.charAt(i)) == -1)
				throw new UnallowedIdentifierException("\"" + undecoratedIdentifier.charAt(i)
				        + "\" is an invalid character.");
		}
	}
	
	/**
	 * Not doing this with ASCII math to be extra careful.
	 * 
	 * @param checkDigit
	 * @return
	 */
	private char convertCheckDigitToChar(int checkDigit) {
		switch (checkDigit) {
			case 0:
				return 'A';
			case 1:
				return 'B';
			case 2:
				return 'C';
			case 3:
				return 'D';
			case 4:
				return 'E';
			case 5:
				return 'F';
			case 6:
				return 'G';
			case 7:
				return 'H';
			case 8:
				return 'I';
			case 9:
				return 'J';
			default:
				return 'X';
		}
	}
}
