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

// Java classes
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;


// Library classes
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.Validate;
import org.springframework.context.MessageSource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.MailException;

// Framework classes
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.report.ReportConfiguration;
import gov.va.med.fw.report.ReportException;
import gov.va.med.fw.report.ReportPersister;
import gov.va.med.fw.report.data.QueryCriteria;
import gov.va.med.fw.security.UserPrincipal;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.mail.MailService;
import gov.va.med.fw.model.lookup.AbstractNamedLookup;

// ESR classes
import gov.va.med.esr.common.persistent.report.ReportDAO;
import gov.va.med.esr.common.model.lookup.ReportPeriodType;
import gov.va.med.esr.common.model.lookup.ReportRunFrequency;
import gov.va.med.esr.common.model.lookup.StandardReport;
import gov.va.med.esr.common.model.report.BaseReportLookupParameter;
import gov.va.med.esr.common.model.report.CompletedReport;
import gov.va.med.esr.common.model.report.ReportPeriod;
import gov.va.med.esr.common.model.report.ReportSetup;
import gov.va.med.esr.common.model.report.ReportParameterSet;
import gov.va.med.esr.common.model.security.ESRUserPrincipal;

/**
 * 
 *
 * Project: Common</br>
 * Created on: 11:10:56 AM </br>
 *
 * @author DNS   LEV
 */
public class DatabaseReportPersister extends AbstractComponent implements ReportPersister {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 8051680457792536251L;

	public static final String NEW_LINE="\n";
    public static final String COLON=": ";
    public static final String COMMA=", ";
    public static final String SPACE=" ";
   
	/**
	 * An instance of reportDAO
	 */
	private ReportDAO reportDAO = null;
   
   /**
    * An instance of mailService
    */
   private MailService mailService = null;
   
   /**
    * An instance of messageTemplate
    */
   private SimpleMailMessage messageTemplate = null;
   
   private ReportParameterConfig reportParameterConfig;
   
   private MessageSource messageSource;

    private String esrWebUrl = null;

    public MessageSource getMessageSource() {
	return messageSource;
}

public void setMessageSource(MessageSource messageSource) {
	this.messageSource = messageSource;
}

	/**
	 * A default constructor
	 */
	public DatabaseReportPersister() {
		super();
	}

	/**
	 * @see gov.va.med.fw.service.AbstractComponent#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		Validate.notNull( reportDAO, "A report DAO must be configured" );
      Validate.notNull( mailService, "A mail service must be configured" );
        Validate.notNull(esrWebUrl, "The ESR Web URL must be configured.");

      Validate.notNull( messageTemplate, "A message template must be configured" );
	}

   /**
    * @param serivce The MailService to set
    */
   public void setMailService( MailService mailService ) {
      this.mailService = mailService;
   }
     
	/**
	 * @param reportDAO The reportDAO to set.
	 */
	public void setReportDAO(ReportDAO reportDAO) {
		this.reportDAO = reportDAO;
	}

    public String getEsrWebUrl()
    {
        return esrWebUrl;
    }

    public void setEsrWebUrl(String esrWebUrl)
    {
        this.esrWebUrl = esrWebUrl;
    }

    /**
    * @param reportDAO The reportDAO to set.
    */
   public void setMessageTemplate( SimpleMailMessage template ) {
      this.messageTemplate = template;
   }
   public ReportParameterConfig getReportParameterConfig() {
       return reportParameterConfig;
   }

   public void setReportParameterConfig(ReportParameterConfig reportParameterConfig) {
       this.reportParameterConfig = reportParameterConfig;
   }
   
	/**
	 * @see gov.va.med.fw.report.ReportPersister#persist(gov.va.med.fw.report.ReportConfiguration, java.io.ByteArrayOutputStream)
	 */
	public void persist(ReportConfiguration config, ByteArrayOutputStream stream)	throws ReportException {

      // Clone the byte[] to allow an e-mail to attach a report as a byte[] stream
      byte[] content = (byte[])stream.toByteArray().clone();
     
      CompletedReport report = createCompletedReport( config, stream ); 
      report.setReportParameterText(getConfigParams(config));
      // Step 1: Save a completed report
      try {
			reportDAO.saveCompletedReport( report );
		}
		catch( DAOException e ) {
			throw new ReportException( "Failed to persist a report", e );
		}
      
      // Step 2: Send notification
      try {
         sendNotificationMail( config, report, content );
      }
      catch( MailException e ) {
         // Do nothing here just log exception
         if( logger.isInfoEnabled() ) {
            logger.info( "Failed to send notification message", e );
         }
      }
	} 
	
