/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.web.images;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;

/**
 * A utility for finding the URL of the correct Placeholder image.
 *
 * The mapping of image URLs to classes is created at startup, and stored in the
 * ServletContext.
 */
public class PlaceholderUtil {
	private static final Log log = LogFactory.getLog(PlaceholderUtil.class);

	private static final String ATTRIBUTE_NAME = PlaceholderUtil.class
			.getName();
	private static final String PROPERTIES_FILE_PATH = "/images/placeholders/placeholders.properties";
	private static final String DEFAULT_IMAGE_PATH = "/images/placeholders/thumbnail.jpg";

	// ----------------------------------------------------------------------
	// Static methods
	// ----------------------------------------------------------------------

	/**
	 * If we have a placeholder image for this exact type, return it. Otherwise,
	 * return the default.
	 */
	public static String getPlaceholderImagePathForType(VitroRequest vreq,
			String typeUri) {
		PlaceholderUtil pu = getPlaceholderUtil(vreq);
		if (pu == null) {
			return DEFAULT_IMAGE_PATH;
		} else {
			return pu.getPlaceholderImagePathForType(typeUri);
		}
	}

	/**
	 * If there is a placeholder image for any type that this Individual
	 * instantiates, return that image. Otherwise, return the default.
	 */
	public static String getPlaceholderImagePathForIndividual(
			VitroRequest vreq, String individualUri) {
		PlaceholderUtil pu = getPlaceholderUtil(vreq);
		if (pu == null) {
			return DEFAULT_IMAGE_PATH;
		} else {
			IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao();
			return pu.getPlaceholderImagePathForIndividual(iDao, individualUri);
		}
	}

	/** Get the PlaceholderUtil instance from the context, or null if none. */
	private static PlaceholderUtil getPlaceholderUtil(VitroRequest vreq) {
		if (vreq == null) {
			return null;
		}
		ServletContext ctx = vreq.getSession().getServletContext();
		Object attrib = ctx.getAttribute(ATTRIBUTE_NAME);
		if (attrib instanceof PlaceholderUtil) {
			return (PlaceholderUtil) attrib;
		} else {
			log.warn("Looked for a PlaceholerUtil, but found " + attrib);
			return null;
		}
	}

	// ----------------------------------------------------------------------
	// The object
	// ----------------------------------------------------------------------

	private final Map<String, String> mapUrlsByClass;

	private PlaceholderUtil(Map<String, String> map) {
		this.mapUrlsByClass = Collections
				.unmodifiableMap(new HashMap<String, String>(map));
	}

	/**
	 * If we have a placeholder image for this exact type, return it. Otherwise,
	 * return the default.
	 */
	private String getPlaceholderImagePathForType(String typeUri) {
		if (typeUri == null) {
			log.debug("getPlaceholderImagePathForType: typeUri is null");
			return DEFAULT_IMAGE_PATH;
		}
		String url = mapUrlsByClass.get(typeUri);
		if (url == null) {
			log.debug("getPlaceholderImagePathForType: no value for '"
					+ typeUri + "'");
			return DEFAULT_IMAGE_PATH;
		}
		log.debug("getPlaceholderImagePathForType: value for '" + typeUri
				+ "' is '" + url + "'");
		return url;
	}

	/**
	 * If there is a placeholder image for any type that this Individual
	 * instantiates, return that image. Otherwise, return the default.
	 */
	private String getPlaceholderImagePathForIndividual(IndividualDao iDao,
			String individualUri) {
		if (individualUri == null) {
			log.debug("getPlaceholderImagePathForIndividual: "
					+ "individualUri is null");
			return DEFAULT_IMAGE_PATH;
		}
		for (String classUri : mapUrlsByClass.keySet()) {
			String imageUrl = mapUrlsByClass.get(classUri);
			if (iDao.isIndividualOfClass(classUri, individualUri)) {
				log.debug("getPlaceholderImagePathForIndividual: individual '"
						+ individualUri + "' is of class '" + classUri
						+ "', value is '" + imageUrl + "'");
				return imageUrl;
			}
		}
		log.debug("getPlaceholderImagePathForIndividual: individual '"
				+ individualUri + "' is not of any recognized class");
		return DEFAULT_IMAGE_PATH;
	}

	// ----------------------------------------------------------------------
	// Classes used for setup
	// ----------------------------------------------------------------------

	public static class Setup implements ServletContextListener {
		@Override
		public void contextInitialized(ServletContextEvent sce) {
			ServletContext ctx = sce.getServletContext();
			StartupStatus ss = StartupStatus.getBean(ctx);

			try {
				File propertiesFile = confirmRealPath(ctx, PROPERTIES_FILE_PATH);
				Map<String, String> map = loadPropertiesToMap(propertiesFile);
				confirmImagesArePresent(ctx, map);
				ctx.setAttribute(ATTRIBUTE_NAME, new PlaceholderUtil(map));
			} catch (SetupException e) {
				if (e.getCause() == null) {
					ss.warning(this, e.getMessage());
				} else {
					ss.warning(this, e.getMessage(), e.getCause());
				}
			}
		}

		private Map<String, String> loadPropertiesToMap(File propertiesFile)
				throws SetupException {
			Properties props = new Properties();
			try {
				props.load(new FileReader(propertiesFile));
			} catch (IOException e) {
				throw new SetupException(
						"Can't load properties from the file at '"
								+ PROPERTIES_FILE_PATH
								+ "'. Is it in a valid format?", e);
			}

			Map<String, String> map = new HashMap<String, String>();
			for (Enumeration<Object> keys = props.keys(); keys
					.hasMoreElements();) {
				String key = (String) keys.nextElement();
				String value = props.getProperty(key);
				map.put(key, value);
			}

			return map;
		}

		private void confirmImagesArePresent(ServletContext ctx,
				Map<String, String> map) throws SetupException {
			Set<String> imageUrls = new HashSet<String>();
			imageUrls.add(DEFAULT_IMAGE_PATH);
			imageUrls.addAll(map.values());
			for (String imageUrl : imageUrls) {
				confirmRealPath(ctx, imageUrl);
			}

		}

		private File confirmRealPath(ServletContext ctx, String url)
				throws SetupException {
			String path = ctx.getRealPath(url);
			if (path == null) {
				throw new SetupException("Can't translate to real path: '"
						+ url + "'");
			}
			File file = new File(path);
			if (!file.exists()) {
				throw new SetupException("No file found at '" + url + "'.");
			}
			if (!file.canRead()) {
				throw new SetupException("Can't read the file at '" + url
						+ "'.");
			}
			return file;
		}

		@Override
		public void contextDestroyed(ServletContextEvent sce) {
			sce.getServletContext().removeAttribute(ATTRIBUTE_NAME);
		}

	}

	private static class SetupException extends Exception {
		public SetupException(String message) {
			super(message);
		}

		public SetupException(String message, Throwable cause) {
			super(message, cause);
		}
	}
}
