/**
 * ScheduleTitlesAction.java
 * Created Jun 4, 2008
 * Copyright (c) TANDBERG Television 2007-2008
 */
package com.tandbergtv.watchpoint.pmm.web.schedule;

import java.io.File;
import java.io.PrintWriter;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionRedirect;
import org.apache.struts.actions.MappingDispatchAction;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.tandbergtv.watchpoint.pmm.entities.ContainerType;
import com.tandbergtv.watchpoint.pmm.entities.DistributionSchedule;
import com.tandbergtv.watchpoint.pmm.entities.IAssetList;
import com.tandbergtv.watchpoint.pmm.entities.IContainer;
import com.tandbergtv.watchpoint.pmm.entities.Partner;
import com.tandbergtv.watchpoint.pmm.entities.Planner;
import com.tandbergtv.watchpoint.pmm.entities.ProgressItem;
import com.tandbergtv.watchpoint.pmm.entities.Schedule;
import com.tandbergtv.watchpoint.pmm.entities.Title;
import com.tandbergtv.watchpoint.pmm.entities.TitleListType;
import com.tandbergtv.watchpoint.pmm.entities.bind.Marshaller;
import com.tandbergtv.watchpoint.pmm.schedule.ISchedulePersistenceService;
import com.tandbergtv.watchpoint.pmm.schedule.ScheduleRuntimeException;
import com.tandbergtv.watchpoint.pmm.schedule.notify.IScheduleNotifier;
import com.tandbergtv.watchpoint.pmm.schedule.notify.Notification;
import com.tandbergtv.watchpoint.pmm.schedule.search.IScheduleSearchService;
import com.tandbergtv.watchpoint.pmm.title.ITitleService;
import com.tandbergtv.watchpoint.pmm.util.XMLDocumentUtility;
import com.tandbergtv.watchpoint.pmm.web.title.TitleProgressBean;
import com.tandbergtv.watchpoint.pmm.web.util.PMMTableConfigHelper;
import com.tandbergtv.workflow.core.service.ServiceRegistry;
import com.tandbergtv.workflow.core.service.cache.ICacheService;

/**
 * This class takes care of the controller functionality for the title basic and activity views
 * of a planner or pitch schedule
 * 
 * @author Sahil Verma
 */
public class ScheduleTitlesAction extends MappingDispatchAction {

	private ISchedulePersistenceService service;
	
	private IScheduleSearchService searchService;
	
	private ITitleService titleService;
	
	private static final String TABLE_ID = "scheduletitles";
	
	private static final String ACTIVITY_TABLE_ID = "scheduleactivity";
	
	private static final String PLANNER_TABLE_ID = "planneractivity";
	
	private static final Logger logger = Logger.getLogger(ScheduleTitlesAction.class);
	
	private static final String DATE_FORMAT = "yyyy-MM-dd";
	
	/**
	 * Creates a ScheduleTitlesAction
	 */
	public ScheduleTitlesAction() {
		this.service = ServiceRegistry.getDefault().lookup(ISchedulePersistenceService.class);
		this.searchService = ServiceRegistry.getDefault().lookup(IScheduleSearchService.class);
		this.titleService = ServiceRegistry.getDefault().lookup(ITitleService.class); 
	}

	/**
	 * Displays the basic view of a schedule
	 */
	public ActionForward getBasicView(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ScheduleForm form = (ScheduleForm)actionform;
		String id = form.getScheduleId();
		Schedule schedule = service.get(Long.parseLong(id));
		
		logger.debug("Schedule " + schedule);
		
		ScheduleFormPopulator.populateScheduleForm(form);
		
		form.setSchedule(schedule);
		setTitles(schedule);
		form.setTitles(prepareTitleProgressBean(schedule, form.getSources()));
		form.setStatistics(getScheduleStatistics(schedule));
		
		form.setTableId(TABLE_ID, PMMTableConfigHelper.getTableConfigFile());
		
		return mapping.findForward("default");
	}
	
	private Collection<TitleProgressBean> prepareTitleProgressBean(
			Schedule schedule, Collection<PartnerBean> sources) {
		Collection<TitleProgressBean> list = new ArrayList<TitleProgressBean>();
		IScheduleNotifier notifier = ServiceRegistry.getDefault().lookup(IScheduleNotifier.class);
		Notification notification = notifier.getNotification(schedule);
		
		for (Title title : schedule.getTitles()) {
			Schedule planner = null;
			Collection<ProgressItem> items = null;
			if (schedule.getType() == TitleListType.PITCH) {
				planner = findPlanner(title, schedule.getDate());
				items = getProgressItemsForPitch(title, planner, schedule);
			} else {
				items = schedule.getProgressItems(title);
			}

			/* Build the TitleProgressBean */
			TitleProgressBean progress = new TitleProgressBean(title, items);
			progress.setTitleFields(title.getAsset().getAllDescendantAssetFields());
			if (planner != null) {
				Long partnerId = planner.getSourcePartnerID();
				PartnerBean plannerSource = null;
				if (sources != null) {
					for (PartnerBean partner : sources) {
						if (partnerId.equals(partner.getId()))
							plannerSource = partner;
					}
				}
				progress.addPlanner(planner, plannerSource);
			}

			if (notification != null) {
				for (Title t : notification.getTitles()) {
					if (t.equals(title))
						progress.setHasAlert(true);
				}
			}

			list.add(progress);
		}

		Collections.sort((List<TitleProgressBean>) list);
		return list;
	}

