/**
 * ScheduleConversionAction.java
 * Created on Jun 18, 2008
 * (C) Copyright TANDBERG Television Ltd.
 */
package com.tandbergtv.watchpoint.pmm.action.schedule.ingest;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.tandbergtv.watchpoint.pmm.action.schedule.ingest.converter.IScheduleConverter;
import com.tandbergtv.watchpoint.pmm.util.PathProperties;
import com.tandbergtv.watchpoint.pmm.util.schedule.ingest.ConversionHelper;
import com.tandbergtv.watchpoint.pmm.util.schedule.ingest.ConversionResult;

/**
 * This class is used to convert the TVN format incoming schedule to PMM planner.
 * 
 * @author spuranik
 */
public class ScheduleConversionAction implements ActionHandler {

	private static final long serialVersionUID = 1L;
	private static final Logger logger = Logger.getLogger(ScheduleConversionAction.class);

	// these variables names should match the one in the template and the message
	// sent by watchfolder.
	private static String ARCHIVE_FILEPATH = "archiveFilepath";
	private static String PROVIDER_ID = "providerId";

	// other template variables
	private static String FILE_TYPE = "type";
	private static String CONVERTED_TEMP_FILE_PATH = "convertedTempFilepath";
	private static String FAILED_TITLE = "failedTitle";
	private static String PROCESS_ID = "processId";
	private static String TOKEN_ID = "tokenId";
	private static String CONVERSION_ERROR_MESSAGE = "conversionErrorMsg";

	private static String TEMP_FILE_DELIMITER = "_";

	private static String CONVERTER_CONFIG = "/template-actions/scheduleConverters.xml";
	private static String CONVERTER_CLASS_ELEMENT = "class";

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jbpm.graph.def.ActionHandler#execute(org.jbpm.graph.exe.ExecutionContext)
	 */
	public void execute(ExecutionContext context) throws Exception {
		boolean foundConverter = false;

		List<IScheduleConverter> converterList = getConverters();
		logger.debug("Got " + converterList.size() + " converter(s).");

		String inputFilepath = (String) context.getVariable(ARCHIVE_FILEPATH);
		String folderPath;
		try {
			folderPath = PathProperties.getProperty(PathProperties.TEMP_CONTENT_PATH);
		} catch (RuntimeException e2) {
			// This should not happen, if the installation is done right.
			// However, adding this as a safety check.
			String errorMsg = "'tempContentPath' not defined in path.properties";
			context.setVariable(CONVERSION_ERROR_MESSAGE, errorMsg);
			throw new RuntimeException(errorMsg);
		}
		
		String inputFileName = ConversionHelper.getFilename(inputFilepath);
		// prefix the temp name with [processId]_[tokenId]_ to ensure that the names are unique
		// across files with same names for different providers. Just a precaution!
		String tempFilename = String.valueOf((Integer) context.getVariable(PROCESS_ID))
				+ TEMP_FILE_DELIMITER + String.valueOf((Integer) context.getVariable(TOKEN_ID))
				+ TEMP_FILE_DELIMITER + inputFileName;
		String outputFilepath = ConversionHelper.generateFileName(folderPath, tempFilename);

		for (int i = 0; i < converterList.size(); i++) {
			IScheduleConverter converter = converterList.get(i);
			// if the converter is not capable of converting this incoming file,
			// try the next one.			
			try {
				boolean canConvert = converter.canConvert(inputFilepath);
				if (!canConvert) {
					logger.debug(converter.getClass().getName() + "cannot convert " + inputFilepath
							+ " to PMM format planner/program list.");
					continue;
				}
			} catch (RuntimeException e1) {
				logger.warn("Failed to determine if converter: " + converter.getClass().getName()
						+ " can convert: " + inputFilepath, e1);
				continue;
			}

			ConversionResult result;
			foundConverter = true;
			try {
				result = converter.convert(inputFilepath, outputFilepath);
			} catch (RuntimeException e) {
				// ensure that the error message is saved in the context variable before
				// exiting and re-throw the same exception. This will fail the work order.
				context.setVariable(CONVERSION_ERROR_MESSAGE, e.getMessage());
				throw new Exception(e.getMessage());
			}

			// if this converter attempted to convert the file and returned an error message
			// set the context var and exit.
			if (result.getErrorMsg() != null && result.getErrorMsg().trim().length() > 0) {
				context.setVariable(CONVERSION_ERROR_MESSAGE, result.getErrorMsg());
				context.setVariable(FAILED_TITLE, result.getFailedTitle());
				throw new Exception(result.getErrorMsg());
			}

			// if the converter attempted to convert the file and successfully
			// generated a converted file, ensure that the file really exists.
			File outfile = new File(outputFilepath);
			if (outfile.exists()) {
				context.setVariable(FILE_TYPE, result.getFileType().getValue());
				context.setVariable(CONVERTED_TEMP_FILE_PATH, outputFilepath);
				String providerId = (String) context.getVariable(PROVIDER_ID);

				logger.info("Incoming file is a " + result.getFileType().getValue()
						+ " from providerId: " + providerId);
				logger.info("Converted incoming file. Output filepath: " + outputFilepath);
				break;
			}
		}

		// report an error if no converter was able to convert this file.
		if (!foundConverter) {
			String errorMsg = "No suitable converter found for file: " + inputFilepath;
			context.setVariable(CONVERSION_ERROR_MESSAGE, errorMsg);
			throw new Exception(errorMsg);
		}
	}

	/**
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private List<IScheduleConverter> getConverters() {
		List<IScheduleConverter> converters = new ArrayList<IScheduleConverter>();

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = factory.newDocumentBuilder();
			Document doc = docBuilder.parse(this.getClass().getResourceAsStream(CONVERTER_CONFIG));

			NodeList converterClasses = doc.getElementsByTagName(CONVERTER_CLASS_ELEMENT);
			for (int i = 0; i < converterClasses.getLength(); i++) {
				// if one converter cannot be loaded, move on to the next one!
				try {
					Class<IScheduleConverter> converter = (Class<IScheduleConverter>) Class
							.forName(converterClasses.item(i).getTextContent());
					converters.add(converter.newInstance());
				} catch (DOMException e) {
					logger.error(e.toString());
				} catch (ClassNotFoundException e) {
					logger.error(e.toString());
				} catch (InstantiationException e) {
					logger.error(e.toString());
				} catch (IllegalAccessException e) {
					logger.error(e.toString());
				}
			}
		} catch (ParserConfigurationException e) {
			logger.error(e.toString());
		} catch (SAXException e) {
			logger.error(e.toString());
		} catch (IOException e) {
			logger.error(e.toString());
		}
		return converters;
	}

}
