/**
 * 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.File;
import java.util.Date;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

import org.dspace.content.Collection;
import org.dspace.content.DCDate;
import org.dspace.content.DCValue;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.packager.PackageIngester;
import org.dspace.content.packager.PackageParameters;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.PluginManager;
import org.dspace.handle.HandleManager;

import org.purl.sword.base.Deposit;
import org.purl.sword.base.SWORDErrorException;

public class SWORDMETSIngester implements SWORDIngester
{
	private SWORDService swordService;

	/** Log4j logger */
	public static final Logger log = Logger.getLogger(SWORDMETSIngester.class);

	/* (non-Javadoc)
	 * @see org.dspace.sword.SWORDIngester#ingest(org.dspace.core.Context, org.purl.sword.base.Deposit)
	 */
	public DepositResult ingest(SWORDService service, Deposit deposit, DSpaceObject dso)
		throws DSpaceSWORDException, SWORDErrorException
	{
		try
		{
			// first, make sure this is the right kind of ingester, and set the collection
			if (!(dso instanceof Collection))
			{
				throw new DSpaceSWORDException("Tried to run an ingester on wrong target type");
			}
			Collection collection = (Collection) dso;

			// now set the sword service
			swordService = service;

			// get the things out of the service that we need
			Context context = swordService.getContext();

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

			// load the plugin manager for the required configuration
			String cfg = ConfigurationManager.getProperty("sword-server", "mets-ingester.package-ingester");
			if (cfg == null || "".equals(cfg))
			{
				cfg = "METS";  // default to METS
			}
			swordService.message("Using package manifest format: " + cfg);
			
			PackageIngester pi = (PackageIngester) PluginManager.getNamedPlugin(PackageIngester.class, cfg);
			swordService.message("Loaded package ingester: " + pi.getClass().getName());
			
			// the licence is either in the zip or the mets manifest.  Either way
			// it's none of our business here
			String licence = null;
			
			// 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 (ConfigurationManager.getBooleanProperty("sword-server", "restore-mode.enable",false))
            {
                params.setRestoreModeEnabled(true);
            }

            // Whether or not to use the collection template
            params.setUseCollectionTemplate(ConfigurationManager.getBooleanProperty("mets.default.ingest.useCollectionTemplate", false));
			
			// ingest the item from the temp file
			DSpaceObject ingestedObject = pi.ingest(context, collection, depositFile, params, licence);
			if (ingestedObject == null)
			{
				swordService.message("Failed to ingest the package; throwing exception");
				throw new SWORDErrorException(DSpaceSWORDErrorCodes.UNPACKAGE_FAIL, "METS package ingester failed to unpack package");
			}
                        
                        //Verify we have an Item as a result -- SWORD can only ingest Items
                        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.
                            swordService.message("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(installedItem);
			
			// DSpace ignores the slug value as suggested identifier, but
			// it does store it in the metadata
			this.setSlug(installedItem, deposit.getSlug());
			
			// 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.
			boolean ignore = context.ignoreAuthorization();
			context.setIgnoreAuthorization(true);
			installedItem.update();
			context.setIgnoreAuthorization(ignore);
			
			// for some reason, DSpace will not give you the handle automatically,
			// so we have to look it up
			String handle = HandleManager.findHandle(context, installedItem);
			
			swordService.message("Ingest successful");
			swordService.message("Item created with internal identifier: " + installedItem.getID());
			if (handle != null)
			{
				swordService.message("Item created with external identifier: " + handle);
			}
			else
			{
				swordService.message("No external identifier available at this stage (item in workflow)");
			}
			
			DepositResult dr = new DepositResult();
			dr.setItem(installedItem);
			dr.setHandle(handle);
			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);
        }
    }

	/**
	 * Add the current date to the item metadata.  This looks up
	 * the field in which to store this metadata in the configuration
	 * sword.updated.field
	 * 
	 * @param item
	 * @throws DSpaceSWORDException
	 */
	private void setUpdatedDate(Item item)
		throws DSpaceSWORDException
	{
		String field = ConfigurationManager.getProperty("sword-server", "updated.field");
		if (field == null || "".equals(field))
		{
			throw new DSpaceSWORDException("No configuration, or configuration is invalid for: sword.updated.field");
		}
		
		DCValue dc = this.configToDC(field, null);
		item.clearMetadata(dc.schema, dc.element, dc.qualifier, Item.ANY);
		DCDate date = new DCDate(new Date());
		item.addMetadata(dc.schema, dc.element, dc.qualifier, null, date.toString());

		swordService.message("Updated date added to response from item metadata where available");
	}
	
	/**
	 * Store the given slug value (which is used for suggested identifiers,
	 * and which DSpace ignores) in the item metadata.  This looks up the
	 * field in which to store this metadata in the configuration
	 * sword.slug.field
	 * 
	 * @param item
	 * @param slugVal
	 * @throws DSpaceSWORDException
	 */
	private void setSlug(Item item, String slugVal)
		throws DSpaceSWORDException
	{
		// if there isn't a slug value, don't set it
		if (slugVal == null)
		{
			return;
		}
		
		String field = ConfigurationManager.getProperty("sword-server", "slug.field");
		if (field == null || "".equals(field))
		{
			throw new DSpaceSWORDException("No configuration, or configuration is invalid for: sword.slug.field");
		}
		
		DCValue dc = this.configToDC(field, null);
		item.clearMetadata(dc.schema, dc.element, dc.qualifier, Item.ANY);
		item.addMetadata(dc.schema, dc.element, dc.qualifier, null, slugVal);

		swordService.message("Slug value set in response where available");
	}
	
	/**
	 * utility method to turn given metadata fields of the form
	 * schema.element.qualifier into DCValue objects which can be 
	 * used to access metadata in items.
	 * 
	 * The def parameter should be null, * or "" depending on how
	 * you intend to use the DCValue object
	 * 
	 * @param config
	 * @param def
	 * @return
	 */
	private DCValue configToDC(String config, String def)
	{
		DCValue dcv = new DCValue();
		dcv.schema = def;
		dcv.element= def;
		dcv.qualifier = def;
		
		StringTokenizer stz = new StringTokenizer(config, ".");
		dcv.schema = stz.nextToken();
		dcv.element = stz.nextToken();
		if (stz.hasMoreTokens())
		{
			dcv.qualifier = stz.nextToken();
		}
		
		return dcv;
	}

	/**
	 * The human readable description of the treatment this ingester has
	 * put the deposit through
	 * 
	 * @return
	 * @throws DSpaceSWORDException
	 */
	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.";
	}
}
