package com.tandbergtv.metadatamanager.consumer;

import static com.tandbergtv.metadatamanager.model.FieldName.FILE_NAME;
import static com.tandbergtv.metadatamanager.model.FieldName.FILE_URL;
import static com.tandbergtv.metadatamanager.model.FieldName.MIME_TYPE;
import static com.tandbergtv.metadatamanager.model.FieldName.RESOLUTION;
import static com.tandbergtv.metadatamanager.model.FieldName.SIZE;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.springframework.web.context.WebApplicationContext;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import com.tandbergtv.metadatamanager.exception.InvalidRevisionException;
import com.tandbergtv.metadatamanager.exception.MetadataException;
import com.tandbergtv.metadatamanager.exception.SearchException;
import com.tandbergtv.metadatamanager.exception.TranslationException;
import com.tandbergtv.metadatamanager.factoryImpl.IdentifierFactory;
import com.tandbergtv.metadatamanager.factoryImpl.SpecHandlerFactory;
import com.tandbergtv.metadatamanager.model.Asset;
import com.tandbergtv.metadatamanager.model.Field;
import com.tandbergtv.metadatamanager.model.FileType;
import com.tandbergtv.metadatamanager.model.RootAssetRevision;
import com.tandbergtv.metadatamanager.model.Item.ItemType;
import com.tandbergtv.metadatamanager.spec.IIdentifier;
import com.tandbergtv.metadatamanager.spec.ISpecHandler;
import com.tandbergtv.metadatamanager.spec.IValidator;
import com.tandbergtv.metadatamanager.specimpl.ttv.TTVId;
import com.tandbergtv.metadatamanager.util.XmlUtil;
import com.tandbergtv.metadatamanager.validation.ValidationError;

/**
 * This class serves as the bean to create a webservice using WTP plugin. Note:
 * 1. The input and return types have been converted to String types due to
 * compatibility with JAX-RPC. 2. Overloading is also not supported by JAX-RPC
 * hence had to use different names for search/delete operations.
 * 
 * 
 * @author spuranik
 * 
 */
public class Consumer {

	public static final String IDENTIFIER_KEY_ID = "id";
	protected final Logger logger = Logger.getLogger(this.getClass());
	WebApplicationContext wac;

	public Consumer(WebApplicationContext wac) {
		super();
		this.wac = wac;
	}

	/**
	 * Saves the given doc via the handler used for the specified spec.
	 * 
	 * @param filePath
	 *            The file name of the input document.
	 * @param spec
	 *            e.g. 'MSTV','CL1_1', 'TTV'
	 * @return ttv id of the root asset in the given doc.
	 */
	public long saveDocument(String filePath, String spec) {
		ISpecHandler handler = SpecHandlerFactory.getInstance(spec);
		logger.debug("Got handler of type: " + handler.getClass());

		// read the input file
		DocumentBuilder builder;
		Document inputDoc;
		try {
			builder = XmlUtil.createDocumentBuilder();
			builder.setEntityResolver(new ADIDTDEntityResolver());
			inputDoc = builder.parse(new File(filePath));
		} catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
		} catch (SAXException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		try {
			List<IIdentifier> ids = handler.put(inputDoc);
			logger.debug("Added doc which generated ids: ");
			if (ids.size() > 0) {
				// since we are returning only one asset id
				IIdentifier id = ids.get(0);
				try {
					Asset asset = id.getAsset();
					logger.info("Added asset with id: " + id.toString()
							+ " [TTVId= " + asset.getId() + "]");
					return asset.getId();
				} catch (SearchException e) {
					throw new RuntimeException(
							"Error while converting to ttvId: ", e);
				}
			} else {
				throw new RuntimeException(
						"There was surely some problem when adding the assets.");
			}
		} catch (MetadataException e1) {
			throw new RuntimeException("Error while putting doc: ", e1);
		}
	}

