package gov.va.med.esr.service.impl;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.hibernate.Hibernate;
import org.springframework.transaction.annotation.Transactional;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.ee.IRS1095B;
import gov.va.med.esr.common.model.ee.MECPeriod;
import gov.va.med.esr.common.model.lookup.TransmissionStatus;
import gov.va.med.esr.common.model.messaging.IrsTransmissionLogData;
import gov.va.med.esr.common.model.messaging.IrsTransmissionLogDetailEntry;
import gov.va.med.esr.common.model.messaging.IrsTransmissionLogEntry;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKeyImpl;
import gov.va.med.esr.common.persistent.messaging.IrsTransmissionLogDetailEntryDAO;
import gov.va.med.esr.common.persistent.messaging.IrsTransmissionLogEntryDAO;
import gov.va.med.esr.common.persistent.person.MECPeriodDAO;
import gov.va.med.esr.service.CommsEmailBulletinService;
import gov.va.med.esr.service.IRSTransmissionService;
import gov.va.med.esr.service.IRSWebServiceDelegate;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.SystemParameterService;
import gov.va.med.esr.service.UnknownLookupCodeException;
import gov.va.med.esr.service.UnknownLookupTypeException;
import gov.va.med.esr.service.trigger.BulletinTrigger;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;


/**
 * common service to handle all interactions between the system and IRS service client and associated audit logs
 * system should never invoke the irs webSvc client delegate directly
 * always wire this common service in order to ensure transaction support, performance monitor, and audit trail
 **/

public class IRSTransmissionServiceImpl extends AbstractComponent implements IRSTransmissionService {


	private static final long serialVersionUID = 8534341803323475694L;
	private static final String SUBMIT_ORIGINAL = "O";
	private static final String SUBMIT_CORRECTION = "C";
	private static final String SUBMIT_REPLACEMENT = "R";
	private static final String SUBMIT_REJECTED = "Rejected";
	private static final String VETERANS_TYPE = "V";
	private static final String BENE_TYPE = "B";
	private static final String IRS_ACK_EXPIRE = "IRS Ack Expiration";
	private static final String POSITION = "1";
	private static final String APP_ID = "SYS12";
	private static final String TYPE_CODE = "T";
	private static final String DELIM = "|";
	private static final String NO_RECEIPT = "No Receipt ID Provided in Submission Response";
	private static final String NO_ACK = "Acknowledgement of IRS Processing Never Received";
	private SystemParameterService systemParameterService = null;
	private IRSWebServiceDelegate irsServiceDelegate = null;
	private LookupService lookupService = null;
	private IrsTransmissionLogEntryDAO irsTransmissionLogEntryDAO = null;
	private IrsTransmissionLogDetailEntryDAO irsTransmissionLogDetailEntryDAO = null;
	private MECPeriodDAO mecPeriodDAO = null;
	private PersonService personService = null;
	private CommsEmailBulletinService bulletinService = null;
	private String transmissionControlCode = null;


	/**
	 * Main entry for submitting Original batch entries or single Correction entries to the IRS
     * @param batch List of 1095 records to include in single submit batch
     * @param batchType V=veteran, B=beneficiary
     * @param correctionInd O=original,C=correction,R=replacement
     * @param taxYear
     *
     */
	public void sendSubmit(ArrayList<IRS1095B> batch, String batchType, String correctionInd, String taxYear) throws ServiceException {

		//possible taxYear not provided by CPE in some cases and should be defaulted to current processing year
		if (taxYear == null) {
			Calendar prevYear = Calendar.getInstance();
		    prevYear.add(Calendar.YEAR, -1);
		    taxYear = Integer.toString(prevYear.get(Calendar.YEAR));
		}

		//Corrections or Replacements from the file data job
		//Corrections from the UI
		try {
			if (correctionInd == null || SUBMIT_ORIGINAL.equalsIgnoreCase(correctionInd)) {
				processOriginal(batch, batchType, taxYear);
			} else if (SUBMIT_CORRECTION.equalsIgnoreCase(correctionInd) || SUBMIT_REPLACEMENT.equalsIgnoreCase(correctionInd)) {
				processCorrectReplace(batch, batchType, taxYear, correctionInd);
			} else {
				logger.error("Failed to submit IRS batch transmission due to invalid Correction Indicator");
			}
		} catch (Exception ex) {
			logger.error("ERROR in IRS Submission for batchType:" + batchType + " ,size: " + batch.size() + " , correctionInd: " + correctionInd + ex);
			ex.printStackTrace();
		}
	}

	public void submitCustomFile(String xmlFile, String correctionInd) {

		IrsTransmissionLogDetailEntry cur = new IrsTransmissionLogDetailEntry();

		try {

			ArrayList<IRS1095B> listing = new ArrayList();
			listing = this.getIrsServiceDelegate().convertAllEntries(xmlFile);

			Calendar prevYear = Calendar.getInstance();
		    prevYear.add(Calendar.YEAR, -1);
		    String taxYear = Integer.toString(prevYear.get(Calendar.YEAR));


			IrsTransmissionLogData dataFile = new IrsTransmissionLogData();
			dataFile.setRequestData(xmlFile);

			IrsTransmissionLogEntry entry = buildLogEntry(listing, dataFile, "M", taxYear, correctionInd, null);

			this.getIrsTransmissionLogEntryDAO().insertObject(entry);
			this.getIrsTransmissionLogEntryDAO().flush();
			handleTransmit(entry, false, null);

		} catch (DAOException e) {
			logger.error("Error processing IRS Manual File entry " + e);
		} catch (ServiceException ex) {
			logger.error("Error processing IRS Manual File entry" + ex);
		}
	}


