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

// Java classes
import gov.va.med.esr.common.batchprocess.BatchProcessTriggerGroups;
import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.lookup.Capability;
import gov.va.med.esr.common.model.lookup.DayOfMonth;
import gov.va.med.esr.common.model.lookup.DayOfQuarter;
import gov.va.med.esr.common.model.lookup.DayOfWeek;
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.ReportScheduleType;
import gov.va.med.esr.common.model.lookup.StandardReport;
import gov.va.med.esr.common.model.lookup.SystemParameterUnit;
import gov.va.med.esr.common.model.lookup.WeekOfMonth;
import gov.va.med.esr.common.model.report.CompletedReport;
import gov.va.med.esr.common.model.report.ReportDayOfWeek;
import gov.va.med.esr.common.model.report.ReportParameterSet;
import gov.va.med.esr.common.model.report.ReportPeriod;
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.SimpleCompletedReport;
import gov.va.med.esr.common.model.security.ESRUserPrincipal;
import gov.va.med.esr.common.model.system.SystemParameter;
import gov.va.med.esr.common.persistent.report.ReportDAO;
import gov.va.med.esr.common.report.data.CommonExtractFileCriteria;
import gov.va.med.esr.common.report.data.StandardReportCriteria;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.ReportFilter;
import gov.va.med.esr.service.StandardReportService;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.model.UserPrincipalImpl;
import gov.va.med.fw.model.lookup.Lookup;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.NoRecordFoundException;
import gov.va.med.fw.report.ReportConfiguration;
import gov.va.med.fw.report.ReportException;
import gov.va.med.fw.report.ReportExportedType;
import gov.va.med.fw.report.ReportService;
import gov.va.med.fw.scheduling.AuditableScheduledProcess;
import gov.va.med.fw.scheduling.ScheduledProcessInvocationContext;
import gov.va.med.fw.scheduling.SchedulingService;
import gov.va.med.fw.scheduling.StatelessScheduledService;
import gov.va.med.fw.security.SecurityService;
import gov.va.med.fw.security.UserPrincipal;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.apache.commons.lang.Validate;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.SchedulerException;

/**
 * Provides services to generate and to query standard reports in ESR
 *
 * Project: Common</br> Created on: 12:00:00 PM </br>
 *
 * @author DNS   LEV
 */
public class StandardReportServiceImpl extends AbstractComponent implements StandardReportService, AuditableScheduledProcess {

    /**
     * Common constant strings
     */
    public static final String  ESR_COMMON_JOBS        = "esr.common.jobs";
    public static final String  ESR_COMMON_TRIGGERS    = BatchProcessTriggerGroups.DYNAMIC_BATCH_JOBS;
    public static final String  SCHEDULED_JOBS         = "scheduledJobs";
    public static final String DEFAULT_AUDIT_NAME = SCHEDULED_PROCESS_AUDIT_PREFIX + "ScheduledReport";
    public static final List<String> RETIRED_REPORTS = Arrays.asList("P8G 4", "EED 20",	"EED 5", "EED 6", "EED 7", "BOS 1", "COR 1", "EED 3", "EED 13",
    		"EED 14", "EED 15",	"EED 16", "EED 17",	"EED 18", "P8G 1", "P8G 2","P8G 3","P8G 4","FTI 1", "VOA 02", "HBO1", "QM 10", "QM 11", "QM 12",
    		"QM 13", "QM 17", "QM 19", "QM 20", "QM 21","QM 22","QM 23","QM 25","QM 26",
    		"QM 27","QM 28","QM 29","QM 3","QM 30","QM 7","QM 8","QM 9");

    /**
     * Constants used to form a cron expression
     */
    private static final String EMPTY_SPACE            = " ";
    private static final String PERIOD                 = ".";
    private static final String QUESTION_MARK          = "?";

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

    /**
     * An instance of reportDAO
     */
    private ReportDAO           reportDAO              = null;

    /**
     * An instance of reportService
     */
    private ReportService       reportService          = null;

    /**
     * An instance of lookupService
     */
    private LookupService       lookupService          = null;

    /**
     * An instance of schedulingService
     */
    private SchedulingService   schedulingService      = null;

    /**
     * An instance of scheduledReportJobName
     */
    private String              scheduledReportJobName = null;

    private SecurityService securityService;


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

    /**
     * @see gov.va.med.fw.service.AbstractComponent#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        Validate.notNull(this.reportDAO, "A report DAO must be configured");
        Validate.notNull(this.reportService, "A report service must be configured");
        Validate.notNull(this.lookupService, "A lookup service must be configured");
        Validate.notNull(this.schedulingService, "A scheduling service must be configured");
        Validate.notNull(this.securityService, "A securityService must be configured");
    }

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

    /**
     * @return The reportDAO
     */
    protected ReportDAO getReportDAO() {
    	return this.reportDAO;
    }

    /**
     * @param reportService
     *            The reportService to set.
     */
    public void setReportService(ReportService reportService) {
        this.reportService = reportService;
    }

