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

package gov.va.med.fw.batchprocess;

import gov.va.med.fw.conversion.DateConverter;
import gov.va.med.fw.mail.FormattedObjectMailStyle;
import gov.va.med.fw.model.AbstractEntity;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * Base class for batch process statistics.  Each statistic should have a
 * getter/setter for javabean semantics to be enforced.
 * 
 * Created Feb 3, 2006 1:27:00 PM
 * 
 * @author DNS   BOHMEG
 */
public class ProcessStatistics extends AbstractEntity {
	/**
	 * serialVersionUID long
	 */
	private static final long serialVersionUID = -8068962742827780928L;

	private String processName;

	private Date processingStartDate;

	private Date processingEndDate;

	private int numberOfTotalRecords;

	private int numberOfSuccessfulRecords;

	private int numberOfErrorRecords;
	
	private boolean wasInterrupted;
	
	private String executedOnServer;

	/**
	 * @return Returns the numberOfErrorRecords.
	 */
	public int getNumberOfErrorRecords() {
		return numberOfErrorRecords;
	}

	public void incrementNumberOfSuccessfulRecords() {
		incrementNumberOfSuccessfulRecords(1);
	}

	public synchronized  void  incrementNumberOfSuccessfulRecords(int recordsCount) {
		numberOfSuccessfulRecords += recordsCount;
		numberOfTotalRecords += recordsCount;
	}

	public void incrementNumberOfTotalRecords() {
		incrementNumberOfTotalRecords(1);
	}

	public synchronized  void incrementNumberOfTotalRecords(int count) {
		numberOfTotalRecords = numberOfTotalRecords + count;
	}
	
	public void incrementNumberOfErrorRecords() {
		incrementNumberOfErrorRecords(1);
	}

	public synchronized  void incrementNumberOfErrorRecords(int recordsCount) {
		numberOfErrorRecords += recordsCount;
		numberOfTotalRecords += recordsCount;
	}

	public synchronized  void decrementNumberOfSuccessfulRecords() {
		if (numberOfSuccessfulRecords > 0)
			numberOfSuccessfulRecords--;
		numberOfErrorRecords++;
	}

	/**
	 * @param numberOfErrorRecords
	 *            The numberOfErrorRecords to set.
	 */
	public synchronized void setNumberOfErrorRecords(int numberOfErrorRecords) {
		this.numberOfErrorRecords = numberOfErrorRecords;
	}

	/**
	 * @return Returns the numberOfSuccessfulRecords.
	 */
	public int getNumberOfSuccessfulRecords() {
		return numberOfSuccessfulRecords;
	}

	/**
	 * @param numberOfSuccessfulRecords
	 *            The numberOfSuccessfulRecords to set.
	 */
	public synchronized void setNumberOfSuccessfulRecords(int numberOfSuccessfulRecords) {
		this.numberOfSuccessfulRecords = numberOfSuccessfulRecords;
	}

	/**
	 * @return Returns the processingStartDate.
	 */
	public Date getProcessingStartDate() {
		return processingStartDate;
	}

	/**
	 * @param processingStartDate
	 *            The processingStartDate to set.
	 */
	public void setProcessingStartDate(Date processingStartDate) {
		this.processingStartDate = processingStartDate;
	}
	