	/**
	 * Main entry for requesting acknowledgments on all pending/processing submissions
     * @param receiptId the receiptId provided by the IRS when the original transmission was submitted
     *
     */
	@Transactional
	public void requestAcknowledgment(String receiptId) throws ServiceException {

		logger.debug("requesting IRS ack for receiptId:" + receiptId);

		int batchSize = 0;

		try {
			IrsTransmissionLogEntry original = this.getIrsTransmissionLogEntryDAO().getByReceiptId(receiptId);

			if (original != null) {
				Hibernate.initialize(original.getIrsTransmissionLogDetails());
				Hibernate.initialize(original.getIrsTransmissionLogData());

				this.getIrsServiceDelegate().requestProcessingStatus(original);

				if (isExpired(original)) {

					batchSize = original.getIrsTransmissionLogDetails().size();

					original.setTransmissionStatus((TransmissionStatus)this.lookupService.getByCode(
						TransmissionStatus.class, TransmissionStatus.EXPIRED.getCode()));
					original.setErrorDescription(NO_ACK);

					sendNotifyAckExpired(batchSize);
				}

				this.getIrsTransmissionLogEntryDAO().saveObject(original);
				this.getIrsTransmissionLogEntryDAO().flush();
			} else {
				logger.error("Could not find ReceiptID for requested IRS ack:" + receiptId);
			}

		} catch (DAOException e) {
			logger.error("Failed to retrieve transmission by receipt Id for receipt:" + receiptId + ", message: " + e);
		} catch (ServiceException ex) {
			logger.error("Service Excpetion during IRS acknowledgement request for receipt:" + receiptId + " , message: " + ex);
		}

	}

	/*
	 * checks if transmission is still processing after specified expiration period
	 */
	private boolean isExpired(IrsTransmissionLogEntry original) {
		boolean expired  = false;
		try {

			if (original.getTransmissionStatus().getCode().equals(TransmissionStatus.PROCESSING.getCode())) {

				int expirationHours = Integer.valueOf(this.getSystemParameterService().getByName(IRS_ACK_EXPIRE).getValue());

				Calendar expirationDate = Calendar.getInstance();
				expirationDate.setTime(original.getTransmissionDate());
			    expirationDate.add(Calendar.HOUR, expirationHours);

			    Calendar now = Calendar.getInstance();

			    if (now.after(expirationDate)) {
			    	expired = true;
			    }
			}
		} catch (ServiceException e) {
			logger.error("Exception checking log entry expiration: " + e);
		}

		return expired;
	}


	/**
	 * Main entry for re-processing a failed/expired/or rejected batch entry
	 * most cases of failed batch will not have a receiptId
     * @param identifier the PK of the original batch submission log entry
     *
     */
	@Transactional
	public void retransmitFailedSubmission(BigDecimal identifier) throws ServiceException {

		try {
			IrsTransmissionLogEntry transmission = this.getIrsTransmissionLogEntryDAO().getById(identifier);
			Hibernate.initialize(transmission.getIrsTransmissionLogDetails());
			Hibernate.initialize(transmission.getIrsTransmissionLogData());


			if (transmission != null) {
				//reset log entry to new processing so ack scheduler will automatically request ack
				transmission.setAckDate(null);
				transmission.setAckId(null);
				transmission.setErrorDescription(null);
				transmission.setTransmissionDate(new Date());
				transmission.setTransmissionStatus((TransmissionStatus) this.lookupService.getByCode(
						TransmissionStatus.class, TransmissionStatus.PROCESSING.getCode()));

				//if has receipt id then send as replacement
				if (transmission.getReceiptId() != null && transmission.getReceiptId().length() > 0) {
					transmission.setCorrectionInd(SUBMIT_REPLACEMENT);
					this.getIrsTransmissionLogEntryDAO().saveObject(transmission);
					handleTransmit(transmission,  true, transmission.getReceiptId());
				} else {
					transmission.setCorrectionInd(SUBMIT_ORIGINAL);
					this.getIrsTransmissionLogEntryDAO().saveObject(transmission);
					handleTransmit(transmission, true, null);
				}
			}

		} catch (DAOException e) {
			logger.error("Unexpected Error in IRS batch retransmission:" + e.getMessage());
		}

	}

	/**
	 * Main entry for re-processing a rejected single person entry when the batch transmission
	 * was successful and error data file is returned for specific entries
     * @param identifier the PK of the original detail log entry that was rejected
     */

