package eu.dnetlib.r2d2.accesstime;

import java.util.Date;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Component;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;

import eu.dnetlib.miscutils.cache.Cache;
import eu.dnetlib.r2d2.neo4j.Neo4jBean;
import eu.dnetlib.r2d2.neo4j.dao.IdBroker;

public class Toucher {
	private static final Log log = LogFactory.getLog(Toucher.class); // NOPMD by marko on 11/24/08 5:02 PM

	@Resource(name = "mongoDB")
	private DB mongo;

	private DBCollection collection;

	@Resource
	private IdBroker idBroker;

	private Cache<String, AccessTime> cache;

	@PostConstruct
	public void init() {
		collection = mongo.getCollection("accessTimes");
	}

	public void touch(final Neo4jBean bean) {
		touch(bean.getId());
	}

	public void touch(final String beanId) {
		log.debug("Touching: " + beanId);

		final AccessTime access = getAccessTime(beanId, true);
		access.setLastAccessDate(System.currentTimeMillis());
		save(access);
	}

	public void modified(final Neo4jBean bean) {
		if (bean != null)
			modified(bean.getId());
		else
			log.warn("touching modification stamp for null bean");
	}

	/**
	 * We can assume that this method is called within a RW transaction so we don't neet to enqueue it to the async.
	 * 
	 * @param beanId
	 */
	public void modified(final String beanId) {
		final AccessTime access = getAccessTime(beanId, true);
		if (access != null) {

			access.setLastModificationDate(System.currentTimeMillis());

			cache.put(beanId, access);
			save(access);
		} else {
			log.info("Didn't store modification time because access bean is not yet created (delayed creation)");
		}
	}

	protected void save(final AccessTime access) {

		final BasicDBObject example = new BasicDBObject();
		example.put("id", access.getId());

		DBObject doc = collection.findOne(example);

		log.debug("saving access to mongo, overwriting existing object: " + doc);

		if (doc == null) {
			doc = new BasicDBObject();
			doc.put("id", access.getId());
		}

		doc.put("creationDate", access.getCreationDate());
		doc.put("lastModificationDate", access.getLastModificationDate());
		doc.put("lastAccessDate", access.getLastAccessDate());

		if (doc != null)
			collection.save(doc);
	}

	public AccessTime ensureAccesTime(final Neo4jBean bean) {
		return getAccessTime(bean.getId());
	}

	/**
	 * Ensure that the access
	 * 
	 * @param beanId
	 * @return
	 */

	public AccessTime getAccessTime(final String beanId) {
		return getAccessTime(beanId, false);
	}

	public AccessTime getAccessTime(final String beanId, final boolean touch) {
		final AccessTime access = getAccessTimeFromWriteThroughCache(beanId);

		if (touch) {
			access.setLastAccessDate(System.currentTimeMillis());
			cache.put(beanId, access);
		}

		return access;
	}

	public AccessTime getAccessTimeFromWriteThroughCache(final String beanId) {
		final AccessTime time = cache.get(beanId);
		if (time != null) {
			if (log.isDebugEnabled())
				log.debug("Returning a cached accesstime (write-through) entry for " + beanId + " since the entry might not be flushed on db. " + time
						+ " Access: " + new Date(time.getLastAccessDate()) + " Modified " + new Date(time.getLastModificationDate()));

			return time;
		}

		final AccessTime access = getAccessTimeFromStorage(beanId);
		cache.put(beanId, access);
		return access;
	}

	public AccessTime getAccessTimeFromStorage(final String beanId) {
		try {

			final DBObject doc = collection.findOne(new BasicDBObject("id", beanId));
			if (doc != null) {
				log.debug("access time for " + beanId + " was found in the db");
				return new AccessTime((String) doc.get("id"), (Long) doc.get("creationDate"), (Long) doc.get("lastModificationDate"), (Long) doc
						.get("lastAccessDate"));
			}

		} catch (final java.util.NoSuchElementException e) {
			log.warn("got exception while getting access time for bean: " + beanId);
		}

		log.debug("access time for " + beanId + " was not found in the db, creating it");

		final AccessTime access = new AccessTime();
		access.setId(beanId);
		access.setLastAccessDate(System.currentTimeMillis());
		access.setLastModificationDate(System.currentTimeMillis());
		access.setCreationDate(System.currentTimeMillis());

		cache.put(beanId, access);
		//		asyncModification.enqueueNewAccess(beanId, access);

		return access;
	}

	public Cache<String, AccessTime> getCache() {
		return cache;
	}

	@Required
	public void setCache(final Cache<String, AccessTime> cache) {
		this.cache = cache;
	}

}