	/**
	 * Validates the given document as per the spec and writes the errors in a
	 * file with the filepath provided.
	 * 
	 * @param filePath
	 *            filePath which contains the input document that needs to be
	 *            validated.
	 * @param spec
	 *            e.g. 'MSTV','CL1_1', 'TTV'. The document will be run against
	 *            the validator(s) known to this spec handler.
	 * @param locale
	 *            Comma separated list of 2 letter language code followed by
	 *            optional country code. e.g. 'en,US'; 'en' Currently only
	 *            english language is supported.
	 * @throws IOException
	 */
	public String validateDocument(String filePath, String spec, String locale)
			throws IOException {
		/*
		 * get the handler and then use its validator to validate the doc in the
		 * given input file path.
		 */
		ISpecHandler handler = SpecHandlerFactory.getInstance(spec);
		Map<String, IValidator> validators = handler.getRuleValidators();
		IValidator validator;
		Iterator<Entry<String, IValidator>> validatorIter = validators
				.entrySet().iterator();
		if (validatorIter.hasNext()) {
			validator = validatorIter.next().getValue();
		} else {
			throw new RuntimeException("Validator not found");
		}

		// validate
		DocumentBuilder builder;
		Document inputDoc;
		try {
			builder = XmlUtil.createDocumentBuilder();
			builder.setEntityResolver(new ADIDTDEntityResolver());
			inputDoc = builder.parse(new File(filePath));
		} catch (ParserConfigurationException e1) {
			throw new RuntimeException(e1);
		} catch (SAXException e) {
			throw new RuntimeException(e);
		}

		List<ValidationError> errors = validator.validate(inputDoc);
		if (errors.size() == 0) {
			logger.info("No errors in validation");
			return "No errors in validation";
		}
		StringBuilder sb = new StringBuilder();
		for (ValidationError error : errors) {
			// Field names can be changed at this stage if required by the
			// consumer app. Sample given below.
			/*
			 * List<String> fields = error.getErrorFields(); if
			 * (fields.contains("StartDate")) { int position =
			 * fields.indexOf("StartDate"); fields.remove("StartDate");
			 * fields.add(position, "StartDate Alias"); }
			 * error.setErrorFields(fields);
			 */

			String[] localeInfo = locale.split(",");
			Locale l = (localeInfo.length == 2) ? new Locale(localeInfo[0]
					.trim(), localeInfo[1].trim()) : new Locale(localeInfo[0]
					.trim());
			String errorString = error.getErrorCode() + ":"
					+ error.getMessage(l) + " at " + error.getErrorLocation() + error.getErrorFields();
			logger.debug(error);
			if (sb.length() > 0) {
				sb.append(System.getProperty("line.separator"));
			}
			sb.append(errorString);
		}
		return sb.toString();
	}

	/**
	 * Searches for an asset given a TTV asset id.
	 * 
	 * @param id
	 *            ttv asset id to look for
	 * @return a TTV document. If this asset is a group, it will build the
	 *         complete tree.
	 * @throws TranslationException
	 * @throws SearchException
	 * @throws MetadataException
	 * @throws IOException
	 */
	public String get(long id) throws MetadataException, SearchException,
			TranslationException, IOException {
		ISpecHandler ttvHandler = SpecHandlerFactory.getInstance("TTV");
		IIdentifier ttvId = getTTVId(id);
		Document assetDoc = ttvHandler.get(ttvId);
		return getString(assetDoc);
	}

	protected IIdentifier getTTVId(long id) {
		IIdentifier ttvId = IdentifierFactory.getTTVIdentifier();
		Map<String, String> identifiers = ttvId.getSpecIdentifiers();
		if (identifiers.containsKey(IDENTIFIER_KEY_ID)) {
			identifiers.put(IDENTIFIER_KEY_ID, String.valueOf(id));
		}
		ttvId.setSpecIdentifiers(identifiers);
		return ttvId;
	}