    /**
     * @param lookupService
     *            The lookupService to set.
     */
    public void setLookupService(LookupService lookupService) {
        this.lookupService = lookupService;
    }

    /**
     * @returns schedulingService
     */
    public SchedulingService getSchedulingService() {
        return schedulingService;
    }

    /**
     * @param schedulingService
     */
    public void setSchedulingService(SchedulingService schedulingService) {
        this.schedulingService = schedulingService;
    }

    /**
     * @param scheduledReportJobName
     */
    public void setScheduledReportJobName(String scheduledReportJobName) {
        this.scheduledReportJobName = scheduledReportJobName;
    }

    /**
     * @see gov.va.med.esr.service.StandardReportService#getStandardReports(gov.va.med.fw.security.UserPrincipal)
     */
    public List getStandardReports() throws ServiceException {
        List result = null;
        List activeResult = new ArrayList();
        try {
            result = lookupService.getAllStandardReports();

            if (result != null) {
            	for (Iterator iter = result.iterator(); iter.hasNext();) {
                    StandardReport stdReport = (StandardReport) iter.next();
                    if (!RETIRED_REPORTS.contains(stdReport.getCode())) {
                        activeResult.add(stdReport);
                    }
                }

            }

        } catch (Exception ex) {
            throw new ServiceException(ex);
        }
        return activeResult;
    }

    /**
     * @see gov.va.med.esr.service.StandardReportService#getStandardReports(gov.va.med.fw.security.UserPrincipal)
     */
    public List getStandardReports(UserPrincipal user) throws ServiceException {

        Validate.notNull(user, "A user principal must not be null");
        List stdReports = new ArrayList();
        List all = this.getStandardReports();
        if (all != null) {
            for (Iterator iter = all.iterator(); iter.hasNext();) {
                StandardReport stdReport = (StandardReport) iter.next();
                if (this.isUserPermittedToScheduleRunDeleteArchive(user, stdReport)) {
                    stdReports.add(stdReport);
                }
            }
        }
        return stdReports;
    }


    /**
     * @see gov.va.med.esr.service.StandardReportService#getReportSetup
     */
    public ReportSetup getReportSetup(EntityKey identifier) throws ServiceException {

        Validate.notNull(identifier, "A user principal must not be null");
        ReportSetup result = null;
        try {
            result = reportDAO.getReportSetup(identifier);
        } catch (DAOException e) {
            throw new ServiceException("Failed to get a list of report setup with identifier "
                    + identifier.getKeyValueAsString(), e);
        }
        return result;
    }

    /**
     * @see gov.va.med.esr.service.StandardReportService#saveReportSetup
     */
    public void saveReportSetup(ReportSetup reportSetup) throws ServiceException {

        Validate.notNull(reportSetup, "ReportSetup to save must not be null");
        try {
            reportDAO.saveReportSetup(reportSetup);
        } catch (DAOException e) {
            throw new ServiceException("Failed to save report setup with identifier "
                    + reportSetup.getEntityKey().getKeyValueAsString(), e);
        }
    }



    /**
     * @see gov.va.med.esr.service.StandardReportService#getCompletedReport
     */
    public CompletedReport getCompletedReport(EntityKey identifier) throws ServiceException {

        Validate.notNull(identifier, "A user principal must not be null");
        CompletedReport result = null;
        try {
            result = reportDAO.getCompletedReport(identifier);
        } catch (DAOException e) {
            throw new ServiceException("Failed to get a list of completed report with identifier "
                    + identifier.getKeyValueAsString(), e);
        }
        return result;
    }


    /**
     * @see gov.va.med.esr.service.StandardReportService#getCompletedReport
     */
    public void saveCompletedReport(CompletedReport completedReport) throws ServiceException {

        Validate.notNull(completedReport, "CompletedReport to save must not be null");
        try {
            reportDAO.saveCompletedReport(completedReport);
        } catch (DAOException e) {
            throw new ServiceException("Failed to save completed report with identifier "
                    + completedReport.getEntityKey().getKeyValueAsString(), e);
        }
    }

