package eu.dnetlib.data.dedup;

import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;

import com.google.common.collect.Lists;

import eu.dnetlib.data.proto.TypeProtos.Type;
import eu.dnetlib.enabling.database.rmi.DatabaseService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.functionality.modular.ui.dedup.SimilarityGroup;
import eu.dnetlib.msro.workflows.hadoop.utils.Similarity;
import eu.dnetlib.msro.workflows.hadoop.utils.SimilarityMeshBuilder;

public class DedupDbDAO {

	private static final Log log = LogFactory.getLog(DedupDbDAO.class);

	@Value("${dnet.dedup.db.name}")
	private String dbName;

	@Resource
	private UniqueServiceLocator serviceLocator;

	public boolean commit(final SimilarityGroup group) throws Exception {

		final DatabaseService dbService = serviceLocator.getService(DatabaseService.class);

		log.info("adding similarities");
		updateGroupSql(dbService, group);

		log.info("adding dissimilarities");
		dissimilaritiesSql(dbService, group);

		return true;
	}

	private void updateGroupSql(final DatabaseService dbService, final SimilarityGroup group) throws Exception {

		log.info("cleanup similarities");
		for (final String id : group.getGroup()) {
			// sql.append(String.format("DELETE FROM entities WHERE objidentifier = '%s'; ", id));
			safeUpdateSql(dbService, dbName, String.format("DELETE FROM similarity_groups WHERE objidentifier = '%s'; ", id));
		}

		// cleanup empty groups
		log.info("cleanup empty groups");
		safeUpdateSql(dbService, dbName, "DELETE FROM groups WHERE id NOT IN (SELECT groupid FROM similarity_groups)");

		// look for dissimilarities to remove
		log.info("reverting dissimilarities");
		for (Similarity s : mesh(group)) {
			safeUpdateSql(dbService, dbName, String.format("DELETE FROM dissimilarities WHERE actionsetid = '%s' AND id1 = '%s' AND id2 = '%s'; ",
					group.getActionSet(), s.getPair().getKey(), s.getPair().getValue()));
		}

		log.info("adding new group, size: " + group.getGroup().size());
		final String type = group.getEntityType().getType();
		safeUpdateSql(dbService, dbName,
				String.format("INSERT INTO groups(id, entitytype, date, actionsetid) VALUES('%s', '%s', '%s', '%s'); ", group.getId(), type, group.getDate(),
						group.getActionSet()));
		for (final String id : group.getGroup()) {
			// add new entity if needed
			if (!dbService.contains(dbName, "entities", "id", id)) {
				safeUpdateSql(dbService, dbName, String.format("INSERT INTO entities(id, entitytype) VALUES('%s', '%s'); ", id, type));
			}

			// throw new Exception("id already defined in a similarity group.");
			safeUpdateSql(dbService, dbName, String.format("INSERT INTO similarity_groups(groupid, objidentifier) VALUES('%s', '%s'); ", group.getId(), id));
		}
	}

	private List<Similarity> mesh(final SimilarityGroup group) {
		return SimilarityMeshBuilder.build(Type.valueOf(group.getEntityType().getType()), Lists.newArrayList(group.getGroup()), false);
	}

	private void dissimilaritiesSql(final DatabaseService dbService, final SimilarityGroup group) throws Exception {

		final String type = group.getEntityType().getType();

		// add potential new entities
		for (final Entry<String, Set<String>> e : group.getDissimilar().entrySet()) {
			if (!dbService.contains(dbName, "entities", "id", e.getKey())) {
				safeUpdateSql(dbService, dbName, String.format("INSERT INTO entities(id, entitytype) VALUES('%s', '%s'); ", e.getKey(), type));
			}
			for (final String id : e.getValue()) {
				if (!dbService.contains(dbName, "entities", "id", id)) {
					safeUpdateSql(dbService, dbName, String.format("INSERT INTO entities(id, entitytype) VALUES('%s', '%s'); ", id, type));
				}
			}
		}

		log.info("store dissimilarities");
		for (final Entry<String, Set<String>> e : group.getDissimilar().entrySet()) {
			for (final String id : e.getValue()) {
				safeUpdateSql(dbService, dbName,
						String.format("INSERT INTO dissimilarities(id1, id2, actionsetid) VALUES('%s', '%s', '%s'); ", e.getKey(), id, group.getActionSet()));
			}
		}
	}

	private void safeUpdateSql(final DatabaseService dbService, final String dbName, final String sql) throws Exception {
		try {
			log.info(sql);
			dbService.updateSQL(dbName, sql);
		} catch (final Exception e) {
			log.error(e.getMessage());
			log.debug(ExceptionUtils.getFullStackTrace(e));
			throw e;
		}
	}

}
