package com.ttv.acs.beans.serviceHandlers;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.vfs.FileSystemException;
import org.apache.log4j.Logger;

import com.ttv.acs.stub.adi.ADI;
import com.ttv.acs.stub.adi.ADIParseUtil;
import com.ttv.acs.stub.adi.Asset;
import com.ttv.acs.stub.adi.WorkOrderRequest;
import com.ttv.acs.util.ADIUtil;
import com.ttv.acs.util.ApplicationProperties;
import com.ttv.acs.util.ChecksumUtil;
import com.ttv.acs.util.FileURIConverter;
import com.ttv.acs.util.PConstants;
import com.ttv.acs.util.VirtualFileSystemManager;

/**
 * LocalContentStoreHandler
 * <p>
 * Manage the local content store and provide a single point of entry into the
 * physical store.
 * 
 * @author <a href="mailto:cbrown@tandbergtv.com">Corey Brown</a>
 * 
 */
public class LocalContentStoreHandler {

	private static LocalContentStoreHandler instance = null;
	private final Logger log = Logger.getLogger(LocalContentStoreHandler.class);
	
	private static String DATE_FORMAT = "MM-dd-yyyy";
	
	private static String TIME_FORMAT = "HHmmssSSS";

	public static synchronized LocalContentStoreHandler getInstance() {
		if (instance == null) {
			instance = new LocalContentStoreHandler();
		}

		return instance;
	}

	/**
	 * <b>provisionPackage</b>
	 * <p>
	 * Provision a package into the content store
	 * 
	 * @param packageName
	 * @param URL
	 * @throws Exception
	 */
	public WorkOrderRequest provisionPackage(String packageName, String URL)
			throws Exception {
		log.info("Received provision request for package : " + packageName
				+ ", at URL: " + URL);

		boolean provisionResult = true;
		List<String> errorMsgs = new ArrayList<String>();

		// ADI.XML or ADI.xml
		String adiXMLFileName = getAdiXMLFileName(URL);

		ADI adi = null;
		try {
			adi = getMetadata(packageName, URL, adiXMLFileName);
		} catch (Exception ex) {
			String errMsg = "Failed to retrieve/parse URL: " + URL;
			log.error(errMsg, ex);
			errorMsgs.add(errMsg);
			provisionResult = false;
		}

		//
		// Acquire the content
		//
		String metadataFilePath = URL;
		if (adi != null) {
			Iterator<Asset> it = adi.getAssets().iterator();
			while (it.hasNext()) {
				Asset asset = it.next();

				boolean acquireResourceResult = true;
				try {
					provisionAsset(asset, adi, metadataFilePath, adiXMLFileName);	
				} catch (Exception ex) {
					log.error(ex.getMessage(), ex);
					errorMsgs.add(ex.getMessage());
					provisionResult = false;
					acquireResourceResult = false;
					it.remove();
				}
				String destinationStr = asset.getPath();
				if (ADIUtil.getCheckChecksumFlag() && acquireResourceResult
						&& !validateCheckSum(destinationStr, asset
								.getCheckSum())) {
					provisionResult = false;
					errorMsgs.add("Checksum validation failed for content "
							+ asset.getContent());
					it.remove();
				}
			}
			metadataFilePath = adi.getContentStoreDirectoryAbsolutePath()
					+ File.separator + adiXMLFileName;
		}

		return constructWorkorderRequest(metadataFilePath, provisionResult,
				errorMsgs, adi);
	}

