package gov.va.med.esr.messaging.builder.message;

import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.lookup.Relationship;
import gov.va.med.esr.common.model.messaging.SiteIdentity;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.messaging.constants.HL7Constants;
import gov.va.med.esr.service.FinancialsHelperService;
import gov.va.med.esr.service.trigger.IncomeYearTriggerEvent;
import gov.va.med.fw.hl7.Segment;
import gov.va.med.fw.hl7.segment.ZDP;
import gov.va.med.fw.hl7.segment.ZIC;
import gov.va.med.fw.hl7.segment.ZIR;
import gov.va.med.fw.hl7.segment.ZMT;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.builder.BuilderException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Superclass for Z10 builders to build common Z10 segments
 * 
 * @author Rajiv Patnaik Created on Dec 22, 2005
 * @version 1.0 Copyright © 2005 VHA. All rights reserved
 */
public class AbstractZ10Builder extends AbstractMessageBuilder {
	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 1211776537962717707L;

	private FinancialsHelperService financialsHelperService;

	private ZBTBuilder zbtBuilder;

	private ZDPBuilder zdpBuilder;

	private ZICBuilder zicBuilder;

	private ZICDependentBuilder zicDependentBuilder;

	private ZIRBuilder zirBuilder;

	private ZIRDependentBuilder zirDependentBuilder;

	private ZMTBuilder zmtBuilder;

	public FinancialsHelperService getFinancialsHelperService() {
		return financialsHelperService;
	}

	public void setFinancialsHelperService(
			FinancialsHelperService financialsHelperService) {
		this.financialsHelperService = financialsHelperService;
	}

	/**
	 * @return Returns the zbtBuilder.
	 */
	public ZBTBuilder getZbtBuilder() {
		return zbtBuilder;
	}

	/**
	 * @param zbtBuilder
	 *            The zbtBuilder to set.
	 */
	public void setZbtBuilder(ZBTBuilder zbtBuilder) {
		this.zbtBuilder = zbtBuilder;
	}

	/**
	 * @return Returns the zdpBuilder.
	 */
	public ZDPBuilder getZdpBuilder() {
		return zdpBuilder;
	}

	/**
	 * @param zdpBuilder
	 *            The zdpBuilder to set.
	 */
	public void setZdpBuilder(ZDPBuilder zdpBuilder) {
		this.zdpBuilder = zdpBuilder;
	}

	/**
	 * @return Returns the zicBuilder.
	 */
	public ZICBuilder getZicBuilder() {
		return zicBuilder;
	}

	/**
	 * @param zicBuilder
	 *            The zicBuilder to set.
	 */
	public void setZicBuilder(ZICBuilder zicBuilder) {
		this.zicBuilder = zicBuilder;
	}

	/**
	 * @return Returns the zirBuilder.
	 */
	public ZIRBuilder getZirBuilder() {
		return zirBuilder;
	}

	/**
	 * @param zirBuilder
	 *            The zirBuilder to set.
	 */
	public void setZirBuilder(ZIRBuilder zirBuilder) {
		this.zirBuilder = zirBuilder;
	}

	/**
	 * @return Returns the zmtBuilder.
	 */
	public ZMTBuilder getZmtBuilder() {
		return zmtBuilder;
	}

	/**
	 * @param zmtBuilder
	 *            The zmtBuilder to set.
	 */
	public void setZmtBuilder(ZMTBuilder zmtBuilder) {
		this.zmtBuilder = zmtBuilder;
	}

	/**
	 * @return Returns the zicDependentBuilder.
	 */
	public ZICDependentBuilder getZicDependentBuilder() {
		return zicDependentBuilder;
	}

	/**
	 * @param zicDependentBuilder
	 *            The zicDependentBuilder to set.
	 */
	public void setZicDependentBuilder(ZICDependentBuilder zicDependentBuilder) {
		this.zicDependentBuilder = zicDependentBuilder;
	}

	/**
	 * @return Returns the zirDependentBuilder.
	 */
	public ZIRDependentBuilder getZirDependentBuilder() {
		return zirDependentBuilder;
	}

	/**
	 * @param zirDependentBuilder
	 *            The zirDependentBuilder to set.
	 */
	public void setZirDependentBuilder(ZIRDependentBuilder zirDependentBuilder) {
		this.zirDependentBuilder = zirDependentBuilder;
	}

