/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
// Package
package gov.va.med.esr.common.report.data.impl;

// Java classes
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.text.DateFormat;

// Library classes
import org.hibernate.Query;
import org.hibernate.Session;

// Framework classes
import gov.va.med.fw.model.lookup.AbstractCode;
import gov.va.med.fw.report.ReportConfiguration;
import gov.va.med.fw.report.data.QueryCriteria;
import gov.va.med.fw.report.data.ReportDataException;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.service.ServiceException;

// ESR classes
import gov.va.med.esr.common.model.lookup.ReportPeriodType;
import gov.va.med.esr.common.model.lookup.Gender;
import gov.va.med.esr.common.model.lookup.MilitarySexualTraumaStatus;
import gov.va.med.esr.common.model.lookup.ReportRunFrequency;
import gov.va.med.esr.common.model.report.ReportParameterSet;
import gov.va.med.esr.common.model.report.ReportSchedule;
import gov.va.med.esr.common.model.report.ReportSetup;
import gov.va.med.esr.common.model.report.ReportPeriod;
import gov.va.med.esr.common.report.data.CommonCriteria;
import gov.va.med.esr.common.report.data.StandardReportCriteria;


/**
 * Provides methods to build a HQL statemenets for retrieving report data.  This interface is 
 * intented to retrieve report data for all ESR standard reports related to veteran MST info.
 *
 * Project: Common</br>
 * Created on: 9:04:30 AM </br>
 *
 * @author DNS   LEV
 */
public class MSTReportDataDAOImpl extends ParamSubstitutionDAOImpl {

	/**
    * A serialization key
    */
   private static final long serialVersionUID = -6627321528592452046L;
   
   //make sure these constants match with order of the select statement.
   public static final int VPID=0;
   public static final int STATUS=1;
   public static final int STATUS_DATE=2;
   public static final int GENDER_CODE=3;
   
   /**
	 * A list MST statuses  
	 */
	private static final MilitarySexualTraumaStatus.Code[] statusCodes = new MilitarySexualTraumaStatus.Code[]{
		MilitarySexualTraumaStatus.CODE_SCREENED_DECLINES_TO_ANSWER,
		MilitarySexualTraumaStatus.CODE_SCREENED_DOES_NOT_REPORT_MST,
		MilitarySexualTraumaStatus.CODE_SCREENED_REPORTS_MST,
		MilitarySexualTraumaStatus.CODE_UNKNOWN_NOT_SCREENED
	};
		
   /**
	 * A default constructor
	 */
	public MSTReportDataDAOImpl() {
		super();
	}

   /** Process MST report parameters to populate a report resource mapping for header
    * and MST report's static text format
    * 
    * @param config A report configuration 
    * @throws ReportDataException In case of errors accessing a collection
    */
   public void preDataRetrieval( ReportConfiguration config ) throws ReportDataException {
      
      QueryCriteria reportCriteria = config.getQueryCriteria();

      if( reportCriteria instanceof StandardReportCriteria ) {
         
         // Get a criteria
         StandardReportCriteria criteria = (StandardReportCriteria)reportCriteria;
         
         ReportSetup setup = criteria.getReportSetup();
         ReportParameterSet parameters = setup.getParameterSet();
         
         // Get a report period
         ReportPeriod period = parameters.getReportPeriod();
         ReportPeriodType reportPeriodType = period.getType();
         
         // Put start and end date in a resource map
         criteria.setStartDate( this.getDate( config, true ) );
         criteria.setEndDate( this.getDate( config, false ) );
         criteria.setReportPeriodType( reportPeriodType.getDescription().toUpperCase() );
         criteria.setReportPeriodTypeCode( reportPeriodType.getCode() );

         // Get a date format to format a report period to display on a header
         Integer year = period.getYear();
         if( year != null ) {
            criteria.setReportPeriod( "As of " + year.intValue() );
         }
         else {
            DateFormat df = DateFormat.getDateInstance( DateFormat.MEDIUM );
            criteria.setReportPeriod( "As of " + df.format(  this.getDate( config, false ) ) );
         }
      }
   }
   
