/********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.esr.common.model;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.ee.MonetaryBenefit;
import gov.va.med.esr.common.model.ee.MonetaryBenefitAward;
import gov.va.med.esr.common.model.ee.RatedDisability;
import gov.va.med.esr.common.model.financials.Asset;
import gov.va.med.esr.common.model.financials.Debt;
import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.Expense;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.Income;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.insurance.InsurancePolicy;
import gov.va.med.esr.common.model.insurance.PrivateInsurance;
import gov.va.med.esr.common.model.lookup.AddressType;
import gov.va.med.esr.common.model.lookup.Disability;
import gov.va.med.esr.common.model.lookup.MonetaryBenefitType;
import gov.va.med.esr.common.model.lookup.PhoneType;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.party.Email;
import gov.va.med.esr.common.model.party.Phone;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.person.Dependent;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.Spouse;
import gov.va.med.esr.common.model.report.ReportBadAddressReason;
import gov.va.med.esr.common.model.report.ReportParameterSet;
import gov.va.med.esr.common.model.report.ReportSetup;
import gov.va.med.esr.common.util.AbstractCommonTestCase;
import gov.va.med.fw.model.AbstractEntity;
import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.model.BaseEntityKey;
import gov.va.med.fw.model.EntityKey;

/**
 * Initial javadoc for class EqualsAndHashCodeTest. TODO - Add content here
 * 
 * Created Aug 25, 2005 10:03:24 PM
 * 
 * @author DNS   BOHMEG
 */
public class EqualsAndHashCodeTest extends AbstractCommonTestCase {
	protected boolean shouldTestCaseStartTransaction() {
		return false;
	}
	
	public void testInheritedFields() throws Exception {
		ReportSetup setup = new ReportSetup();		
		ReportBadAddressReason reason = new ReportBadAddressReason();		
		ReportParameterSet set = new ReportParameterSet();
		setup.setParameterSet(set);		
		set.addBadAddressReason(reason);		
		doEqualsAndHashCodeTest(reason);
	}

	public void testEntityKey() throws Exception {
		EntityKey key1 = new BaseEntityKey("123", Object.class);
		EntityKey key2 = new BaseEntityKey("123", new Integer(12), Object.class);
		assertEquals(key1, key2);
		EntityKey key3 = new BaseEntityKey("123", String.class);
		assertTrue(!key1.equals(key3));
		EntityKey key4 = new BaseEntityKey("123", new Integer(15), Object.class);
		assertEquals(key1, key4);
	}

	public void testPerson0Graph() throws Exception {
		doEqualsAndHashCodeTest(new Person());
	}

	public void testPerson1Graph() throws Exception {
		// because RatedDisability's are always different, make sure none present first
		doEqualsAndHashCodeTest(stripOutRatedDisability(buildPerson()));
	}
	private Person stripOutRatedDisability(Person person) throws Exception {
		person.setServiceConnectionAward(createServiceConnectionAward(55, false));
		return person;
	}

	public void testPerson8Graph() throws Exception {
		doEqualsAndHashCodeTest(buildSimplePerson());
	}

	public void testPerson2Graph() throws Exception {
		Person person = new Person();
		person.addName(createName());
		doEqualsAndHashCodeTest(person);
	}

	public void testPerson3Graph() throws Exception {
		Person person = new Person();
		person.addPhone(createPhone());
		doEqualsAndHashCodeTest(person);
	}

	public void testPerson4Graph() throws Exception {
		Person person = new Person();
		person.addAddress(createAddress());
		Person clonedPerson = (Person) person.clone();
		doEqualsAndHashCodeTest(person, clonedPerson);
	}

	public void testPerson6Graph() throws Exception {
		Person person = new Person();
		person.addAddress(createAddress());
		Person clonedPerson = (Person) person.clone();
		clonedPerson.setClaimFolderNumber("bogus");
		assertFalse(person.hashCode() == clonedPerson.hashCode());
		assertFalse(person.equals(clonedPerson));
	}

	public void testPerson7Graph() throws Exception {
		Person person1 = new Person();
		person1.setClaimFolderNumber("bogus1");
		Person person2 = new Person();
		person2.setClaimFolderNumber("bogus2");
		assertFalse(person1.hashCode() == person2.hashCode());
		assertFalse(person1.equals(person2));
	}

	public void testName() throws Exception {
		doEqualsAndHashCodeTest(createName());
	}

	public void testPerson5Graph() throws Exception {
		Person person = new Person();
		person.addAssociation(createAssociation("org", "rshp"));
		doEqualsAndHashCodeTest(person);
	}

	public void testPersonSpouse() throws Exception {
		Person person = new Person();
		Spouse spouse = createSpouse();
		spouse.setPerson(person);

		doEqualsAndHashCodeTest(spouse);
	}

	public void testPersonDependent() throws Exception {
		Person person = new Person();
		Dependent dependent = createDependent();
		dependent.setPerson(person);
		doEqualsAndHashCodeTest(dependent);
	}

