/*
 * Created on Feb 4, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package gov.va.med.esr.common.util;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.apache.commons.lang.StringUtils;

import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.person.Name;

/**
 * @author DNS   TSAIG
 * 
 * Utilities for various letter formatter
 */
public class LetterFormatHelper {
	public static final String BARCODE_POA_TOKEN = "-@barCode@-";
	
	private static final String FOUR_SPACES = "    ";

	private static final String FIVE_SPACES = "     ";

	private static final String AAC_FORM_PREFIX = "F";

	private static final String DATE_SEPARATOR = "/";

	private static final String SSN_SEPARATOR = "-";

	private static final String US_STATE_CODE = "US-";

	private static final char DATE_PADDING = '0';


	/**
	 * Add days to starting Date and return a string of specified
	 * SimpleDateFormat
	 * 
	 * @param startDate
	 *            A starting Date
	 * @param days
	 *            Number of days to be added, can be negtive
	 * @param sdf
	 *            A SimpleDateFormat for the returing date string
	 * 
	 * @return String The resulting date string in the SimpleDateFormat provided
	 */
	public static String addDays(Date startDate, int days, SimpleDateFormat sdf) {
		return sdf.format(addDays(startDate, days));
	}

	/**
	 * Add days to starting Date and return the resulting new Date
	 * 
	 * @param startDate
	 *            A starting Date
	 * @param days
	 *            Number of days to be added, can be negtive
	 * 
	 * @return Date The resulting date string in the SimpleDateFormat provided
	 */
	public static Date addDays(Date startDate, int days) {
		if (startDate == null)
			return null;

		if (days == 0)
			return startDate;

		Calendar cal = new GregorianCalendar();
		cal.setTime(startDate);
		cal.add(Calendar.DATE, days);
		return cal.getTime();
	}

	/**
	 * Convert BigDecimal into string of US dollar amount with dicimalDigits
	 * places and roundingMode. e.g. display US currency with 2 decimal digits,
	 * "$1,234,567.00", "-$1,234.12"
	 * 
	 * Supported rounding Mode: BigDecimal.ROUND_CEILING - Rounding mode to
	 * round towards positive infinity. BigDecimal.ROUND_DOWN - Rounding mode to
	 * round towards zero. BigDecimal.ROUND_FLOOR - Rounding mode to round
	 * towards negative infinity. BigDecimal.ROUND_HALF_DOWN - Rounding mode to
	 * round towards "nearest neighbor" unless both neighbors are equidistant,
	 * in which case round down. BigDecimal.ROUND_HALF_EVEN - Rounding mode to
	 * round towards the "nearest neighbor" unless both neighbors are
	 * equidistant, in which case, round towards the even neighbor.
	 * BigDecimal.ROUND_HALF_UP - Rounding mode to round towards "nearest
	 * neighbor" unless both neighbors are equidistant, in which case round up.
	 * BigDecimal.ROUND_UNNECESSARY - Rounding mode to assert that the requested
	 * operation has an exact result, hence no rounding is necessary.
	 * BigDecimal.ROUND_UP - Rounding mode to round away from zero.
	 * 
	 * @param amt
	 *            A Bigdecimal amount
	 * @param decimalDigits
	 *            Scale, how many dicimal places
	 * @param roundingMode
	 *            Rounding mode
	 * @return String US Dollar Amount as String
	 */
	public static String ToDollarAmount(BigDecimal amt, int decimalDigits,
			int roundingMode) {
		// display US currency with decimal digits, e.g. "$1,234,567.00",
		// "-$1,234.12"

		if (amt == null)
			return "";

		String strNumber = amt.setScale(decimalDigits, roundingMode).toString();

		boolean isNegtive = false;
		if (strNumber.startsWith("-")) {
			isNegtive = true;
			strNumber = strNumber.substring(1); // remove negtive sign
		}

		int index = strNumber.indexOf(".");

		// detect decimal if any, set the index for adding ","
		if (index > 0)
			index = strNumber.length() - 3 - index;
		else
			index = strNumber.length() - 3;

		while (index > 0) {
			// adding "," every three digits
			strNumber = strNumber.substring(0, index) + ","
					+ strNumber.substring(index);
			index -= 3;
		}

		strNumber = "$" + strNumber; // add the dollar sign

		if (isNegtive)
			strNumber = "-" + strNumber; // add back the negtive sign

		return strNumber;
	}