	/* (non-Javadoc)
	 * @see gov.va.med.esr.service.StandardReportService#generateReport(java.util.Map)
	 */
	public void generateReport(Map reportData) throws ServiceException {
		if(!reportData.containsKey(REPORT_USER_ID) && !reportData.containsKey(REPORT_USER_LOGIN))
			throw new IllegalArgumentException("Either " + REPORT_USER_ID + " or " + REPORT_USER_LOGIN + " is required");
		if(!reportData.containsKey(REPORT_SETUP_ID))
			throw new IllegalArgumentException(REPORT_SETUP_ID + " is required");

		UserPrincipal user = null;
		if(reportData.containsKey(REPORT_USER_ID)) {
			user = this.securityService.getUserById(CommonEntityKeyFactory.createUserPrincipalEntityKey((String)
					reportData.get(REPORT_USER_ID)));
		} else {
			user = new UserPrincipalImpl((String) reportData.get(REPORT_USER_LOGIN),
					(String) reportData.get(REPORT_USER_PASSWORD));
			if(StringUtils.isBlank(user.getUserCredentials().getPassword()))
				user.getUserCredentials().setAnonymous(true);
		}
		ReportSetup setup = null;
		try {
			setup = this.reportDAO.getReportSetup(CommonEntityKeyFactory.createReportSetupEntityKey((String)
					reportData.get(REPORT_SETUP_ID)));
		} catch(DAOException e) {
			throw new ServiceException("Unable to retrieve ReportSetup from the database with id: " + reportData.get(REPORT_SETUP_ID));
		}
		computeScheduleStartEndDates(setup);
		generateReport(user, setup);
	}
    /**
     *
     * @param setup
     */
	private void computeScheduleStartEndDates(ReportSetup setup)throws ServiceException
	{

		ReportParameterSet paramsSet=setup!=null?setup.getParameterSet():null;
		ReportSchedule schedule = setup!=null?setup.getSchedule():null;
		String frequency = schedule!=null?schedule.getRunFrequency().getCode():"";
		Calendar [] startEndDates=null;
		//IV 4 doesn't need date parameters.
		if(StandardReport.CODE_IV_4.getCode().equals(setup.getReport().getCode()))
		{
				return;
		}// QM 10, set the report period type also.
		else if(StandardReport.CODE_QM_10.getCode().equals(setup.getReport().getCode()))
		{
			ReportPeriod reportPeriod = paramsSet!=null?paramsSet.getReportPeriod():null;
			if (reportPeriod==null) {
				reportPeriod=new ReportPeriod();
			}

			if (paramsSet != null) {
				paramsSet.setReportPeriod(reportPeriod);
			}

			try{
				if (ReportRunFrequency.QUARTERLY.getCode().equals(frequency)) {
					reportPeriod.setType((ReportPeriodType)this.lookupService.getReportPeriodTypeByCode(ReportPeriodType.CODE_QUARTERLY.getCode()));
				}else if (ReportRunFrequency.YEARLY.getCode().equals(frequency)) {
					reportPeriod.setType((ReportPeriodType)this.lookupService.getReportPeriodTypeByCode(ReportPeriodType.CODE_FISCAL_YEAR.getCode()));
				}else {
					reportPeriod.setType((ReportPeriodType)this.lookupService.getReportPeriodTypeByCode(ReportPeriodType.CODE_DATE_RANGE.getCode()));
				}
			}catch (Exception ex)
			{
				throw new ServiceException("Unable to set ReportPeriodType for the report ID: " + setup.getReport().getCode());
			}
		}
		if (ReportRunFrequency.DAILY_WEEKLY.getCode().equals(frequency)) {
			startEndDates=DateUtils.getStartEndDateForPreviousWeek(DateUtils.getCurrentDate());
		}else if (ReportRunFrequency.MONTHLY.getCode().equals(frequency)) {
			startEndDates=DateUtils.getStartEndDateForPreviousMonth(DateUtils.getCurrentDate());
		}else if (ReportRunFrequency.QUARTERLY.getCode().equals(frequency)) {
			startEndDates=DateUtils.getStartEndDateForPreviousQuarter(DateUtils.getCurrentDate());
		}else if (ReportRunFrequency.YEARLY.getCode().equals(frequency)) {
			startEndDates=DateUtils.getStartEndDateForPreviousFiscalYear(DateUtils.getCurrentDate());
		}
		//If report ID is QM 28 or QM 29 or QM 30, set start date to previous day.
		if(StandardReport.CODE_QM_28.getCode().equals(setup.getReport().getCode()) ||
				StandardReport.CODE_QM_29.getCode().equals(setup.getReport().getCode()) ||
				StandardReport.CODE_QM_30.getCode().equals(setup.getReport().getCode()))
		{
			if(startEndDates != null && startEndDates[0]!=null)
				startEndDates[0].setTime(DateUtils.getYesterdayDate());
		}
		if (startEndDates!=null && paramsSet!=null && startEndDates.length>1)
		{	//EED requires only AsOfDate.
			if (StandardReport.CODE_EED_3.getCode().equals(setup.getReport().getCode()))
			{
				paramsSet.setAsOfDate(startEndDates[1].getTime());
			}else
			{
				paramsSet.setFromDate(startEndDates[0].getTime());
				paramsSet.setToDate(startEndDates[1].getTime());
			}
		}
	}

    /**
     * @see gov.va.med.esr.service.StandardReportService#generateReport(gov.va.med.fw.security.UserPrincipal,
     *      gov.va.med.esr.common.model.report.ReportSetup)
     */
    public void generateReport(UserPrincipal user, ReportSetup setup) throws ServiceException {

        Validate.notNull(user, "A user must not be null");
        Validate.notNull(setup, "A report setup must not be null");

        // Creates a report configuration for a PDF format
        try {
            // Gets a report configuration
            ReportConfiguration config = this.getReportConfiguration(user, setup);

            if(config != null && logger.isInfoEnabled()) {
            	logger.info("Generating Report [" + config.getReportID() + "]");
            }

            // Generate a report with the specific report data set
            reportService.generateReport(config);
        } catch (IOException e) {
            throw new ServiceException("Failed to generate a report", e);
        }
    }