	/**
	 * Searches for an asset which has the given providerId and assetId. The
	 * spec is used only to map the providerId and assetId xpaths to appropriate
	 * TTV Xpaths.
	 * 
	 * @param providerId
	 *            providerId to match with
	 * @param assetId
	 *            assetId to match with
	 * @param spec
	 *            supported values are 'MSTV' and 'CL1_1'
	 * 
	 * @return Document in the format defined by the specified spec. If this
	 *         asset is a group asset, it builds the complete tree.
	 * @throws TranslationException
	 * @throws SearchException
	 * @throws MetadataException
	 * @throws IOException
	 */
	public String searchAsset(String providerId, String assetId, String spec)
			throws MetadataException, SearchException, TranslationException,
			IOException {
		ISpecHandler handler = SpecHandlerFactory.getInstance(spec);
		IIdentifier id = handler.getIdentifier();
		// populate the id fields correctly for the specs.
		Map<String, String> idFields = id.getSpecIdentifiers();
		idFields = populate(idFields, providerId, assetId, spec);
		id.setSpecIdentifiers(idFields);
		Document assetDoc = handler.get(id);
		return getString(assetDoc);
	}

	/**
	 * Deletes the asset with the given ttv id.
	 * 
	 * @param id
	 *            ttv id for the asset that needs to be deleted.
	 * @return true if the asset was successfully deleted. This is to done
	 *         because testing tools e.g. WebLOAD may be waiting for some
	 *         response.
	 */
	public boolean delete(long id) {
		ISpecHandler ttvHandler = SpecHandlerFactory.getInstance("TTV");
		IIdentifier ttvId = getTTVId(id);
		try {
			ttvHandler.deleteUnique(ttvId);
		} catch (SearchException e) {
			throw new RuntimeException("Error when deleting asset with id: "
					+ id, e);
		}
		return true;
	}

	/**
	 * Deletes the asset which has the given providerId and assetId. The spec is
	 * used only to map the providerId and assetId xpaths to appropriate.
	 * 
	 * @param providerId
	 *            providerId to match with assetId to match with
	 * @param spec
	 *            supported values are 'MSTV' and 'CL1_1'
	 * @throws SearchException
	 */
	public boolean deleteAsset(String providerId, String assetId, String spec)
			throws SearchException {
		ISpecHandler handler = SpecHandlerFactory.getInstance(spec);
		IIdentifier id = handler.getIdentifier();
		Map<String, String> identifiers = id.getSpecIdentifiers();
		identifiers = populate(identifiers, providerId, assetId, spec);
		id.setSpecIdentifiers(identifiers);
		handler.deleteUnique(id);
		return true;
	}

	/**
	 * Deletes all the assets given the ttv id list.
	 * 
	 * @param ids
	 *            comma separated list of ttv ids of the assets to be deleted
	 * @return comma separated list of ttv ids of the deleted assets
	 */
	public String deleteAssets(String ids) {
		String[] idsToDelete = ids.split(",");
		List<IIdentifier> ttvIds = new ArrayList<IIdentifier>();
		for (String id : idsToDelete) {
			IIdentifier ttvId = getTTVId(Long.valueOf(id.trim()));
			ttvIds.add(ttvId);
		}

		ISpecHandler handler = SpecHandlerFactory.getInstance("TTV");
		List<IIdentifier> deletedIdList = handler.deleteAll(ttvIds);
		StringBuilder sb = new StringBuilder();
		for (IIdentifier ttvId : deletedIdList) {
			sb.append(((TTVId) ttvId).getId()).toString();
			if (sb.length() > 0) {
				sb.append(",");
			}
		}
		return (sb.toString().length() > 0) ? sb.toString()
				: "No assets were deleted.";
	}
	
