/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.content;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.embargo.service.EmbargoService;
import org.dspace.event.Event;
import org.dspace.identifier.Identifier;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.IdentifierService;
import org.dspace.supervision.SupervisionOrder;
import org.dspace.supervision.service.SupervisionOrderService;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Support to install an Item in the archive.
 *
 * @author dstuve
 * @version $Revision$
 */
public class InstallItemServiceImpl implements InstallItemService {

    @Autowired(required = true)
    protected ContentServiceFactory contentServiceFactory;
    @Autowired(required = true)
    protected CollectionService collectionService;
    @Autowired(required = true)
    protected EmbargoService embargoService;
    @Autowired(required = true)
    protected IdentifierService identifierService;
    @Autowired(required = true)
    protected ItemService itemService;
    @Autowired(required = true)
    protected SupervisionOrderService supervisionOrderService;
    @Autowired(required = false)

    Logger log = LogManager.getLogger(InstallItemServiceImpl.class);

    protected InstallItemServiceImpl() {
    }

    @Override
    public Item installItem(Context c, InProgressSubmission is)
        throws SQLException, AuthorizeException {
        return installItem(c, is, null);
    }

    @Override
    public Item installItem(Context c, InProgressSubmission is,
                            String suppliedHandle) throws SQLException,
        AuthorizeException {
        Item item = is.getItem();
        Collection collection = is.getCollection();
        // Get map of filters to use for identifier types.
        Map<Class<? extends Identifier>, Filter> filters = FilterUtils.getIdentifierFilters(false);
        try {
            if (suppliedHandle == null) {
                // Register with the filters we've set up
                identifierService.register(c, item, filters);
            } else {
                // This will register the handle but a pending DOI won't be compatible and so won't be registered
                identifierService.register(c, item, suppliedHandle);
            }
        } catch (IdentifierException e) {
            throw new RuntimeException("Can't create an Identifier!", e);
        }

        populateMetadata(c, item);

        // Finish up / archive the item
        item = finishItem(c, item, is);

        // As this is a BRAND NEW item, as a final step we need to remove the
        // submitter item policies created during deposit and replace them with
        // the default policies from the collection.
        itemService.inheritCollectionDefaultPolicies(c, item, collection, false);

        return item;
    }

