/*
 * Created on Jul 3, 2008 (C) Copyright TANDBERG Television Ltd.
 */

package com.tandbergtv.watchpoint.pmm.communication.handlers;

import java.io.File;
import java.io.FileOutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.tandbergtv.watchpoint.pmm.communication.HandlerErrorCode;
import com.tandbergtv.watchpoint.pmm.communication.MessageHandler;
import com.tandbergtv.watchpoint.pmm.communication.MessageHandlerException;
import com.tandbergtv.watchpoint.pmm.entities.Schedule;
import com.tandbergtv.watchpoint.pmm.entities.ScheduleStatus;
import com.tandbergtv.watchpoint.pmm.schedule.bind.ScheduleMarshaller;
import com.tandbergtv.watchpoint.pmm.schedule.search.IScheduleSearchService;
import com.tandbergtv.workflow.core.service.ServiceRegistry;
import com.tandbergtv.workflow.message.IMessageKey;
import com.tandbergtv.workflow.message.IMessageUID;
import com.tandbergtv.workflow.message.WorkflowMessage;
import com.tandbergtv.workflow.message.WorkflowMessage.MessageType;

/**
 * Generates the Pitch Schedules for the given Partner / Service using the provided date range.
 * 
 * @author Vijay Silva
 */
public class GeneratePitchSchedulesMessageHandler implements MessageHandler {

	/* The logger */
	private static final Logger logger = Logger.getLogger(GeneratePitchSchedulesMessageHandler.class);
	
	/* The Context Id in the input message */
	private static final String CONTEXT_ID_PARAM = "contextId";

	/* The Starting Date for Pitch Schedules the input message */
	private static final String START_DATE_PARAM = "startDate";

	/* The number of Days after the Starting Date for Pitch Schedules the input message */
	private static final String DAYS_AFTER_PARAM = "daysAfter";

	/* The Output File Path in which to generate the Pitch Schedules the input message */
	private static final String OUTPUT_FILEPATH_PARAM = "outputFilePath";

	/* The Date Format for the date present in the message */
	private static final String DATE_FORMAT = "yyyy-MM-dd";

	/* The Name of the Root Element in the Pitch Schedule XML file generated */
	private static final String ROOT_ELEMENT = "distribution-schedules";

	/* The Root Element Attribute for the context Id for the Pitch Schedule XML file generated */
	private static final String CONTEXT_ID_ATTR = "contextId";

	/* The Root Element Attribute for the start date for the Pitch Schedule XML file generated */
	private static final String START_DATE_ATTR = "startDate";

	/* The Root Element Attribute for the end date for the Pitch Schedule XML file generated */
	private static final String END_DATE_ATTR = "endDate";

	/**
	 * Gets the list of Pitch Schedules for the given Context Id and date range. Writes the
	 * schedules to the specified output file path.
	 * 
	 * @see com.tandbergtv.watchpoint.pmm.communication.MessageHandler#handleMessage(com.tandbergtv.workflow.message.WorkflowMessage)
	 */
	public WorkflowMessage handleMessage(WorkflowMessage message) throws MessageHandlerException {
		long contextId = this.getLongParameter(message, CONTEXT_ID_PARAM);
		Date startDate = this.getDateParameter(message, START_DATE_PARAM);
		int daysAfter = (int) this.getLongParameter(message, DAYS_AFTER_PARAM);
		String outputFilePath = this.getStringParameter(message, OUTPUT_FILEPATH_PARAM);

		/* Ensure that Days After is >= 0 */
		if (daysAfter < 0) {
			String msg = "The " + DAYS_AFTER_PARAM + " message parameter must have "
					+ "a number value greater than or equal to 0, got value: " + daysAfter;
			throw new MessageHandlerException(msg);
		}

		/* Calculate the End Date in the Date Range */
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(startDate);
		calendar.add(Calendar.DAY_OF_MONTH, daysAfter);
		Date endDate = calendar.getTime();

		/* Get the Service required and get the schedules */
		ServiceRegistry registry = ServiceRegistry.getDefault();
		IScheduleSearchService service = registry.lookup(IScheduleSearchService.class);
		Collection<? extends Schedule> schedules = service.getPitchSchedulesByDateRange(contextId,
				startDate, endDate);

		/* Validate Schedule Status */
		this.validateScheduleStatus(schedules);

		/* Build an XML Document containing all returned schedules using the specified path */
		this.writeSchedulesToFile(schedules, contextId, startDate, endDate, outputFilePath);

		/* Build the Workflow Message Response */
		IMessageUID uid = message.getMessageUID();
		IMessageKey key = message.getKey();
		WorkflowMessage response = new WorkflowMessage(uid, key, MessageType.ack);

		return response;
	}