	@Transactional
	public void retransmitFailedPersonSubmission(BigDecimal identifier) throws ServiceException {

		try {
			//get original id from the reject to submit with the replacement
			IrsTransmissionLogDetailEntry prevTransmission = this.getIrsTransmissionLogDetailEntryDAO().getById(identifier);

			if (prevTransmission != null) {

				Hibernate.initialize(prevTransmission.getBatchTransmissionEntry().getIrsTransmissionLogData());

				String originalReceipt = getUniqueId(prevTransmission);

				//get original mec period from original
				IRS1095B prevSubmission = this.getIrsServiceDelegate().convertSingleEntry(prevTransmission.getBatchTransmissionEntry().
						getIrsTransmissionLogData().iterator().next(), prevTransmission.getRecordSequence());

				if (prevSubmission != null) {

					//construct with current traits and address of existing person
					Person onFile = this.getPersonService().getPerson(CommonEntityKeyFactory.createPersonIdEntityKey(prevTransmission.getPersonId()));
					IRS1095B newSubmission = new IRS1095B(onFile);

					newSubmission.setMecPeriod(prevSubmission.getMecPeriod());

					ArrayList<IRS1095B> ls = new ArrayList<IRS1095B>();
					ArrayList<IRS1095B> errorList = new ArrayList<IRS1095B>();
					ls.add(newSubmission);

					//create the payload mtom data file
					IrsTransmissionLogData dataFile = this.getIrsServiceDelegate().buildRequestDataFile(ls, SUBMIT_CORRECTION,
							prevTransmission.getBatchTransmissionEntry().getTaxYear(), originalReceipt, errorList);

					IrsTransmissionLogEntry entry =  buildLogEntry(ls, dataFile, VETERANS_TYPE, prevTransmission.getBatchTransmissionEntry().getTaxYear(), SUBMIT_CORRECTION, errorList);

					this.getIrsTransmissionLogEntryDAO().insertObject(entry);
					this.getIrsTransmissionLogEntryDAO().flush();
					handleTransmit(entry,  false, originalReceipt);
				} else {
					logger.error("No previous Entry found to retransmit detail entry:" + identifier.toString());
				}
			}



		} catch (DAOException e) {
			logger.error("Unexepected Error in Retransmit IRS Person Record:" + e.getMessage());
		}
	}

	/*
	 * Builds the unique id string from the original receiptId, batch number, and submission sequence
	 * identifies to the IRS which original entry you are replacing or correcting
	 */
	private String getUniqueId(IrsTransmissionLogDetailEntry transmission) {
		//1095B-15|1|1
		int sequence = transmission.getRecordSequence();
		String receiptId = transmission.getBatchTransmissionEntry().getReceiptId();
		//String originalReceipt = receiptId + ":" + APP_ID + ":" +  this.getTransmissionControlCode() + "::" + TYPE_CODE + DELIM + POSITION + DELIM + Integer.toString(sequence);
		String originalReceipt = receiptId + DELIM + POSITION + DELIM + Integer.toString(sequence);
		return originalReceipt;
	}


	/*
	 * Long entry wrapper to commit all request data prior to transmittal
	 */
	private void processOriginal(ArrayList<IRS1095B> batch, String batchType, String taxYear) throws ServiceException {

		IrsTransmissionLogDetailEntry cur = new IrsTransmissionLogDetailEntry();
		ArrayList<IRS1095B> errorList = new ArrayList<IRS1095B>();

		try {
				//create the payload mtom data file
				IrsTransmissionLogData dataFile = this.getIrsServiceDelegate().buildRequestDataFile(batch, SUBMIT_ORIGINAL, taxYear, null, errorList);
				IrsTransmissionLogEntry entry = buildLogEntry(batch, dataFile, batchType, taxYear, SUBMIT_ORIGINAL, errorList);
				this.getIrsTransmissionLogEntryDAO().insertObject(entry);
				this.getIrsTransmissionLogEntryDAO().flush();
				handleTransmit(entry, false, null);

		} catch (DAOException e) {
			logger.error("Error processing IRS Correction entry " + e);
		} catch (ServiceException ex) {
			logger.error("Error processing IRS Correction entry" + ex);
		}

	}

