/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.workflow.simple;

import java.util.ArrayList;
import java.util.Iterator;
import java.math.BigDecimal;
import java.util.Date;

import com.arsdigita.kernel.User;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.ACSObjectInstantiator;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectInstantiator;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainObject;


import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.Filter;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.util.UncheckedWrapperException;

// Support for Logging.
import org.apache.log4j.Logger;

/**
 * Class representing the workflow engine.
 *
 * @version $Revision: #12 $ $DateTime: 2004/04/07 16:07:11 $
 */
public class Engine {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/workflow/simple/Engine.java#12 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final Logger s_log =
        Logger.getLogger(Engine.class);

    private static Engine s_engine = null;

    /**
     * Gets an instance of the workflow engine.
     * @return an instance of the workflow engine.
     */

    public static synchronized Engine getInstance() {
        if (s_engine == null)
            s_engine = new Engine();

        return s_engine;
    }

    /**
     * Constructor: Set up the instantiators for subclasses of task
     */
    Engine() {
        DomainObjectInstantiator instTask = new ACSObjectInstantiator() {
                public DomainObject doNewInstance(DataObject dataObject) {
                    return new Task(dataObject);
                }
            };

        DomainObjectFactory.registerInstantiator(
                                                 Task.BASE_DATA_OBJECT_TYPE,
                                                 instTask);

        DomainObjectInstantiator instUserTask = new ACSObjectInstantiator() {
                public DomainObject doNewInstance(DataObject dataObject) {
                    return new UserTask(dataObject);
                }
            };

        DomainObjectFactory.registerInstantiator(
                                                 UserTask.BASE_DATA_OBJECT_TYPE,
                                                 instUserTask);

        DomainObjectInstantiator instWorkflow = new ACSObjectInstantiator() {
                public DomainObject doNewInstance(DataObject dataObject) {
                    return new Workflow(dataObject);
                }
            };

        DomainObjectFactory.registerInstantiator(
                                                 Workflow.BASE_DATA_OBJECT_TYPE,
                                                 instWorkflow);

//         DomainObjectInstantiator instWorkflowTemplate =
//             new ACSObjectInstantiator() {
//                 public DomainObject doNewInstance(DataObject dataObject) {
//                     return new WorkflowTemplate(dataObject);
//                 }
//             };

        /*
          DomainObjectFactory.registerInstantiator(
          WorkflowTemplate.BASE_DATA_OBJECT_TYPE,
          instWorkflowTemplate);
        */
    }


    /**
     * From the query put the assigned list into assignedTask Array List
     * The result set from query needs to have taskID.
     *
     * @param assignedTasks assigned tasks
     * @param query the query object
     *
     */
    private void populateAssignedTaskUser(ArrayList assignedTasks ,
                                          DataQuery query) {
        Session session = SessionManager.getSession();
        BigDecimal taskID = null;

        while (query.next()) {
            taskID = (BigDecimal)query.get("taskID");

            DataObject taskDataObject =
                session.retrieve(new OID("com.arsdigita.workflow.simple.Task",
                                         taskID));
            assignedTasks.add(DomainObjectFactory.newInstance(taskDataObject));
        }
    }

    /**
     * From the query put the assigned list into assignedTask Array List
     * The result set needs to have taskID and groupID
     *
     * @param assignedTasks the assigned task
     * @param query the query object
     * @param user the user to check groups
     *
     */
    private void populateAssignedTaskGroup(ArrayList assignedTasks,
                                           DataQuery query ,
                                           User user) {
        BigDecimal taskID = null;
        BigDecimal groupID = null;

        Task tempTask = null;
        Group group = null;
        Session session = SessionManager.getSession();

        try {
            while (query.next()) {
                taskID = (BigDecimal)query.get("taskID");
                groupID = (BigDecimal)query.get("groupID");

                // Load the group and check user is a member of the group
                try {
                    group = new Group(groupID);
                } catch (DataObjectNotFoundException e) {
                    throw new UncheckedWrapperException("Could not create task group ("+ groupID +")", e);
                }

                if (group.hasMember(user)) {
                    DataObject taskDataObject =
                        session.retrieve(new OID("com.arsdigita.workflow.simple.Task",
                                                 taskID));
                    tempTask = (Task)DomainObjectFactory.newInstance(taskDataObject);

                    // Since task is already in list we do not
                    // need to check
                    if (assignedTasks.contains(tempTask)) {
                        continue;
                    }

                    assignedTasks.add(tempTask);
                }
            }

        } finally {
            query.close();
        }
    }