	/* Validate that the status of each of the schedules is approved */
	private void validateScheduleStatus(Collection<? extends Schedule> schedules)
			throws MessageHandlerException {
		if (schedules == null)
			return;

		boolean isValid = true;
		StringBuilder buf = new StringBuilder();
		buf.append("The Pitch Schedules in specified date range with Ids: ");

		/* Go through the schedules and find schedules that are not approved */
		boolean firstSchedule = true;
		for (Schedule schedule : schedules) {
			if (schedule.getStatus() != ScheduleStatus.APPROVED) {
				// Schedule list is invalid
				isValid = false;

				// Add the comma separator if not first schedule, else mark as not first schedule
				if (!firstSchedule)
					buf.append(", ");
				else
					firstSchedule = false;

				// Append invalid schedule Id to the list
				buf.append(schedule.getId());
			}
		}

		if (!isValid) {
			buf.append(" are not approved, ");
			buf.append("cannot generate file with containing Pitch Schedules.");
			throw new MessageHandlerException(HandlerErrorCode.RUNTIME_ERROR, buf.toString());
		}
	}

	/* Write the List of schedules in the collection to an XML file */
	private void writeSchedulesToFile(Collection<? extends Schedule> schedules, long contextId,
			Date startDate, Date endDate, String outputFilePath) throws MessageHandlerException {
		/* Make sure that the collection is not null */
		if (schedules == null) {
			schedules = new ArrayList<Schedule>();
		}

		/* Build the Pitch Schedules Document */
		Document document = null;
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		} catch (Exception e) {
			String msg = "Failed to create an XML document for storing the Pitch Schedules, error: "
					+ e.getMessage();
			throw new MessageHandlerException(HandlerErrorCode.RUNTIME_ERROR, msg, e);
		}

		/* Create the Root Element for the document */
		Element documentElement = document.createElement(ROOT_ELEMENT);
		documentElement.setAttribute(CONTEXT_ID_ATTR, Long.toString(contextId));
		DateFormat format = new SimpleDateFormat(DATE_FORMAT);
		documentElement.setAttribute(START_DATE_ATTR, format.format(startDate));
		documentElement.setAttribute(END_DATE_ATTR, format.format(endDate));
		document.appendChild(documentElement);
		ScheduleMarshaller marshaller = ScheduleMarshaller.newInstance();

		/* Iterate through each Schedule and add to XML Document */
		for (Schedule schedule : schedules) {
			Node scheduleNode = marshaller.marshal(schedule);
			if (scheduleNode instanceof Document) {
				scheduleNode = ((Document) scheduleNode).getDocumentElement();
			}
			documentElement.appendChild(document.adoptNode(scheduleNode));
		}

		/* Write out the document to a file */
		FileOutputStream outputStream = null;
		try {
			File outputFile = new File(outputFilePath);
			outputStream = new FileOutputStream(outputFile);
			Transformer transformer = TransformerFactory.newInstance().newTransformer();

			/* Indentation Properties (indent-amount only works with the new version of XALAN) */
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

			/* Transform */
			transformer.transform(new DOMSource(document), new StreamResult(outputStream));
		} catch (Exception e) {
			String msg = "Failed to write XML document containing the Pitch Schedules to file: "
					+ outputFilePath + ", error: " + e.getMessage();
			throw new MessageHandlerException(HandlerErrorCode.RUNTIME_ERROR, msg, e);
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Exception e) {
					logger.warn("Failed to close output stream for file: " + outputFilePath, e);
				}
			}
		}
	}

	/* Get a String Parameter from the Workflow Message */
	private String getStringParameter(WorkflowMessage message, String name)
			throws MessageHandlerException {
		String value = (message.getPayload() != null) ? message.getValue(name) : null;

		if (value == null || value.trim().length() == 0) {
			throw new MessageHandlerException(HandlerErrorCode.INVALID_INPUT, "The " + name
					+ " message parameter is blank / missing.");
		}

		return value;
	}

	/* Get a Long Parameter from the Workflow Message */
	private long getLongParameter(WorkflowMessage message, String name)
			throws MessageHandlerException {
		long result = 0;

		String value = (message.getPayload() != null) ? message.getValue(name) : null;
		if (value == null || value.trim().length() == 0) {
			throw new MessageHandlerException(HandlerErrorCode.INVALID_INPUT, "The " + name
					+ " message parameter is blank / missing when a number value was expected.");
		}

		try {
			result = Long.parseLong(value);
		} catch (Exception e) {
			throw new MessageHandlerException(HandlerErrorCode.INVALID_INPUT, "The " + name
					+ " message parameter does not have a valid number value, received value: "
					+ value, e);
		}

		return result;
	}

	/* Get a Date Parameter from the Workflow Message */
	private Date getDateParameter(WorkflowMessage message, String name)
			throws MessageHandlerException {
		Date result = null;

		String value = (message.getPayload() != null) ? message.getValue(name) : null;
		if (value == null || value.trim().length() == 0) {
			throw new MessageHandlerException(HandlerErrorCode.INVALID_INPUT, "The " + name
					+ " message parameter is blank / missing when a date value was expected.");
		}

		try {
			DateFormat format = new SimpleDateFormat(DATE_FORMAT);
			result = format.parse(value);
		} catch (Exception e) {
			throw new MessageHandlerException(HandlerErrorCode.INVALID_INPUT, "The " + name
					+ " message parameter does not have a valid date value. Expected format: "
					+ DATE_FORMAT + ", received value: " + value, e);
		}

		return result;
	}
}
