package eu.dnetlib.data.utility.objectpackaging;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.SAXReader;

import eu.dnetlib.data.utility.objectpackaging.rmi.ObjectPackagingException;

public class ObjectPackagesCollection extends ObjectPackagingItems {
	public static final String OPC_PREFIX = "opc.";
	public static final int DEFAULTPAGESIZE = 10;

	private String collID;
	private List<ObjectQueue> listQueues;
	private List<String> historyList;
	private static CacheManager cacheManager = CacheManager.create();
	private Cache cache;
	private int numberOfComputedItems;
	private String lastID;

	protected ObjectPackagesCollection(List<ObjectQueue> list) throws ObjectPackagingException {

		if (list == null) {
			throw new ObjectPackagingException("ObjectQueue List is null");
		}
		if (list.size() == 0) {
			throw new ObjectPackagingException("ObjectQueue List is empty");
		}

		this.collID = OPC_PREFIX + UUID.randomUUID();
		this.listQueues = list;
		this.numberOfComputedItems = 0;
		this.historyList = new ArrayList<String>();
		this.lastID = "";

		this.cache = new Cache(collID, // name
				EHCACHE_MAX_ELEMENT_IN_MEMORY, // maxElementsInMemory
				false, // overflowToDisk
				false, // eternal
				EHCACHE_TTL, // timeToLiveSeconds,
				EHCACHE_TTL); // timeToIdleSeconds
		cacheManager.addCache(cache);

		updateCache();
	}

	private void updateCache() {
		cache.put(new Element("queues", listQueues));
		cache.put(new Element("numberOfComputedItems", Integer.valueOf(numberOfComputedItems)));
		cache.put(new Element("historyList", historyList));
		cache.put(new Element("lastID", lastID));
	}

	@SuppressWarnings("unchecked")
	protected ObjectPackagesCollection(String collID) throws ObjectPackagingException {
		this.cache = cacheManager.getCache(collID);
		if (this.cache == null) {
			throw new ObjectPackagingException("Invalid Collection Id: " + collID);
		}

		Object queues = cache.get("queues").getObjectValue();
		if (!(queues instanceof List)) {
			throw new ObjectPackagingException("Invalid cached element");
		}
		this.listQueues = (List<ObjectQueue>) queues;

		Object noci = cache.get("numberOfComputedItems").getObjectValue();
		if (!(noci instanceof Integer)) {
			throw new ObjectPackagingException("Invalid cached element");
		}
		this.numberOfComputedItems = (Integer) noci;

		Object hist = cache.get("historyList").getObjectValue();
		if (!(hist instanceof List)) {
			throw new ObjectPackagingException("Invalid cached element");
		}
		this.historyList = (List<String>) hist;

		Object lastIdCached = cache.get("lastID").getObjectValue();
		if (!(lastIdCached instanceof String)) {
			throw new ObjectPackagingException("Invalid cached element");
		}
		this.lastID = (String) lastIdCached;

		this.collID = collID;
	}

	public String getID() {
		return collID;
	}

	@Override
	public List<String> calculateMoreItems(int size) throws DocumentException, ObjectPackagingException {
		List<String> response = new ArrayList<String>();

		String testID = null;

		org.dom4j.Element item = DocumentHelper.createElement("objectRecord");

		String historyItem = "";

		while (response.size() < size) {
			if (areElementsInQueues()) {
				int idxQueue = findMinElementInQueues();
				String currID = listQueues.get(idxQueue).watchNextElementId();
				if (isOrdered(currID, lastID)) {
					throw new ObjectPackagingException("Queues are not ordered");
				} else {
					lastID = currID + "";
				}

				int posQueue = listQueues.get(idxQueue).getGivenItems() + 1; // +1 because ResultSets begin with 1  

				if (testID == null) {
					testID = currID;
					addToItem(item, listQueues.get(idxQueue).fetchNextElement());
					historyItem += "|" + idxQueue + "," + posQueue;
				} else if (testID.equals(currID)) {
					addToItem(item, listQueues.get(idxQueue).fetchNextElement());
					historyItem += "|" + idxQueue + "," + posQueue;
				} else {
					addItemToResponse(response, item, historyItem);
					testID = currID;
					item = DocumentHelper.createElement("objectRecord");
					historyItem = "";
				}
			} else {
				addItemToResponse(response, item, historyItem);
				break;
			}
		}

		updateCache();

		return response;
	}