	private synchronized ADI getMetadata(String packageName, String URL,
			CharSequence adiXMLFileName) throws Exception {

		//
		// Create a temporary file to house the ADI.XML
		//

		File xmlTempFile = File.createTempFile("ADI", ".XML");
		File dtdTempFile = File.createTempFile("ADI", ".DTD");

		//
		// Retrieve the XML from the remote system
		//

		URI sourceURI = new URI(URL);
		URI destinationURI = xmlTempFile.toURI();
		acquireResource(sourceURI, destinationURI);

		//
		// Retrieve the DTD from the remote system
		//

		sourceURI = new URI(URL.replace(adiXMLFileName, "ADI.DTD"));
		destinationURI = dtdTempFile.toURI();
		acquireResource(sourceURI, destinationURI);

		//
		// Parse the resulting file to determine package AssetId and ProviderId
		//
		ADI adi = ADIParseUtil.parseFile(xmlTempFile, dtdTempFile);

		//
		// Create content store directory structure
		//

		File contentStoreDirectoryPath = new File(getContentStoreAbsolutePath()
				+ File.separator + adi.getMetadata().getProviderId()
				+ File.separator + new SimpleDateFormat(DATE_FORMAT).format(new Date())
				+ File.separator + new SimpleDateFormat(TIME_FORMAT).format(new Date())
				+ File.separator + adi.getMetadata().getAssetId());

		if (!getVFS().localStorageDirectoryIsReady(
				contentStoreDirectoryPath.getAbsolutePath())) {
			String errorString = "Failed to create content store path: "
					+ contentStoreDirectoryPath.getAbsolutePath();
			log.error(errorString);
			throw new Exception(errorString);
		}

		adi.setContentStoreDirectoryAbsolutePath(contentStoreDirectoryPath
				.getAbsolutePath());

		//
		// Copy the XML and DTD into the proper directory
		//
		sourceURI = xmlTempFile.toURI();
		destinationURI = new File(contentStoreDirectoryPath.getAbsolutePath()
				+ File.separator + adiXMLFileName).toURI();
		acquireResource(sourceURI, destinationURI);

		sourceURI = dtdTempFile.toURI();
		destinationURI = new File(contentStoreDirectoryPath.getAbsolutePath()
				+ File.separator + "ADI.DTD").toURI();
		acquireResource(sourceURI, destinationURI);

		//
		// Get rid of the temporary file(s)
		//

		if (!xmlTempFile.delete()) {
			xmlTempFile.deleteOnExit();
		}

		if (!dtdTempFile.delete()) {
			dtdTempFile.deleteOnExit();
		}

		return adi;

	}

	private String getAdiXMLFileName(String url) {
		return url.substring(url.length() - 7);
	}

	private WorkOrderRequest constructWorkorderRequest(String metadataFilePath,
			boolean provisionResult, List<String> errorMsgs, ADI adi) {
		return new WorkOrderRequest(metadataFilePath, provisionResult,
				errorMsgs, adi);
	}

	private boolean validateCheckSum(String contentFilePath, String checkSum)
			throws NoSuchAlgorithmException, IOException {
		String newChecksum = ChecksumUtil.getCheckSum(contentFilePath);
		boolean validateResult = checkSum.equals(newChecksum);
		if (!validateResult) {
			log
					.error("Checksum validation failed for file: "
							+ contentFilePath);
			File contentFile = new File(contentFilePath);
			boolean deleteResult = contentFile.delete();
			if (!deleteResult) {
				log.error("Failed to delete content file " + contentFilePath);
			}
		}

		return validateResult;
	}

	/**
	 * <b>acquireResource</b>
	 * <p>
	 * Use the VirtualFileSystemManager to copy files. Depending on the URI, the
	 * source file may be on a remote system.
	 * 
	 * @param source
	 * @param destination
	 * @throws Exception
	 */
	public void acquireResource(URI source, URI destination) throws Exception {

		log.debug("Source resource: " + source.toString());
		log.debug("Destination resource: " + destination.toString());

		try {
			getVFS().copyURI(source, destination);
		} catch (FileSystemException fse) {
			String errorString = "Failed to copy sourcefile: "
					+ source.toString() + " from remote system.";
			log.error(errorString, fse);
			throw new Exception(errorString);
		} catch (IOException ioe) {
			String errorString = "Failed to copy sourcefile: "
					+ source.toString() + " from remote system.";
			log.error(errorString, ioe);
			throw new Exception(errorString);
		} catch (Exception e) {
			String errorString = "Failed to copy sourcefile: "
					+ source.toString() + " from remote system.";
			log.error(errorString, e);
			throw new Exception(errorString);
		}
	}

