/*
 * Created on Jul 30, 2008 (C) Copyright TANDBERG Television Ltd.
 */

package com.tandbergtv.watchpoint.pmm.web.title;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.actions.MappingDispatchAction;

import com.tandbergtv.watchpoint.pmm.entities.Title;
import com.tandbergtv.watchpoint.pmm.title.ITitleService;
import com.tandbergtv.watchpoint.pmm.title.TitleServiceException;
import com.tandbergtv.watchpoint.pmm.title.provider.ITitleProvider;
import com.tandbergtv.watchpoint.pmm.title.provider.ITitleProviderRegistry;
import com.tandbergtv.watchpoint.pmm.util.validation.ValidationException;
import com.tandbergtv.watchpoint.pmm.util.validation.ValidationMessage;
import com.tandbergtv.watchpoint.pmm.web.util.TitleFacade;
import com.tandbergtv.watchpoint.pmm.web.util.TitleHelper;
import com.tandbergtv.workflow.core.service.ServiceRegistry;

/**
 * Action that handles the 'import' and 'sync' features for one or more external titles
 * 
 * @author Vijay Silva
 */
public class TitleImportAction extends MappingDispatchAction {

	/* The Logger */
	private static final Logger logger = Logger.getLogger(TitleImportAction.class);

	/* Resource Key for failure importing a title */
	private static final String IMPORT_FAILED_KEY = "error.title.import.failure";

	/* Resource Key for failure synchronizing a title */
	private static final String SYNC_FAILED_KEY = "error.title.sync.failure";

	/**
	 * Imports several titles from an External Source.
	 * 
	 * @param mapping
	 * @param actionform
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ActionForward importTitles(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ExternalTitleForm form = (ExternalTitleForm) actionform;

		String[] selectedTitles = form.getSelectedTitles();
		if (selectedTitles == null || selectedTitles.length == 0) {
			throw new RuntimeException("No titles selected for import.");
		}

		/* Import each of the Titles */
		List<Title> importedTitles = new ArrayList<Title>();
		Map<String, String> failedTitles = new LinkedHashMap<String, String>();
		ActionMessages messages = new ActionMessages();
		for (String selectedTitle : selectedTitles) {
			try {
				importedTitles.add(this.importTitle(form, selectedTitle));
			} catch (Exception e) {
				this.handleFailure(e, selectedTitle, messages, request, true);
				failedTitles.put(selectedTitle, this.describeTitleExternalKey(selectedTitle));
			}
		}

		form.setSuccessfulTitles(importedTitles);
		form.setFailedTitles(failedTitles);
		this.setExternalSourceName(form);
		form.setOperationName("imported");

		/* check if there are action messages to show */
		if (!messages.isEmpty()) {
			this.saveMessages(request, messages);
		}

