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

import java.io.IOException;
import java.sql.SQLException;

import org.jdom.Element;

import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.crosswalk.MetadataValidationException;
import org.dspace.core.Context;
import org.dspace.core.Constants;

/**
 * Subclass of the METS packager framework to ingest a DSpace
 * Archival Information Package (AIP).  The AIP is intended to be, foremost,
 * a _complete_ and _accurate_ representation of one object in the DSpace
 * object model.  An AIP contains all of the information needed to restore
 * the object precisely in another DSpace archive instance.
 * <p>
 * This ingester recognizes two distinct types of AIPs: "Manifest-Only" and "External".
 * The Manifest-Only AIP, which is selected by specifying a PackageParameters
 * key "manifestOnly" with the value "true", refers to all its contents by 
 * reference only. For Community or Collection AIPs this means all references to their
 * child objects are just via Handles. For Item AIPs all Bitreams are just
 * referenced by their asset store location instead of finding them in the "package".
 * The Manifest-Only AIP package format is simply a METS XML document serialized into a file.
 * <p>
 * An "external" AIP (the default), is a conventional Zip-file based package
 * that includes copies of all bitstreams referenced by the object as well
 * as a serialized METS XML document in the path "mets.xml".
 *
 * Configuration keys:
 *
 *  # instructs which xwalk plugin to use for a given type of metadata
 *  mets.dspaceAIP.ingest.crosswalk.{mdSecName} = {pluginName}
 *  mets.dspaceAIP.ingest.crosswalk.DC = QDC
 *  mets.dspaceAIP.ingest.crosswalk.DSpaceDepositLicense = NULLSTREAM
 *
 *  # Option to save METS manifest in the item: (default is false)
 *  mets.default.ingest.preserveManifest = false
 *
 * @author Larry Stone
 * @author Tim Donohue
 * @version $Revision: 1.1 $
 *
 * @see AbstractMETSIngester
 * @see AbstractPackageIngester
 * @see PackageIngester
 * @see org.dspace.content.packager.METSManifest
 */