    /**
     * @see gov.va.med.esr.service.StandardReportService#generateOPPReport(gov.va.med.fw.security.UserPrincipal,
     *      gov.va.med.esr.common.report.data.CommonExtractFileCriteria)
     */
    public void generateOPPReport(UserPrincipal user, CommonExtractFileCriteria criteria)
            throws ServiceException {

        Validate.notNull(user, "A user must not be null");
        Validate.notNull(criteria, "An OPP report criteria must not be null");

        try {
            // Create a report setup with a type and output format
            ReportParameterSet param = new ReportParameterSet();
            param.setFileType(lookupService.getReportExportedTypeByCode(ReportExportedType.PDF.getCode()));
            String opp_type = criteria.getReportType().getCode();

            ReportSetup setup = new ReportSetup();
            setup.setReport(lookupService.getStandardReportByCode(opp_type));
            setup.setSetupUser(user instanceof ESRUserPrincipal ? (ESRUserPrincipal) user : null);
            setup.setParameterSet(param);

            // Populate a report criteria
            criteria.setReportSetup(setup);
            criteria.setReportUser(user);

            // Create a report config
            ReportConfiguration config = this.getReportConfiguration(user, setup, criteria);
            config.setResourceMapping(criteria.getCriteria());
            config.setQueryCriteria(criteria);

            // Generate a report with the specific report data set
            reportService.generateReport(config);
        } catch (IOException e) {
            throw new ServiceException("Failed to generate a report", e);
        }
    }

