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

import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.InProgressSubmission;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRowIterator;
import org.dspace.authorize.AuthorizeException;
import org.dspace.eperson.EPerson;
import org.apache.log4j.Logger;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.XmlWorkflowManager;

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

/**
 * Class representing an item going through the workflow process in DSpace
 *
 * @author Bram De Schouwer (bram.deschouwer at dot com)
 * @author Kevin Van de Velde (kevin at atmire dot com)
 * @author Ben Bosman (ben at atmire dot com)
 * @author Mark Diggory (markd at atmire dot com)
 */
public class XmlWorkflowItem implements InProgressSubmission {

    /*
     * The current step in the workflow system in which this workflow item is present
     */
    private static Logger log = Logger.getLogger(XmlWorkflowItem.class);

    private Collection collection;

    private Item item;

    private TableRow wfRow;

    private Context ourContext;


    public static XmlWorkflowItem create(Context context) throws AuthorizeException, IOException, SQLException {
        TableRow row = DatabaseManager.create(context, "cwf_workflowitem");

        return new XmlWorkflowItem(context, row);
    }

    /*
     * In the case where multiple epersons can claim and perform an action, this map will represent the progress
     * of each of these epersons in the step
     */
//    private ArrayList<StepRecord> activeSteps;

    XmlWorkflowItem(Context context, TableRow row) throws SQLException {
        ourContext = context;
        wfRow = row;
 //       activeSteps = new ArrayList<StepRecord>();

        item = Item.find(context, wfRow.getIntColumn("item_id"));
        collection = Collection.find(context, wfRow.getIntColumn("collection_id"));
        // Cache ourselves
        context.cache(this, row.getIntColumn("workflowitem_id"));
//        initialize();
//        update();
    }

