/**
 * 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.sword2;

import java.io.File;

import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.packager.PackageIngester;
import org.dspace.content.packager.PackageParameters;
import org.dspace.content.packager.PackageUtils;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Context;
import org.dspace.core.factory.CoreServiceFactory;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.swordapp.server.Deposit;
import org.swordapp.server.SwordAuthException;
import org.swordapp.server.SwordError;
import org.swordapp.server.SwordServerException;

public class SwordMETSContentIngester extends AbstractSwordContentIngester {
    /**
     * Log4j logger
     */
    public static final Logger log = org.apache.logging.log4j.LogManager.getLogger(
        SwordMETSContentIngester.class);

    protected WorkspaceItemService workspaceItemService =
        ContentServiceFactory.getInstance().getWorkspaceItemService();

    protected CollectionService collectionService =
        ContentServiceFactory.getInstance().getCollectionService();

    protected HandleService handleService =
        HandleServiceFactory.getInstance().getHandleService();

    @Override
    public DepositResult ingest(Context context, Deposit deposit,
                                DSpaceObject dso, VerboseDescription verboseDescription)
        throws DSpaceSwordException, SwordError, SwordAuthException,
        SwordServerException {
        return this.ingest(context, deposit, dso, verboseDescription, null);
    }

    @Override
    public DepositResult ingestToCollection(Context context, Deposit deposit,
                                            Collection collection, VerboseDescription verboseDescription,
                                            DepositResult result)
        throws DSpaceSwordException, SwordError, SwordAuthException,
        SwordServerException {
        try {
            // if we are actuall given an item in the deposit result of a previous operation
            // then we do an ingestToItem
            if (result != null) {
                Item item = result.getItem();
                return this.ingestToItem(
                    context, deposit, item, verboseDescription, result);
            }

            // otherwise, go on and do a create ...

            // create the an item in the workspace.  This is necessary, because later
            // we are going to ask a package ingester to /replace/ this item, which gives
            // us finer control over the workflow state of the item, whereas asking
            // the ingester to /create/ this item causes it to be injected into the workflow,
            // irrespective of the In-Progress header provided by the depositor
            WorkspaceItem wsi = workspaceItemService.create(
                context, collection, true);
            Item item = wsi.getItem();

            // need to add a licence file, otherwise the METS replace function raises a NullPointerException
            String licence = collectionService.getLicense(collection);
            if (PackageUtils.findDepositLicense(context, item) == null) {
                PackageUtils.addDepositLicense(
                    context, licence, item, collection);
            }

            // get deposited file as InputStream
            File depositFile = deposit.getFile();

            // load the plugin manager for the required configuration
            String cfg = configurationService.getProperty("swordv2-server.mets-ingester.package-ingester");
            if (cfg == null || "".equals(cfg)) {
                cfg = "METS";  // default to METS
            }
            verboseDescription.append("Using package manifest format: " + cfg);

            PackageIngester pi = (PackageIngester) CoreServiceFactory.getInstance().getPluginService()
                                                                     .getNamedPlugin(PackageIngester.class, cfg);
            verboseDescription.append("Loaded package ingester: " +
                                          pi.getClass().getName());

            // Initialize parameters to packager
            PackageParameters params = new PackageParameters();

            // Force package ingester to respect Collection workflows
            params.setWorkflowEnabled(true);

            // Should restore mode be enabled, i.e. keep existing handle?
            if (configurationService.getBooleanProperty(
                "swordv2-server.restore-mode.enable", false)) {
                params.setRestoreModeEnabled(true);
            }

            // Whether or not to use the collection template
            params.setUseCollectionTemplate(configurationService
                                                .getBooleanProperty(
                                                    "mets.default.ingest.useCollectionTemplate", false));

            // ingest the item from the temp file
            DSpaceObject ingestedObject = pi.replace(
                context, item, depositFile, params);
            if (ingestedObject == null) {
                verboseDescription.append(
                    "Failed to ingest the package; throwing exception");
                throw new SwordError(DSpaceUriRegistry.UNPACKAGE_FAIL,
                                     "METS package ingester failed to unpack package");
            }

            // Verify we have an Item as a result
            if (!(ingestedObject instanceof Item)) {
                throw new DSpaceSwordException(
                    "DSpace Ingester returned wrong object type -- not an Item result.");
            } else {
                //otherwise, we have an item, and a workflow should have already been started for it.
                verboseDescription.append("Workflow process started");
            }

            // get reference to item so that we can report on it
            Item installedItem = (Item) ingestedObject;

            // update the item metadata to inclue the current time as
            // the updated date
            this.setUpdatedDate(context, installedItem, verboseDescription);

            // DSpace ignores the slug value as suggested identifier, but
            // it does store it in the metadata
            this.setSlug(context, installedItem, deposit.getSlug(),
                         verboseDescription);

            // in order to write these changes, we need to bypass the
            // authorisation briefly, because although the user may be
            // able to add stuff to the repository, they may not have
            // WRITE permissions on the archive.
            context.turnOffAuthorisationSystem();
            itemService.update(context, installedItem);
            context.restoreAuthSystemState();

            // for some reason, DSpace will not give you the handle automatically,
            // so we have to look it up
            String handle = handleService.findHandle(context, installedItem);

            verboseDescription.append("Ingest successful");
            verboseDescription.append(
                "Item created with internal identifier: " +
                    installedItem.getID());
            if (handle != null) {
                verboseDescription.append(
                    "Item created with external identifier: " + handle);
            } else {
                verboseDescription.append(
                    "No external identifier available at this stage (item in workflow)");
            }

            DepositResult dr = new DepositResult();
            dr.setItem(installedItem);
            dr.setTreatment(this.getTreatment());

            return dr;
        } catch (RuntimeException re) {
            log.error("caught exception: ", re);
            throw re;
        } catch (Exception e) {
            log.error("caught exception: ", e);
            throw new DSpaceSwordException(e);
        }
    }

    @Override
    public DepositResult ingestToItem(Context context, Deposit deposit,
                                      Item item, VerboseDescription verboseDescription,
                                      DepositResult result)
        throws DSpaceSwordException, SwordError, SwordAuthException,
        SwordServerException {
        if (result == null) {
            result = new DepositResult();
        }

        try {
            // get deposited file as InputStream
            File depositFile = deposit.getFile();

            // load the plugin manager for the required configuration
            String cfg = configurationService.getProperty(
                "swordv2-server.mets-ingester.package-ingester");
            if (cfg == null || "".equals(cfg)) {
                cfg = "METS";  // default to METS
            }
            verboseDescription.append("Using package manifest format: " + cfg);

            PackageIngester pi = (PackageIngester) CoreServiceFactory.getInstance().getPluginService()
                                                                     .getNamedPlugin(PackageIngester.class, cfg);
            verboseDescription.append("Loaded package ingester: " +
                                          pi.getClass().getName());

            // Initialize parameters to packager
            PackageParameters params = new PackageParameters();

            // Force package ingester to respect Collection workflows
            params.setWorkflowEnabled(true);

            // Should restore mode be enabled, i.e. keep existing handle?
            if (configurationService.getBooleanProperty(
                "swordv2-server.restore-mode.enable", false)) {
                params.setRestoreModeEnabled(true);
            }

            // Whether or not to use the collection template
            params.setUseCollectionTemplate(configurationService
                                                .getBooleanProperty(
                                                    "mets.default.ingest.useCollectionTemplate", false));

            // ingest the item from the temp file
            DSpaceObject ingestedObject = pi.replace(
                context, item, depositFile, params);
            if (ingestedObject == null) {
                verboseDescription.append(
                    "Failed to replace the package; throwing exception");
                throw new SwordError(DSpaceUriRegistry.UNPACKAGE_FAIL,
                                     "METS package ingester failed to unpack package");
            }

            // Verify we have an Item as a result
            if (!(ingestedObject instanceof Item)) {
                throw new DSpaceSwordException(
                    "DSpace Ingester returned wrong object type -- not an Item result.");
            }

            // get reference to item so that we can report on it
            Item installedItem = (Item) ingestedObject;

            // update the item metadata to inclue the current time as
            // the updated date
            this.setUpdatedDate(context, installedItem, verboseDescription);

            // in order to write these changes, we need to bypass the
            // authorisation briefly, because although the user may be
            // able to add stuff to the repository, they may not have
            // WRITE permissions on the archive.
            context.turnOffAuthorisationSystem();
            itemService.update(context, installedItem);
            context.restoreAuthSystemState();

            // for some reason, DSpace will not give you the handle automatically,
            // so we have to look it up
            verboseDescription.append("Replace successful");

            result.setItem(installedItem);
            result.setTreatment(this.getTreatment());

            return result;
        } catch (RuntimeException re) {
            log.error("caught exception: ", re);
            throw re;
        } catch (Exception e) {
            log.error("caught exception: ", e);
            throw new DSpaceSwordException(e);
        }
    }

    /**
     * The human readable description of the treatment this ingester has
     * put the deposit through
     *
     * @return human readable description
     * @throws DSpaceSwordException can be thrown by the internals of the DSpace SWORD implementation
     */
    private String getTreatment() throws DSpaceSwordException {
        return "The package has been deposited into DSpace.  Each file has been unpacked " +
            "and provided with a unique identifier.  The metadata in the manifest has been " +
            "extracted and attached to the DSpace item, which has been provided with " +
            "an identifier leading to an HTML splash page.";
    }
}