	/*
	 * builds the log entry object hierarchy for cascade insert
	 */
	private IrsTransmissionLogEntry buildLogEntry(ArrayList<IRS1095B> batch, IrsTransmissionLogData dataFile , String batchType, String taxYear, String correctionInd, ArrayList<IRS1095B> errorList) throws UnknownLookupTypeException, UnknownLookupCodeException {

		//put any errors that were removed from the batch during the data file construction into a separate failed log entry
		if (errorList != null && errorList.size() > 0) {
			try {
				IrsTransmissionLogEntry errEntry = new IrsTransmissionLogEntry();
				Set<IrsTransmissionLogDetailEntry> errDetails = new HashSet<IrsTransmissionLogDetailEntry>();
				for (int i = 0; i < errorList.size(); i++) {
					IRS1095B l = (IRS1095B)errorList.get(i);
					IrsTransmissionLogDetailEntry errDetail = new IrsTransmissionLogDetailEntry();
					errDetail.setIen(l.getIEN());
					if (batchType.equalsIgnoreCase(VETERANS_TYPE)) {
						try {
							errDetail.setPersonId((BigDecimal)this.getPersonService().getPersonIdByVPID
									(CommonEntityKeyFactory.createVPIDEntityKey(l.getIEN())).getKeyValue());
						} catch (Exception e) {
							logger.error("failed to retrieve person key by vpid for icn: " + l.getIEN());
							errDetail.setPersonId(null);
						}
					}
					errDetail.setIen(l.getIEN());
					if (l.getIEN() == null || l.getIEN().isEmpty()) {
						if (l.getSSN() != null && !l.getSSN().isEmpty()) {
							errDetail.setIen(l.getSSN());
						} else {
							errDetail.setIen(l.getDOB());
						}
					}
					errDetail.setRecordSequence(i+1);
					errDetail.setBatchTransmissionEntry(errEntry);
					if (l.getErrorText() != null) {
						if (l.getErrorText().length() > 900) {
							errDetail.setErrorDescription(l.getErrorText().substring(0, 900));
						} else {
							errDetail.setErrorDescription(l.getErrorText());
						}
					} else {
						errDetail.setErrorDescription("Unknown Error");
					}

					errDetails.add(errDetail);


				}
				errEntry.setIrsTransmissionLogDetails(errDetails);
				errEntry.setBatchTypeInd(batchType);
				errEntry.setCorrectionInd(correctionInd);
				errEntry.setTaxYear(taxYear);
				errEntry.setTransmissionDate(new Date());
				errEntry.setTransmissionStatus(((TransmissionStatus) this.lookupService.getByCode(
						TransmissionStatus.class, TransmissionStatus.ACCEPTED_WITH_ERRORS.getCode())));
				errEntry.setErrorDescription("ES Records Cannot be Transmitted due to Internal Errors");
				this.getIrsTransmissionLogEntryDAO().saveObject(errEntry);
			} catch (Exception ex) {
				logger.error(ex.getMessage());
			}

		}

		IrsTransmissionLogEntry entry = new IrsTransmissionLogEntry();

		dataFile.setBatchTransmissionEntry(entry);
		Set<IrsTransmissionLogDetailEntry> details = new HashSet<IrsTransmissionLogDetailEntry>();

		for (int i = 0; i < batch.size(); i++) {
			IRS1095B l = (IRS1095B)batch.get(i);
			IrsTransmissionLogDetailEntry detail = new IrsTransmissionLogDetailEntry();
			detail.setIen(l.getIEN());
			if (batchType.equalsIgnoreCase(VETERANS_TYPE)) {
				try {
					detail.setPersonId((BigDecimal)this.getPersonService().getPersonIdByVPID
							(CommonEntityKeyFactory.createVPIDEntityKey(l.getIEN())).getKeyValue());
				} catch (Exception e) {
					logger.error("failed to retrieve person key by vpid for icn: " + l.getIEN());
					detail.setPersonId(null);
				}
			}
			detail.setIen(l.getIEN());
			if (l.getIEN() == null || l.getIEN().isEmpty()) {
				if (l.getSSN() != null && !l.getSSN().isEmpty()) {
					detail.setIen(l.getSSN());
				} else {
					detail.setIen(l.getDOB());
				}
			}
			detail.setRecordSequence(i+1);
			detail.setBatchTransmissionEntry(entry);
			details.add(detail);

		}

		Set<IrsTransmissionLogData> data = new HashSet<IrsTransmissionLogData>();
		data.add(dataFile);

		entry.setIrsTransmissionLogDetails(details);
		entry.setIrsTransmissionLogData(data);
		entry.setBatchTypeInd(batchType);
		entry.setCorrectionInd(correctionInd);
		entry.setTaxYear(taxYear);
		entry.setTransmissionDate(new Date());
		entry.setTransmissionStatus(((TransmissionStatus) this.lookupService.getByCode(
				TransmissionStatus.class, TransmissionStatus.PROCESSING.getCode())));

		return entry;
	}

	//todo remove

	/*
	 * log entry wrapper to commit request and payload data prior to transmit
	 */
	private void processCorrectReplace(ArrayList<IRS1095B> batch, String batchType, String taxYear, String correctionInd) {

			IrsTransmissionLogDetailEntry cur = new IrsTransmissionLogDetailEntry();
			String originalReceipt = null;
			ArrayList<IRS1095B> errorList = new ArrayList<IRS1095B>();

			if (batch != null && batch.size() ==1) {
				IRS1095B l = (IRS1095B)batch.get(0);

			try {
				cur = this.getIrsTransmissionLogDetailEntryDAO().findOriginalForYear(l.getIEN(), taxYear);
				if (correctionInd.equalsIgnoreCase(SUBMIT_CORRECTION) && batchType.equalsIgnoreCase(VETERANS_TYPE)) {
					PersonEntityKey key = new PersonIdEntityKeyImpl(cur.getPersonId());
					cur = this.getMostRecentTransmissionForYear(key, taxYear, false);
				}
				if (cur != null) {
					if (cur.getBatchTransmissionEntry().getReceiptId() == null || cur.getBatchTransmissionEntry().getReceiptId().isEmpty()) {
						correctionInd = SUBMIT_ORIGINAL;
					} else {

						originalReceipt = getUniqueId(cur);
					}

					//create the payload data file
					IrsTransmissionLogData dataFile = this.getIrsServiceDelegate().buildRequestDataFile(batch, correctionInd, taxYear, originalReceipt, errorList);

					IrsTransmissionLogEntry entry = buildLogEntry(batch, dataFile, batchType, taxYear,  correctionInd, errorList);

					this.getIrsTransmissionLogEntryDAO().insertObject(entry);
					this.getIrsTransmissionLogEntryDAO().flush();
					handleTransmit(entry, false, originalReceipt);

				} else {
					logger.error("No Entry found for allowable correction submission for IEN " + //l.getIEN() +
							" and year"); // + taxYear);
				}
			} catch (DAOException e) {
				logger.error("Error processing IRS Correction/Replacement entry " + e);
			} catch (ServiceException ex) {
				logger.error("Error processing IRS Correction/Replacement entry" + ex);
			}
		}
	}

