/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
// Package
package gov.va.med.fw.report;

// Java classes
import java.util.Map;

// Library classes
import org.apache.commons.lang.Validate;

// Framework classes
import gov.va.med.fw.report.data.ReportData;
import gov.va.med.fw.report.data.ReportDataService;
import gov.va.med.fw.service.AbstractComponent;

// ESR classes

/**
 * Allows concrete report builder implementations to be configured through spring
 * context file
 *
 * Project: Framework</br>
 * Created on: 4:59:46 PM </br>
 *
 * @author DNS   LEV
 */
public class ConfigurableReportBuilder extends AbstractComponent implements ReportBuilder {

	public static final String DATA_SERVICE_TAG = ".dataService";
	public static final String REPORT_TEMPLATE_TAG = ".reportTemplate";
	
	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 8201546777696454757L;

	/**
	 * A map containing all report templates
	 */
	private Map reportTemplates = null;
	
	/**
	 * A map containing all report data service names
	 */
	private Map reportDataServices = null;
	
	/**
	 * A default constructor
	 */
	public ConfigurableReportBuilder() {
		super();
	}

	/**
	 * @param reportDataServices The reportDataServices to set.
	 */
	public void setReportDataServices(Map reportDataServices) {
		this.reportDataServices = reportDataServices;
	}

	/**
	 * @param reportTemplates The reportTemplates to set.
	 */
	public void setReportTemplates(Map reportTemplates) {
		this.reportTemplates = reportTemplates;
	}
	
	/**
	 * @see gov.va.med.fw.service.AbstractComponent#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		Validate.notEmpty( this.reportDataServices, "A report data service map must be configured" );
		Validate.notEmpty( this.reportTemplates, "A report template map must be configured" );
	}
	
	/**
	 * @see gov.va.med.fw.report.ReportBuilder#buildReport(gov.va.med.fw.report.ReportConfiguration)
	 */
	public void buildReport(ReportConfiguration configuration) throws ReportException {
		this.buildReport( configuration, this.doRequestData( configuration ) );
	}

	/**
	 * @see gov.va.med.fw.report.ReportBuilder#buildReport(gov.va.med.fw.report.ReportConfiguration, gov.va.med.fw.report.data.ReportData)
	 */
	public void buildReport(ReportConfiguration configuration, ReportData data) throws ReportException {
		this.doBuildReport( configuration, data );
	}

	/**
	 * @param configuration
	 * @return
	 * @throws ReportException
	 */
	protected ReportData doRequestData( ReportConfiguration configuration ) throws ReportException {
		
		// If a service is configured to obtain a report's data, use it to request for data
		ReportDataService service = this.getReportDataService( configuration );
		ReportData data = service != null ? service.requestReportData( configuration ) : null;

		return data;
	}
	
	/**
	 * @param configuration
	 * @param data
	 * @return
	 * @throws ReportException
	 */
	protected void doBuildReport( ReportConfiguration configuration, ReportData data ) throws ReportException {

		ReportExporter exporter = this.getReportExporter( configuration.getReportOutputType() ); 
		exporter.exportReport( configuration, data, this.getReportTemplate( configuration ) );
	}

	/**
	 * @param format
	 * @return
	 * @throws ReportException
	 */
	protected ReportExporter getReportExporter( ReportExportedType.Code type ) throws ReportException {
		
		Validate.notNull( type, "A report output type must not be NULL" );
		
		ReportExporter exporter = null;
		try {
         // A report export type lookup code is defined as follow in a database
         // CODE(1) DESCRIPTION(PDF) NAME(Report output file in portable document format)
         // CODE(2) DESCRIPTION(CSV) NAME(Report Output file in comma-separated value format)
			exporter = (ReportExporter)this.getComponent( type.getAlias(), ReportExporter.class );
		}
		catch( Exception e ) {
			throw new ReportException( "Failed to obtain a report exporter ", e );
		}
		return exporter;
	}
	
	/** Returns a service that is used to retrieve report data
	 * @param config A report configuration 
	 * @return ReportDataService a service for getting report data
	 */
	protected ReportDataService getReportDataService( ReportConfiguration config ) throws ReportException {
		String name = config.getReportName();
		if( !this.reportDataServices.containsKey( name ) ) {
			throw new ReportException("A report data service service is missing for report: " + name );
		}
		Object componentName = reportDataServices.get( name );
		return this.getReportDataService( (String)componentName );
	}
	
	/** Returns a service that is used to retrieve report data
	 * @param name A report's data service name 
	 * @return ReportDataService a service for getting report data
	 * @throws ReportException in case of error getting a report data
	 */
	protected ReportDataService getReportDataService( String name ) throws ReportException {
		ReportDataService service = null;
		try {
			Object tmp = this.getComponent( name );
			service = tmp instanceof ReportDataService ? (ReportDataService)tmp : null;
		}
		catch( Exception e ) {
			throw new ReportException("A report data service " + name + " is not defined", e ); 
		}
		return service;
	}

	/** Returns a class that encapsulates an actual report template
	 * @param config A report configuration 
	 * @return ReportTemplate a class that encapsulates an actual report template
	 * @throws ReportException In case of errors loading a template
	 */
	protected ReportTemplate getReportTemplate( ReportConfiguration config ) throws ReportException {

		String name = config.getReportName();
		if( !this.reportTemplates.containsKey( name ) ) {
			throw new ReportException("A report template is missing for report: " + name );
		}
		return this.getReportTemplate( (String)reportTemplates.get( name ) );	
	}
	
	/** Returns a class that encapsulates an actual report template
	 * @param name A template name
	 * @return ReportTemplate a class that encapsulates an actual report template
	 * @throws ReportException In case of errors loading a template
	 */
	protected ReportTemplate getReportTemplate( String name ) throws ReportException {
		
		ReportTemplate tpl = null;
		try {
			Object tmp = this.getComponent( (String)name );
			tpl = tmp instanceof ReportTemplate ? (ReportTemplate)tmp : null;
		}
		catch( Exception e ) {
			throw new ReportException("A report tpl " + name + " is not defined", e ); 
		}
		return tpl;
	}
}