/*
 *  Copyright 2016 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.ametys.plugins.workflow.support;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;

import com.opensymphony.workflow.AbstractWorkflow;
import com.opensymphony.workflow.FactoryException;
import com.opensymphony.workflow.Workflow;
import com.opensymphony.workflow.WorkflowException;
import com.opensymphony.workflow.loader.ActionDescriptor;
import com.opensymphony.workflow.loader.StepDescriptor;
import com.opensymphony.workflow.loader.WorkflowDescriptor;
import com.opensymphony.workflow.spi.Step;
import com.opensymphony.workflow.spi.WorkflowStore;

/**
 * Helper to get information on the workflow structures
 */
public class WorkflowHelper extends AbstractLogEnabled implements Component, Serviceable
{
    /** The Avalon role */
    public static final String ROLE = WorkflowHelper.class.getName();

    /** The content types extension point */
    protected WorkflowProvider _workflowProvider;

    @Override
    public void service(ServiceManager smanager) throws ServiceException
    {
        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
    }
    
    /**
     * Get a list of workflow names available
     * @return String[] an array of workflow names.
     */
    public String[] getWorkflowNames()
    {
        try
        {
            return _workflowProvider.getWorkflowFactory().getWorkflowNames();
        }
        catch (FactoryException e)
        {
            getLogger().error("Error getting workflow names", e);
        }
        
        return new String[0];
    }
    
    /**
     * Returns a workflow definition object associated with the given name.
     * @param workflowName the name of the workflow
     * @return the object graph that represents a workflow definition
     */
    public WorkflowDescriptor getWorkflowDescriptor(String workflowName)
    {
        try
        {
            return _workflowProvider.getWorkflowFactory().getWorkflow(workflowName);
        }
        catch (FactoryException e)
        {
            getLogger().error("Error loading workflow " + workflowName, e);
        }
        
        return null;
    }
    
    /**
     * Retrieves all actions of the workflow of a particular type of workflow
     * except initial actions.
     * @param workflowName the name of the workflow.
     * @return all actions ids.
     * @throws IllegalArgumentException If the workflow name is not valid.
     */
    public int[] getAllActions(String workflowName) throws IllegalArgumentException
    {
        WorkflowDescriptor workflowDesc = getWorkflowDescriptor(workflowName);
        
        if (workflowDesc == null)
        {
            throw new IllegalArgumentException("Bad workflow name \"" + workflowName + "\"");
        }
        
        Set<Integer> actions = new HashSet<>();
        
        // Récupérer des actions globales
        Iterator iterGlobalActions = workflowDesc.getGlobalActions().iterator();
        
        // Parcourir la liste des actions globales
        while (iterGlobalActions.hasNext())
        {
            // Récupérer la description de l'action globale courante
            ActionDescriptor actionDesc = (ActionDescriptor) iterGlobalActions.next();
            
            // Ajouter l'action globale courante
            actions.add(new Integer(actionDesc.getId()));
        }
        
        // Récupérer la liste des steps
        Iterator iterSteps = workflowDesc.getSteps().iterator();
        
        // Parcourir la liste des steps
        while (iterSteps.hasNext())
        {
            // Récupérer le step courant
            StepDescriptor stepDesc = (StepDescriptor) iterSteps.next();
            
            // Récupérer la liste des actions associés au step courant
            Iterator iterActions = stepDesc.getActions().iterator();
            
            // Parcourir la liste des actions
            while (iterActions.hasNext())
            {
                // Récupérer la description de chaque action
                ActionDescriptor actionDesc = (ActionDescriptor) iterActions.next();
                
                // Ajouter l'action courante
                actions.add(new Integer(actionDesc.getId()));
            }
        }
        
        // Convertir la liste en tableau d'entier
        int[] array = new int[actions.size()];
        Iterator<Integer> itAction = actions.iterator();
        
        // Remplir le tableau
        for (int i = 0; itAction.hasNext(); i++)
        {
            array[i] = itAction.next().intValue();
        }
        
        return array;
    }
    
    /**
     * Retrieves the name of an action.
     * @param workflowName The name of the workflow.
     * @param actionID The id of the action.
     * @return The name of the action or an empty string.
     */
    public String getActionName(String workflowName, int actionID)
    {
        // Récupérer la description du workflow
        WorkflowDescriptor workflowDesc = getWorkflowDescriptor(workflowName);
        
        if (workflowDesc == null)
        {
            return "";
        }
        
        // Récupérer la description de l'action
        ActionDescriptor actionDesc = workflowDesc.getAction(actionID);
        
        if (actionDesc == null)
        {
            return "";
        }
        
        return actionDesc.getName();
    }
    