	/**
	 * Created common segments for ORUZ10 and ORFZ10 e.g PID, ZIC, ZIR, etc
	 * 
	 * @param person
	 * @param siteIdentity
	 * @param triggerEvent
	 * @return List of segments
	 * @throws BuilderException
	 */
	protected List buildCommonZ10Segments(List segments, Person person,
			SiteIdentity siteIdentity, IncomeYearTriggerEvent triggerEvent)
			throws BuilderException {

		Integer incomeYear = triggerEvent.getIncomeYear();

		// Build PID
		segments.add(super.buildPIDSegment(person, siteIdentity));

		// Build ZIC
		ZICMetaData zicMetaData = new ZICMetaData(person, incomeYear);
		segments.add(this.zicBuilder.build(zicMetaData));

		// Build ZIR
		ZIRMetaData zirMetaData = new ZIRMetaData(person, incomeYear);
		segments.add(this.zirBuilder.build(zirMetaData));

		// Build ZDP, ZIC, ZIR segments - repeatable

		List[] activeInactiveSpouses = getActiveInactiveSpouses(person, incomeYear);
		List activeSpouses = activeInactiveSpouses[0];
		List inActiveSpouses = activeInactiveSpouses[1];

		List[] activeInactiveDependendents = getActiveInactiveDependents(person, incomeYear);
		List activeDependendents = activeInactiveDependendents[0];
		List inactiveDependendents = activeInactiveDependendents[1];

		// For each spouse and dependent, we need to build those three segment.
		segments = buildActiveSpouseSegments(person, incomeYear, activeSpouses,
				segments);

		// Build active dependents
		segments = buildDependentSegments(person, incomeYear,
				activeDependendents, segments, true);

		// Add any inactive spouses
		segments = buildInactiveSpouseSegments(person, incomeYear,
				inActiveSpouses, segments);

		// Add inactive dependents
		segments = buildDependentSegments(person, incomeYear,
				inactiveDependendents, segments, false);

		try {
			// ZMTBuilder builds 3 ZMT segments
			ZMTMetaData zmtMetaData = new ZMTMetaData(person,
					getLookupService().getMessageTypeByCode(
							MessageType.CODE_ORFZ10_TO_SITE.getName()));
			zmtMetaData.setIncomeYear(incomeYear);
            zmtMetaData.setDeletedIncomeTest(triggerEvent.getDeletedIncomeTest());
            zmtMetaData.setSiteIdentity(siteIdentity);
            
			List zmtSegments = (List) zmtBuilder.build(zmtMetaData);
			for (Iterator iter = zmtSegments.iterator(); iter.hasNext();) {
				ZMT zmt = (ZMT) iter.next();
				segments.add(zmt);
			}
		} catch (ServiceException e) {
			throw new BuilderException("Could not lookup Message Type ORFZ10 ",
					e);
		}

		ZBTMetaData zbtMetadata = new ZBTMetaData(person, siteIdentity,
				incomeYear);
		segments.add(zbtBuilder.build(zbtMetadata));

		return segments;
	}

	private List[] getActiveInactiveDependents(Person person, Integer incomeYear) {

		List[] activeInactiveDependents = new List[2];
		List activeDependents = new ArrayList();
		List inActiveDependents = new ArrayList();

		FinancialStatement financialStatement = person
				.getFinancialStatement(incomeYear);
		Set dependentFinancialsList = financialStatement == null ? null
				: financialStatement.getDependentFinancials();

		if (dependentFinancialsList != null) {
			for (Iterator iter = dependentFinancialsList.iterator(); iter
					.hasNext();) {

				DependentFinancials dependentFinancials = (DependentFinancials) iter
						.next();

				if (dependentFinancials != null
						&& dependentFinancials.getReportedOn() != null) {

					if (financialsHelperService.isActiveDependentForVistaTransmission(dependentFinancials)) {
						activeDependents.add(dependentFinancials);
					} else {
						inActiveDependents.add(dependentFinancials);
					}
				}
			}
		}

		activeInactiveDependents[0] = activeDependents;
		activeInactiveDependents[1] = inActiveDependents;

		return activeInactiveDependents;

	}

	private List[] getActiveInactiveSpouses(Person person, Integer incomeYear) {

		List[] activeInactiveSpouses = new List[2];
		List activeSpouses = new ArrayList();
		List inActiveSpouses = new ArrayList();

		FinancialStatement financialStatement = person
				.getFinancialStatement(incomeYear);
		Set spouseFinancialsList = financialStatement == null ? null
				: financialStatement.getSpouseFinancials();

		if (spouseFinancialsList != null) {
			for (Iterator iter = spouseFinancialsList.iterator(); iter
					.hasNext();) {

				SpouseFinancials spouseFinancials = (SpouseFinancials) iter
						.next();

				if (spouseFinancials != null
						&& spouseFinancials.getReportedOn() != null) {

					if (financialsHelperService.isActiveSpouseForVistaTransmission(spouseFinancials)) {
						activeSpouses.add(spouseFinancials);
					} else {
						inActiveSpouses.add(spouseFinancials);
					}
				}
			}
		}

		activeInactiveSpouses[0] = activeSpouses;
		activeInactiveSpouses[1] = inActiveSpouses;

		return activeInactiveSpouses;

	}