	/**
	 * @see gov.va.med.fw.report.data.ReportDataDAO#postDataRetrieval(java.util.List, gov.va.med.fw.report.ReportConfiguration)
	 */
	public List postDataRetrieval( final List results, final ReportConfiguration config ) throws ReportDataException {
		
		Collection vpids = new HashSet();
		// TreeMap: key=period, value=Map(statues)
		// TreeMap(statuses): key=status description, value=Map(genders)
		// TreeMap(genders): key=gender code, value=HashSet(vpids)
		// HashSet(vpids): vpid values
		//
		
		List reportData = new ArrayList();
		try {
			
			Map data = new TreeMap();
			Map genders = null;
			Map statuses = null;
			vpids = null;
			for( Iterator i=results.iterator(); i.hasNext();  ) {
				Object [] row = (Object [])i.next();
				String vpid = (String)row[VPID];				
				
				String status = (String)row[STATUS];
				String period = this.getPeriod( (Date)row[STATUS_DATE], config );
				String genderCode = (String)row[GENDER_CODE];
				
				//ccr 9555. gendercode coming as null for some record in production.
				//When gender code came back  as null from qry , treat it as ‘unknown’
				if(genderCode==null){
					genderCode=Gender.UNKNOWN.getCode();
				}
				
				if( !data.containsKey( period ) ) {
						statuses = new TreeMap();
						data.put( period, statuses );
				}
				else {
            	  	statuses = (Map)data.get( period );
              	}
					
				if( !statuses.containsKey( status ) ) {
					genders = new TreeMap();
					statuses.put( status, genders );
				}
				else {
                	genders = (Map)statuses.get( status );
				} 
				
				if( !genders.containsKey( genderCode ) ) {
					vpids = new HashSet();
					genders.put( genderCode, vpids );
				}
				else {
					vpids = (Collection)genders.get( genderCode );
				}
					vpids.add( vpid );			
			}
			
			// Structure report data
			for( Iterator i=data.entrySet().iterator(); i.hasNext(); ) {
				
				// Get a list of periods
				Map.Entry entry = (Map.Entry)i.next();
				String period = (String)entry.getKey();
				
				statuses = (Map)entry.getValue();
				for( Iterator iter=statuses.entrySet().iterator(); iter.hasNext(); ) {
					Map.Entry status_entry = (Map.Entry)iter.next();
					
					// A MST status code
					Map row = new HashMap();
					row.put( "status", status_entry.getKey() );
					
					genders = (Map)status_entry.getValue();
					
					// Get a total of male
					vpids = (Set)genders.get( Gender.MALE.getName() );
					int male = (vpids != null) ? vpids.size() : 0;
					row.put( "male", new Integer(male) );
					
					// Get a total of female
					vpids = (Set)genders.get( Gender.FEMALE.getName() );
					int female = (vpids != null) ? vpids.size() : 0;
					row.put( "female", new Integer(female) );
					
					// Get a total of other
					vpids = (Set)genders.get( Gender.OTHER.getName() );
					int other = (vpids != null) ? vpids.size() : 0;
					row.put( "other", new Integer(other) );
					
					// Get a total of unknown
					vpids = (Set)genders.get( Gender.UNKNOWN.getName() );
					int unknown = (vpids != null) ? vpids.size() : 0;
					row.put( "unknown", new Integer(unknown) );
					
					// Subtotal
					int subtotal = male + female + other + unknown;
					row.put( "subtotal", new Integer( subtotal ) );
					
					// Percentage 
					double percentage = ( ((double)subtotal) / ((double)this.getTotalVetsByPeriod( statuses )) );
					row.put( "percentage", new Double( percentage ) );
					
					// A report period - from input param
					row.put( "period", period );
	
					// Populate MST reported positive
					String desc = this.getLookupDescription( MilitarySexualTraumaStatus.CODE_SCREENED_REPORTS_MST );
					row.putAll( this.getPercentageOfScreenedVetsByPeriod( statuses, desc, "positive") );
					
					// Populate MST reported negative
					desc = this.getLookupDescription( MilitarySexualTraumaStatus.CODE_SCREENED_DOES_NOT_REPORT_MST );
					row.putAll( this.getPercentageOfScreenedVetsByPeriod( statuses, desc, "negative") );
					
					// Complete with one row of data
					reportData.add( row );
				}
			}
			processGrandTotal( data, reportData, config );
		}
		catch( ServiceException e ) {
			throw new ReportDataException("Failed to get report data", e); 
		}
		return reportData;
	}

	/**
	 * @param code
	 * @return
	 * @throws Exception
	 */
	protected String getLookupDescription( AbstractCode code ) throws ServiceException {
		MilitarySexualTraumaStatus status = this.lookupService.getMilitarySexualTraumaStatusByCode( code.getName() );
		return status != null ? status.getDescription() : null;
	}
	