    /**
     * Retrieves the name of a step.
     * @param workflowName the name of the workflow.
     * @param stepId the id of the step.
     * @return the name of the step or an empty string.
     */
    public String getStepName(String workflowName, int stepId)
    {
        // Récupérer la description du workflow
        WorkflowDescriptor workflowDesc = getWorkflowDescriptor(workflowName);
        
        if (workflowDesc == null)
        {
            return "";
        }
        
        // Récupérer la description de l'action
        StepDescriptor stepDesc = workflowDesc.getStep(stepId);
        
        if (stepDesc == null)
        {
            return "";
        }
        
        return stepDesc.getName();
    }
    
    /**
     * Retrieves the initial action id of a workflow.
     * @param workflowName the name of the workflow.
     * @return the first initial action id or <code>-1</code>
     *         if the workflow does not exist.
     */
    public int getInitialAction(String workflowName)
    {
        // Récupérer la description du workflow
        WorkflowDescriptor workflowDesc = getWorkflowDescriptor(workflowName);
        
        if (workflowDesc == null)
        {
            return -1;
        }
        
        // Retourner la première action initiale
        return ((ActionDescriptor) workflowDesc.getInitialActions().get(0)).getId();
    }
    
    /**
     * Retrieves the action ids from a particular step.
     * @param workflowName the name of the workflow.
     * @param stepId the id of the step.
     * @return the ids of the actions of the step.
     */
    public int[] getAllActionsFromStep(String workflowName, int stepId)
    {
        // Récupérer la description du workflow
        WorkflowDescriptor workflowDesc = getWorkflowDescriptor(workflowName);
        
        if (workflowDesc == null)
        {
            return new int[0];
        }
        
        List<Integer> actions = new ArrayList<>();
        
        // Vérifier que l'identifiant est valide
        StepDescriptor stepDesc = workflowDesc.getStep(stepId);
        
        if (stepDesc == null)
        {
            return new int[0];
        }
        
        // Récupérer la liste des actions associés au step courant
        Iterator iterator = stepDesc.getActions().iterator();
        
        // Parcourir la liste des actions
        while (iterator.hasNext())
        {
            // Récupérer la description de chaque action
            ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();
            
            // Ajouter l'action courante
            actions.add(new Integer(actionDesc.getId()));
        }
        
        // Récupérer les actions globales
        iterator = workflowDesc.getGlobalActions().iterator();
        
        // Parcourir la liste des actions globales
        while (iterator.hasNext())
        {
            // Récupérer la description de l'action globale courante
            ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();
            
            // Ajouter l'action globale courant
            actions.add(new Integer(actionDesc.getId()));
        }
        
        // Convertir la liste en tableau d'entier
        int[] array = new int[actions.size()];
        
        // Remplir le tableau
        for (int i = 0; i < array.length; i++)
        {
            array[i] = actions.get(i).intValue();
        }
        
        return array;
    }
    
    /**
     * Get the steps the workflow was "in" at a given date.
     * @param workflow workflow
     * @param entryId the workflow entry ID.
     * @param timestamp the date.
     * @return the list of steps the workflow was in.
     * @throws WorkflowException if an error occurs.
     */
    public List<Step> getStepAt(Workflow workflow, long entryId, Date timestamp) throws WorkflowException
    {
        return getStepsBetween(workflow, entryId, timestamp, timestamp);
    }
    
    /**
     * Get the steps the workflow was "in" between two dates.
     * @param workflow workflow
     * @param entryId the workflow entry ID.
     * @param start the start date.
     * @param end the end date.
     * @return the list of steps the workflow was in between the two dates.
     * @throws WorkflowException if an error occurs.
     */
    public List<Step> getStepsBetween(Workflow workflow, long entryId, Date start, Date end) throws WorkflowException
    {
        WorkflowStore store = ((AbstractWorkflow) workflow).getConfiguration().getWorkflowStore();
        
        List<Step> steps = new ArrayList<>();
        
        List<Step> allSteps = new ArrayList<>();
        
        allSteps.addAll(store.findCurrentSteps(entryId));
        allSteps.addAll(store.findHistorySteps(entryId));
        
        for (Step step : allSteps)
        {
            Date stepStartDate = step.getStartDate();
            Date stepFinishDate = step.getFinishDate();
            
            if (stepStartDate != null)
            {
                if (end.after(stepStartDate))
                {
                    if (stepFinishDate == null || start.before(stepFinishDate))
                    {
                        steps.add(step);
                    }
                }
            }
            else
            {
                if (stepFinishDate == null || start.before(stepFinishDate))
                {
                    steps.add(step);
                }
            }
        }
        
        return steps;
    }
}