    /**
     * @see gov.va.med.esr.service.StandardReportService#scheduleReport(gov.va.med.fw.security.UserPrincipal,
     *      gov.va.med.esr.common.model.report.ReportSetup)
     */
    public void scheduleReport(UserPrincipal user, ReportSetup setup) throws ServiceException {

        Validate.notNull(setup, "ReportSetup to save must not be null");
        try {
            ReportSchedule schedule = setup.getSchedule();
            ReportScheduleType type = schedule.getScheduleType();
            if (type != null) {

                // Step 1: Persist a report set
                reportDAO.saveReportSetup(setup);

                // Step 2: Build a cron expression is in this format
                // seconds + minutes + hours + day of month + month + day of
                // week + year
                String schedule_type = schedule.getScheduleType().getCode();
                String frequency = schedule.getRunFrequency().getCode();

                String second = "0";
                String dayOfWeek = null;
                String dayOfMonth = null;
                String month = null;
                String year = "*";

                int hour_val = schedule.getHour().intValue();
                String hour = String.valueOf(hour_val == 24 ? 0 : hour_val);
                String minute = String.valueOf(schedule.getMinute().intValue());

                // User selects daily/weekly
                if (ReportRunFrequency.DAILY_WEEKLY.getCode().equals(frequency)) {
                    Set days = schedule.getDaysOfWeek(); // collection of
                                                            // report day of
                                                            // weeks
                    Iterator iterator = days != null ? days.iterator() : null;
                    while (iterator != null && iterator.hasNext()) {
                        ReportDayOfWeek dw = (ReportDayOfWeek) iterator.next();
                        if (dayOfWeek == null) {
                            dayOfWeek = DayOfWeek.Code.getByCode(dw.getDayOfWeek().getCode()).getAlias();
                        } else {
                            dayOfWeek += ("," + DayOfWeek.Code.getByCode(dw.getDayOfWeek().getCode())
                                    .getAlias());
                        }
                    }
                }
                // User selects monthly
                else if (ReportRunFrequency.MONTHLY.getCode().equals(frequency)) {
                    // and last/first/15th date of a month
                    if (ReportScheduleType.DAY_OF_MONTH.getCode().equals(schedule_type)) {
                        dayOfMonth = DayOfMonth.Code.getByCode(schedule.getDayOfMonth().getCode()).getAlias();
                    } else {
                        // get week of month and day of month
                        dayOfWeek = DayOfWeek.Code.getByCode(schedule.getDayOfWeek().getCode()).getAlias()
                                + WeekOfMonth.Code.getByCode(schedule.getWeekOfMonth().getCode()).getAlias();
                    }
                }
                // User selects quarterly
                else if (ReportRunFrequency.QUARTERLY.getCode().equals(frequency)) {
                    DayOfQuarter dq = schedule.getDayOfQuarter();
                    if (DayOfQuarter.CODE_FIRST.getCode().equals(dq.getCode())) {
                        // First days of every quarter are 10/1, 1/1, 4/1, 7/1
                        // (mm/dd)
                        month = "1,4,7,10";
                        dayOfMonth = "1";
                    } else {
                        // Last days of every quarter are 12/31, 3/31, 6/30,
                        // 9/30 (mm/dd)
                        month = "3,6,9,12";
                        dayOfMonth = "L";
                    }
                }
                // User selects yearly
                else if (ReportRunFrequency.YEARLY.getCode().equals(frequency)) {
                    // and day of month
                    if (ReportScheduleType.WEEK_DAY_MONTH.getCode().equals(schedule_type)) {
                        // get week of month
                        // get day of week
                        // get month
                        dayOfWeek = DayOfWeek.Code.getByCode(schedule.getDayOfWeek().getCode()).getAlias()
                                + WeekOfMonth.Code.getByCode(schedule.getWeekOfMonth().getCode()).getAlias();
                        month = schedule.getMonth().getCode();
                    } else {
                        month = schedule.getMonth().getCode();
                        dayOfMonth = schedule.getNumericDayOfMonth().toString();
                    }
                }
                // User selects other
                else if (ReportRunFrequency.OTHER.getCode().equals(frequency)) {
                    // get day to generate
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(schedule.getDateToGenerate());
                    year = String.valueOf(calendar.get(Calendar.YEAR));
                    //Month starts with 0.
                    month = String.valueOf(calendar.get(Calendar.MONTH)+1);
                    dayOfMonth = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
                }

                // seconds + minutes + hours + day of month + month + day of
                // week + year
                StringBuffer cron = new StringBuffer();
                cron.append(second).append(EMPTY_SPACE).append(minute).append(EMPTY_SPACE).append(hour)
                        .append(EMPTY_SPACE).append(dayOfMonth == null ? QUESTION_MARK : dayOfMonth).append(
                                EMPTY_SPACE).append(month == null ? "*" : month).append(EMPTY_SPACE).append(
                                dayOfWeek == null ? QUESTION_MARK : dayOfWeek).append(EMPTY_SPACE).append(
                                year);

                // Step 3: Get a job detail and populate with user and report
                // setup
                StringBuffer group_name = new StringBuffer(ESR_COMMON_JOBS);
                String name = getJobName(setup);

                JobDetail jobDetail = (JobDetail) this.getComponent(this.scheduledReportJobName);
                jobDetail.setName(name);
                jobDetail.setGroup(group_name.toString());

                // Populate dynamic arguments to pass to a method for invocation
                Map data = jobDetail.getJobDataMap();
                Map reportData = new HashMap();
                EntityKey userKey = user.getEntityKey();
                if(userKey != null) {
                	reportData.put(REPORT_USER_ID, userKey.getKeyValueAsString());
                } else {
	                reportData.put(REPORT_USER_LOGIN, user.getUserCredentials().getUserID());
	                reportData.put(REPORT_USER_PASSWORD, user.getUserCredentials().getPassword());
                }
                reportData.put(REPORT_SETUP_ID,setup.getEntityKey().getKeyValueAsString());
                data.put(StatelessScheduledService.ARGUMENTS_METHOD, new Object[] { reportData });
                this.schedulingService.getScheduler().addJob(jobDetail, true);

                // Step 4: Get a scheduling service to schedule a job
                group_name = new StringBuffer(ESR_COMMON_TRIGGERS);
                CronTrigger trigger = new CronTrigger();
                trigger.setName(name);
                trigger.setGroup(group_name.toString());
                trigger.setJobName(jobDetail.getName());
                trigger.setJobGroup(jobDetail.getGroup());
                trigger.setCronExpression(cron.toString());
                trigger.setStartTime(Calendar.getInstance().getTime());
                trigger.setTimeZone(TimeZone.getDefault());

                Date runtime = this.schedulingService.schedule(trigger);
                if (logger.isInfoEnabled()) {
                    logger.info("Schedule a report to run at " + runtime + " using cron expression: "
                            + cron.toString());
                }
            }
        } catch (DAOException e) {
            throw new ServiceException("Failed to save report setup", e);
        } catch (SchedulerException e) {
            throw new ServiceException("Failed to obtain a schedule report", e);
        } catch (ParseException e) {
            throw new ServiceException("Failed to build a schedule's cron expression", e);
        }
    }

    /**
	 * Construct the job name from ReportSetup object
	 *
	 * @param setup
	 * @return
	 */
	private String getJobName(ReportSetup setup) {
		StringBuffer name = new StringBuffer(SCHEDULED_JOBS);
		name.append(PERIOD).append(
				setup.getSetupUser().getUserCredentials().getUserID()).append(
				PERIOD).append(setup.getReport().getCode()).append(PERIOD)
				.append(setup.getEntityKey().getKeyValueAsString());
		return name.toString();
	}

