package eu.dnetlib.goldoa.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.dnetlib.goldoa.domain.*;
import eu.dnetlib.goldoa.domain.Currency;
import eu.dnetlib.goldoa.service.dao.JournalDAO;
import eu.dnetlib.goldoa.service.dao.PublicationDAO;
import eu.dnetlib.goldoa.service.dao.PublisherDAO;
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.*;

/**
 * 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.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();
    }
}
