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

package gov.va.med.fw.batchprocess;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.Validate;
import org.springframework.core.io.FileSystemResource;

import gov.va.med.fw.io.parser.WildcardFileFilter;
import gov.va.med.fw.util.FileModifiedDateComparator;
import gov.va.med.fw.util.builder.Builder;

/**
 * Abstract base class for batch processing of bulk data where the input source
 * is one or more File(s).
 * 
 * Created Feb 1, 2006 2:41:17 PM
 * 
 * @author DNS   BOHMEG
 */
public abstract class AbstractDataFileProcess extends AbstractDataProcess {
	private Builder inputFileReader;

	private String inputFileLocation;

	private FilenameFilter inputFileFilter;
	
	private boolean handleEachFileSeparately = true;
	
	private boolean caseInsensitive = true;	
	
	private boolean oneFileProcess = false;
	
	

	public boolean isOneFileProcess() {
		return oneFileProcess;
	}

	public void setOneFileProcess(boolean oneFileProcess) {
		this.oneFileProcess = oneFileProcess;
	}

	protected DataProcessExecutionContext createDataProcessExecutionContext() {
		DataFileProcessStatistics stats = (DataFileProcessStatistics) createProcessStatistics();
		stats.setProcessedFilePath(inputFileLocation);
		
		DataProcessExecutionContext context = new DataFileProcessExecutionContext();
		context.setProcessStatistics(stats);
		return context;
	}
	 
	protected ProcessStatistics createProcessStatistics() {
		return new DataFileProcessStatistics();
	}	
	
	protected final void executeProcess(DataProcessExecutionContext context)
			throws Exception {
		if (hasExactlyOneInputFileToProcess())
			super.executeProcess(context);
		else {
			// perform processing one File at a time...
			DataFileProcessExecutionContext ctxt = (DataFileProcessExecutionContext) context; 
			DataFileProcessStatistics dataStats = (DataFileProcessStatistics) ctxt.getProcessStatistics();
			FileSystemResource fileSystemResource = new FileSystemResource(	inputFileLocation);
        
            
            /** CR 7601 added code to get the sort list of files based the file modified time
            String[] fileNames = fileSystemResource.getFile().list(
                    inputFileFilter);
            ****/
            File[] files = fileSystemResource.getFile().listFiles(inputFileFilter);
            String[] fileNames = null;
            
            if (files != null  )
            {
                Arrays.sort(files, new FileModifiedDateComparator());
                fileNames = new String[files.length];
                for ( int i=0; i<files.length;i++) {
                    fileNames[i] = files[i].getName();
                }
            }
            
            //dereference the files to clear the memory below we deal with them one at a time
            files = null;
            
 
			List specificFileData = null;
			fileSystemResource = null;
			if(fileNames == null) {
				if (logger.isErrorEnabled())
					logger.error("There were problems (eg, not a directory) trying to get input files at ["
							+ inputFileLocation + "] for " + getProcessName());				
			} else {			
				if (fileNames.length == 0) {
					if (logger.isWarnEnabled())
						logger.warn("There are no input files at ["
								+ inputFileLocation + "] for " + getProcessName());
				}
	
				for (int i = 0; i < fileNames.length; i++) {
					fileSystemResource = new FileSystemResource(inputFileLocation
							+ File.separator + fileNames[i]);
                    
					ctxt.setCurrentFile(fileSystemResource.getFile());				
					specificFileData = doAcquireData(ctxt, fileSystemResource);
					processData(ctxt, specificFileData);
					dataStats.addProcessedFile(fileNames[i]);
					if(shouldUpdateJobResult(context))
						this.updateJobResult(context);					
					if(handleEachFileSeparately) {
						logParsedFile(ctxt);
						handleDataProcessCompleted(ctxt);
						if(isOneFileProcess()){
							break;
						}
					}
				}
			}
			
			if(!handleEachFileSeparately) {
				logParsedFile(ctxt);
				handleDataProcessCompleted(ctxt);
			}
		}
	}