    @Override
    public Item restoreItem(Context c, InProgressSubmission is,
                            String suppliedHandle)
        throws SQLException, IOException, AuthorizeException {
        Item item = is.getItem();

        try {
            if (suppliedHandle == null) {
                identifierService.register(c, item);
            } else {
                identifierService.register(c, item, suppliedHandle);
            }
        } catch (IdentifierException e) {
            throw new RuntimeException("Can't create an Identifier!");
        }

        // Even though we are restoring an item it may not have the proper dates. So let's
        // double check its associated date(s)
        DCDate now = DCDate.getCurrent();

        // If the item doesn't have a date.accessioned, set it to today
        List<MetadataValue> dateAccessioned = itemService
            .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "accessioned", Item.ANY);
        if (dateAccessioned.isEmpty()) {
            itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
                                    "date", "accessioned", null, now.toString());
        }

        // If issue date is set as "today" (literal string), then set it to current date
        // In the below loop, we temporarily clear all issued dates and re-add, one-by-one,
        // replacing "today" with today's date.
        // NOTE: As of DSpace 4.0, DSpace no longer sets an issue date by default
        List<MetadataValue> currentDateIssued = itemService
            .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY);
        itemService.clearMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY);
        for (MetadataValue dcv : currentDateIssued) {
            if (dcv.getValue() != null && dcv.getValue().equalsIgnoreCase("today")) {
                DCDate issued = new DCDate(now.getYear(), now.getMonth(), now.getDay(), -1, -1, -1);
                itemService.addMetadata(c, item, dcv.getMetadataField(), dcv.getLanguage(), issued.toString());
            } else if (dcv.getValue() != null) {
                itemService.addMetadata(c, item, dcv.getMetadataField(), dcv.getLanguage(), dcv.getValue());
            }
        }

        // Record that the item was restored
        String provDescription = "Restored into DSpace on " + now + " (GMT).";
        itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
                                "description", "provenance", "en", provDescription);

        return finishItem(c, item, is);
    }


    protected void populateMetadata(Context c, Item item)
        throws SQLException, AuthorizeException {
        // create accession date
        DCDate now = DCDate.getCurrent();
        itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
                                "date", "accessioned", null, now.toString());

        // add date available if not under embargo, otherwise it will
        // be set when the embargo is lifted.
        // this will flush out fatal embargo metadata
        // problems before we set inArchive.
        if (embargoService.getEmbargoTermsAsDate(c, item) == null) {
            itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
                                    "date", "available", null, now.toString());
        }

        // If issue date is set as "today" (literal string), then set it to current date
        // In the below loop, we temporarily clear all issued dates and re-add, one-by-one,
        // replacing "today" with today's date.
        // NOTE: As of DSpace 4.0, DSpace no longer sets an issue date by default
        List<MetadataValue> currentDateIssued = itemService
            .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY);
        itemService.clearMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY);
        for (MetadataValue dcv : currentDateIssued) {
            if (dcv.getValue() != null && dcv.getValue().equalsIgnoreCase("today")) {
                DCDate issued = new DCDate(now.getYear(), now.getMonth(), now.getDay(), -1, -1, -1);
                itemService.addMetadata(c, item, dcv.getMetadataField(), dcv.getLanguage(), issued.toString());
            } else if (dcv.getValue() != null) {
                itemService.addMetadata(c, item, dcv.getMetadataField(), dcv.getLanguage(), dcv.getValue());
            }
        }

        String provDescription = "Made available in DSpace on " + now
            + " (GMT). " + getBitstreamProvenanceMessage(c, item);

        // If an issue date was passed in and it wasn't set to "today" (literal string)
        // then note this previous issue date in provenance message
        if (!currentDateIssued.isEmpty()) {
            String previousDateIssued = currentDateIssued.get(0).getValue();
            if (previousDateIssued != null && !previousDateIssued.equalsIgnoreCase("today")) {
                DCDate d = new DCDate(previousDateIssued);
                provDescription = provDescription + "  Previous issue date: "
                    + d.toString();
            }
        }

        // Add provenance description
        itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
                                "description", "provenance", "en", provDescription);
    }

    /**
     * Final housekeeping when adding a new Item into the archive.
     * This method is used by *both* installItem() and restoreItem(),
     * so all actions here will be run for a newly added item or a restored item.
     *
     * @param c    DSpace Context
     * @param item Item in question
     * @param is   InProgressSubmission object
     * @return final "archived" Item
     * @throws SQLException       if database error
     * @throws AuthorizeException if authorization error
     */
    protected Item finishItem(Context c, Item item, InProgressSubmission is)
        throws SQLException, AuthorizeException {
        // create collection2item mapping
        collectionService.addItem(c, is.getCollection(), item);

        // set owning collection
        item.setOwningCollection(is.getCollection());

        // set in_archive=true
        item.setArchived(true);

        // save changes ;-)
        itemService.update(c, item);

        // Notify interested parties of newly archived Item
        c.addEvent(new Event(Event.INSTALL, Constants.ITEM, item.getID(),
                             item.getHandle(), itemService.getIdentifiers(c, item)));

        // remove in-progress submission
        contentServiceFactory.getInProgressSubmissionService(is).deleteWrapper(c, is);

        // set embargo lift date and take away read access if indicated.
        embargoService.setEmbargo(c, item);

        // delete all related supervision orders
        deleteSupervisionOrders(c, item);

        return item;
    }

    private void deleteSupervisionOrders(Context c, Item item) throws SQLException, AuthorizeException {
        List<SupervisionOrder> supervisionOrders = supervisionOrderService.findByItem(c, item);
        for (SupervisionOrder supervisionOrder : supervisionOrders) {
            supervisionOrderService.delete(c, supervisionOrder);
        }
    }

    @Override
    public String getBitstreamProvenanceMessage(Context context, Item myitem)
        throws SQLException {
        // Get non-internal format bitstreams
        List<Bitstream> bitstreams = itemService.getNonInternalBitstreams(context, myitem);

        // Create provenance description
        StringBuilder myMessage = new StringBuilder();
        myMessage.append("No. of bitstreams: ").append(bitstreams.size()).append("\n");

        // Add sizes and checksums of bitstreams
        for (Bitstream bitstream : bitstreams) {
            myMessage.append(bitstream.getName()).append(": ")
                     .append(bitstream.getSizeBytes()).append(" bytes, checksum: ")
                     .append(bitstream.getChecksum()).append(" (")
                     .append(bitstream.getChecksumAlgorithm()).append(")\n");
        }

        return myMessage.toString();
    }

    @Override
    public String getSubmittedByProvenanceMessage(Context context, Item item) throws SQLException {
        // get date
        DCDate now = DCDate.getCurrent();

        // Create provenance description
        StringBuffer provmessage = new StringBuffer();

        if (item.getSubmitter() != null) {
            provmessage.append("Submitted by ").append(item.getSubmitter().getFullName())
                .append(" (").append(item.getSubmitter().getEmail()).append(") on ")
                .append(now.toString());
        } else {
            // else, null submitter
            provmessage.append("Submitted by unknown (probably automated) on")
                .append(now.toString());
        }
        provmessage.append("\n");

        // add sizes and checksums of bitstreams
        provmessage.append(getBitstreamProvenanceMessage(context, item));
        return provmessage.toString();
    }
}