	/* Go through the Planners associated with the titles and find the most recent planner */
	private Schedule findPlanner(Title title, Date scheduleDate) {
		Schedule planner = null;

		for (IAssetList assetlist : title.getTitlelists()) {
			Schedule schedule = Schedule.class.cast(assetlist);
			if (schedule.getType() == TitleListType.PLANNER) {
				if (!schedule.getDate().after(scheduleDate)) {
					if (planner == null || planner.getDate().before(schedule.getDate())) {
						planner = schedule;
					}
				}
			}
		}

		return planner;
	}
	
	/* Get all the Progress Items that need to be associated with this title */
	private Collection<ProgressItem> getProgressItemsForPitch(Title title,
			Schedule planner, Schedule schedule) {
		Collection<ProgressItem> progressItems = new ArrayList<ProgressItem>();

		/* Get all Progress Items associated with the planner first */
		if (planner != null) {
			for (ProgressItem progressItem : title.getProgressItems()) {
				if (planner.getId().equals(progressItem.getAssetListId()))
					progressItems.add(progressItem);
			}
		}

		/* Get Progress Items associated with current schedule or with no schedule */
		for (ProgressItem progressItem : title.getProgressItems()) {
			Long assetListId = progressItem.getAssetListId();
			if (assetListId == null || schedule.getId().equals(assetListId)) {
				progressItems.add(progressItem);
			}
		}

		return progressItems;
	}

	/**
	 * Displays the activity view of a schedule
	 */
	public ActionForward getActivityView(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ScheduleForm form = (ScheduleForm)actionform;
		String id = form.getScheduleId();
		Schedule schedule = service.get(Long.parseLong(id));
		
		logger.debug("Schedule " + schedule);
		
		ScheduleFormPopulator.populateScheduleForm(form);
		
		form.setSchedule(schedule);
		setTitles(schedule);
		form.setTitles(prepareTitleProgressBean(schedule, form.getSources()));
		form.setStatistics(getScheduleStatistics(schedule));
		
		File configFile = PMMTableConfigHelper.getTableConfigFile();		
		if (schedule instanceof Planner) {
			form.setTableId(PLANNER_TABLE_ID, configFile);
		} else {
			form.setTableId(ACTIVITY_TABLE_ID, configFile);	
		}
		
		return mapping.findForward("default");
	}

	/**
	 * Replace the title in the schedule with the complete title (including metadata)
	 * 
	 * @param schedule
	 */
	private void setTitles(Schedule schedule) {
		if (!schedule.getTitles().isEmpty()) {
			List<Long> titleIds = new ArrayList<Long>();
			for(Title t : schedule.getTitles()) {
				titleIds.add(t.getId());
			}
			Collection<Title> completeTitles = titleService.getCompleteTitles(titleIds);
			for (Title t : completeTitles) {
				for (Title st : schedule.getTitles()) {
					if (st.getId().equals(t.getId())) {
						/* Not going thru schedule api to avoid events */
						schedule.getTitles().remove(st);
						schedule.getTitles().add(t);
						break;
					}
				}
			}
		}
	}

	/**
	 * Deletes titles from the schedule
	 */
	public ActionForward deleteTitles(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ScheduleForm form = (ScheduleForm)actionform;
		String id = form.getScheduleId();
		Schedule schedule = service.get(Long.parseLong(id));
		
		logger.debug("Schedule " + schedule);
		
		if (form.getSelectedTitles() == null)
			throw new ScheduleRuntimeException("At least one title must be selected");
		List<Title> titles = new ArrayList<Title>();
		
		for (String selected : form.getSelectedTitles()) {
			Long titleId = Long.parseLong(selected);
			logger.debug("Title id " + titleId);
			Title title = schedule.getTitle(titleId);
			
			if (title != null)
				titles.add(title);
		}
		schedule.removeTitles(titles);
		service.save(schedule);
		
		ActionRedirect redirect = new ActionRedirect(mapping.findForward("default"));
		
		redirect.addParameter("scheduleId", schedule.getId().toString());
		redirect.addParameter("spec", form.getSpec());
		
		return redirect;
	}
	