	public void testFinancialStatement() throws Exception {
		Person person = new Person();
		FinancialStatement fStmt = new FinancialStatement();
		person.setFinancialStatement(new Integer(2000), fStmt);
		doEqualsAndHashCodeTest(fStmt);
	}

	public void testDependentFinancials() throws Exception {
		Person person = new Person();
		FinancialStatement fStmt = new FinancialStatement();
		person.setFinancialStatement(new Integer(2000), fStmt);
		DependentFinancials df = new DependentFinancials();
		df.setReportedOn(new Dependent());
		fStmt.addDependentFinancials(df);
		doEqualsAndHashCodeTest(df);
	}

	public void testSpouseFinancials() throws Exception {
		Person person = new Person();
		FinancialStatement fStmt = new FinancialStatement();
		person.setFinancialStatement(new Integer(2000), fStmt);
		SpouseFinancials sf = new SpouseFinancials();
		sf.setReportedOn(new Spouse());        
		fStmt.addSpouseFinancials(sf);
		doEqualsAndHashCodeTest(sf);
	}

	public void testFinancialAsset() throws Exception {
		FinancialStatement fStmt = new FinancialStatement();
		Asset asset = createAsset(3453.44);
		doEqualsAndHashCodeTest(asset);
		fStmt.setAsset(getRandomAssetType(), asset);
		doEqualsAndHashCodeTest(asset);
	}

	public void testFinancialIncome() throws Exception {
		FinancialStatement fStmt = new FinancialStatement();
		Income income = createIncome(45646);
		doEqualsAndHashCodeTest(income);
		fStmt.setIncome(getRandomIncomeType(), income);
		doEqualsAndHashCodeTest(income);
	}

	public void testFinancialExpense() throws Exception {
		FinancialStatement fStmt = new FinancialStatement();
		Expense expense = createExpense(446);
		doEqualsAndHashCodeTest(expense);
		fStmt.setExpense(getRandomExpenseType(), expense);
		doEqualsAndHashCodeTest(expense);
	}

	public void testFinancialDebt() throws Exception {
		FinancialStatement fStmt = new FinancialStatement();
		Debt debt = createDebt(446);
		doEqualsAndHashCodeTest(debt);
		fStmt.setDebt(debt);
		doEqualsAndHashCodeTest(debt);
	}

	public void testPersonAssociation1() throws Exception {
		Person person = new Person();
		Association ass = createAssociation("org", "rshp");
		person.addAssociation(ass);
		doEqualsAndHashCodeTest(ass);
	}

	public void testPersonAssociation2() throws Exception {
		Person person = new Person();
		Association ass = createAssociation("org", "rshp");
		person.addAssociation(ass);
		ass.setPerson(null);
		doEqualsAndHashCodeTest(ass);
	}

	public void testAssociation() throws Exception {
		doEqualsAndHashCodeTest(createAssociation("org", "rshp"));
	}

	public void testAssociationAddress() throws Exception {
		Association ass = createAssociation("org", "rshp");
		doEqualsAndHashCodeTest(ass.getAddress());
	}

	public void testPhone() throws Exception {
		doEqualsAndHashCodeTest(createPhone());
	}

	public void testTwoPhones() throws Exception {
		Person person = new Person();
		Phone phone1 = new Phone();
		Phone phone2 = new Phone();
		person.addPhone(phone1);
		person.addPhone(phone2);
		assertNotNull(phone1.getPerson());
		assertNotNull(phone2.getPerson());
		doEqualsAndHashCodeTest(phone1, phone2);
	}

	public void testMonetaryBenefit() throws Exception {
		Person person = new Person();
		MonetaryBenefitAward mbAward = new MonetaryBenefitAward();
		person.setMonetaryBenefitAward(mbAward);
		MonetaryBenefit mb = new MonetaryBenefit();
		mb.setType(getLookupService().getMonetaryBenefitTypeByCode(
				MonetaryBenefitType.CODE_AID_AND_ATTENDANCE.getCode()));
		doEqualsAndHashCodeTest(mb);
		mbAward.addMonetaryBenefit(mb);
		doEqualsAndHashCodeTest(mbAward);
	}

	public void testInsurancePhone() throws Exception {
		InsurancePolicy insurance = new PrivateInsurance();
		Phone phone = createPhone(PhoneType.CODE_BUSINESS);
		insurance.setBusinessPhone(phone);
		doEqualsAndHashCodeTest(phone);
	}

	public void testEmail() throws Exception {
		doEqualsAndHashCodeTest(createEmail());
	}

	public void testTwoEmails() throws Exception {
		Person person = new Person();
		Email email1 = new Email();
		Email email2 = new Email();
		person.addEmail(email1);
		person.addEmail(email2);
		assertNotNull(email1.getPerson());
		assertNotNull(email2.getPerson());
		doEqualsAndHashCodeTest(email1, email2);
	}