	private List buildDependentSegments(Person person, Integer incomeYear,
			List dependentFinancialsList, List segments, boolean isActive)
			throws BuilderException {

		if (dependentFinancialsList != null) {
			// Start with the the counter passed in param
			int setIdCounter = getSetIDCounterForZDP(segments) + 1;

			for (Iterator iter = dependentFinancialsList.iterator(); iter
					.hasNext();) {

				DependentFinancials dependentFinancials = (DependentFinancials) iter
						.next();

				Relationship relationship = dependentFinancials.getReportedOn()
						.getRelationship();

				// Build ZDP, ZIC and ZIR for each dependent
				ZDPMetaData zdpMetaData = new ZDPMetaData(person, relationship,
						dependentFinancials, String.valueOf(setIdCounter));

				ZICMetaData zicMetaData = new ZICMetaData(person, incomeYear);
				zicMetaData.setRelationship(relationship);
				zicMetaData.setRelationFinancials(dependentFinancials);
				zicMetaData.setSetId(String.valueOf(setIdCounter));

				ZIRMetaData zirMetaData = new ZIRMetaData(person, incomeYear);
				zirMetaData.setRelationFinancials(dependentFinancials);
				zirMetaData.setRelationship(relationship);
				zirMetaData.setSetId(String.valueOf(setIdCounter));

				/*
				 * For active dependents, add ZDP, ZIC, ZIR For inactive
				 * dependents, add ZDP only Active dependents need to be added
				 * first followed by the inactive ones
				 */
				if (isActive) {
					segments.add(zdpBuilder.build(zdpMetaData));
					segments.add(zicDependentBuilder.build(zicMetaData));
					segments.add(zirDependentBuilder.build(zirMetaData));

				} else {
					segments.add(zdpBuilder.build(zdpMetaData));
				}

				setIdCounter++;
			}
		}

		return segments;
	}

	/**
	 * @param person
	 * @param incomeYear
	 * @param segments
	 * @return
	 * @throws BuilderException
	 */
	private List buildActiveSpouseSegments(Person person, Integer incomeYear,
			List activeSpouseFinancials, List segments) throws BuilderException {
		try {
			if (activeSpouseFinancials != null && !activeSpouseFinancials.isEmpty()) {

				for (Iterator iter = activeSpouseFinancials.iterator(); iter.hasNext();) {

					SpouseFinancials spouseFinancials = (SpouseFinancials) iter
							.next();

					Relationship relationship = getLookupService()
							.getRelationshipByCode(
									Relationship.CODE_SPOUSE.getName());

					ZDPMetaData zdpMetaData = new ZDPMetaData(person,
							relationship, spouseFinancials,
							HL7Constants.SPOUSE_SETID);

					ZICMetaData zicMetaData = new ZICMetaData(person,
							incomeYear);
					zicMetaData.setSetId(HL7Constants.SPOUSE_SETID);
					zicMetaData.setRelationship(relationship);
					zicMetaData.setRelationFinancials(spouseFinancials);

					ZIRMetaData zirMetaData = new ZIRMetaData(person,
							incomeYear);
					zirMetaData.setSetId(HL7Constants.SPOUSE_SETID);
					zirMetaData.setRelationFinancials(spouseFinancials);
					zirMetaData.setRelationship(relationship);

					segments.add(zdpBuilder.build(zdpMetaData));
					segments.add(zicDependentBuilder.build(zicMetaData));
					segments.add(zirDependentBuilder.build(zirMetaData));
				}
			} else {
				// Create empty spouse financials to represent the active spouse
				// financial
				segments = setEmptyZDPForSpouse(segments);
				segments = setEmptyZICZIRForSpouse(segments);
			}

			return segments;
		} catch (ServiceException e) {
			throw new BuilderException("Exception looking up code" + e);
		}
	}

	private List buildInactiveSpouseSegments(Person person, Integer incomeYear,
			List inactiveSpouseFinancials, List segments)
			throws BuilderException {

		try {
			Relationship relationship = getLookupService()
					.getRelationshipByCode(Relationship.CODE_SPOUSE.getName());

			if (inactiveSpouseFinancials != null) {
				for (Iterator iter = inactiveSpouseFinancials.iterator(); iter
						.hasNext();) {

					SpouseFinancials spouseFinancials = (SpouseFinancials) iter
							.next();
					ZDPMetaData zdpMetaData = new ZDPMetaData(person,
							relationship, spouseFinancials,
							String.valueOf(getSetIDCounterForZDP(segments) + 1));

					segments.add(zdpBuilder.build(zdpMetaData));
				}
			}
		} catch (ServiceException e) {
			throw new BuilderException("Exception looking up code" + e);
		}
		return segments;
	}


	private List setEmptyZICZIRForSpouse(List segments) {
		ZIC zic = new ZIC();
		zic.setSetID(HL7Constants.SPOUSE_SETID);

		ZIR zir = new ZIR();
		zir.setSetID(HL7Constants.SPOUSE_SETID);

		segments.add(zic);
		segments.add(zir);

		return segments;

	}

	private int getSetIDCounterForZDP(List segments) {

		ZDP lastZDPSegment = null;

		for (Iterator iter = segments.iterator(); iter.hasNext();) {

			Segment segment = (Segment) iter.next();

			if (segment instanceof ZDP) {
				ZDP zdp = (ZDP) segment;

				lastZDPSegment = zdp;
			}
		}
		return lastZDPSegment == null ? 0 : Integer.parseInt(lastZDPSegment
				.getSetID());
	}

	private List setEmptyZDPForSpouse(List segments) {

		ZDP zdp = new ZDP();
		zdp.setSetID(HL7Constants.SPOUSE_SETID);
		zdp.setRelationship(Relationship.CODE_SPOUSE.getCode());

		segments.add(zdp);

		return segments;
	}

}