	/**
	 * <b>getContentStoreAbsolutePath</b>
	 * <p>
	 * Get the content store absolute path
	 * 
	 * @return String
	 */
	public String getContentStoreAbsolutePath() {
		try {
			return ApplicationProperties.getInstance().getProperty(
					PConstants.ACS_CONTENTSTORE,
					PConstants.ADPOINT_ACS_DEFAULT_CONTENTSTORE);
		} catch (Exception e) {
			log.error("Failed to get property "
					+ PConstants.ACS_NAME_SERVER_CORBALOC, e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * <b>initializeContentStore</b>
	 * <p>
	 * Initialize the content store directory path
	 * 
	 */
	private void initializeContentStore() {

		VirtualFileSystemManager vfs = getVFS();

		if (!vfs.localStorageDirectoryIsReady(getContentStoreAbsolutePath())) {
			log.error("Failed to create content store directory structure.",
					null);
		}
	}

	/**
	 * <b>getVFS</b>
	 * <p>
	 * Get an instance of a VirtualFileSystemManager
	 * 
	 * @return
	 */
	private VirtualFileSystemManager getVFS() {
		return new VirtualFileSystemManager();
	}

	/**
	 * Private constructor
	 * 
	 */
	private LocalContentStoreHandler() {
		initializeContentStore();
	}

	private void provisionAsset(Asset asset, ADI adi, String metadataFilePath,
			String metadataFileName) throws Exception {
		if (asset.hasFileURI())
			provisionAssetWithFileURI(asset,adi);
		else
			provisionAssetWithFTPURI(asset, adi, metadataFilePath,
					metadataFileName);
	}
	/**
	 * converts file URI to linux path and sets the value in Asset object. The idea here is not to
	 * copy the file but use the same path on filesystem.
	 * @param asset 
	 * @throws Exception when file URI cannot be converted to linux path
	 */
	private void provisionAssetWithFileURI(Asset asset, ADI adi) throws Exception {
		String content = asset.getContent();
		log.debug("Processing content :" + content);
		String contentPath = new FileURIConverter().convertURIToUnix(
				content);
		log.debug("Converted path :" + contentPath);
		if (contentPath == null) {
			asset.setPath(content);
			log
					.error("[Configuration Error] "
							+ content
							+ " fileURIConverter.properties cannot find converter entry for "
							+ content);
			throw new Exception("Fail to convert URI:" + content);
		} else {
			asset.setPath(contentPath);
		}
	}

	/**
	 * converts content URI to FTP URI if metadata contains only asset file name. If metadata contains
	 * a FTP URI to asset file then use the FTP URI to download the file. It also prepares the destination
	 * path where the file will be downloaded.
	 * 
	 * @param asset
	 * @param adi
	 * @param metadataFilePath
	 * @param metadataFileName
	 * @throws Exception
	 */
	private void provisionAssetWithFTPURI(Asset asset, ADI adi,
			String metadataFilePath, String metadataFileName) throws Exception {
		String content = asset.getContent();
		log.debug("Processing content :" + content);
		URI sourceURI = null;
		URI destinationURI = null;
		/* calculate source path and destination path for provisioning */
		if (asset.hasFTPURI()) {
			sourceURI = new URI(content);
			/* get file name from source path */
			String path = sourceURI.getPath();
			String fileName = path.substring(path.lastIndexOf("/") + 1, path
					.length());
			/* set destination URI */
			destinationURI = new URI(adi.getContentStoreDirectoryAbsolutePath()
					+ File.separator + fileName);
			asset.setPath(destinationURI.getPath());
			
		} else {
			destinationURI = new URI(adi.getContentStoreDirectoryAbsolutePath()
					+ File.separator + content);
			asset.setPath(destinationURI.getPath());
			sourceURI = new URI(metadataFilePath
					.replace(metadataFileName, content));
		}
		try {
			acquireResource(sourceURI, destinationURI);
		} catch (Exception ex) {
			String errMsg = "Failed to acquire content: "
					+ sourceURI.toString();
			throw new Exception(errMsg, ex);
		
		}
	}
	
}