	/**
	 * Based on the spec, this method populates the map of identifying fields
	 * using the given providerId and assetId.
	 * 
	 * @param identifiers
	 * @param providerId
	 * @param assetId
	 * @param spec
	 * @return a map containing the providerId and assetId with the apprpriate
	 *         xpath as the key.
	 */
	private Map<String, String> populate(Map<String, String> identifiers,
			String providerId, String assetId, String spec) {
		String MSTV_PROVIDERID_XPATH = "assetPackages/assetPackage/providerID";
		String MSTV_ASSETID_XPATH = "assetPackages/assetPackage/metadata/assetID";

		String CL1_1_PROVIDERID_XPATH = "ADI/Metadata/AMS/Provider_ID";
		String CL1_1_ASSETID_XPATH = "ADI/Metadata/AMS/Asset_ID";

		Iterator<Entry<String, String>> fieldsIter = identifiers.entrySet()
				.iterator();
		while (fieldsIter.hasNext()) {
			Entry<String, String> entry = fieldsIter.next();
			if (spec.equals("MSTV")) {
				if (entry.getKey().equals(MSTV_PROVIDERID_XPATH)) {
					identifiers.put(entry.getKey(), providerId);
				} else if (entry.getKey().equals(MSTV_ASSETID_XPATH)) {
					identifiers.put(entry.getKey(), assetId);
				}
			} else if (spec.equals("CL1_1")) {
				if (entry.getKey().equals(CL1_1_PROVIDERID_XPATH)) {
					identifiers.put(entry.getKey(), providerId);
				} else if (entry.getKey().equals(CL1_1_ASSETID_XPATH)) {
					identifiers.put(entry.getKey(), assetId);
				}
			}
		}
		return identifiers;
	}

	/**
	 * Util method to convert a doc object to a string.
	 * 
	 * @param doc
	 *            xml doc which needs to be converted.
	 * @return string representation of the input xml doc
	 * @throws IOException
	 */
	private String getString(Document doc) throws IOException {
		StringWriter out = new StringWriter();
		OutputFormat format = new OutputFormat(doc);
		format.setIndenting(false);
		XMLSerializer serializer = new XMLSerializer(out, format);
		serializer.serialize(doc);
		return out.toString();
	}
	
	
	public class ADIDTDEntityResolver implements EntityResolver {
		public InputSource resolveEntity(String publicId, String systemId) throws SAXException, java.io.IOException {
			InputStream s = this.getClass().getClassLoader().getResourceAsStream("ADI.DTD");
			return new InputSource(s);
		}
	}
	
	public List<RootAssetRevision> getVersions(long id) throws MetadataException, SearchException, TranslationException{
		ISpecHandler ttvHandler = SpecHandlerFactory.getInstance("TTV");
		IIdentifier ttvId = getTTVId(id);
		return ttvHandler.getRevisions(ttvId);
	}
	
	public String get(long id, String version) throws MetadataException, SearchException, TranslationException,
			IOException {
		ISpecHandler ttvHandler = SpecHandlerFactory.getInstance("TTV");
		IIdentifier ttvId = getTTVId(id);
		Document assetDoc = ttvHandler.get(ttvId, version);
		return getString(assetDoc);
	}
	
	public String rollback(long id, String version) throws MetadataException, SearchException, TranslationException,
			IOException, InvalidRevisionException {
		ISpecHandler ttvHandler = SpecHandlerFactory.getInstance("TTV");
		IIdentifier ttvId = getTTVId(id);
		Document assetDoc = ttvHandler.rollBackToRevision((TTVId)ttvId, version);
		return getString(assetDoc);
	}
	
	public com.tandbergtv.metadatamanager.model.File createfile() {
		com.tandbergtv.metadatamanager.model.File file = new com.tandbergtv.metadatamanager.model.File();
		
		file.setType(ItemType.VIDEO);
		file.setFileType(FileType.ORIGINAL);
		
		file.addField(new Field(FILE_URL.toString(), "rtsp://rtsp2.youtube.com/ChoLENy73wIaEQlbwqbpvT6ZzhMYDSANFEgGDA==/0/0/0/video.3gp"));
		file.addField(new Field(FILE_NAME.toString(), "ChoLENy73wIaEQlbwqbpvT6ZzhMYDSANFEgGDA==/0/0/0/video.3gp"));
		file.addField(new Field(MIME_TYPE.toString(), "video/3gpp"));
		file.addField(new Field(RESOLUTION.toString(), "1920.0x1080.0"));
		file.addField(new Field(SIZE.toString(), "26010364"));
		
		return file;
	}
}
