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

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.purl.sword.base.Deposit;
import org.purl.sword.base.ErrorCodes;
import org.purl.sword.base.SWORDErrorException;

public class ItemDepositor extends Depositor {
    protected ItemService itemService = ContentServiceFactory.getInstance()
                                                             .getItemService();

    protected BundleService bundleService = ContentServiceFactory.getInstance()
                                                                 .getBundleService();

    protected BitstreamService bitstreamService = ContentServiceFactory
        .getInstance().getBitstreamService();

    protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory
        .getInstance().getBitstreamFormatService();

    private final ConfigurationService configurationService
            = DSpaceServicesFactory.getInstance().getConfigurationService();

    private Item item;

    public ItemDepositor(SWORDService swordService, DSpaceObject dso)
        throws DSpaceSWORDException {
        super(swordService, dso);

        if (!(dso instanceof Item)) {
            throw new DSpaceSWORDException(
                "You tried to initialise the item depositor with something" +
                    " other than an item object");
        }

        this.item = (Item) dso;
    }

    @Override
    public DepositResult doDeposit(Deposit deposit)
        throws SWORDErrorException, DSpaceSWORDException {
        // get the things out of the service that we need
        Context context = swordService.getContext();
        SWORDConfiguration swordConfig = swordService.getSwordConfig();
        SWORDUrlManager urlManager = swordService.getUrlManager();

        // FIXME: the spec is unclear what to do in this situation.  I'm going
        // the throw a 415 (ERROR_CONTENT) until further notice
        //
        // determine if this is an acceptable file format
        if (!swordConfig
            .isAcceptableContentType(context, deposit.getContentType(),
                                     item)) {
            throw new SWORDErrorException(ErrorCodes.ERROR_CONTENT,
                                          "Unacceptable content type in deposit request: " +
                                              deposit.getContentType());
        }

        // determine if this is an acceptable packaging type for the deposit
        // if not, we throw a 415 HTTP error (Unsupported Media Type, ERROR_CONTENT)
        if (!swordConfig
            .isSupportedMediaType(deposit.getPackaging(), this.item)) {
            throw new SWORDErrorException(ErrorCodes.ERROR_CONTENT,
                                          "Unacceptable packaging type in deposit request: " +
                                              deposit.getPackaging());
        }

        // Obtain the relevant ingester from the factory
        SWORDIngester si = SWORDIngesterFactory
            .getInstance(context, deposit, item);
        swordService.message("Loaded ingester: " + si.getClass().getName());

        // do the deposit
        DepositResult result = si.ingest(swordService, deposit, item);
        swordService.message("Archive ingest completed successfully");

        // if there's an item availalble, and we want to keep the original
        // then do that
        try {
            if (swordConfig.isKeepOriginal()) {
                swordService.message(
                    "DSpace will store an original copy of the deposit file, " +
                        "as well as attaching it to the item");

                // in order to be allowed to add the file back to the item, we need to ignore authorisations
                // for a moment
                context.turnOffAuthorisationSystem();

                String bundleName = configurationService
                    .getProperty("sword-server.bundle.name");
                if (StringUtils.isBlank(bundleName)) {
                    bundleName = "SWORD";
                }

                List<Bundle> bundles = item.getBundles();
                Bundle swordBundle = null;
                for (Bundle bundle : bundles) {
                    if (bundleName.equals(bundle.getName())) {
                        // we found one
                        swordBundle = bundle;
                        break;
                    }
                }
                if (swordBundle == null) {
                    swordBundle = bundleService
                        .create(context, item, bundleName);
                }

                String fn = swordService.getFilename(context, deposit, true);

                Bitstream bitstream;
                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(deposit.getFile());
                    bitstream = bitstreamService
                        .create(context, swordBundle, fis);
                } finally {
                    if (fis != null) {
                        fis.close();
                    }
                }
                bitstream.setName(context, fn);
                bitstream.setDescription(context,
                                         "Original file deposited via SWORD");

                BitstreamFormat bf = bitstreamFormatService
                    .findByMIMEType(context, deposit.getContentType());
                if (bf != null) {
                    bitstreamService.setFormat(context, bitstream, bf);
                }

                bitstreamService.update(context, bitstream);
                bundleService.update(context, swordBundle);
                itemService.update(context, item);

                swordService.message("Original package stored as " + fn +
                                         ", in item bundle " + swordBundle);

                // now reset the context ignore authorisation
                context.restoreAuthSystemState();

                // set the media link for the created item
                result.setMediaLink(urlManager.getMediaLink(bitstream));
            } else {
                // set the media link for the created item using the archived version (since it's just a file)
                result.setMediaLink(
                    urlManager.getMediaLink(result.getBitstream()));
            }
        } catch (SQLException | AuthorizeException | IOException e) {
            throw new DSpaceSWORDException(e);
        }

        return result;
    }

    @Override
    public void undoDeposit(DepositResult result) throws DSpaceSWORDException {
        try {
            SWORDContext sc = swordService.getSwordContext();
            BundleService bundleService = ContentServiceFactory.getInstance()
                                                               .getBundleService();

            // obtain the bitstream's owning bundles and remove the bitstream
            // from them.  This will ensure that the bitstream is physically
            // removed from the disk.
            Bitstream bs = result.getBitstream();
            Iterator<Bundle> bundles = bs.getBundles().iterator();
            while (bundles.hasNext()) {
                Bundle bundle = bundles.next();
                bundles.remove();
                bundleService.removeBitstream(sc.getContext(), bundle, bs);
                bundleService.update(sc.getContext(), bundle);
            }

            swordService.message("Removing temporary files from disk");

            // abort the context, so no database changes are written
            sc.abort();
            swordService.message("Database changes aborted");
        } catch (IOException | AuthorizeException | SQLException e) {
            throw new DSpaceSWORDException(e);
        }
    }
}