	/**
	 * Exports a schedule into its equivalent XML format
	 */
	public ActionForward export(ActionMapping mapping, ActionForm actionform,
		HttpServletRequest request, HttpServletResponse response) throws Exception {
		ScheduleForm form = (ScheduleForm)actionform;
		String id = form.getScheduleId();
		logger.debug("Schedule id " + id);
		
		Schedule schedule = service.get(Long.parseLong(id));
		
		addTransientProperties(schedule);
		
		String spec = form.getSpec();
		addTitleMetadata(schedule, spec);
		
		Marshaller<Schedule> marshaller = new Marshaller<Schedule>();
		Node node = marshaller.marshal(schedule);
		String xml = XMLDocumentUtility.convertToString(node);
		xml = xml.replaceAll("&lt;", "<");
		xml = xml.replaceAll("&gt;", ">");
		String exportFileName = generateFileName(schedule);
		writeHeaders(response, exportFileName);
		writeScheduleXml(response, xml);
		
		return null;
	}
	
	private String generateFileName(Schedule schedule) {
		String exportFileName = "";
		
		if (schedule instanceof Planner){
			Long partnerId = schedule.getSourcePartnerID();
			exportFileName = getPartner(partnerId).getName();			
		} else if (schedule instanceof DistributionSchedule){
			exportFileName = ((DistributionSchedule)schedule).getPartner();			
		}
		Date scheduleDate = schedule.getDate();
		DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
		exportFileName +=  "_" + formatter.format(scheduleDate) + ".xml";
		return exportFileName;
	}

	/**
	 * @param schedule
	 * @return
	 */
	private ScheduleStatistics getScheduleStatistics(Schedule schedule) {
		String bundle = this.getClass().getPackage().getName() + ".schedule";
		int count = Integer.parseInt(ResourceBundle.getBundle(bundle).getString("history"));
		Collection<Schedule> previous = searchService.getPreviousSchedules(schedule, count);
		ScheduleStatistics stats = new ScheduleStatistics(schedule, previous);
		
		return stats;
	}
		
	private void addTitleMetadata(Schedule schedule, String spec) throws Exception {
		ITitleService titleService = (ITitleService) ServiceRegistry
				.getDefault().lookup(ITitleService.class);
		Collection<Title> titles = schedule.getTitles();
		logger.debug("titles.size()="+titles.size());
		for (Title title : titles) {
			Document doc = titleService.getMetadata(title.getId(), spec);
			String metadataContent = XMLDocumentUtility.convertToString(doc);
			/* remove the xml declaration generated */
			int xmlDeclarationEndInd = metadataContent.indexOf("?>");
			if (xmlDeclarationEndInd != -1)
				metadataContent = metadataContent.substring(xmlDeclarationEndInd + 2);
			title.setMetadataContent(metadataContent);			
		}
	}
	
	private void addTransientProperties(Schedule schedule) {
		Long partnerId = schedule.getSourcePartnerID();
		
		if (partnerId != null) {
			String providerID = getPartner(partnerId).getProviderId();
			
			schedule.setProviderID(providerID);
		}
		
		Long contextID = schedule.getContextID();
		
		if (schedule instanceof DistributionSchedule) {
			DistributionSchedule d = DistributionSchedule.class.cast(schedule);
			String destination = getDestination(contextID);
			
			d.setPartner(destination);
		}
	}
	
	private void writeHeaders(HttpServletResponse response, String exportFileName) {
		response.setContentType("application/xml");
		
		/* I think this is some kinda hack for IE */
		response.setHeader("Pragma", "public");
		
		/* Don't cache */
		response.setHeader("Cache-Control", "max-age=0");
		
		response.setHeader("Content-Disposition","attachment; filename=" + exportFileName );
	}
	
	@SuppressWarnings("unchecked")
	private Partner getPartner(Long partnerId) {
		ICacheService<IContainer> cache = 
			(ICacheService<IContainer>)ServiceRegistry.getDefault().lookup("Container Cache");
		
		for (Serializable key : cache.getKeys()) {
			IContainer container = cache.get(key);
			
			if (container.getContainerType() == ContainerType.PARTNER) {
				Partner partner = Partner.class.cast(container);
				
				if (partnerId.longValue() == partner.getId())
					return partner;
			}
		}
		
		throw new RuntimeException("Partner not found: " + partnerId);
	}
	
	@SuppressWarnings("unchecked")
	private String getDestination(Long contextID) {
		ICacheService<IContainer> cache = 
			(ICacheService<IContainer>)ServiceRegistry.getDefault().lookup("Container Cache");
		IContainer container = cache.get(contextID.longValue());
		
		return container.getContainerName();
	}
	
	private void writeScheduleXml(HttpServletResponse response, String xml) throws Exception {
		PrintWriter out = response.getWriter();
		
		out.write(xml);
		out.println();
		out.close();		
	}
}