	/**
	 * @param statusDate
	 * @return
	 */
	protected String getPeriod( Date statusDate, ReportConfiguration config ) {
		
		String period = null;
		String code = (String)config.getResourceMapping().get( CommonCriteria.REPORT_TYPE_CODE );
		if( ReportPeriodType.CODE_ALL_FISCAL_YEARS.getCode().equals( code ) ) {
			period = String.valueOf( DateUtils.getFiscalYear( statusDate ) ); 
		}
		else if( ReportPeriodType.CODE_FISCAL_YEAR.getCode().equals( code ) || 
               ReportPeriodType.CODE_QUARTERLY.getCode().equals( code ) ) {
			period = DateUtils.getQuarterAsString( statusDate ) + ": " + DateUtils.getQuarterDateRangeAsString( statusDate );
		}
		else if( ReportPeriodType.CODE_DATE_RANGE.getCode().equals( code ) ) {
			DateFormat df = DateFormat.getDateInstance( DateFormat.MEDIUM );
			Date startDate = (Date)config.getResourceMapping().get( CommonCriteria.START_DATE );
			Date endDate = (Date)config.getResourceMapping().get( CommonCriteria.END_DATE );
			period = df.format( startDate ) + " - " + df.format( endDate );
		}
		return period;
	}

	/**
	 * @param data
	 * @param code
	 * @param codeValue
	 * @return
	 */
	protected Map getPercentageOfScreenedVetsByPeriod( Map data, String desc, String codeValue ) {
		
		Map row = new HashMap();
		
		// Calculate percentage of MST positive and negative
		Map status = (Map)data.get( desc );
		AbstractCode gender = Gender.MALE;
		Set vets = status != null ? (Set)status.get( gender.getName() ) : null;
		int subtotal = (vets != null) ? vets.size() : 0;
		double percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetByGenderAndPeriod( data, gender )) : 0;
		row.put( ("male_" + codeValue), new Double( percentage ) );
		