	/*
	 * gets all partial periods for the calyear being processed
	 * combines to make one coverage map for the entire year
	 * to avoid sending multiple submissions to IRS with separately covered months,
	 * which is not allowed for original submits
	 */
	@SuppressWarnings("rawtypes")
	public MECPeriod combineAllPeriods(BigDecimal personId, int calYear) {
		MECPeriod period = new MECPeriod();
		period.setPersonId(personId);
		HashMap coverageMap = new HashMap();
		DateFormat df  = new SimpleDateFormat("MM/dd/yyyy");
		Calendar cal = Calendar.getInstance();

		Calendar year = Calendar.getInstance();
		year.set(Calendar.YEAR, calYear);

		String calendarYear = Integer.toString(year.get(Calendar.YEAR));


		try {

			List allPeriods = this.getMecPeriodDAO().getByPersonId(personId);


			Calendar start = Calendar.getInstance();
			start.clear();
			start.set(Calendar.YEAR,Integer.valueOf(calendarYear));
			start.set(Calendar.MONTH, 0);
		    start.set(Calendar.DATE, 1);

		    Calendar nextTaxYear = Calendar.getInstance();
		    nextTaxYear.clear();
		    nextTaxYear.set(Calendar.YEAR,Integer.valueOf(calendarYear) + 1);
		    nextTaxYear.set(Calendar.MONTH, 0);
		    nextTaxYear.set(Calendar.DATE, 1);

		    boolean notEnded = false;

		    if (allPeriods != null) {
		    	for (int i = 0; i < allPeriods.size(); i++) {
		    		MECPeriod partial = (MECPeriod)allPeriods.get(i);

		    		//old period ended before this year, ignore it
		    		if (partial.getMecEndDate() != null && partial.getMecEndDate().before(start.getTime())) {
		    			continue;
		    		}

		    		//started the next year, ignore
		    		if (partial.getMecStartDate() != null && partial.getMecStartDate().after(nextTaxYear.getTime())) {
		    			continue;
		    		}

		    		if (partial.getMecEndDate() != null && partial.getMecEndDate().before(partial.getMecStartDate())) {
		    			continue;
		    		}


	    			if (partial.getMecStartDate().before(start.getTime()) && partial.getMecEndDate() == null) {
	    				period.setCoveredAll12Months(true);
	    				period.setCoverageMonths(completeMap());

	    				if (period.getMecStartDate() == null) {
	    					period.setMecStartDate(partial.getMecStartDate());
	    				} else {
	    					if (partial.getMecStartDate().before(period.getMecStartDate())) {
	    						period.setMecStartDate(partial.getMecStartDate());
	    					}
	    				}

	    				period.setMecEndDate(null);

	    				break;
	    			} else if (partial.getMecStartDate().before(start.getTime())){ //start date last year, end date this year
		    			cal.setTime(partial.getMecStartDate());
	    				int monthStart = 1;
	    				int monthEnd;
	    				if (partial.getMecEndDate() == null || partial.getMecEndDate().after(nextTaxYear.getTime())) {
	    					monthEnd = 12;
	    				} else {
		    				cal.setTime(partial.getMecEndDate());
		    				monthEnd =  cal.get(Calendar.MONTH) + 1;
	    				}

	    				if (period.getMecStartDate() == null) {
	    					period.setMecStartDate(partial.getMecStartDate());
	    				} else {
	    					if (partial.getMecStartDate().before(period.getMecStartDate())) {
	    						period.setMecStartDate(partial.getMecStartDate());
	    					}
	    				}

	    				if (period.getMecEndDate() == null) {
	    					period.setMecEndDate(partial.getMecEndDate());
	    				} else {
	    					if (partial.getMecEndDate().after(period.getMecEndDate())) {
	    						period.setMecEndDate(partial.getMecEndDate());
	    					}
	    				}

	    				buildMap(monthStart, monthEnd, coverageMap);
		    		} else { //start date this year, end date this tax year or next tax year
		    			cal.setTime(partial.getMecStartDate());
	    				int monthStart = cal.get(Calendar.MONTH) + 1;
	    				int monthEnd;
	    				if (partial.getMecEndDate() == null || partial.getMecEndDate().after(nextTaxYear.getTime())) {
	    					monthEnd = 12;
	    				} else {
		    				cal.setTime(partial.getMecEndDate());
		    				monthEnd =  cal.get(Calendar.MONTH) + 1;
	    				}

	    				if (period.getMecStartDate() == null) {
	    					period.setMecStartDate(partial.getMecStartDate());
	    				} else {
	    					if (partial.getMecStartDate().before(period.getMecStartDate())) {
	    						period.setMecStartDate(partial.getMecStartDate());
	    					}
	    				}


	    				if (partial.getMecEndDate() != null) {
	    					if (period.getMecEndDate() == null) {
		    					period.setMecEndDate(partial.getMecEndDate());
		    				} else {
		    					if (partial.getMecEndDate().after(period.getMecEndDate())) {
		    						period.setMecEndDate(partial.getMecEndDate());
		    					}
		    				}
	    				} else {
	    					notEnded = true;
	    				}

	    				buildMap(monthStart, monthEnd, coverageMap);
		    		}
		    	}
		    }

		    if (notEnded) {
		    	period.setMecEndDate(null);
		    }

		    //set all months if all partial periods combine to a full year
		    boolean all = true;
		    for (int i = 1; i <= 12; i++) {
		    	if (!coverageMap.containsKey(Integer.toString(i))) {
		    		all = false;
		    	}
		    }
		    if (all) {
		    	period.setCoveredAll12Months(true);
		    	//coverageMap.clear();
		    }

		    period.setCoverageMonths(coverageMap);
		} catch (Exception ex) {
			logger.error("GenerateIRSSubmitProcess Failed to combine MECPeriods for person: " + personId + " reason:" + ex.getMessage());
		}


		return period;
	}