	private void addItemToResponse(List<String> response, org.dom4j.Element item, String historyItem) {
		if (item.elements().size() > 0) {
			response.add(item.asXML());
			if (historyItem != null) {
				historyList.add(historyItem + "|");
				this.numberOfComputedItems += 1;
			}
		}
	}

	private void addToItem(org.dom4j.Element itemRoot, String s) throws DocumentException {
		SAXReader reader = new SAXReader();
		Document child = reader.read(new StringReader(s));
		itemRoot.add(child.getRootElement());
	}

	protected int findMinElementInQueues() throws ObjectPackagingException {
		int ret = -1;
		String val = null;
		for (int i = 0; i < listQueues.size(); i++) {
			if (val == null) {
				val = listQueues.get(i).watchNextElementId();
				ret = i;
			} else {
				String tmp = listQueues.get(i).watchNextElementId();
				if ((tmp != null) && (isOrdered(tmp, val))) {
					val = tmp;
					ret = i;
				}
			}
		}
		return ret;
	}

	private boolean isOrdered(String val1, String val2) {
		return (val1.compareTo(val2) < 0);
	}

	protected boolean areElementsInQueues() {
		for (ObjectQueue q : listQueues) {
			if (!q.isEmpty()) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int getNumberOfComputedItems() {
		return numberOfComputedItems;
	}

	@Override
	public List<String> retrieveOldItems(int fromPosition, int toPosition) throws ObjectPackagingException, DocumentException {
		Map<Integer, Map<String, Integer>> intervals = new HashMap<Integer, Map<String, Integer>>();

		for (int i = fromPosition; i <= toPosition; i++) {
			String[] arr = historyList.get(i - 1).split("\\|");
			for (int j = 0; j < arr.length; j++) {
				String[] a = arr[j].split(",");
				if (a.length != 2) {
					continue;
				}
				int rs = Integer.parseInt(a[0]);
				int pos = Integer.parseInt(a[1]); // -1 ;

				Map<String, Integer> value;
				if (!intervals.containsKey(rs)) {
					value = new HashMap<String, Integer>();
					value.put("from", pos);
				} else {
					value = intervals.get(rs);
				}
				value.put("to", pos);
				intervals.put(rs, value);
			}
		}

		Map<Integer, List<String>> mapList = new HashMap<Integer, List<String>>();
		for (Integer key : intervals.keySet()) {
			Map<String, Integer> value = intervals.get(key);
			mapList.put(key, listQueues.get(key).obtainOldItems(value.get("from"), value.get("to")));
		}

		List<String> res = new ArrayList<String>();
		for (int i = fromPosition; i <= toPosition; i++) {

			org.dom4j.Element item = DocumentHelper.createElement("objectRecord");
			String[] arr = historyList.get(i - 1).split("\\|");
			for (int j = 0; j < arr.length; j++) {
				String[] a = arr[j].split(",");
				if (a.length != 2) {
					continue;
				}
				int rs = Integer.parseInt(a[0]);
				int pos = Integer.parseInt(a[1]); // - 1;
				addToItem(item, mapList.get(rs).get(pos - intervals.get(rs).get("from")));
			}
			addItemToResponse(res, item, null);
		}

		return res;
	}

	@Override
	public boolean isComputationItemsEnded() {
		return (!areElementsInQueues());
	}

	public List<String> getHistoryList() {
		return historyList;
	}

	public List<ObjectQueue> getListQueues() {
		return listQueues;
	}

}