	private boolean hasExactlyOneInputFileToProcess() {
		boolean hasExactlyOneInputFileToProcess = false;
		FileSystemResource fileSystemResource = new FileSystemResource(
				inputFileLocation);
		if ((inputFileFilter == null
				&& !fileSystemResource.getFile().isDirectory()))
			hasExactlyOneInputFileToProcess = true;
		
		return hasExactlyOneInputFileToProcess;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see gov.va.med.fw.scheduling.AbstractScheduledProcess#executeProcess(java.lang.Object)
	 */
	protected final List acquireData(DataProcessExecutionContext context)
			throws Exception {
		FileSystemResource fileSystemResource = new FileSystemResource(
				inputFileLocation);
		List data = Collections.EMPTY_LIST;
		if (fileSystemResource.exists()) {
            if (context instanceof DataFileProcessExecutionContext) {
                ((DataFileProcessExecutionContext)context).setCurrentFile(fileSystemResource.getFile());              
            }
			data = doAcquireData(context, new FileSystemResource(inputFileLocation));          
        }
		else {
			if (logger.isWarnEnabled())
				logger.warn("The inputFile [" + inputFileLocation + "] for "
						+ getProcessName() + " does not exist");
		}

		return data;
	}

	// subclasses can override (eg, use context to process different file than configured for) 
	protected List doAcquireData(DataProcessExecutionContext context,
			FileSystemResource fileSystemResource) throws Exception {
		return (List) inputFileReader.build(new Object[] { context,
				fileSystemResource });
	}

	protected final void logParsedFile(DataFileProcessExecutionContext context) {
		if (logger.isInfoEnabled()) {
			logger.info("File Parsing completed for inputFile ["
					+ context.getCurrentFile() + "]");
		}		
	}


	/**
	 * @return Returns the inputFileLocation.
	 */
	public String getInputFileLocation() {
		return inputFileLocation;
	}

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

	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		Validate.notNull(inputFileLocation,
				"A file location is needed to acquire data");
		Validate.notNull(inputFileReader, "A Builder is needed to parse the data");
		
		//If the input file location contains a *, treat it as a wild card entry.
		if(inputFileLocation.indexOf("*") != -1)
		{
			//Get the last index of file path separator '/' or '\'in the file location 
		    int lastIndex = inputFileLocation.lastIndexOf("/");
			if(lastIndex == -1)
			    lastIndex = inputFileLocation.lastIndexOf("\\") ;
			
			inputFileFilter = new WildcardFileFilter(inputFileLocation.substring(lastIndex + 1), caseInsensitive);
			inputFileLocation = inputFileLocation.substring(0,lastIndex);
		}
	}


	/**
	 * @return Returns the inputFileReader.
	 */
	public Builder getInputFileReader() {
		return inputFileReader;
	}

	/**
	 * @param inputFileReader The inputFileReader to set.
	 */
	public void setInputFileReader(Builder inputFileReader) {
		this.inputFileReader = inputFileReader;
	}

	/**
	 * @return Returns the handleEachFileSeparately.
	 */
	public boolean isHandleEachFileSeparately() {
		return handleEachFileSeparately;
	}

	/**
	 * @param handleEachFileSeparately The handleEachFileSeparately to set.
	 */
	public void setHandleEachFileSeparately(boolean handleEachFileSeparately) {
		this.handleEachFileSeparately = handleEachFileSeparately;
	}

	/**
	 * @return Returns the caseInsensitive.
	 */
	public boolean isCaseInsensitive() {
		return caseInsensitive;
	}

	/**
	 * @param caseInsensitive The caseInsensitive to set.
	 */
	public void setCaseInsensitive(boolean caseInsensitive) {
		this.caseInsensitive = caseInsensitive;
	}
	
	/**
	 * @return Returns the updateJobResult.
	 */
	protected boolean shouldUpdateJobResult(DataProcessExecutionContext context) {
		return true;
	}
	
}