	/**
	 * Left-aligns the characters in this string, padding on the right with
	 * spaces, for a specified total length
	 * 
	 * @param str
	 *            The string to pad
	 * @param length
	 *            Total length
	 * 
	 * @return String The resulting padded string in the length specified
	 */
	public static String padRight(String str, int length) {
		return padRight(str, length, ' ');
	}

	private static String getRepeatedChar(int len, char ch) {
		StringBuffer strBuf = new StringBuffer(ch);
		if (len > 0) {
			for (int i = 0; i < len; i++) {
				strBuf.append(ch);
			}
		}
		return strBuf.toString();
	}

	/**
	 * Left-aligns the characters in this string, padding on the right with a
	 * specified Unicode character, for a specified total length
	 * 
	 * @param length
	 *            Total length
	 * @param ch
	 *            Unicode character to pad with
	 * 
	 * @return String The resulting padded string in the length specified
	 */
	public static String padRight(String str, int length, char ch) {
		if (str == null || str.length() == 0)
			return getRepeatedChar(length, ch);

		int len = str.length();
		if (len >= length)
			return str;

		return str + getRepeatedChar(length - len, ch);
	}

	/**
	 * Right-aligns the characters in this string, padding on the left with
	 * spaces for a specified total length
	 * 
	 * @param length
	 *            Total length
	 * 
	 * @return String The resulting padded string in the length specified
	 */
	public static String padLeft(String str, int length) {
		return padLeft(str, length, ' ');
	}

	/**
	 * Right-aligns the characters in this string, padding on the left with a
	 * specified Unicode character, for a specified total length
	 * 
	 * @param length
	 *            Total length
	 * @param ch
	 *            Unicode character to pad with
	 * 
	 * @return String The resulting padded string in the length specified
	 */
	public static String padLeft(String str, int length, char ch) {
		if (str == null || str.length() == 0)
			return getRepeatedChar(length, ch);

		int len = str.length();
		if (len >= length)
			return str;

		return getRepeatedChar(length - len, ch) + str;
	}

	/***************************************************************************
	 * AAC letter specific formatings *
	 **************************************************************************/

	// --------------------------------------------------------------------
	// Static method(s)
	// --------------------------------------------------------------------
	public static String toAACFormNumber(String formNumber) {
		if (formNumber == null || formNumber.length() == 0)
			return FIVE_SPACES; // 5 spaces
		
		/*
		 * Left-aligns the characters in this string, padding on the right with
		 * spaces or a specified Unicode character, for a specified total length
		 */
		return LetterFormatHelper.padRight(formNumber.replaceAll(
				ComLetterTemplateType.FORM_NUMBER_PREFIX, AAC_FORM_PREFIX), 5);
	}

	public static String toAACVerNumber(String aacVerNum) {
		if (aacVerNum == null || aacVerNum.length() == 0)
			return FOUR_SPACES; // 4 spaces

		// int startIndex = aacVerNum.length() -5;
		// if (startIndex < 0)
		// startIndex = 0;
		//
		// return aacVerNum.Substring(startIndex).PadRight(5);
		/*
		 * Right-aligns the characters in this instance, padding on the left
		 * with spaces or a specified Unicode character for a specified total
		 * length.
		 */
		return LetterFormatHelper.padLeft(aacVerNum, 4, '0');

	}

	// / <summary>
	// / Take SSN in 123-45-6789 format to AAC format '12345678 '.
	// / Returns fix lenght of 10 chars. If a 9-digit valid SSN, right
	// / pad with a space. If a pseudo ssn, which always ends with a 'P'.
	// / </summary>
	// /
	// / <returns>void</returns>
	public static String toAACSsn(String ssn) {
		/*
		 * if (ssn == null || ssn.length() == 0) return TEN_SPACES; //10 spaces
		 */
		// old format, R1/R2 only
		if (ssn == null || ssn.length() == 0)
			return "         "; // 9 spaces

		// 123-45-6789 -> '123456789 '
		// return LetterFormatHelper.padRight(ssn.replaceAll(SSN_SEPARATOR, ""),
		// 10);

		// old format, R1/R2 only
		return ssn.replaceAll(SSN_SEPARATOR, "");
	}