	public void testTwoAddresses() throws Exception {
		Person person = new Person();
		Address address1 = new Address();
		Address address2 = new Address();
		person.addAddress(address1);
		person.addAddress(address2);
		assertNotNull(address1.getPerson());
		assertNotNull(address2.getPerson());
		doEqualsAndHashCodeTest(address1, address2);
	}

	public void testTwoAddressesWithPersonClone() throws Exception {
		Person person = new Person();
		Person clonedPerson = (Person) person.clone();
		Address address1 = createAddress(getLookupService()
				.getAddressTypeByCode(
						AddressType.CODE_PERMANENT_ADDRESS.getName()));
		Address address2 = createAddress(getLookupService()
				.getAddressTypeByCode(
						AddressType.CODE_PERMANENT_ADDRESS.getName()));
		person.addAddress(address1);
		clonedPerson.addAddress(address2);
		assertNotNull(address1.getPerson());
		assertNotNull(address2.getPerson());
		doEqualsAndHashCodeTest(address1, address2);
	}

	public void testAddress() throws Exception {
		doEqualsAndHashCodeTest(createSimpleAddress());
	}

	public void testAddressWithBackReference() throws Exception {
		Address address = createSimpleAddress();
		new Person().addAddress(address);

		doEqualsAndHashCodeTest(address);
	}

	public void testPhoneWithBackReference() throws Exception {
		Phone phone = createPhone();
		new Person().addPhone(phone);
		doEqualsAndHashCodeTest(phone);
	}

	public void testRatedDisability() throws Exception {
		Set items = new HashSet();
		// this a special case since 2 RD can not equal so dups are
		// allowed in Set operations.  Recall that if a RatedDisabiliity has no identifier,
		// it will use the memory address.  See its implementation and doco.
		
		// no identifier
		items.clear();
		RatedDisability rd1 = new RatedDisability();
		rd1.setDisability(getLookupService().getDisabilityByCode(
				Disability.CODE_RESPIRATORY_CONDITION_GENERAL.getName()));
		rd1.setPercentage(new Integer(60));
		rd1.setServiceConnected(Boolean.TRUE);
		rd1.setOriginalEffectiveRatingDate(new ImpreciseDate(new Date()));
		RatedDisability rd2 = (RatedDisability) rd1.clone();
		assertTrue(rd1.hashCode() != rd2.hashCode());
		assertTrue(!rd1.equals(rd2));
		items.add(rd1);
		items.add(rd2);
		assertTrue(items.size() == 2);
		
		// yes (same) identiifer
		items.clear();
		setIdentifier(rd1, new BigDecimal(123));
		setIdentifier(rd2, new BigDecimal(123));
		assertEquals(rd1.hashCode(), rd2.hashCode());
		assertEquals(rd1, rd2);
		items.add(rd1);
		items.add(rd2);
		assertTrue(items.size() == 1);

		// yes (different) identiifer
		items.clear();
		setIdentifier(rd1, new BigDecimal(123));
		setIdentifier(rd2, new BigDecimal(12993));
		assertTrue(rd1.hashCode() != rd2.hashCode());
		assertTrue(!rd1.equals(rd2));
		items.add(rd1);
		items.add(rd2);
		assertTrue(items.size() == 2);		
	}

	private void setIdentifier(AbstractKeyedEntity obj, Serializable id) {
		try {
			Method method = AbstractKeyedEntity.class.getDeclaredMethod(
					"setIdentifier", new Class[] { Serializable.class });
			method.setAccessible(true);
			method.invoke(obj, new Object[] { id });
		} catch (Exception e) {
			throw new RuntimeException("Unable to set identifier.", e);
		}
	}

	private void doEqualsAndHashCodeTest(AbstractEntity obj) {
		doEqualsAndHashCodeTest(obj, (AbstractEntity) obj.clone());
	}

	private void doEqualsAndHashCodeTest(AbstractEntity obj1,
			AbstractEntity obj2) {
		doEqualsTest(obj1, obj2);
		doHashCodeTest(obj1, obj2);
	}

	private void doEqualsTest(AbstractEntity obj1) {
		doEqualsTest(obj1, (AbstractEntity) obj1.clone());
	}

	private void doEqualsTest(AbstractEntity obj1, AbstractEntity obj2) {
		assertTrue(obj1.equals(obj1));
		assertTrue(obj2.equals(obj2));
		assertTrue("NOT EQUAL: ob1: " + obj1 + " and obj2: " + obj2, obj1
				.equals(obj2));
	}

	private void doHashCodeTest(AbstractEntity obj1) {
		doHashCodeTest(obj1, (AbstractEntity) obj1.clone());
	}

	private void doHashCodeTest(AbstractEntity obj1, AbstractEntity obj2) {
		assertEqualsHashCode(obj1, obj2);
	}

	private void assertEqualsHashCode(Object obj1, Object obj2) {
		assertTrue(obj1.hashCode() == obj2.hashCode());
	}
}
