package eu.dnetlib.goldoa.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.dnetlib.goldoa.domain.Affiliation;
import eu.dnetlib.goldoa.domain.BankAccount;
import eu.dnetlib.goldoa.domain.Currency;
import eu.dnetlib.goldoa.domain.Journal;
import eu.dnetlib.goldoa.domain.ManagerException;
import eu.dnetlib.goldoa.domain.OrganizationManagerException;
import eu.dnetlib.goldoa.domain.Person;
import eu.dnetlib.goldoa.domain.PersonManagerException;
import eu.dnetlib.goldoa.domain.Publication;
import eu.dnetlib.goldoa.domain.PublicationIdentifier;
import eu.dnetlib.goldoa.domain.Publisher;
import eu.dnetlib.goldoa.service.dao.PublicationDAO;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

/**
 * Created by antleb on 3/13/15.
 */
@Transactional
public class PublicationManagerImpl implements PublicationManager {

	private static Logger logger = Logger.getLogger(PublicationManagerImpl.class);

	@Autowired
	private PublicationDAO publicationDAO;
	@Autowired
	private JournalManager journalManager;
	@Autowired
	private PublisherManager publisherManager;
	@Autowired
	private PersonManager personManager;
	@Autowired
	private OrganizationManager organizationManager;

	@Override
	public Publication getPublication(String publicationId) {
		Publication publication = publicationDAO.getPublication(publicationId);

		loadPublicationRelations(publication);

		if (publication.getJournal() != null && publication.getJournal().getId() != null)
			publication.setJournal(journalManager.getJournal(publication.getJournal().getId()));
		else
			publication.setJournal(null);

		if (publication.getPublisher() != null && publication.getPublisher().getId() != null)
			publication.setPublisher(publisherManager.getPublisher(publication.getPublisher().getId()));
		else
			publication.setPublisher(null);

		return publication;
	}

	private void loadPublicationRelations(Publication publication) {
		publicationDAO.loadAffiliations(publication);
	}

	@Override
	public Publication savePublication(Publication publication) {

		if (publication.getId() == null) {
			publication.setSource("portal");
			publication.setId("portal::" + DigestUtils.md5Hex(publication.getTitle()));
		}

		publicationDAO.savePublication(publication);
		deletePublicationRelations(publication);
		try {
			insertPublicationRelations(publication);
		} catch (PersonManagerException e) {
			e.printStackTrace();
		} catch (OrganizationManagerException e) {
			e.printStackTrace();
		}

		if (publication.getJournal() != null) {
			if (!"doaj".equals(publication.getJournal().getSource())) {

				if (publication.getJournal().getPublisher() != null && !"doaj".equals(publication.getJournal().getPublisher().getSource()))
					publication.getJournal().setPublisher(publisherManager.savePublisher(publication.getJournal().getPublisher()));

				publication.setJournal(journalManager.saveJournal(publication.getJournal()));
			}

			publication.setJournal(journalManager.getJournal(publication.getJournal().getId()));
		} else {
			publication.setJournal(null);
		}

		if (publication.getPublisher() != null) {
			if (!"doaj".equals(publication.getPublisher().getSource()))
				publication.setPublisher(publisherManager.savePublisher(publication.getPublisher()));

			publication.setPublisher(publisherManager.getPublisher(publication.getPublisher().getId()));
		}

		return publication;
	}

	private void insertPublicationRelations(Publication publication) throws PersonManagerException, OrganizationManagerException {
		publicationDAO.insertIdentifiers(publication);

		if (publication.getAuthors() != null)
			for (Affiliation aff : publication.getAuthors()) {
				if (aff.getOrganization() != null && aff.getOrganization().getId() == null)
					aff.getOrganization().setId(organizationManager.saveOrganization(aff.getOrganization()));

				if (aff.getPerson() != null) {
					String personId = personManager.generateId(aff.getPerson());

					try {
						boolean affiliationExists = false;
						Person temp = personManager.getById(personId);

						for (Affiliation a : temp.getAffiliations()) {
							if (a.getOrganization() != null && aff.getOrganization() != null && a.getOrganization().getId().equals(aff.getOrganization().getId()))
								affiliationExists = true;
						}

						if (!affiliationExists) {
							temp.getAffiliations().add(aff);

							personManager.savePerson(temp);
						}

						aff.setPerson(temp);
					} catch (PersonManagerException e) {
						if (e.getErrorCause() == PersonManagerException.ErrorCause.NOT_EXISTS) {
							List<Affiliation> affiliations = new ArrayList<Affiliation>();

							affiliations.add(aff);

							aff.getPerson().setAffiliations(affiliations);

							personManager.savePerson(aff.getPerson());
						}
					}
				}
			}

		publicationDAO.saveAffiliations(publication);
	}

