package eu.dnetlib.functionality.collection.app;

import eu.dnetlib.api.functionality.CollectionService;
import eu.dnetlib.api.functionality.CollectionServiceException;
import eu.dnetlib.domain.functionality.Collection;
import eu.dnetlib.domain.functionality.CollectionSearchCriteria;
import eu.dnetlib.functionality.collection.dao.CollectionDao;
import gr.uoa.di.driver.app.DriverServiceImpl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

public class CollectionServiceImpl extends DriverServiceImpl implements
		CollectionService {
	private static Logger logger = Logger.getLogger(CollectionServiceImpl.class);
	private CollectionDao dao = null;

	public void setDao(CollectionDao dao) {
		this.dao = dao;
	}
	
	@Override
	public String createCollection(Collection collection)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();
		
		currentThread.setName("createCollection");

		try {
			logger.debug("Saving new collection");
			
			if (collection.getResourceId() != null)
				throw new CollectionServiceException("Collection is not new");

			validateCollection(collection);
			
			String id = dao.save(collection).getResourceId();
			
			// update father
			if (collection.getFather() != null) {
				Collection father = dao.getById(collection.getFather());
				
				father.getChildren().add(id);
				
				dao.save(father);
			}
			
			// update children
			for (String childId:collection.getChildren()) {
				Collection child = dao.getById(childId);
				child.setFather(collection.getResourceId());
				dao.save(child);
			}
			
			return id;
		} catch (CollectionServiceException e) {
			logger.error("Error saving collection", e);
			throw e;
		} catch (Exception e) {
			logger.error("Error saving collection", e);
			throw new CollectionServiceException("Error saving collection", e);
		} finally {
			currentThread.setName(threadName);
		}
	}
	
	@Override
	public void updateCollection(Collection collection)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();

		currentThread.setName("updateCollection");

		try {
			if (collection.getResourceId() == null)
				throw new CollectionServiceException(
						"Cannot update new collection. Collection id is null");

			validateCollection(collection);

			Collection oldVersion = dao.getById(collection.getResourceId());
			// check if father was null and now isn't
			if (oldVersion.getFather() == null
					&& collection.getFather() != null) {
				Collection father = dao.getById(collection.getFather());

				father.getChildren().add(collection.getResourceId());

				dao.save(father);
			} else if (oldVersion.getFather() != null
					&& collection.getFather() == null) {
				Collection father = dao.getById(collection.getFather());

				father.getChildren().remove(collection.getResourceId());

				dao.save(father);
			} else if (oldVersion.getFather() != null
					&& collection.getFather() != null
					&& !collection.getFather().equals(oldVersion.getFather())) {
				Collection father = dao.getById(collection.getFather());

				father.getChildren().add(collection.getResourceId());
				dao.save(father);

				father = dao.getById(oldVersion.getFather());
				father.getChildren().remove(collection.getResourceId());
				dao.save(father);
			}
			
			// check children list
			// children whose father will be set to null:
			Set<String> toRemove = new HashSet<String>(collection.getChildren());
			toRemove.addAll(oldVersion.getChildren());
			toRemove.removeAll(collection.getChildren());
			
			for (String colId:toRemove) {
				Collection child = dao.getById(colId);
				child.setFather(null);
				dao.save(child);
			}
			
			// children whose father will be set to collection:
			Set<String> toAdd = new HashSet<String>(collection.getChildren());
			toAdd.addAll(oldVersion.getChildren());
			toAdd.removeAll(oldVersion.getChildren());
			
			for (String colId:toAdd) {
				Collection child = dao.getById(colId);
				child.setFather(collection.getResourceId());
				dao.save(child);
			}
			
			// finally save the new version
			dao.save(collection);
		} catch (CollectionServiceException e) {
			logger.error("Error updating collection", e);
			throw e;
		} catch (Exception e) {
			logger.error("Error updating collection", e);
			throw new CollectionServiceException("Error updating collection");
		} finally {
			currentThread.setName(threadName);
		}
	}

	private void validateCollection(Collection collection)
			throws CollectionServiceException {
		
		if (collection.getName() == null)
			throw new CollectionServiceException("name is null");
		if (collection.getOwner() == null)
			throw new CollectionServiceException("owner is null");
		if (collection.getDescription().size() == 0)
			throw new CollectionServiceException("no description");
		if (collection.getQuery() == null && !collection.isContainer())
			throw new CollectionServiceException("query is null");
	}

	@Override
	public void deleteCollection(String collectionId)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();
		
		currentThread.setName("deleteCollection");

		try {
			logger.debug("Deleting collection");
			Collection col = dao.getById(collectionId);
			
			// update father
			if (col.getFather() != null) {
				Collection father = dao.getById(col.getFather());
				father.getChildren().remove(col.getResourceId());
				dao.save(father);
			}
			
			//update children
			for (String childId:col.getChildren()) {
				Collection child = dao.getById(childId);
				child.setFather(null);
				dao.save(child);
			}

			dao.delete(col);
		} catch (Exception e) {
			logger.error("Error deleting collection", e);
			throw new CollectionServiceException("Error deleting collection", e);
		} finally {
			currentThread.setName(threadName);
		}
	}

	@Override
	public Collection getCollection(String collectionId)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();
		
		currentThread.setName("getCollection");

		try {
			return dao.getById(collectionId);
		} catch (Exception e) {
			logger.error("Error getting collection", e);
			throw new CollectionServiceException("Error getting collection");
		} finally {
			currentThread.setName(threadName);
		}
	}

	@Override
	public List<Collection> getCollections(List<String> collectionIds)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();
		
		currentThread.setName("getCollections");

		try {
			List<Collection> res = new ArrayList<Collection>();

			for (String id : collectionIds)
				res.add(dao.getById(id));

			return res;
		} catch (Exception e) {
			logger.error("Error getting collections", e);
			throw new CollectionServiceException("Error getting collections");
		} finally {
			currentThread.setName(threadName);
		}
	}

	@Override
	public List<Collection> searchCollections(CollectionSearchCriteria criteria)
			throws CollectionServiceException {
		Thread currentThread = Thread.currentThread();
		String threadName = currentThread.getName();
		
		currentThread.setName("searchCollections");
		
		try {
			return this.dao.search(criteria);
		} catch (Exception e) {
			throw new CollectionServiceException("Error searching collection", e);
		} finally {
			currentThread.setName(threadName);
		}
	}
}