		gender = Gender.FEMALE;
		vets = status != null ? (Set)status.get( gender.getName() ) : null;
		subtotal = (vets != null) ? vets.size() : 0;
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetByGenderAndPeriod( data, gender )) : 0;
		row.put( ("female_" + codeValue), new Double( percentage ) );
		
		gender = Gender.OTHER;
		vets = status != null ? (Set)status.get( gender.getName() ) : null;
		subtotal = (vets != null) ? vets.size() : 0;
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetByGenderAndPeriod( data, gender )) : 0;
		row.put( ("other_" + codeValue), new Double( percentage ) );
		
		gender = Gender.UNKNOWN;
		vets = status != null ? (Set)status.get( gender.getName() ) : null;
		subtotal = (vets != null) ? vets.size() : 0;
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetByGenderAndPeriod( data, gender )) : 0;
		row.put( ("unknown_" + codeValue), new Double( percentage ) );
		
		return row;
	}
	
	/**
	 * @param data
	 * @param reportData
	 * @throws ServiceException
	 */
	protected void processGrandTotal( Map data, Collection reportData, ReportConfiguration config ) throws ServiceException {
		
		// If there are more than one fiscal years, add them up here
		if( data.size() > 1 ) {
			
			String period = ((TreeMap)data).firstKey() + " - " + ((TreeMap)data).lastKey();
			String code = (String)config.getResourceMapping().get( CommonCriteria.REPORT_TYPE_CODE );
			if( ReportPeriodType.CODE_FISCAL_YEAR.getCode().equals( code ) ) {
				Date startDate = (Date)config.getResourceMapping().get( CommonCriteria.START_DATE );
				period = "Fiscal Year " + String.valueOf( DateUtils.getFiscalYear( startDate ) );
			}

			int total = this.getTotalVets( data );
			for( int i=0; i<statusCodes.length; i++) {
				Map row = new HashMap();
				row.put( "status", getLookupDescription( statusCodes[i] ) );
				
				// Get a total of male
				int male = this.getTotalVetsByGenderAndStatus( data, statusCodes[i], Gender.MALE );
				row.put( "male", new Integer(male) );
				
				// Get a total of female
				int female = this.getTotalVetsByGenderAndStatus( data, statusCodes[i], Gender.FEMALE );
				row.put( "female", new Integer(female) );
				
				// Get a total of other
				int other = this.getTotalVetsByGenderAndStatus( data, statusCodes[i], Gender.OTHER );
				row.put( "other", new Integer(other) );
				
				// Get a total of unknown
				int unknown = this.getTotalVetsByGenderAndStatus( data, statusCodes[i], Gender.UNKNOWN );
				row.put( "unknown", new Integer(unknown) );
				
				// Subtotal
				int subtotal = male + female + other + unknown;
				row.put( "subtotal", new Integer( subtotal ) );
				
				// Percentage 
				double percentage = ( ((double)subtotal) / ((double)total) );
				row.put( "percentage", new Double( percentage ) );
				
				// A report period - from input param
				row.put( "period", period );
				
				// Populate MST reported positive
				row.putAll( this.getPercentageOfScreenedVets( data, MilitarySexualTraumaStatus.CODE_SCREENED_REPORTS_MST, "positive") );
				
				// Populate MST reported negative
				row.putAll( this.getPercentageOfScreenedVets( data, MilitarySexualTraumaStatus.CODE_SCREENED_DOES_NOT_REPORT_MST, "negative") );
				
				reportData.add( row );
			}
		}
	}

	/**
	 * @param data
	 * @param code
	 * @param codeValue
	 * @return
	 * @throws ServiceException
	 */
	protected Map getPercentageOfScreenedVets( Map data, MilitarySexualTraumaStatus.Code code, String codeValue ) throws ServiceException {
		
		Map row = new HashMap();
		
		int subtotal = this.getTotalVetsByGenderAndStatus( data, code, Gender.MALE );
		double percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetsByGender( data, Gender.MALE )) : 0;
		row.put( ("male_" + codeValue), new Double( percentage ) );
		
		subtotal = this.getTotalVetsByGenderAndStatus( data, code, Gender.FEMALE );
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetsByGender( data, Gender.FEMALE )) : 0;
		row.put( ("female_" + codeValue), new Double( percentage ) );
		
		subtotal = this.getTotalVetsByGenderAndStatus( data, code, Gender.OTHER );
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetsByGender( data, Gender.OTHER )) : 0;
		row.put( ("other_" + codeValue), new Double( percentage ) );
		
		subtotal = this.getTotalVetsByGenderAndStatus( data, code, Gender.UNKNOWN );
		percentage = subtotal > 0 ? ((double)subtotal)/((double)getTotalVetsByGender( data, Gender.UNKNOWN )) : 0;
		row.put( ("unknown_" + codeValue), new Double( percentage ) );
		
		return row;
	}
	
	/**
	 * @param data
	 * @param code
	 * @return
	 */
	protected int getTotalVetByGenderAndPeriod( Map data, AbstractCode code ) {
		
		int total = 0;
		for( Iterator i=data.entrySet().iterator(); i.hasNext(); ) {
			Map.Entry entry = (Map.Entry)i.next();
			Object value = entry.getValue(); 
			Map genders = (Map)value;
			Collection vets = (Collection)genders.get( code.getName() );
			total += (vets != null) ? vets.size() : 0;
		}
		return total;
	}
	
	/**
	 * @param data
	 * @return
	 */
	protected int getTotalVetsByPeriod( Map data ) {
		int total = 0;
		for( Iterator i=data.entrySet().iterator(); i.hasNext(); ) {
			Map.Entry entry = (Map.Entry)i.next();
			Map genders = (Map)entry.getValue();
			for( Iterator iter=genders.entrySet().iterator(); iter.hasNext(); ) {
				Map.Entry e = (Map.Entry)iter.next();
				Object value = e.getValue();
				total += (value instanceof Collection) ? ((Collection)value).size() : 0;
			}
		}
		return total;
	}
	
	/**
	 * @param data
	 * @return
	 */
	protected int getTotalVets( Map data ) {
		int total = 0;
		for( Iterator iter=data.entrySet().iterator(); iter.hasNext(); ) {
			Map.Entry entry = (Map.Entry)iter.next();
			total += this.getTotalVetsByPeriod( (Map)entry.getValue() );
		}
		return total;
	}
	
	/**
	 * @param data
	 * @param statusCode
	 * @param genderCode
	 * @return
	 * @throws ServiceException
	 */
	protected int getTotalVetsByGenderAndStatus( Map data, 
																MilitarySexualTraumaStatus.Code statusCode,
																Gender.Code genderCode ) throws ServiceException {
		int total = 0;
		for( Iterator i=data.entrySet().iterator(); i.hasNext(); ) {
			Map.Entry entry = (Map.Entry)i.next();
			Map statuses = (Map)entry.getValue();
			Map genders = (Map)statuses.get( this.getLookupDescription( statusCode ) );
			if( genders != null ) {
				Collection vets = (Collection)genders.get( genderCode.getName() );
				total += vets != null ? vets.size() : 0;
			}
		}
		return total;
	}
	
	/**
	 * @param data
	 * @param genderCode
	 * @return
	 */
	protected int getTotalVetsByGender( Map data, Gender.Code genderCode ) {
		int total = 0;
		for( Iterator i=data.entrySet().iterator(); i.hasNext(); ) {
			Map.Entry entry = (Map.Entry)i.next();
			Map statuses = (Map)entry.getValue();
			for( Iterator iter=statuses.entrySet().iterator(); iter.hasNext(); ) {
				Map.Entry e = (Map.Entry)iter.next();
				Map genders = (Map)e.getValue();
				Collection vets = (Collection)genders.get( genderCode.getName() );
				total += vets != null ? vets.size() : 0;
			}
		}
		return total;
	}
   
   protected Date getDate( ReportConfiguration config, boolean isStartDate ) {
      
      Date date = null;
      QueryCriteria reportCriteria = config.getQueryCriteria();
      if( reportCriteria instanceof StandardReportCriteria ) {
         
         // Get a criteria
         StandardReportCriteria criteria = (StandardReportCriteria)reportCriteria;
         ReportSetup setup = criteria.getReportSetup();
         ReportParameterSet parameters = setup.getParameterSet();
         
         // Get a report period
         ReportPeriod period = parameters.getReportPeriod();
         ReportPeriodType reportPeriodType = period.getType();
         String reportPeriod = reportPeriodType.getCode();
         ReportSchedule schedule = setup!=null?setup.getSchedule():null;
 		 String frequency = schedule!=null?schedule.getRunFrequency().getCode():null;
 		 //If it is scheduled report and run frequency is not other get dates from from and to dates.
 		if (frequency!=null && !ReportRunFrequency.OTHER.getCode().equals(frequency) )
 		{
 			return super.getDate(config, isStartDate);
 		}
         if( ReportPeriodType.CODE_ALL_FISCAL_YEARS.getCode().equals( reportPeriod ) ) {
        // Start date is always 10/1/2002 and end date is the last date of
        // a fiscal year's fourth quarter for a summary report view
        	 if( isStartDate ) {
               date = DateUtils.createCalendar( 2002, 10, 1 ).getTime();
            }
            else {
               int year = period.getYear().intValue();
               date = DateUtils.getFiscalYearFourthQuarter( year, false ).getTime();
            }
         }
         else if( ReportPeriodType.CODE_FISCAL_YEAR.getCode().equals( reportPeriod ) ) {
            int year = period.getYear().intValue();
            if( isStartDate ) {
               date = DateUtils.getFiscalYearFirstQuarter( year, true ).getTime();
            }
            else {
               date = DateUtils.getFiscalYearFourthQuarter( year, false ).getTime();
            }
         }
         else if( ReportPeriodType.CODE_QUARTERLY.getCode().equals( reportPeriod ) ) {
            int year = period.getYear().intValue();
            String quarter = period.getQuarter().getCode();
            if( isStartDate ) {
               date = DateUtils.getFiscalYearQuarter( year, true, quarter ).getTime();
            }
            else {
               date = DateUtils.getFiscalYearQuarter( year, false, quarter ).getTime();
            }
         }
         else if( ReportPeriodType.CODE_DATE_RANGE.getCode().equals( reportPeriod ) ) {
            date = isStartDate ? period.getFromDate() : period.getToDate();
         }
      }
      return date;
   }
   
   /**
    * @see gov.va.med.esr.common.report.data.MSTReportDataDAO#buildMSTStatusCriteria(gov.va.med.fw.report.ReportConfiguration)
    */
   protected Query buildQuery(ReportConfiguration config, Session session ) throws ReportDataException {
   
      Query query = super.buildQuery(config, session );
      query.setDate( CommonCriteria.START_DATE, this.getDate( config, true ) );
      query.setDate( CommonCriteria.END_DATE, this.getDate( config, false ) );
      return query;
   }
}