    /*
	 * @see gov.va.med.esr.service.StandardReportService#cancelScheduledReport(gov.va.med.fw.model.EntityKey)
	 */
    public void cancelScheduledReport(EntityKey identifier, UserPrincipal user) throws NoRecordFoundException, ServiceException {

    	Validate.notNull(identifier, "ReportSetup identifier must not be null");
        Validate.notNull(identifier.getKeyValue(), "ReportSetup identifier key can not be null.");

        ReportSetup setup = null;

        try {
        	setup = reportDAO.getReportSetup(identifier);

        	if (setup == null) {
        		throw new NoRecordFoundException("ReportSetup does not exist for identifier: " + identifier.getKeyValueAsString());
        	}
        } catch (DAOException e) {
            throw new ServiceException("Failed to get report setup with identifier "
                    + identifier.getKeyValueAsString(), e);
        }

        try {
        	if(isUserPermittedToScheduleRunDeleteArchive(user,setup.getReport()))
        	{
	        	this.schedulingService.unschedule(getJobName(setup), ESR_COMMON_TRIGGERS);
	            reportDAO.deleteReportSetup(identifier);
        	}

         } catch (SchedulerException e) {
        	throw new ServiceException("Failed to unschedule triggers for report setup with identifier "
        			+ identifier.getKeyValueAsString(), e);
         } catch (DAOException e) {
             throw new ServiceException("Failed to delete report setup with identifier "
                     + identifier.getKeyValueAsString(), e);
        }
	}


 	/**
     * @param user
     * @param setup
     * @return
     * @throws IOException
     */
    protected ReportConfiguration getReportConfiguration(UserPrincipal user, ReportSetup setup)
            throws IOException {

        StandardReportCriteria criteria = new StandardReportCriteria();
        criteria.setReportSetup(setup);
        criteria.setReportUser(user);

        return this.getReportConfiguration(user, setup, criteria);
    }

    protected ReportConfiguration getReportConfiguration(UserPrincipal user, ReportSetup setup,
            StandardReportCriteria criteria) throws IOException {

        StandardReport report = setup.getReport();
        String id = report.getCode();
        String reportName = StringUtils.deleteWhitespace(id);

        ReportParameterSet parameterSet = setup.getParameterSet();
        ReportExportedType type = parameterSet != null ? parameterSet.getFileType() : null;
        ReportExportedType.Code code = type != null ? ReportExportedType.Code.getByCode(type.getCode())
                : null;

        String formatCode = getLookupDesc(parameterSet != null ? parameterSet.getReportFormat() : null);
        String detailBy = getLookupDesc(parameterSet != null ? parameterSet.getDetailBy() : null);
        String detailFor = getLookupDesc(parameterSet != null ? parameterSet.getDetailFor() : null);

        StringBuffer output = new StringBuffer();
        output.append(id).append("-").append(report.getDescription());
        if (formatCode != null) {
            output.append("-").append(formatCode);
        }
        if (detailBy != null) {
            output.append("-").append(detailBy);
        }
        if (detailFor != null) {
            output.append("-").append(detailFor);
        }
        output.append(".").append((code != null) ? code.getAlias().toLowerCase() : "");

        ReportConfiguration config = new ReportConfiguration(reportName, id, code, StringUtils.deleteWhitespace(output.toString()));
        config.setReportTitle(ReportConfiguration.DEFAULT_REPORT_TITLE + " - " + reportName + " - " + report.getDescription());
        config.setReportUser(user);
        config.setQueryCriteria(criteria);
        config.setResourceMapping(criteria.getCriteria());

        return config;
    }

    private String getLookupDesc(Lookup lookup) {
        return lookup != null ? lookup.getDescription() : null;
    }

    /**
     * Verifies whether user has run/schedule/archive/delete privilege.
     * @param user
     * @param report
     * @return
     */
    protected boolean isUserPermittedToScheduleRunDeleteArchive(UserPrincipal user, StandardReport report) {
        Set rptCaps = (report != null && report.getType() != null) ? report.getType().getCapabilities()
                : null;
        if (rptCaps != null) {
            for (Iterator capIter = rptCaps.iterator(); capIter.hasNext();) {
            	String code=(String)((Capability) capIter.next()).getCode();
            	if(Capability.SCHEDULE_RUN_REPORTS.indexOf(code)!=-1)
            	{
	                if (user.isPermissionGranted(code)) {
	                    return true;
	                }
            	}
            }
        }
        return false;
    }

    /**
     * Verifies whether user has privilege to inactivate a report.
     * @param user
     * @param report
     * @return
     */
    protected boolean isUserPermittedToInactivate(UserPrincipal user, SimpleCompletedReport report) {
        /* If the report is generated by same user or System Adminstrator
           and has a capability of Schedule/Run/Delete/Archive,
           return true.
         */
         if(report.getUser()!=null && user.getEntityKey().equals(report.getUser().getEntityKey()) ||
                user.isPermissionGranted(Capability.SYSTEM_ADMINISTRATOR.getCode()) )
        {
            return isUserPermittedToScheduleRunDeleteArchive(user, report.getStandardReport());
        }
        return false;
    }