    /**
     * Get a workflow item from the database. The item, collection and submitter
     * are loaded into memory.
     *
     * @param context
     *            DSpace context object
     * @param id
     *            ID of the workspace item
     *
     * @return the workflow item, or null if the ID is invalid.
     */
    public static XmlWorkflowItem find(Context context, int id)
            throws SQLException, AuthorizeException, IOException {
        // First check the cache
        XmlWorkflowItem fromCache = (XmlWorkflowItem) context.fromCache(
                XmlWorkflowItem.class, id);

        if (fromCache != null)
        {
            return fromCache;
        }

        TableRow row = DatabaseManager.find(context, "cwf_workflowitem", id);
//        TableRow row = DatabaseManager.querySingle(context, "SELECT * FROM workflowitem WHERE item_id= "+id);

        if (row == null)
        {
            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "find_workflow_item",
                        "not_found,workflowitem_id=" + id));
            }

            return null;
        }
        else
        {
            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "find_workflow_item",
                        "workflowitem_id=" + id));
            }

            return new XmlWorkflowItem(context, row);
        }
    }

    /**
     * return all workflowitems
     *
     * @param c  active context
     * @return WorkflowItem [] of all workflows in system
     */
    public static XmlWorkflowItem[] findAll(Context c) throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
        List wfItems = new ArrayList();
        TableRowIterator tri = DatabaseManager.queryTable(c, "cwf_workflowitem",
                "SELECT * FROM cwf_workflowitem");

        try
        {
            // make a list of workflow items
            while (tri.hasNext())
            {
                TableRow row = tri.next();
                XmlWorkflowItem wi = new XmlWorkflowItem(c, row);
                wfItems.add(wi);
            }
        }
        finally
        {
            if (tri != null)
                tri.close();
        }

        XmlWorkflowItem[] wfArray = new XmlWorkflowItem[wfItems.size()];
        wfArray = (XmlWorkflowItem[]) wfItems.toArray(wfArray);

        return wfArray;
    }

    /**
     * return all workflowitems for a certain page
     *
     * @param c  active context
     * @return WorkflowItem [] of all workflows in system
     */
    public static XmlWorkflowItem[] findAll(Context c, int page, int pagesize) throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
        return findAllInCollection(c, page, pagesize, -1);
    }

    /**
     * return all workflowitems for a certain page with a certain collection
     *
     * @param c  active context
     * @return WorkflowItem [] of all workflows in system
     */
    public static XmlWorkflowItem[] findAllInCollection(Context c, int page, int pagesize, int collectionId) throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
        List wfItems = new ArrayList();
        StringBuffer query = new StringBuffer();

        query.append("SELECT * FROM cwf_workflowitem ");
        if(collectionId != -1){
            query.append("WHERE collection_id=").append(collectionId);
        }
        int offset = (page - 1) * pagesize;
        if ("oracle".equals(ConfigurationManager.getProperty("db.name"))) {
            // First prepare the query to generate row numbers
            if (pagesize > 0 || offset > 0) {
                query.insert(0, "SELECT /*+ FIRST_ROWS(n) */ rec.*, ROWNUM rnum  FROM (");
                query.append(") ");
            }
            // Restrict the number of rows returned based on the limit
            if (pagesize > 0) {
                query.append("rec WHERE rownum<=? ");
                // If we also have an offset, then convert the limit into the maximum row number
                if (offset > 0) {
                    pagesize += offset;
                }
            }
            // Return only the records after the specified offset (row number)
            if (offset > 0) {
                query.insert(0, "SELECT * FROM (");
                query.append(") WHERE rnum>?");
            }
        } else {
            if (pagesize > 0) {
                query.append(" LIMIT ? ");
            }
            if (offset > 0) {
                query.append(" OFFSET ? ");
            }
        }
        String queryString = query.toString();
        Object[] paramArr = new Object[]{};
        if (pagesize > 0 && offset > 0) {
            paramArr = new Object[]{pagesize, offset};
        } else if (pagesize > 0) {
            paramArr = new Object[]{pagesize};
        } else if (offset > 0) {
            paramArr = new Object[]{offset};
        }
        // Get all the epeople that match the query
        TableRowIterator tri = DatabaseManager.query(c, queryString, paramArr);
        try
        {
            // make a list of workflow items
            while (tri.hasNext())
            {
                TableRow row = tri.next();
                XmlWorkflowItem wi = new XmlWorkflowItem(c, row);
                wfItems.add(wi);
            }
        }
        finally
        {
            if (tri != null)
                tri.close();
        }

        XmlWorkflowItem[] wfArray = new XmlWorkflowItem[wfItems.size()];
        wfArray = (XmlWorkflowItem[]) wfItems.toArray(wfArray);

        return wfArray;
    }


    /**
         * return all workflowitems
         *
         * @param c  active context
         * @return WorkflowItem [] of all workflows in system
         */
        public static int countAll(Context c) throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
            return countAllInCollection(c, -1);
        }

    /**
         * return all workflowitems
         *
         * @param c  active context
         * @return WorkflowItem [] of all workflows in system
         */
        public static int countAllInCollection(Context c, int collId) throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
        StringBuffer query = new StringBuffer();
        query.append("SELECT count(*) AS count FROM cwf_workflowitem ");
        if(collId != -1){
            query.append(" WHERE collection_id= ").append(collId);
        }

        TableRow tr = DatabaseManager.querySingle(c,query.toString());

            return new Long(tr.getLongColumn("count")).intValue();
        }


    /*
     * Returns all workflow items submitted by an eperson
     */
    public static XmlWorkflowItem[] findByEPerson(Context context, EPerson ep)
            throws SQLException, AuthorizeException, IOException, WorkflowConfigurationException {
            List wfItems = new ArrayList();

            TableRowIterator tri = DatabaseManager.queryTable(context, "cwf_workflowitem",
                    "SELECT cwf_workflowitem.* FROM cwf_workflowitem, item WHERE " +
                    "cwf_workflowitem.item_id=item.item_id AND " +
                    "item.submitter_id= ? " +
                    "ORDER BY cwf_workflowitem.workflowitem_id",
                    ep.getID());

            while (tri.hasNext())
            {
                TableRow row = tri.next();

                // Check the cache
                XmlWorkflowItem wi = (XmlWorkflowItem) context.fromCache(
                        XmlWorkflowItem.class, row.getIntColumn("workflowitem_id"));

                if (wi == null)
                {
                    wi = new XmlWorkflowItem(context, row);
                }

                wfItems.add(wi);
            }

            tri.close();

            XmlWorkflowItem[] wfArray = new XmlWorkflowItem[wfItems.size()];
            wfArray = (XmlWorkflowItem[]) wfItems.toArray(wfArray);

            return wfArray;
    }

    /**
     * Get all workflow items for a particular collection.
     *
     * @param context
     *            the context object
     * @param c
     *            the collection
     *
     * @return array of the corresponding workflow items
     */
    public static XmlWorkflowItem[] findByCollection(Context context, Collection c)
            throws SQLException, AuthorizeException, IOException {
        List wsItems = new ArrayList();

        TableRowIterator tri = DatabaseManager.queryTable(context, "cwf_workflowitem",
                "SELECT cwf_workflowitem.* FROM cwf_workflowitem WHERE " +
                "cwf_workflowitem.collection_id= ? ",
                c.getID());

        try
        {
            while (tri.hasNext())
            {
                TableRow row = tri.next();

                // Check the cache
                XmlWorkflowItem wi = (XmlWorkflowItem) context.fromCache(
                        XmlWorkflowItem.class, row.getIntColumn("workflowitem_id"));

                // not in cache? turn row into workflowitem
                if (wi == null)
                {
                    wi = new XmlWorkflowItem(context, row);
                }

                wsItems.add(wi);
            }
        }
        finally
        {
            if (tri != null)
                tri.close();
        }
        XmlWorkflowItem[] wsArray = new XmlWorkflowItem[wsItems.size()];
        wsArray = (XmlWorkflowItem[]) wsItems.toArray(wsArray);

        return wsArray;
    }

    /**
     * Check to see if a particular item is currently under Workflow.
     * If so, its WorkflowItem is returned.  If not, null is returned
     *
     * @param context
     *            the context object
     * @param item
     *            the item
     *
     * @return workflow item corresponding to the item, or null
     */
    public static XmlWorkflowItem findByItem(Context context, Item item) throws SQLException{
        TableRow row = DatabaseManager.findByUnique(context, "cwf_workflowitem", "item_id", item.getID());

        XmlWorkflowItem wi = null;
        if(row != null){
            // Check the cache
            wi = (XmlWorkflowItem) context.fromCache(XmlWorkflowItem.class, row.getIntColumn("workflowitem_id"));

            // not in cache? turn row into workflowitem
            if (wi == null)
            {
                wi = new XmlWorkflowItem(context, row);
            }
        }
        return wi;
    }

    /**
     * Update the workflow item, including the unarchived item.
     */
    public void update() throws SQLException, AuthorizeException {
        // FIXME check auth
        log.info(LogManager.getHeader(ourContext, "update_workflow_item",
                "workflowitem_id=" + getID()));

        // Update the item
        item.update();

        // Update ourselves
        DatabaseManager.update(ourContext, wfRow);
    }


    /**
     * Get the internal ID of this workflow item
     *
     * @return the internal identifier
     */
    public int getID()
    {
        return wfRow.getIntColumn("workflowitem_id");
    }


    public Collection getCollection(){
        return this.collection;
    }

    public void setCollection(Collection collection){
        this.collection = collection;
        wfRow.setColumn("collection_id", collection.getID());
    }

    public Item getItem()
    {
        return item;
    }

    public void setItem(Item item){
        this.item = item;
        wfRow.setColumn("item_id", item.getID());
    }

    public EPerson getSubmitter() throws SQLException
    {
        return item.getSubmitter();
    }

    public boolean hasMultipleFiles()
    {
        return wfRow.getBooleanColumn("multiple_files");
    }

    public void setMultipleFiles(boolean b)
    {
        wfRow.setColumn("multiple_files", b);
    }

    public boolean hasMultipleTitles()
    {
        return wfRow.getBooleanColumn("multiple_titles");
    }

    public void setMultipleTitles(boolean b)
    {
        wfRow.setColumn("multiple_titles", b);
    }

    public boolean isPublishedBefore()
    {
        return wfRow.getBooleanColumn("published_before");
    }

    public void setPublishedBefore(boolean b)
    {
        wfRow.setColumn("published_before", b);
    }

    /**
     * delete the WorkflowItem, retaining the Item
     */
    public void deleteWrapper() throws SQLException, IOException,
            AuthorizeException
    {
        // Remove from cache
        ourContext.removeCached(this, getID());

        WorkflowItemRole[] roles = WorkflowItemRole.findAllForItem(ourContext, this.getID());
        for(WorkflowItemRole role: roles){
            role.delete();
        }
        XmlWorkflowManager.deleteAllTasks(ourContext, this);

        // FIXME - auth?
        DatabaseManager.delete(ourContext, wfRow);
    }
}