	// / <summary>
	// / Convert to AAC date format: MMDDYYYY
	// / It takes two input formats: MM/DD/YYYY and YYYYMMDD.
	// / The default for empty date is empty String "";
	// / and unknow format is return as it is.
	// / examples: 11/1/2003 -> 11012003, 19320509 -> 05091932
	// / </summary>
	// /
	// / <returns>void</returns>
	public static String toAACDate(String dtString) {

		if (dtString == null || dtString.length() == 0)
			return "";

		String[] dates = dtString.split(DATE_SEPARATOR);

		if (dates.length == 1 && dtString.length() == 8) {
			// from yyyymmdd to mmddyyyy
			return dtString.substring(4) + dtString.substring(0, 4);
		} else {
			if (dates.length == 3) {
				// from mm/dd/yyyy to MMddyyyy
				return LetterFormatHelper.padLeft(dates[0], 2, DATE_PADDING) + // MM
						LetterFormatHelper.padLeft(dates[1], 2, DATE_PADDING) + // DD
						LetterFormatHelper.padLeft(dates[2], 4, DATE_PADDING); // YYYY
			}
		}

		return dtString; // unknow format, return as it is
	}

	public static String toAACDate(Date dTime) {

		if (dTime == null)
			return "";

		SimpleDateFormat aacDateFormat = new java.text.SimpleDateFormat("MMddyyyy");		
		return aacDateFormat.format(dTime); // MMddyyyy
	}

	public static String toAACState(String state) {
		if (state == null || state.length() == 0)
			return "  "; // 2 spaces

		return state.replaceAll(US_STATE_CODE, "");
	}

	public static String getString(String str)
	{
	    return str == null ? StringUtils.EMPTY : str;
	}
	
	
	/* These are methods useful for determining the mailing address information for the envelope */

	public static String getMailingAddressLine2and3(Address address) {
		String addressLine2 = address.getLine2();
		String addressLine3 = address.getLine3();
		String combinedAddressLine2 = addressLine2;
		if(addressLine2 != null && addressLine3 != null)
			combinedAddressLine2 += " " + addressLine3;
		else if(addressLine2 == null && addressLine3 != null)
			combinedAddressLine2 = addressLine3;
		
		return getString(combinedAddressLine2);		
	}
	
	public static String getMailingAddressState(Address address) {
		String val = null;
		if(address.isUSAddress())
			val =address.getState();
		
		return val;		
	}
	
	public static String getMailingAddressZipOrPostal(Address address) {
		String val = null;
		
		if(address.isUSAddress()) {
			if(StringUtils.isNotBlank(address.getZipCode())) {
				if(StringUtils.isNotBlank(address.getZipPlus4()))
					val = address.getZipCode() + "-" + address.getZipPlus4();
				else
					val = address.getZipCode();
			}
		} else {
			val = address.getPostalCode();
		}
		
		return val;
	}
	
	public static String getMailingAddressCountyOrProvince(Address address) {
		String val = null;
		
		if(address.isUSAddress())
			 val = address.getCounty();
		else
			val = address.getProvince();
		
		return val;
	}
	
	public static String getMailingAddressCountry(Address address) {
		// country preference is postal name
		return address.getCountryObject() != null && StringUtils.isNotBlank(address.getCountryObject().getPostalName()) ?
				address.getCountryObject().getPostalName() : address.getCountry();
	}
	
	public static String getPOAName(Association poa) {
		String poaName = null;
		// check organization name first
		if(poa.getOrganizationName() != null)
			poaName = poa.getOrganizationName();
		else if(poa.getRepresentativeName() != null)
			poaName = poa.getRepresentativeName().getFormattedName(Name.NAME_FORMAT_FIRST_SPACE_LAST);
		
		return getString(poaName);
	}
	
	public static String formatBarcode(String barcode) {
		return padRight(barcode, 21, ' ');		
	}
}
