package eu.dnetlib.r2d2.accesstime;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.graphdb.GraphDatabaseService;

import com.google.common.collect.Lists;

import eu.dnetlib.miscutils.collections.TypeFilteredCollection;
import eu.dnetlib.r2d2.neo4j.dao.AccessTimeDao;
import eu.dnetlib.r2d2.neo4j.domain.AccessTime;

public abstract class AsyncToucher<Q extends AsyncToucher.AsyncAction> implements Runnable {
	private static final Log log = LogFactory.getLog(AsyncToucher.class); // NOPMD by marko on 11/24/08 5:02 PM

	public static class AccessTimeDescription {
		private String id;
		private long creationDate;
		private long lastAccessDate;
		private long lastModificationDate;

		public AccessTimeDescription(AccessTime timeStamp) {
			this.id = timeStamp.getId();
			this.creationDate = timeStamp.getCreationDate();
			this.lastAccessDate = timeStamp.getLastAccessDate();
			this.lastModificationDate = timeStamp.getLastModificationDate();
		}

		public long getCreationDate() {
			return creationDate;
		}

		public void setCreationDate(long creationDate) {
			this.creationDate = creationDate;
		}

		public long getLastAccessDate() {
			return lastAccessDate;
		}

		public void setLastAccessDate(long lastAccessDate) {
			this.lastAccessDate = lastAccessDate;
		}

		public long getLastModificationDate() {
			return lastModificationDate;
		}

		public void setLastModificationDate(long lastModificationDate) {
			this.lastModificationDate = lastModificationDate;
		}

		public String getId() {
			return id;
		}

		public void setId(String id) {
			this.id = id;
		}

		@Override
		public String toString() {
			return "AccessTimeDescription [creationDate=" + creationDate + ", id=" + id + ", lastAccessDate=" + lastAccessDate
					+ ", lastModificationDate=" + lastModificationDate + "]";
		}
		
		

	}

	public static abstract class AsyncAction {
		protected AccessTimeDescription timeStamp;

		public AsyncAction(final AccessTime timeStamp) {
			super();
			this.timeStamp = new AccessTimeDescription(timeStamp);
		}

		public void save(final AccessTimeDao accessTimeDao) {
			AccessTime access = accessTimeDao.getBean(timeStamp.getId());
			if (access == null)
				access = accessTimeDao.newBean();

			access.setCreationDate(timeStamp.getCreationDate());
			access.setCreationDate(timeStamp.getCreationDate());
			access.setLastAccessDate(timeStamp.getLastAccessDate());
			access.setLastModificationDate(timeStamp.getLastAccessDate());

			accessTimeDao.saveBean(access);
		}

		public AccessTimeDescription getTimeStamp() {
			return timeStamp;
		}

		public void setTimeStamp(AccessTimeDescription timeStamp) {
			this.timeStamp = timeStamp;
		}

		@Override
		public String toString() {
			return "AsyncAction [timeStamp=" + timeStamp + "]";
		}

		
	}

	@Resource
	private AccessTimeDao accessTimeDao;
	/**
	 * So that AOP can create transactions.
	 */
	@Resource
	private AccessTimeSaver saver;

	private BlockingQueue<Q> queue;

	/**
	 * how long to wait before we flush the access times to disc, so that we can group more things in batches.
	 */
	private long updateDelay;

	@PostConstruct
	public void init() {
		queue = newQueue();
		new Thread(this).start();
	}

	public BlockingQueue<Q> newQueue() {
		return new LinkedBlockingQueue<Q>();
	}

	@Override
	public void run() {
		while (true) {
			try {
				unsafeRun();
			} catch (final Throwable e) {
				log.warn("something happened in " + getClass().getSimpleName() + " async toucher, restarting", e);
				setQueue(newQueue());
			}
		}
	}

	private void unsafeRun() throws InterruptedException {
		while (true) {
			final Q head = getQueue().take();

			Thread.sleep(getUpdateDelay());

			final List<Q> actions = Lists.newArrayList(head);
			getQueue().drainTo(actions);

			log.debug("looking if we have some deferred accesstimes to save: " + getClass().getSimpleName());
			if (!actions.isEmpty()) {
				if (log.isDebugEnabled())
					log.debug("saving access times: " + getClass().getSimpleName() + " " + actions);
				getSaver().save(actions);
			}

		}
	}

	public BlockingQueue<Q> getQueue() {
		return queue;
	}

	public void setQueue(final BlockingQueue<Q> queue) {
		this.queue = queue;
	}

	public AccessTimeDao getAccessTimeDao() {
		return accessTimeDao;
	}

	public void setAccessTimeDao(final AccessTimeDao accessTimeDao) {
		this.accessTimeDao = accessTimeDao;
	}

	public AccessTimeSaver getSaver() {
		return saver;
	}

	public void setSaver(final AccessTimeSaver saver) {
		this.saver = saver;
	}

	public long getUpdateDelay() {
		return updateDelay;
	}

	public void setUpdateDelay(final long updateDelay) {
		this.updateDelay = updateDelay;
	}

}