	public void setWasInterrupted(boolean flag) {
		wasInterrupted = flag;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see gov.va.med.fw.model.AbstractEntity#buildToString(org.apache.commons.lang.builder.ToStringBuilder)
	 */
	protected void buildToString(ToStringBuilder builder) {
		builder.append("processName", processName);
		builder.append("processingStartDate", processingStartDate);
		builder.append("processingEndDate", processingEndDate);
		builder.append("calculated duration", getProcessingDuration());
		builder.append("numberOfTotalRecords", numberOfTotalRecords);
		builder.append("numberOfSuccessfulRecords", numberOfSuccessfulRecords);
		builder.append("numberOfErrorRecords", numberOfErrorRecords);
		builder.append("executedOnServer", executedOnServer);
		if(wasInterrupted)
			builder.append("IMPORTANT INFO", "** THIS PROCESS WAS MANUALLY INTERRUPTED AND MAY NOT HAVE COMPLETED **");
	}

	public final String toFormattedMailString() {
		ToStringBuilder builder = new ToStringBuilder(this,
				new FormattedObjectMailStyle());
		this.buildToString(builder);
		return builder.toString();
	}

	/**
	 * @return Returns the processingEndDate.
	 */
	public Date getProcessingEndDate() {
		return processingEndDate;
	}

	/**
	 * @param processingEndDate
	 *            The processingEndDate to set.
	 */
	public void setProcessingEndDate(Date processingEndDate) {
		this.processingEndDate = processingEndDate;
	}

	public String getProcessingDuration() {
		String duration = StringUtils.EMPTY;
		if (processingStartDate != null && processingEndDate != null) {
			if (processingStartDate.equals(processingEndDate))
				duration = "0 secs";
			long durationMillis = processingEndDate.getTime()
					- processingStartDate.getTime();
			duration = durationMillis / 1000.0 + " secs";
		}
		return duration;
	}

	/**
	 * @return Returns the processName.
	 */
	public String getProcessName() {
		return processName;
	}

	/**
	 * @param processName
	 *            The processName to set.
	 */
	public void setProcessName(String processName) {
		this.processName = processName;
	}

	/**
	 * @return Returns the numberOfTotalRecords.
	 */
	public int getNumberOfTotalRecords() {
		return numberOfTotalRecords;
	}

	/**
	 * @param numberOfTotalRecords
	 *            The numberOfTotalRecords to set.
	 */
	public synchronized void setNumberOfTotalRecords(int numberOfTotalRecords) {
		this.numberOfTotalRecords = numberOfTotalRecords;
	}
	
	/**
	 * @return Returns the wasInterrupted.
	 */
	public boolean getWasInterrupted() {
		return wasInterrupted;
	}
		
	public final String exportAsCSV() throws Exception {
		return StringUtils.removeEnd(StringUtils.removeStart(statsMap().toString(), "{"), "}");
	}
	
	/** can be overridden by subclasses for special handling */
	protected Map statsMap() throws Exception {
		Map data = BeanUtils.describe(this);
		formatDateField(data, "processingStartDate", getProcessingStartDate());
		formatDateField(data, "processingEndDate", getProcessingEndDate());
		data.remove("class");
		return data;
	}
	
	protected final static String listAsString(List list) {
		StringBuffer buf = new StringBuffer();
		Iterator itr = list != null ? list.iterator() : null;
		while(itr != null && itr.hasNext()) {
			buf.append(itr.next());
			if(itr.hasNext())
				buf.append(":");
		}
		return buf.toString();
	}

	protected final static List listFromString(String str) {
		if(StringUtils.isBlank(str))
			return null;
		List list = new ArrayList();
		CollectionUtils.addAll(list, str.split(":"));
		return list;
	}
	
	protected final void formatDateField(Map data, String fieldName, Date fieldValue) {
		// note the date format must be one that is supported by the DateConverter used in importFromCSV
		if(fieldValue != null)
			data.put(fieldName, new SimpleDateFormat("yyyyMMddHHmmss").format(fieldValue));
		else
			data.remove(fieldName);		
	}
		
	public final void importFromCSV(String csv) throws Exception {
		if(StringUtils.isBlank(csv))
			return;
		
		if(ConvertUtils.lookup(Date.class) == null)
			ConvertUtils.register(new DateConverter(null), Date.class);
		
		BeanUtils.copyProperties(this, mapFromCSV(csv));
	}
	
	/** can be overridden by subclasses for special handling */
	protected Map mapFromCSV(String csv) {
		Map data = new HashMap();
		String[] vals = csv.split(",");
		String[] val = null;
		for(int i=0; i<vals.length; i++) {
			val = vals[i].split("=");
			if(val.length == 2)
				data.put(val[0].trim(), val[1]);
		}
		return data;		
	}
	
	/** can be overridden by subclasses for special handling */
	public boolean containsErrors() {
		return numberOfErrorRecords != 0;
	}
	
	public synchronized final void overlayStats(ProcessStatistics stats) {
		this.setProcessName(stats.getProcessName()); 
		/* // since these are for overall processing time, exclude from this incremental overlay
		this.setProcessingStartDate(stats.getProcessingStartDate());
		this.setProcessingEndDate(stats.getProcessingEndDate());
		this.setExecutedOnServer(stats.getExecutedOnServer());		
		*/		
		this.setNumberOfTotalRecords(getNumberOfTotalRecords() + stats.getNumberOfTotalRecords());
		this.setNumberOfSuccessfulRecords(getNumberOfSuccessfulRecords() + stats.getNumberOfSuccessfulRecords());
		this.setNumberOfErrorRecords(getNumberOfErrorRecords() + stats.getNumberOfErrorRecords());
		appendStats(stats);
	}
	
	protected void appendStats(ProcessStatistics stats) {
		// no-op, subclasses implement this
	}
	
	public boolean isTotalNumberMod(int factor) {
		return getNumberOfTotalRecords() != 0 ? (getNumberOfTotalRecords() % factor) == 0 : false;
	}

	/**
	 * @return Returns the executedOnServer.
	 */
	public String getExecutedOnServer() {
		return executedOnServer;
	}

	/**
	 * @param executedOnServer The executedOnServer to set.
	 */
	public void setExecutedOnServer(String executedOnServer) {
		this.executedOnServer = executedOnServer;
	}
}