		return mapping.findForward("default");
	}

	/**
	 * Imports a single Title from an External Source.
	 * 
	 * @param mapping
	 * @param actionform
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ActionForward importTitle(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ExternalTitleForm form = (ExternalTitleForm) actionform;
		String titleKey = form.getExternalKey();

		try {
			Title importedTitle = this.importTitle(form, titleKey);
			form.setTitle(importedTitle);
		} catch (Exception e) {
			ActionMessages messages = new ActionMessages();
			this.handleFailure(e, titleKey, messages, request, true);

			Map<String, String> failedTitles = new HashMap<String, String>();
			failedTitles.put(titleKey, this.describeTitleExternalKey(titleKey));
			form.setFailedTitles(failedTitles);
			this.saveMessages(request, messages);
		}

		form.setOperationName("imported");
		this.setExternalSourceName(form);

		return mapping.findForward("default");
	}

	/**
	 * Imports several titles from an External Source.
	 * 
	 * @param mapping
	 * @param actionform
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ActionForward synchronizeTitles(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ExternalTitleForm form = (ExternalTitleForm) actionform;

		/* Validate that at least one Title was selected */
		String[] selectedTitles = form.getSelectedTitles();
		if (selectedTitles == null || selectedTitles.length == 0) {
			throw new RuntimeException("No titles selected for sync.");
		}

		/* Get the Title Service */
		ServiceRegistry registry = ServiceRegistry.getDefault();
		ITitleService titleService = registry.lookup(ITitleService.class);

		/* Sync each of the Titles */
		ActionMessages messages = new ActionMessages();
		List<Title> successfulTitles = new ArrayList<Title>();
		Map<String, Title> failedTitles = new LinkedHashMap<String, Title>();
		for (String selectedTitle : selectedTitles) {
			Title title = null;
			try {
				title = TitleFacade.findTitle(Long.parseLong(selectedTitle));
				title = titleService.syncTitle(title);
				successfulTitles.add(title);
			} catch (Exception e) {
				this.handleFailure(e, selectedTitle, messages, request, false);
				failedTitles.put(selectedTitle, title);
			}
		}

		/* check if there are action messages to show */
		if (!messages.isEmpty())
			this.saveMessages(request, messages);

		form.setSuccessfulTitles(successfulTitles);
		form.setFailedTitles(failedTitles);
		form.setOperationName("synchronized");

		return mapping.findForward("default");
	}

	/**
	 * Sync the existing title with the external source.
	 * 
	 * @param mapping
	 * @param actionform
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ActionForward synchronizeTitle(ActionMapping mapping, ActionForm actionform,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		ExternalTitleForm form = (ExternalTitleForm) actionform;

		Title title = null;
		try {
			/* Get the Title from the database */
			title = TitleFacade.findTitle(form.getId());

			/* Update the Title Metadata with the submitted data if new external location set */
			String externalLocationId = title.getExternalLocation();
			if (externalLocationId == null || externalLocationId.trim().length() == 0) {
				title.setExternalLocation(form.getAssetDB());
				TitleHelper.copyMetadata(form.getTitlesData(), title);
			}

			/* Synchronize the Title */
			ServiceRegistry registry = ServiceRegistry.getDefault();
			ITitleService titleService = registry.lookup(ITitleService.class);
			title = titleService.syncTitle(title);

			/* Set the title in the form */
			form.setTitle(title);
		} catch (Exception e) {
			ActionMessages messages = new ActionMessages();
			String key = Long.toString(form.getId());
			this.handleFailure(e, key, messages, request, false);

			Map<String, Title> failedTitles = new HashMap<String, Title>();
			failedTitles.put(key, title);
			form.setFailedTitles(failedTitles);
			this.saveMessages(request, messages);
		}

		form.setOperationName("synchronized");
		this.setExternalSourceName(form);

		return mapping.findForward("default");
	}

	/* Import the single title given the title search key */
	private Title importTitle(ExternalTitleForm form, String titleExternalKey) {
		String specName = form.getType();
		String externalSourceId = form.getAssetDB();

		/* Get the External keys */
		TitleExternalKeySerializer serializer = TitleExternalKeySerializer.newInstance();
		Map<String, String> keys = serializer.deserialize(titleExternalKey);

		/* Get the External Title */
		ServiceRegistry registry = ServiceRegistry.getDefault();
		ITitleService titleService = registry.lookup(ITitleService.class);
		Title title = titleService.getExternalTitle(specName, externalSourceId, keys);

		/* Import the External Title */
		return titleService.importTitle(title);
	}

	/* Handle the failure of the Import of a title */
	private void handleFailure(Exception exception, String titleKey, ActionMessages actionMessages,
			HttpServletRequest request, boolean isImport) {
		/* Log the problem */
		String actionName = isImport ? "import" : "sync";
		String msg = "Failure during title " + actionName + ", failed Title key[" + titleKey + "].";
		logger.error(msg, exception);

		/* Store the action messages for the failed title */
		if (exception instanceof ValidationException) {
			ValidationException ve = (ValidationException) exception;
			List<ValidationMessage> messages = ve.getValidationMessages();
			this.addActionMessages(actionMessages, request, titleKey, messages);
		} else if (exception instanceof TitleServiceException) {
			TitleServiceException tse = (TitleServiceException) exception;
			this.addActionMessages(actionMessages, request, titleKey, tse.getFailureMessage());
		} else {
			String messageKey = isImport ? IMPORT_FAILED_KEY : SYNC_FAILED_KEY;
			actionMessages.add(titleKey, new ActionMessage(messageKey, true));
		}
	}

	/* Get a String representation of the Title External Key */
	private String describeTitleExternalKey(String externalKey) {
		try {
			TitleExternalKeySerializer serializer = TitleExternalKeySerializer.newInstance();
			Map<String, String> externalKeys = serializer.deserialize(externalKey);

			StringBuilder buf = new StringBuilder();
			boolean isFirst = true;
			for (String key : externalKeys.keySet()) {
				String value = externalKeys.get(key);

				if (isFirst)
					isFirst = false;
				else
					buf.append(", ");

				buf.append(key);
				buf.append(": ");
				buf.append(value);
			}

			return buf.toString();
		} catch (Exception e) {
			logger.warn("Failed to parse the Title external key: " + externalKey
					+ " when creating displayable key.", e);
			return externalKey;
		}
	}

	/* Get the external source name given the external source Id */
	private void setExternalSourceName(ExternalTitleForm form) {
		String externalSourceId = form.getAssetDB();
		String name = "";
		if (externalSourceId != null && externalSourceId.trim().length() > 0) {
			ServiceRegistry serviceRegistry = ServiceRegistry.getDefault();
			ITitleProviderRegistry registry = serviceRegistry.lookup(ITitleProviderRegistry.class);
			ITitleProvider externalProvider = registry.getProviderForTitle(externalSourceId);
			name = externalProvider.getProviderInstance(externalSourceId).getName();
			if (name == null)
				name = "";
		}
		form.setAssetDBName(name);
	}

	/* Build the Action Messages for the validation messages given an appropriate key */
	private void addActionMessages(ActionMessages actionMessages, HttpServletRequest request,
			String messageKey, List<ValidationMessage> validationMessages) {
		for (ValidationMessage message : validationMessages) {
			this.addActionMessages(actionMessages, request, messageKey, message);
		}
	}

	/* Build the Action Message for the validation message given an appropriate key */
	private void addActionMessages(ActionMessages actionMessages, HttpServletRequest request,
			String messageKey, ValidationMessage validationMessage) {
		String key = validationMessage.getCode();
		Object[] values = validationMessage.getProperties().toArray();
		ActionMessage actionMessage = new ActionMessage(key, values);
		actionMessages.add(messageKey, actionMessage);
	}
}