    /**
     * Returns an ArrayList containing the set of enabled tasks in all
     * processes to which the specified user is assigned.

     * @param user a system user
     * @return the iterator
     *
     **/
    public ArrayList getEnabledTasks(User user) {

        Session session = SessionManager.getSession();

        // Retrieve all tasks directly assigned to
        // user
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedUsers");
        Filter filter = query.addFilter("userID = :user_id");
        filter.set("user_id", user.getID());
        query.addFilter("taskState = 'enabled'");
        query.addFilter("isActive = '1'");

        ArrayList usersTask = new ArrayList();
        populateAssignedTaskUser(usersTask, query);

        query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedGroups");

        query.addFilter("isActive = '1'");
        query.addFilter("taskState = 'enabled'");

        populateAssignedTaskGroup(usersTask, query, user);
        return usersTask;
    }

    public ArrayList getEnabledTasks(User user, BigDecimal workflowId) {

        Session session = SessionManager.getSession();

        // Retrieve all tasks directly assigned to
        // user
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedUsers");
        query.addFilter("userID = :user_id");
        Filter userWorkflowFilter = query.addFilter("parentID = :workflowId");

        userWorkflowFilter.set("user_id", user.getID());
        userWorkflowFilter.set("workflowId", workflowId);
        query.addFilter("taskState = 'enabled'");
        query.addFilter("isActive = '1'");

        ArrayList usersTask = new ArrayList();
        populateAssignedTaskUser(usersTask, query);

        query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedGroups");

        Filter groupWorkflowFilter = query.addFilter("parentID = :workflowId");
        groupWorkflowFilter.set("workflowId", workflowId);
        query.addFilter("isActive = '1'");
        query.addFilter("taskState = 'enabled'");

        populateAssignedTaskGroup(usersTask, query, user);
        return usersTask;
    }


    /**
     * Returns an array list over the set of overdue enabled tasks in all processes
     * to which the specified user is assigned.
     *
     * @param user a system user
     * @return the array list
     *
     **/
    public ArrayList getOverdueTasks(User user) {
        ArrayList overdueTasks = new ArrayList();

        ArrayList enabledTasks = getEnabledTasks(user);

        Iterator itr = enabledTasks.iterator();
        UserTask tempTask = null;


        while (itr.hasNext()) {
            tempTask = (UserTask)itr.next();
            if (tempTask.isOverdue()) {
                overdueTasks.add(tempTask);
            }
        }
        return overdueTasks;
    }

    /**
     * Returns an iterator over the set of tasks in all processes
     * that a user has finished in a specified period of time.
     *
     * @param user a system user
     * @param start the start date
     * @param end the end date
     * @return the iterator
     *
     **/
    public ArrayList getFinishedTasks(User user, Date start,  Date end) {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedUsers");

        Filter startDateFilter = null;
        Filter dueDateFilter =  null;

        query.addFilter("taskState = 'finished'");
        query.addFilter("isActive = '1'");

        ArrayList finishedTasks = new ArrayList();
        populateAssignedTaskUser(finishedTasks, query);

        query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getTaskAssignedGroups");

        query.addFilter("isActive = '1'");

        if (start != null) {
            startDateFilter = query.addFilter("taskStartDate >= :start");
            startDateFilter.set("start",start);
        }
        if (end != null) {
            dueDateFilter =  query.addFilter("taskDueDate <= :end");
            dueDateFilter.set("end",end);
        }

        populateAssignedTaskGroup(finishedTasks, query, user);
        return finishedTasks;
    }

    /**
     * Returns an iterator over the set of processes that currently have
     * overdue user tasks.
     * @return the iterator
     *
     **/
    public ArrayList getOverdueProcesses() {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getOverDueProcesses");

        BigDecimal processID = null;
        ArrayList overdueProcesses = new ArrayList();

        try {
            while (query.next()) {
                processID = (BigDecimal)query.get("processID");
                overdueProcesses.add(new Workflow(processID));
            }

        } catch(DataObjectNotFoundException e) {
            s_log.error("Error loading workflow", e);
            throw e;
        } finally {
            query.close();
        }

        return overdueProcesses;
    }


    /**
     * Returns an iterator over the set of processes that currently have
     * enabled tasks.
     *
     * @return the iterator
     **/
    public ArrayList getActiveProcesses() {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.workflow.simple.getActiveProcesses");

        BigDecimal processID = null;
        ArrayList activeProcesses = new ArrayList();

        while (query.next()) {
            processID = (BigDecimal)query.get("processID");
            try {
                activeProcesses.add(new Workflow(processID));
            } catch (DataObjectNotFoundException e) {
                query.close();
                throw new Error("Faled loading process");
            }
        }
        return activeProcesses;
    }

}