   /**
    * @param config
    * @param report
    * @param content
    * @throws MailException
    */
   protected void sendNotificationMail( ReportConfiguration config, CompletedReport report, byte[] content ) throws MailException
   {
      // Extract data from a report configuration
      QueryCriteria criteria = config.getQueryCriteria();
      StandardReportCriteria reportCriteria = criteria instanceof StandardReportCriteria ? (StandardReportCriteria)criteria : null;
      ReportSetup reportSetup = reportCriteria != null ? reportCriteria.getReportSetup() : null;
      
      // Step 1: Send out an e-mail if e-mail addresses are specified
      // Send out an e-mail with a report attachment if e-mail is present
      String email = (reportSetup != null) ? reportSetup.getEmail() : "";
      
      if( !StringUtils.isBlank( email ) ) {

         // Build a message text
         StandardReport generatedReport = (reportSetup != null) ? reportSetup.getReport() : null;
         String code = (generatedReport != null) ? generatedReport.getCode() : "";
         String desc = (generatedReport != null) ? generatedReport.getDescription() : "";
         String reportType = (generatedReport != null && generatedReport.getType() != null) ? generatedReport.getType().getDescription() : "";
         StringBuffer text = new StringBuffer();
         text.append("The following HECMS report is available for your viewing:");
         text.append(NEW_LINE + "Report Type: ").append(reportType);
         text.append(NEW_LINE + "Report ID: ").append(code);
         text.append(NEW_LINE + "Report Title: ").append(desc);
         text.append(NEW_LINE + "Date Run: ").append(report.getCompletedDate());
         text.append(NEW_LINE + NEW_LINE + "Please access the HECMS via this weblink '").append(esrWebUrl).append("' to login into the system.");
         text.append(NEW_LINE + NEW_LINE + "If you have any questions please call the HEC for assistance.");

         // Set a message recipient list.  An e-mail string is in a comma-delimited format
         SimpleMailMessage message = new SimpleMailMessage(this.messageTemplate);
         message.setTo(StringUtils.split(email, ","));
         
         // Set the e-mail subject.
         StringBuffer subject = new StringBuffer("HEC Notification of Report Available for Viewing");
         subject.append(" - ").append(code).append(" - ").append(desc).append(".");
         message.setSubject(subject.toString());
         
         // Send a message
         this.mailService.send(text.toString(), message);
         if (logger.isInfoEnabled())
         {
            logger.info( "Sending generated reports " + ((generatedReport != null) ? generatedReport.getDescription() : "") + " to " + email);
         }
      }
   }
	
	/**
	 * @param config
	 * @param stream
	 * @return
	 * @throws ReportException
	 */
	protected CompletedReport createCompletedReport( ReportConfiguration config, ByteArrayOutputStream stream ) throws ReportException {
		
		// Extract data from a report configuration
		QueryCriteria criteria = config.getQueryCriteria();
		StandardReportCriteria reportCriteria = criteria instanceof StandardReportCriteria ? (StandardReportCriteria)criteria : null;
		ReportSetup reportSetup = reportCriteria != null ? reportCriteria.getReportSetup() : null;
		
		// Get a report user
		UserPrincipal user = reportCriteria != null ? reportCriteria.getReportUser() : null;
		ESRUserPrincipal esr_user = user instanceof ESRUserPrincipal ? (ESRUserPrincipal)user : null;
		ReportParameterSet reportParam = reportSetup != null ? reportSetup.getParameterSet() : null;
		
		// Populate a completed report object
		CompletedReport report = new CompletedReport();
		report.setCompletedDate( Calendar.getInstance().getTime() );
		report.setUser( esr_user );
		report.setStandardReport( (reportSetup != null) ? reportSetup.getReport() : null );
		report.setFileType( (reportParam != null) ? reportParam.getFileType() : null );
		
		try {
         // Set a generated report to a CompletedReport for persistence
			stream.flush();
			report.setReportFileContent( stream.toByteArray() );
			stream.close();
		}
		catch( IOException e ) {
			throw new ReportException( "Failed to write a report to a stream ", e );
		}
		return report;
	}
    
	 /**
	  * 
	  * @param config
	  * @return
	  */
    
    private String getConfigParams(ReportConfiguration config)
    {
    	Map parameterMap=this.getReportParameterConfig()!=null?this.getReportParameterConfig().getParameterMap():null;
    	Map reportIDParamMap=parameterMap!=null && config!=null?(Map) parameterMap.get(config.getReportName()):null;
    	QueryCriteria criteria = config.getQueryCriteria();
		StandardReportCriteria reportCriteria = criteria instanceof StandardReportCriteria ? (StandardReportCriteria)criteria : null;
		ReportSetup reportSetup = reportCriteria != null ? reportCriteria.getReportSetup() : null;
    	return reportIDParamMap!=null && reportSetup!=null ?getConfigParams(reportSetup,reportIDParamMap,this.getMessageSource()):null;
    }
   