	@SuppressWarnings("unchecked")
	public List<MECPeriod> getAllPeriods(BigDecimal personId) {
		List allPeriods = null;
		try {
			 allPeriods = this.getMecPeriodDAO().getByPersonId(personId);
		} catch (Exception ex ) {
			logger.error(ex.getMessage());
		}

		return allPeriods;
	}

	public MECPeriod combineAllPeriods(BigDecimal personId) {
		MECPeriod period = new MECPeriod();
		period.setPersonId(personId);
		DateFormat df  = new SimpleDateFormat("MM/dd/yyyy");


		boolean notEnded = false;

		try {

			List allPeriods = this.getMecPeriodDAO().getByPersonId(personId);

		    if (allPeriods != null) {
		    	for (int i = 0; i < allPeriods.size(); i++) {
		    		MECPeriod partial = (MECPeriod)allPeriods.get(i);



		    		if (partial.getMecEndDate() == null) {
		    			notEnded = true;
		    		}

		    		if (period.getMecStartDate() == null) {
    					period.setMecStartDate(partial.getMecStartDate());
    				} else {
    					if (partial.getMecStartDate().before(period.getMecStartDate())) {
    						period.setMecStartDate(partial.getMecStartDate());
    					}
    				}

		    		if (period.getMecEndDate() == null) {
    					period.setMecEndDate(partial.getMecEndDate());
    				} else {
    					if (partial.getMecEndDate() != null && partial.getMecEndDate().after(period.getMecEndDate())) {
    						period.setMecEndDate(partial.getMecEndDate());
    					}
    				}
		    	}
		    }

		    if (notEnded) {
		    	period.setMecEndDate(null);
		    }

		} catch (Exception ex) {
			logger.error("combine periods Failed to combine MECPeriods for person: " + personId + " reason:" + ex.getMessage());
		}


		return period;
	}




	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void buildMap(int start, int end, HashMap coverageMap) {
		for (int i = start; i <= end; i++) {
			coverageMap.put(Integer.toString(i), true);
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private HashMap completeMap() {
		HashMap fullYear = new HashMap();
		for (int i = 1; i <= 12; i++) {
			fullYear.put(Integer.toString(i), true);
		}
		return fullYear;
	}


	/*
	 * main entry to the web service client
	 * invokes the transmission based on entry type and collects receiptId
	 * updates log entry status upon response
	 */
	private void handleTransmit(IrsTransmissionLogEntry transmission, boolean entireBatch, String originalReceipt) throws UnknownLookupTypeException, UnknownLookupCodeException {

		String receiptId = null;
		String status = null;
		String errMsg = null;
		String responseMsg = null;

		try {
			if (transmission.getCorrectionInd().equalsIgnoreCase(SUBMIT_REPLACEMENT) || entireBatch) {
				//if (entireBatch) {
					responseMsg = this.getIrsServiceDelegate().retransmitBatchSubmission(transmission, originalReceipt);
				//} else {
					//responseMsg = this.getIrsServiceDelegate().retransmitPersonSubmission(transmission, originalReceipt);
				//}
			} else if (transmission.getCorrectionInd().equalsIgnoreCase(SUBMIT_CORRECTION)) {
				responseMsg = this.getIrsServiceDelegate().retransmitPersonSubmission(transmission, originalReceipt);
			} else {
				responseMsg = this.getIrsServiceDelegate().transmitOriginal(transmission);
			}

			if (responseMsg != null) {
				String[] values = responseMsg.split("\\^");
				receiptId = values[0];

				if (values != null && values.length > 1) {
					status = values[1];
				}
				if (values != null && values.length > 2) {
					errMsg = values[2];
				}
			} else {
				transmission.setTransmissionStatus((TransmissionStatus) this.lookupService.getByCode(
						TransmissionStatus.class, TransmissionStatus.FAILED.getCode()));
				transmission.setErrorDescription(NO_RECEIPT);

				sendNotifySubmissionFailed(transmission.getIrsTransmissionLogDetails().size());
				throw new ServiceException("Transmit Returned with NULL response");
			}

			//need to add retry mechanism possibly, instead of failing first try
			if (receiptId != null && receiptId.length() > 0) {
				//for replacements, always keep same receipt id as original
				if (!transmission.getCorrectionInd().equalsIgnoreCase(SUBMIT_REPLACEMENT)) {
					transmission.setReceiptId(receiptId);
				}
				if (status != null && status.equalsIgnoreCase(SUBMIT_REJECTED)) {
					transmission.setTransmissionStatus((TransmissionStatus) this.lookupService.getByCode(
							TransmissionStatus.class, TransmissionStatus.REJECTED.getCode()));
					transmission.setErrorDescription(errMsg);

					sendNotifySubmissionFailed(transmission.getIrsTransmissionLogDetails().size());
				}
			} else {
				transmission.setTransmissionStatus((TransmissionStatus) this.lookupService.getByCode(
						TransmissionStatus.class, TransmissionStatus.FAILED.getCode()));
				transmission.setErrorDescription(NO_RECEIPT);

				sendNotifySubmissionFailed(transmission.getIrsTransmissionLogDetails().size());
			}

		} catch (ServiceException e) {
			transmission.setTransmissionStatus((TransmissionStatus) this.lookupService.getByCode(
					TransmissionStatus.class, TransmissionStatus.FAILED.getCode()));

			transmission.setErrorDescription(e.getMessage());

			sendNotifySubmissionFailed(transmission.getIrsTransmissionLogDetails().size());
		}


		try {
			this.getIrsTransmissionLogEntryDAO().saveObject(transmission);
			this.getIrsTransmissionLogEntryDAO().flush();
		} catch (DAOException e) {
			logger.error("Error saving IRS Log entry:" + e);
		}

	}

	private void sendNotifySubmissionFailed(int size) {
		logger.debug("in sendNotifySubmissionFailed() size: " + size);
		CommsEmailBulletinService emailSrv = this.getBulletinService();
		Map bulletinData = new HashMap();
		bulletinData.put("RecordCount", size);
		try {
			emailSrv.sendEmailBulletin(BulletinTrigger.DataType.IRS_SUBMISSION_FAILURE, bulletinData, null);
		} catch (ServiceException ex) {
			logger.error("sendNotifySubmissionFailed - cannot send email notification" + ex.getMessage());
		}
	}

	private void sendNotifyAckExpired(int size) {
		logger.debug("in sendNotifyAckExpired() size: " + size);
		CommsEmailBulletinService emailSrv = this.getBulletinService();
		Map bulletinData = new HashMap();
		bulletinData.put("RecordCount", size);
		try {
			emailSrv.sendEmailBulletin(BulletinTrigger.DataType.IRS_ACKNOWLEDGEMENT_EXPIRY, bulletinData, null);
		} catch (ServiceException ex) {
			logger.error("sendNotifyAckExpired - cannot send email notification" + ex.getMessage());
		}
	}

	/**
	 * Returns list of all log transmission detail entries
	 *
     * @param personID the entity key of requested person
     */
	@Transactional
	public List getTransmissionsByPersonId(PersonEntityKey personID) throws ServiceException {


		List<IrsTransmissionLogDetailEntry> ls = new ArrayList<IrsTransmissionLogDetailEntry>();


		try {
			ls = this.getIrsTransmissionLogDetailEntryDAO().findByPersonId(personID);


			if (ls != null) {
				Iterator<IrsTransmissionLogDetailEntry> s = ls.iterator();
				while (s.hasNext()) {
					IrsTransmissionLogDetailEntry d = s.next();
					Hibernate.initialize(d.getBatchTransmissionEntry().getIrsTransmissionLogData());
					IrsTransmissionLogData dataEntry = null;
					//Iterator<IrsTransmissionLogData> i = d.getBatchTransmissionEntry().getIrsTransmissionLogData().iterator();
					//if (i.hasNext()) dataEntry = i.next();
					if (d.getBatchTransmissionEntry().getIrsTransmissionLogData() != null && d.getBatchTransmissionEntry().getIrsTransmissionLogData().iterator().hasNext()){
						dataEntry = d.getBatchTransmissionEntry().getIrsTransmissionLogData().iterator().next();

						d.setSubmittedData(this.getIrsServiceDelegate().convertSingleEntry(dataEntry, d.getRecordSequence()));
						dataEntry = null;
					} else {
						d.setSubmittedData(new IRS1095B());
					}
				}
			}

		} catch (DAOException e) {
			logger.error("Faile to retreive IRS transmissions for person:" + personID.getKeyValueAsString() + e);
		}

        return ls;
	}

	/**
	 * Returns detail entry of the most current attempted transmission for specified tax year
	 *
     * @param personID the entity key of requested person
     * @param taxyear tax year requested
     */
	@Transactional
	public IrsTransmissionLogDetailEntry getMostRecentTransmissionForYear(PersonEntityKey personId, String taxYear, boolean dataRequired) {

		IrsTransmissionLogDetailEntry result = null;
		try {
			result = this.getIrsTransmissionLogDetailEntryDAO().findMostRecentEntry(personId, taxYear);
			if (result != null) {
				if (dataRequired) {
					Hibernate.initialize(result.getBatchTransmissionEntry().getIrsTransmissionLogData());
					IrsTransmissionLogData dataEntry = null;
					Iterator<IrsTransmissionLogData> i = result.getBatchTransmissionEntry().getIrsTransmissionLogData().iterator();
					if (i.hasNext()) {
						dataEntry = i.next();

						result.setSubmittedData(this.getIrsServiceDelegate().convertSingleEntry(dataEntry, result.getRecordSequence()));
						dataEntry = null;
					}
				}

			}
		} catch (DAOException e1) {
			logger.error("Failed to get recent trannsmission for person:" + personId.getKeyValueAsString() + e1);
		} catch (ServiceException ex) {
			logger.error("Failed to convert data entry for person:" + personId.getKeyValueAsString() + ex);
		}

		return result;
	}

	/**
	 * Returns list of years where identified person has an accepted previous transmission
	 *
     * @param personID the entity key of requested person
     */
	public List getAllowedCorrectionYears(PersonEntityKey personId) {

		List<String> taxYearList = new ArrayList<String>();
		List<String> allyears = new ArrayList<String>();
		try {
			List<IrsTransmissionLogDetailEntry> results = this.getIrsTransmissionLogDetailEntryDAO().findAllowedCorrectionYears(personId);

			if (results != null) {
				for (int i = 0; i < results.size(); i++) {
					allyears.add(results.get(i).getBatchTransmissionEntry().getTaxYear());
				}

			}

		} catch (DAOException e) {
			logger.error("Failed to get IRS correction years for person:" + personId.getKeyValueAsString() + e);
		}

		Set<String> uniques = new HashSet<String>(allyears);
		for (String value : uniques) {
			   taxYearList.add(value);
			}

   	 	return taxYearList;
	}

	/**
	 * Returns listing of IRS batch transmission entries in any failed status
     */
	public List getFailedBatchTransmissions() {
		//TODO
		//get all batches in Failed, expired, or reject state

		try {
			List rejectedBatches = this.getIrsTransmissionLogEntryDAO().getFailedBatchTransmissions();
			return rejectedBatches;
		} catch (DAOException e1) {
			logger.error("Failed to retrieve batch transmission failure listing" + e1);
		}

		return null;
	}

	/**
	 * Returns listing of current failed person transmission entries
     */
	@Transactional
	public List getFailedPersonTransmissions() {
		//list of all person detail records that have error description
		List<IrsTransmissionLogDetailEntry> errorList= new ArrayList<IrsTransmissionLogDetailEntry>();

		//list of those above error records that are still valid
		List<IrsTransmissionLogDetailEntry> failedList= new ArrayList<IrsTransmissionLogDetailEntry>();

		try {
			errorList = this.getIrsTransmissionLogDetailEntryDAO().getFailedPersonTransmissions();

			//Put on person failure list only if the failure is most recent transaction for the person for that year
			//this usage does not care about past failures if they have since been resubmitted and are in processing/pending/accepted status
			//past failures will show on person comm tab from usage of getTransmissionsByPersonId()
			//but should not be recognized on the current system person failures listings

			if (errorList != null) {
				for (int i = 0; i < errorList.size(); i++) {
					IrsTransmissionLogDetailEntry cur = errorList.get(i);

					String curId = cur.getEntityKey().getKeyValueAsString();

					if (cur != null && cur.getPersonId() != null) {
						IrsTransmissionLogDetailEntry mostRecent = getMostRecentTransmissionForYear(CommonEntityKeyFactory.createPersonIdEntityKey
								(cur.getPersonId().toString()), cur.getBatchTransmissionEntry().getTaxYear(), false);

						if (mostRecent != null && mostRecent.getEntityKey()!= null && curId.equals(mostRecent.getEntityKey().getKeyValueAsString())) {
							failedList.add(cur);
						}
					}

				}
			}
		} catch (DAOException e) {
			logger.error("Failed to retrieve current person failure listing " + e);
		}

		return failedList;
	}


	public SystemParameterService getSystemParameterService() {
		return systemParameterService;
	}


	public void setSystemParameterService(
			SystemParameterService systemParameterService) {
		this.systemParameterService = systemParameterService;
	}


	public IRSWebServiceDelegate getIrsServiceDelegate() {
		return irsServiceDelegate;
	}


	public void setIrsServiceDelegate(IRSWebServiceDelegate irsServiceDelegate) {
		this.irsServiceDelegate = irsServiceDelegate;
	}


	public LookupService getLookupService() {
		return lookupService;
	}


	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}


	public IrsTransmissionLogEntryDAO getIrsTransmissionLogEntryDAO() {
		return irsTransmissionLogEntryDAO;
	}


	public void setIrsTransmissionLogEntryDAO(
			IrsTransmissionLogEntryDAO irsTransmissionLogEntryDAO) {
		this.irsTransmissionLogEntryDAO = irsTransmissionLogEntryDAO;
	}


	public IrsTransmissionLogDetailEntryDAO getIrsTransmissionLogDetailEntryDAO() {
		return irsTransmissionLogDetailEntryDAO;
	}


	public MECPeriodDAO getMecPeriodDAO() {
		return mecPeriodDAO;
	}

	public void setMecPeriodDAO(MECPeriodDAO mecPeriodDAO) {
		this.mecPeriodDAO = mecPeriodDAO;
	}

	public void setIrsTransmissionLogDetailEntryDAO(
			IrsTransmissionLogDetailEntryDAO irsTransmissionLogDetailEntryDAO) {
		this.irsTransmissionLogDetailEntryDAO = irsTransmissionLogDetailEntryDAO;
	}

	public PersonService getPersonService() {
		return personService;
	}

	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}

	public CommsEmailBulletinService getBulletinService() {
		return bulletinService;
	}

	public void setBulletinService(CommsEmailBulletinService bulletinService) {
		this.bulletinService = bulletinService;
	}

	public String getTransmissionControlCode() {
		return transmissionControlCode;
	}

	public void setTransmissionControlCode(String transmissionControlCode) {
		this.transmissionControlCode = transmissionControlCode;
	}


}