	private void deletePublicationRelations(Publication publication) {
		publicationDAO.deleteIdentifiers(publication);
		publicationDAO.deleteAffiliations(publication);
	}

	@Override
	public Publication resolveDOI(String doi) throws ManagerException {
		Publication publication = new Publication();
		InputStream is = null;

		try {
			is = new URL("http://api.crossref.org/works/" + doi).openStream();
			ObjectMapper mapper = new ObjectMapper();
			JsonNode root = mapper.readTree(is);

			if (root.get("status").textValue().equals("ok")) {
				publication.setTitle(root.path("message").path("title").path(0).textValue());
				publication.setDoi(doi);
				publication.setSubjects(root.path("message").path("subject").textValue());

				publication.setSource("crossref");
				publication.setId("crossref::" + DigestUtils.md5Hex(doi));

				if ("journal-article".equals(root.path("message").path("type").textValue()))
					publication.setType(Publication.Type.ARTICLE);
				else
					publication.setType(Publication.Type.MONOGRAPH);

				publication.setPublicationDate(this.parsePublicationDate(root.path("message").path("issued")));
				publication.setJournal(this.parseJournal(root.path("message").path("container-title"), root.path("message").path("ISSN")));
				publication.getJournal().setPublisher(this.parsePublisher(root.path("message").path("publisher")));

				publication.setAuthors(new ArrayList<Affiliation>());
				for (JsonNode author : root.path("message").path("author")) {
					publication.getAuthors().add(this.parseAffiliation(author));
				}

				publication.setLicense(root.path("message").path("license").path(0).path("URL").textValue());

				publication.setIdentifiers(new ArrayList<PublicationIdentifier>());
				publication.getIdentifiers().add(new PublicationIdentifier("doi", doi));
			} else {
				throw new ManagerException(ManagerException.ErrorCause.NOT_EXISTS);
			}
		} catch (ManagerException e) {
			throw e;
		} catch (Exception e) {
			logger.error("Error resolving doi", e);

			throw new ManagerException(ManagerException.ErrorCause.UNKNOWN);
		} finally {
			try {
				if (is != null)
					is.close();
			} catch (Exception e) {
			}
		}

		return publication;
	}

	private Affiliation parseAffiliation(JsonNode author) {
		Person person = new Person();

		person.setName(author.path("given").textValue());
		person.setLastname(author.path("family").textValue());
		person.setAffiliations(new ArrayList<Affiliation>());

		Affiliation affiliation = new Affiliation();
		affiliation.setPerson(person);

		person.getAffiliations().add(affiliation);

		return affiliation;
	}

	private Publisher parsePublisher(JsonNode publisherName) {
		Publisher publisher = new Publisher();

		publisher.setName(publisherName.textValue());
		publisher.setBankAccount(new BankAccount());

		publisher.setId("crossref::" + DigestUtils.md5Hex(publisher.getName()));
		publisher.setSource("crossref");

		return publisher;
	}

	private Journal parseJournal(JsonNode name, JsonNode issn) {
		Journal journal = journalManager.getJournalByTitle(name.path(0).textValue());

		if (journal == null) {
			journal = new Journal();

			journal.setTitle(name.path(0).textValue());
			journal.setAlternativeTitle(name.path(1).textValue());

			StringBuilder sb = new StringBuilder();
			Iterator<JsonNode> ssn = issn.elements();
			while (ssn.hasNext()) {
				sb.append(ssn.next().textValue()).append(",");
			}

			if (sb.length() > 0)
				sb.deleteCharAt(sb.length() - 1);

			journal.setIssn(sb.toString());

			journal.setCurrency(Currency.EUR);
			journal.setSource("crossref");
			journal.setId("crossref::" + DigestUtils.md5Hex(journal.getTitle()));
		}

		return journal;
	}

	private Date parsePublicationDate(JsonNode node) {
		Calendar c = Calendar.getInstance();

		c.clear();
		c.setTimeZone(TimeZone.getTimeZone("UTC"));

		c.set(Calendar.YEAR, node.path("date-parts").path(0).path(0).intValue());
		c.set(Calendar.MONTH, node.path("date-parts").path(0).path(1).intValue() - 1);
		c.set(Calendar.DAY_OF_MONTH, 1 + node.path("date-parts").path(0).path(2).intValue());
		c.set(Calendar.HOUR_OF_DAY, 0);


		return c.getTime();
	}
}