    /**
     * 
     * @param reportSetup
     * @param displayParams
     * @return
     */
    private String getConfigParams(ReportSetup reportSetup,  Map repParamsMap,MessageSource messageResources)
    {    	
    	if(reportSetup==null || repParamsMap==null || messageResources==null)
    		return null;
    	
    	StringBuffer strBuff=new StringBuffer();
    	String dispProperty=null;
    	 
    	for (Iterator it=repParamsMap.keySet().iterator();it.hasNext();)
    	{
    		String key=(String)it.next();
    		ParameterConfig property=(ParameterConfig)repParamsMap.get(key);    		
    		if(property != null)
    		{    			    		
	    		try {
	    			dispProperty=messageResources.getMessage(property.getLabel(),null,Locale.getDefault());
	    			Object obj=PropertyUtils.getProperty(reportSetup,property.getParameterName());
	    			if (property.getParameterName()!=null && property.isDisplayParameter() && obj!=null)
	    			{		    			
		    			strBuff.append(dispProperty).append(COLON);
		    			strBuff.append(getParamValue(obj,NEW_LINE)); 
	    			}
	    		}catch (Exception e)
	    		{
	    			
	    		}
    		}
    	}
    	ReportRunFrequency frequency=reportSetup.getSchedule()!=null?reportSetup.getSchedule().getRunFrequency():null;
    	if(frequency!=null)
    		strBuff.append("Schedule Type: ").append(getParamValue(frequency,NEW_LINE));      	
    	return strBuff.toString();
    }
    
    /**
     * 
     * @param obj
     * @param strBuff
     * @return
     */
    private String getParamValue(Object obj, String separator)
    {
    	StringBuffer str=new StringBuffer();
    	if (obj==null)
    		return "";
    	if(obj instanceof Date)
    		str.append(DateUtils.format((Date)obj, DateUtils.MMDDYYYY)).append(separator);
    	else if(obj instanceof AbstractNamedLookup){
    		String name=((AbstractNamedLookup)obj).getName();
    		name=(name==null)?((AbstractNamedLookup)obj).getDescription():name;
			str.append(name).append(separator);
    	}
		else if(obj instanceof BaseReportLookupParameter){			
			 str.append(((BaseReportLookupParameter)obj).getLookup().getDescription()).append(separator);
		}else if (obj instanceof ReportPeriod)
		{
			str=getReportPeriodParamValue((ReportPeriod)obj);			
		}
		else if (obj instanceof Collection)
		{
			Collection collection=(Collection)obj;			
			int size=collection.size();
			int index=0;
			for(Iterator iter=collection.iterator();iter.hasNext();)
			{
				index++;
				if(index<size)
					str.append(getParamValue(iter.next(),COMMA));
				else 
					str.append(getParamValue(iter.next(),NEW_LINE));
			}			 
		}
		else
			 str.append(obj.toString()).append(NEW_LINE);      	
    	
    	return str.toString();
    }  
    
    /**
     * 
     * @param obj
     * @return
     */
    private StringBuffer getReportPeriodParamValue(ReportPeriod obj)
    {
    	StringBuffer str=new StringBuffer();
    	if(obj!=null)
    	{
    		ReportPeriodType reportPeriodType = obj.getType();
            String reportPeriod = reportPeriodType.getCode();    		
    		
             if( ReportPeriodType.CODE_ALL_FISCAL_YEARS.getCode().equals( reportPeriod ) ||
            		 ReportPeriodType.CODE_FISCAL_YEAR.getCode().equals( reportPeriod )) { 
            	 str.append(getParamValue( reportPeriodType, COLON));
            	 str.append(obj.getYear()).append(NEW_LINE);
             }
             else if( ReportPeriodType.CODE_QUARTERLY.getCode().equals( reportPeriod ) ) {
            	 str.append(getParamValue( reportPeriodType, COLON));
            	 str.append(obj.getYear()).append(SPACE);
            	 str.append(getParamValue(obj.getQuarter(),SPACE)).append("Quarter");
             }
             else if( ReportPeriodType.CODE_DATE_RANGE.getCode().equals( reportPeriod ) &&
            		 obj.getFromDate()!=null &&obj.getToDate()!=null ) {
            	 str.append(getParamValue( reportPeriodType, COLON));
            	 str.append(DateUtils.format(obj.getFromDate(), DateUtils.MMDDYYYY)).append(" - ");
            	 str.append(DateUtils.format(obj.getToDate(), DateUtils.MMDDYYYY)).append(NEW_LINE);
             }
          }
    	return str;
    }
}