    /**
     * Verifies whether user has privilege to delete a report.
     * @param user
     * @param report
     * @return
     */
    protected boolean isUserPermittedToDelete(UserPrincipal user, SimpleCompletedReport report) {

        /* If the report is generated by same user or System Adminstrator or if it a inactive report
           and has a capability of Schedule/Run/Delete/Archive,
           return true.
        */
         if(report.getUser()!=null && user.getEntityKey().equals(report.getUser().getEntityKey()) ||
                user.isPermissionGranted(Capability.SYSTEM_ADMINISTRATOR.getCode()) ||
                report.getInactivatedDate()!=null)
        {
            return isUserPermittedToScheduleRunDeleteArchive(user, report.getStandardReport());
        }
        return false;
    }

    /**
     * Verifies whether user has privilege to view a report.
     * @param user
     * @param report
     * @return
     */
    protected boolean isUserPermittedToView(UserPrincipal user, StandardReport report) {

    	if (isViewCLEARReport(report, user))
    		return true;

        Set rptCaps = (report != null && report.getType() != null) ? report.getType().getCapabilities()
                : null;
        if (rptCaps != null) {
            for (Iterator capIter = rptCaps.iterator(); capIter.hasNext(); ) {
            	String code=(String)((Capability) capIter.next()).getCode();
            	if(Capability.VIEW_ONLY_REPORTS.indexOf(code) != -1 )
            	{
	                if (user.isPermissionGranted(code)) {
	                    return true;
	                }
            	}
            }
        }
        return false;
    }

	//temporary fix for CLEAR report to share the same capability of VIEW_ENROLLMENT_REPORTS because
	//the RPT_TYPE (22, CLEAR Import) does not have RPT_PERMISIONS populated in DB yet
	private boolean isViewCLEARReport (StandardReport rep, UserPrincipal user){

		if (rep != null && rep.getType() != null &&
				(StandardReport.CODE_CLR_01.getCode().equals(rep.getCode()) ||
            	 StandardReport.CODE_CLR_02.getCode().equals(rep.getCode()) )  ) {

            if (user.isPermissionGranted(Capability.VIEW_ENROLLMENT_REPORTS.getCode())) {
    			//CLEAR reports added because capabilities are not added in time
    			//share as capability as Enrollment report type (per Magda)
                return true;
            } else {
            	return false;
            }
		}
		return false;
	}

    /*
     * @see gov.va.med.esr.service.StandardReportService#getSimpleCompletedReport(EntityKey identifier)
     */
    public SimpleCompletedReport getSimpleCompletedReport(EntityKey identifier) throws ServiceException {

        Validate.notNull(identifier, "SimpleCompletedReport identifier must not be null");
        SimpleCompletedReport result = null;
        try {
            result = getReportDAO().getSimpleCompletedReport(identifier);
        } catch (DAOException e) {
            throw new ServiceException("Failed to get a SimpleCompletedReport report with identifier "
                    + identifier.getKeyValueAsString(), e);
        }
        return result;
    }


     /*
     * @see gov.va.med.esr.service.StandardReportService#deleteCompletedReport(gov.va.med.fw.model.EntityKey, UserPrincipal user )
     */
    public void deleteCompletedReport(EntityKey identifier, UserPrincipal user) throws NoRecordFoundException, ServiceException {

        Validate.notNull(identifier, "CompletedReport identifier must not be null");
        Validate.notNull(identifier.getKeyValue(), "CompletedReport identifier key can not be null.");

        SimpleCompletedReport simpleCompletedReport = getSimpleCompletedReport(identifier);

        if (simpleCompletedReport == null) {
            throw new NoRecordFoundException("SimpleCompletedReport does not exist for identifier: " + identifier.getKeyValueAsString());
        }
        try {
            if(isUserPermittedToDelete(user,simpleCompletedReport))
            {
                getReportDAO().deleteCompletedReport(identifier);
            }
            else
                throw new ServiceException("User doesn't have permissions to delete a report: "
                        + user.getName());
         } catch (DAOException e) {
             throw new ServiceException("Failed to delete SimpleCompletedReport with identifier "
                     + identifier.getKeyValueAsString(), e);
        }
    }

    /*
     * @see gov.va.med.esr.service.StandardReportService#inactivateCompletedReport(gov.va.med.fw.model.EntityKey, UserPrincipal user )
     */
    public void inactivateCompletedReport(EntityKey identifier, UserPrincipal user) throws NoRecordFoundException,
            ServiceException, ReportException {

        Validate.notNull(identifier, "CompletedReport identifier must not be null");
        Validate.notNull(identifier.getKeyValue(), "CompletedReport identifier key can not be null.");

       CompletedReport completedReport = getCompletedReport(identifier);

        if (completedReport == null) {
            throw new NoRecordFoundException("CompletedReport does not exist for identifier: " + identifier.getKeyValueAsString());
        }
        // If it is an inactive report throw an exception
        if(completedReport.getInactivatedDate()!= null)
            throw new ReportException("This report is an inactive report" );
        try {
            //If user has permission to inactivate
            //Set inactivate date to sys date.
            if(isUserPermittedToInactivate(user,completedReport) )
            {
                completedReport.setInactivatedDate(new Date());
                getReportDAO().saveCompletedReport(completedReport);
            }else
                throw new ServiceException("User doesn't have permissions to inactivate a report:"
                        + user.getName());
         } catch (DAOException e) {
             throw new ServiceException("Failed to inactivate CompletedReport with identifier "
                     + identifier.getKeyValueAsString(), e);
        }
    }