public class DSpaceAIPIngester
       extends AbstractMETSIngester
{
    /** log4j category */
    private static Logger log = Logger.getLogger(DSpaceAIPIngester.class);

    /**
     * Ensure it's an AIP generated by the complementary AIP disseminator.
     */
    @Override
    void checkManifest(METSManifest manifest)
        throws MetadataValidationException
    {
        String profile = manifest.getProfile();
        if (profile == null)
        {
            throw new MetadataValidationException("Cannot accept METS with no PROFILE attribute!");
        }
        else if (!profile.equals(DSpaceAIPDisseminator.PROFILE_1_0))
        {
            throw new MetadataValidationException("METS has unacceptable PROFILE attribute, profile=" + profile);
        }
    }


    /**
     * Choose DMD section(s) to crosswalk.
     * <p>
     * The algorithm is:<br>
     * 1. Use whatever the <code>dmd</code> parameter specifies as the primary DMD.<br>
     * 2. If (1) is unspecified, find DIM (preferably) or MODS as primary DMD.<br>
     * 3. If (1) or (2) succeeds, crosswalk it and ignore all other DMDs with
     *    same GROUPID<br>
     * 4. Crosswalk remaining DMDs not eliminated already.
     * @throws PackageValidationException validation error
     * @throws CrosswalkException if crosswalk error
     * @throws IOException if IO error
     * @throws SQLException if database error
     * @throws AuthorizeException if authorization error
     */
    @Override
    public void crosswalkObjectDmd(Context context, DSpaceObject dso,
                              METSManifest manifest,
                              MdrefManager callback,
                              Element dmds[], PackageParameters params)
        throws CrosswalkException, PackageValidationException,
               AuthorizeException, SQLException, IOException
    {
        int found = -1;

        // Check to see what dmdSec the user specified in the 'dmd' parameter
        String userDmd = null;
        if (params != null)
        {
            userDmd = params.getProperty("dmd");
        }
        if (userDmd != null && userDmd.length() > 0)
        {
            for (int i = 0; i < dmds.length; ++i)
            {
                if (userDmd.equalsIgnoreCase(manifest.getMdType(dmds[i])))
                {
                    found = i;
                }
            }
        }

        // DIM is preferred, if nothing specified by user
        if (found == -1)
        {
            // DIM is preferred for AIP
            for (int i = 0; i < dmds.length; ++i)
            {
                //NOTE: METS standard actually says this should be DIM (all uppercase). But,
                // just in case, we're going to be a bit more forgiving.
                if ("DIM".equalsIgnoreCase(manifest.getMdType(dmds[i])))
                {
                    found = i;
                }
            }
        }

        // MODS is acceptable otehrwise..
        if (found == -1)
        {
            for (int i = 0; i < dmds.length; ++i)
            {
                //NOTE: METS standard actually says this should be MODS (all uppercase). But,
                // just in case, we're going to be a bit more forgiving.
                if ("MODS".equalsIgnoreCase(manifest.getMdType(dmds[i])))
                {
                    found = i;
                }
            }
        }

        String groupID = null;
        if (found >= 0)
        {
            manifest.crosswalkItemDmd(context, params, dso, dmds[found], callback);
            groupID = dmds[found].getAttributeValue("GROUPID");

            if (groupID != null)
            {
                for (int i = 0; i < dmds.length; ++i)
                {
                    String g = dmds[i].getAttributeValue("GROUPID");
                    if (g != null && !g.equals(groupID))
                    {
                        manifest.crosswalkItemDmd(context, params, dso, dmds[i], callback);
                    }
                }
            }
        }

        // otherwise take the first.  Don't xwalk more than one because
        // each xwalk _adds_ metadata, and could add duplicate fields.
        else if (dmds.length > 0)
        {
            manifest.crosswalkItemDmd(context, params, dso, dmds[0], callback);
        }

        // it's an error if there is nothing to crosswalk:
        else
        {
            throw new MetadataValidationException("DSpaceAIPIngester: Could not find an acceptable object-wide DMD section in manifest.");
        }
    }


    /**
     * Ignore license when restoring an manifest-only AIP, since it should
     * be a bitstream in the AIP already.
     * Otherwise:  Check item for license first; then, take deposit
     * license supplied by explicit argument next, else use collection's
     * default deposit license.
     * Normally the rightsMD crosswalks should provide a license.
     * @throws PackageValidationException validation error
     * @throws IOException if IO error
     * @throws SQLException if database error
     * @throws AuthorizeException if authorization error
     */
    @Override
    public void addLicense(Context context, Item item, String license,
                                    Collection collection, PackageParameters params)
        throws PackageValidationException,
               AuthorizeException, SQLException, IOException
    {
        boolean newLicense = false;

        if(!params.restoreModeEnabled())
        {
            //AIP is not being restored/replaced, so treat it like a SIP -- every new SIP needs a new license
            newLicense = true;
        }

        // Add deposit license if there isn't one in the object,
        // and it's not a restoration of an "manifestOnly" AIP:
        if (!params.getBooleanProperty("manifestOnly", false) &&
            PackageUtils.findDepositLicense(context, item) == null)
        {
            newLicense = true;
        }

        if(newLicense)
        {
            PackageUtils.addDepositLicense(context, license, item, collection);
        }
    }

    /**
     * Last change to fix up a DSpace Object.
     * <P>
     * For AIPs, if the object is an Item, we may want to make sure all of its
     * metadata fields already exist in the database (otherwise, the database
     * will throw errors when we attempt to save/update the Item)
     *
     * @param context DSpace Context
     * @param dso DSpace object
     * @param params Packager Parameters
     * @throws org.dspace.content.packager.PackageValidationException
     * @throws java.sql.SQLException
     * @throws org.dspace.content.crosswalk.CrosswalkException
     * @throws java.io.IOException
     * @throws org.dspace.authorize.AuthorizeException
     */
    @Override
    public void finishObject(Context context, DSpaceObject dso, PackageParameters params)
        throws PackageValidationException, CrosswalkException,
         AuthorizeException, SQLException, IOException
    {
        //Metadata fields are now required before adding, so this logic isn't needed anymore
        /*if(dso.getType()==Constants.ITEM)
        {
            // Check if 'createMetadataFields' option is enabled (default=true)
            // This defaults to true as by default we should attempt to restore as much metadata as we can.
            // When 'createMetadataFields' is set to false, an ingest will fail if it attempts to ingest content to a missing metadata field.
            if (params.getBooleanProperty("createMetadataFields", true))
            {
                // We want to verify that all the Metadata Fields we've crosswalked
                // actually *exist* in the DB.  If not, we'll try to create them
                createMissingMetadataFields(context, (Item) dso);
            }
        }
        */
    }

    /**
     * Nothing extra to do to bitstream after ingestion.
     * @throws MetadataValidationException if validation error
     * @throws IOException if IO error
     * @throws SQLException if database error
     * @throws AuthorizeException if authorization error
     */
    @Override
    public void finishBitstream(Context context,
                                                Bitstream bs,
                                                Element mfile,
                                                METSManifest manifest,
                                                PackageParameters params)
        throws MetadataValidationException, SQLException, AuthorizeException, IOException
    {
        // nothing to do.
    }

    /**
     * Return the type of DSpaceObject in this package; it is
     * in the TYPE attribute of the mets:mets element.
     * @return type
     * @throws PackageValidationException if package validation error
     */
    @Override
    public int getObjectType(METSManifest manifest)
        throws PackageValidationException
    {
        Element mets = manifest.getMets();
        String typeStr = mets.getAttributeValue("TYPE");
        if (typeStr == null || typeStr.length() == 0)
        {
            throw new PackageValidationException("Manifest is missing the required mets@TYPE attribute.");
        }
        if (typeStr.startsWith("DSpace "))
        {
            typeStr = typeStr.substring(7);
        }
        int type = Constants.getTypeID(typeStr);
        if (type < 0)
        {
            throw new PackageValidationException("Manifest has unrecognized value in mets@TYPE attribute: " + typeStr);
        }
        return type;
    }

    /**
     * Name used to distinguish DSpace Configuration entries for this subclass.
     * @return config name
     */
    @Override
    public String getConfigurationName()
    {
        return "dspaceAIP";
    }

    /**
     * Returns a user help string which should describe the
     * additional valid command-line options that this packager
     * implementation will accept when using the <code>-o</code> or
     * <code>--option</code> flags with the Packager script.
     *
     * @return a string describing additional command-line options available
     * with this packager
     */
    @Override
    public String getParameterHelp()
    {
        String parentHelp = super.getParameterHelp();

        //Return superclass help info, plus the extra parameters/options that this class supports
        return parentHelp +
                "\n\n" +
                "* createMetadataFields=[boolean]      " +
                   "If true, ingest attempts to create any missing metadata fields." +
                   "If false, ingest will fail if a metadata field is encountered which doesn't already exist. (default = true)" +
                "\n\n" +
                "* dmd=[dmdSecType]      " +
                   "Type of the METS <dmdSec> which should be used to restore item metadata (defaults to DIM, then MODS)";
    }

}
