/**
 * AbstractNotificationGenerator.java
 * Created Jul 3, 2008
 * Copyright (c) TANDBERG Television 2007-2008
 */
package com.tandbergtv.watchpoint.pmm.schedule.notify;

import static java.io.File.separator;
import static javax.xml.xpath.XPathConstants.NODE;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.tandbergtv.cms.portal.util.transaction.Transactional;
import com.tandbergtv.metadatamanager.ITTVDataModelHandler;
import com.tandbergtv.metadatamanager.model.Asset;
import com.tandbergtv.metadatamanager.model.Field;
import com.tandbergtv.watchpoint.pmm.dao.hibernate.ApplicationContextHelper;
import com.tandbergtv.watchpoint.pmm.entities.Schedule;
import com.tandbergtv.watchpoint.pmm.entities.Title;
import com.tandbergtv.watchpoint.pmm.schedule.ScheduleRuntimeException;

/**
 * Common functionality for notification generators
 * 
 * @author Sahil Verma
 */
public abstract class AbstractNotificationGenerator implements INotificationGenerator {

	private static final String PRODUCT_DIR = "com.tandbergtv.cms.product.dir";
	private static final String PMM_CONFIG_DIR = "pmm";
	private String metadataFieldName;
	private String metadataFieldType;
	protected ITTVDataModelHandler ttvSpecHandler;
	
	private static final Logger logger = Logger.getLogger(AbstractNotificationGenerator.class);

	AbstractNotificationGenerator() {
		setTitleMetadataInfo();
		ApplicationContext springContext = ApplicationContextHelper.getInstance().getContext();
		ttvSpecHandler = (ITTVDataModelHandler) springContext.getBean("TTVSpecHandler");
	}
	
	/* (non-Javadoc)
	 * @see com.tandbergtv.watchpoint.pmm.schedule.notify.INotificationGenerator#getNotification(com.tandbergtv.watchpoint.pmm.entities.Schedule)
	 */
	@Transactional
	public Notification getNotification(Schedule schedule) {
		if(schedule.getTitles().isEmpty()) {
			return null;
		}

		Notification notification = null;
		
		/* Get all the notification settings */
		for (NotificationSetting setting : getSettings()) {
			int delta = setting.offset;
			String status = setting.status;
			Date deadline = diff(schedule.getDate(), delta);

			logger.debug("Deadline " + deadline);
			
			if (deadline.after(getToday())) {
				logger.debug(schedule + ", is after cutoff date for status " + status);
				continue;
			}
			
			/* Now we have to figure out if titles reached the desired status */
			boolean warn = false;
			Collection<Title> titles = new HashSet<Title>();
			
			for (Title title : schedule.getTitles()) {
				if (isTitleDelayed(title, schedule, status)) {
					logger.debug("Adding notification for " + schedule + ", title " + title);
					
					if (notification == null)
						notification = new Notification(schedule);
					
					notification.addTitle(title);
					titles.add(title);
					warn = true;
				}
			}
			
			/* It's possible that there aren't any problems with this schedule */
			if (warn)
				addMessage(notification, status, titles);
		}
		
		return notification;
	}
	
	/**
	 * Creates the string representation of a warning message for the specified title within the
	 * schedule
	 * 
	 * @param status
	 * @param titles 
	 */
	@Transactional
	protected String constructMessage(String status, Collection<Title> titles) {
		String message = "";
		if (this.metadataFieldName == null || this.metadataFieldType == null)
			return null;
		
		
		// This class just puts in titles in the message, allow subclasses to customize further 
		for (Title title : titles) {
			// get the complete asset to read the metadata
			Asset asset = title.getAsset();
			List<Field> fields = asset.getAllFieldsForXpath(metadataFieldType, metadataFieldName);
			
			if (fields.isEmpty())
				continue;
			
			// picking the value from the first field only.
			String value = fields.get(0).getValue();
			
			if (value == null || value.length() == 0)
				value = title.getId().toString();
			
			if (message.length() > 0)
				message += ", ";
			
			message += value;
		}
		
		return message;
	}
	
	/**
	 * Adds a message to the specified notification to indicate that the list of titles did not
	 * meet the desired status
	 * 
	 * @param notification
	 * @param status
	 */
	protected abstract void addMessage(Notification notification, String status, Collection<Title> titles);
	
	/**
	 * Returns the type of the schedule
	 * 
	 * @return
	 */
	protected abstract String getType();
	
	protected Date getToday() {
		Date date = new Date();

		Calendar calendar = new GregorianCalendar();

		calendar.setTime(date);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		return calendar.getTime();
	}

	protected Date diff(Date date, int days) {
		Calendar calendar = new GregorianCalendar();

		calendar.setTime(date);
		calendar.add(Calendar.DATE, days);

		return calendar.getTime();
	}
	
	protected Collection<NotificationSetting> getSettings() {
		Collection<NotificationSetting> settings = new ArrayList<NotificationSetting>();
		
		try {
			Document document = getConfiguration();
			XPath xpath = XPathFactory.newInstance().newXPath();
			String path = "//schedule[@type='" + getType() + "']/status";
			
			logger.debug("Path " + path);
			
			NodeList nodes = (NodeList) xpath.evaluate(path, document, XPathConstants.NODESET);
			
			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);
				String status = (String) xpath.evaluate("name", node, XPathConstants.STRING);
				String s = (String) xpath.evaluate("offset", node, XPathConstants.STRING);
				Integer offset = Integer.parseInt(s);

				logger.debug("Progress name " + status + " offset " + offset);
				settings.add(new NotificationSetting(status, offset));
			}
		} catch (Exception e) {
			throw new ScheduleRuntimeException("Unable to read progress item name", e);
		}
		
		return settings;
	}
	
	protected void setTitleMetadataInfo() {
		String fieldNamePath = "//variable/name";
		String fieldTypePath = "//variable/type";
		
		try {
			Document document = getConfiguration();
			XPath xpath = XPathFactory.newInstance().newXPath();
			Node fieldNameNode = (Node) xpath.evaluate(fieldNamePath, document, NODE);
			Node fieldTypeNode = (Node) xpath.evaluate(fieldTypePath, document, NODE);
			
			if(fieldNameNode == null || fieldTypeNode == null) {
				throw new ScheduleRuntimeException("Variable name and type must be specified.");
			}
			
			metadataFieldName = fieldNameNode.getTextContent();
			logger.debug("metadataFieldName " + metadataFieldName);
			
			metadataFieldType = fieldTypeNode.getTextContent();
			logger.debug("metadataFieldType " + metadataFieldType);
		} catch (Exception e) {
			throw new ScheduleRuntimeException("Unable to read metadata field name", e);
		}
	}

	protected Document getConfiguration() throws Exception {
		String dir = System.getProperty(PRODUCT_DIR) + separator + "conf";
		String path = dir + separator + PMM_CONFIG_DIR + separator + "progress.xml";
		
		File file = new File(path);
		Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
		
		return document;
	}
	
	protected abstract boolean isTitleDelayed(Title title, Schedule schedule, String status);
}