    /*
     * @see gov.va.med.esr.service.StandardReportService#inactivateCompletedReports(String purgeQueryString,String auditInfo,
     *  SystemParameter systemParamete )
     */
    public int inactivateCompletedReports(String updatedQueryString, String auditInfo, SystemParameter systemParameter) throws ServiceException
    {
        int noOfRecordsUpdated=0;
        try
        {
            Date date=getSubtractedDate(systemParameter);
            if(date!=null)
            {
                Date sysDate=new Date();
                Object [] params={sysDate,sysDate,auditInfo,date};
                if (updatedQueryString != null)
                {
                    noOfRecordsUpdated= getReportDAO().bulkUpdate(updatedQueryString, params);
                }
            }
        }
        catch (DAOException e)
        {
            throw new ServiceException("Error during Inactivate Completed Reports data " + e);
        }

        return noOfRecordsUpdated;
    }

    /*
     * @see gov.va.med.esr.service.StandardReportService#purgeCompletedReports(String purgeQueryString,String auditInfo,
     * SystemParameter systemParameter )
     */
    public int purgeCompletedReports(String purgeQueryString,String auditInfo, SystemParameter systemParameter) throws ServiceException
    {
        int noOfRecordsUpdated=0;
        try
        {
            Date date=getSubtractedDate(systemParameter);
            if(date!=null)
            {
                Object [] params={date};
                if (purgeQueryString != null)
                {
                    noOfRecordsUpdated= getReportDAO().bulkUpdate(purgeQueryString, params);
                }
            }
        }
        catch (DAOException e)
        {
            throw new ServiceException("Error Purging Completed Reports data " + e);
        }

        return noOfRecordsUpdated;
    }

    /**
     *
     * @param parameter
     * @return
     */
    private Date getSubtractedDate(SystemParameter parameter)
    {
        if(parameter== null)
            return null;
        SystemParameterUnit unit = parameter.getUnit();
        Date date=null;
         if(SystemParameterUnit.CODE_DAYS.getCode().equals(unit.getCode())) {
             date = DateUtils.getAddedDate(Calendar.DATE, - Integer.parseInt(parameter.getValue()));
        } else if(SystemParameterUnit.CODE_WEEKS.getCode().equals(unit.getCode())) {
            date = DateUtils.getAddedDate(Calendar.WEEK_OF_MONTH, - Integer.parseInt(parameter.getValue()));
        } else if(SystemParameterUnit.CODE_MONTHS.getCode().equals(unit.getCode())) {
            date = DateUtils.getAddedDate(Calendar.MONTH, - Integer.parseInt(parameter.getValue()));
        }
       return date;
    }


    /*
     * @see gov.va.med.esr.service.StandardReportService#getCompletedReportFilter()
     */
    public ReportFilter getCompletedReportFilter() throws ServiceException
    {
        ReportFilter reportFilter=new ReportFilter();
        try
        {
            reportFilter.setStandardReports(getReportDAO().getDistinctStandardReports());
            reportFilter.setReportFileTypes(getReportDAO().getDistinctReportExportTypes());
            Map counts=getReportDAO().getActiveInactiveReportsCount();
            reportFilter.setActiveReportsCount(counts.containsKey("activeReportsCount")? ((Integer)counts.get("activeReportsCount")).intValue(): 0 );
            reportFilter.setInactiveReportsCount(counts.containsKey("inactiveReportsCount")? ((Integer)counts.get("inactiveReportsCount")).intValue(): 0 );
        }
        catch (DAOException e)
        {
            throw new ServiceException("Error getting the standard reports " + e);
        }
        return reportFilter;
    }

	/**
	 * @return Returns the lookupService.
	 */
	public LookupService getLookupService() {
		return lookupService;
	}

	/**
	 * @return Returns the securityService.
	 */
	public SecurityService getSecurityService() {
		return securityService;
	}

	/**
	 * @param securityService The securityService to set.
	 */
	public void setSecurityService(SecurityService securityService) {
		this.securityService = securityService;
	}

	/* (non-Javadoc)
	 * @see gov.va.med.fw.scheduling.AuditableScheduledProcess#getAuditInfo(gov.va.med.fw.scheduling.ScheduledProcessInvocationContext)
	 */
	public String getAuditInfo(ScheduledProcessInvocationContext context) {
		// default implementation
		return DEFAULT_AUDIT_NAME;
	}
}