package org.msh.tb.application.tasks;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.msh.tb.entities.TaskLog;
import org.msh.tb.entities.UserLogin;
import org.msh.tb.entities.Workspace;

import javax.persistence.EntityManager;
import java.util.*;

/**
 * Manage execution of backgroup tasks in the system and allow monitoring of its
 * execution
 * @author Ricardo Memoria
 *
 */

@Name("taskManager")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class TaskManager implements TaskListener {

    private Map<AsyncTask, Date> tasks = new HashMap<AsyncTask, Date>();

    public String runTask(Class taskClazz) {
        return runTask(taskClazz, null);
    }

    /**
     * Start a new task and return its unique ID
     *
     * @param taskClazz
     * @param params
     * @return
     */
    public String runTask(Class taskClazz, Map<String, Object> params) {
        AsyncTask task = findTaskByClass(taskClazz);
        if ((task != null) && (task.isUnique())) {
            throw new RuntimeException("There is already a task running as " + task.getDisplayName());
        }

        try {
            task = (AsyncTask) taskClazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Error when trying to run task " + taskClazz.toString());
            throw new RuntimeException(e);
        }

        if (task == null) {
            throw new RuntimeException("Interface " + AsyncTask.class.toString() + " in class " + taskClazz.toString());
        }

        task.addListener(this);

        // feed parameters to the task
        if (params != null) {
            for (String param : params.keySet()) {
                task.addParameter(param, params.get(param));
            }
        }

        // common variables used by the system
        UserLogin userLogin = (UserLogin) Component.getInstance("userLogin");
        if (userLogin != null) {
            task.addParameter("userLogin", userLogin);
            task.setUser(userLogin.getUser());
        }

        Object userWorkspace = Component.getInstance("userWorkspace");
        if (userWorkspace != null) {
            task.addParameter("userWorkspace", userWorkspace);
        }

        Workspace workspace = (Workspace) Component.getInstance("defaultWorkspace");
        if (workspace != null) {
            task.setWorkspace(workspace);
        }

        AsyncTaskRunner runner = (AsyncTaskRunner) Component.getInstance("asyncTaskRunner", true);

        assignIdToTask(task);

        runner.runTask(task);

        return task.getId();
    }

    private void assignIdToTask(AsyncTask task) {
        task.setId(UUID.randomUUID().toString());
    }


    /**
     * Search for a task by its implementation class
     *
     * @param clazz
     * @return
     */
    public AsyncTask findTaskByClass(Class clazz) {
        for (AsyncTask task : tasks.keySet()) {
            if (task.getClass() == clazz) {
                return task;
            }
        }
        return null;
    }

    /**
     * Log the task status for future reference
     *
     * @param task
     */
    private void logTask(AsyncTask task) {
        Date execStart = tasks.get(task);
        if (execStart == null) {
            return;
        }

        TaskLog taskLog = new TaskLog();
        taskLog.setTaskId(task.getId());
        taskLog.setTaskClass(task.getClass().getCanonicalName());
        taskLog.setExecStart(execStart);
        taskLog.setExecEnd(new Date());
        taskLog.setData(task.getLogData());
        taskLog.setStatus(task.getStatus());

        EntityManager em = getEntityManager();
        em.persist(taskLog);
        em.flush();
    }

    /**
     * Search for the task log by its task ID. The task log is just available
     * for tasks that have already finished and are stored
     *
     * @param taskId the task ID
     * @return instance of {@link TaskLog} or null if task is not found
     */
    public TaskLog findTaskLog(String taskId) {
        EntityManager em = getEntityManager();
        List<TaskLog> lst = em.createQuery("from TaskLog where taskId = :taskId")
                .setParameter("taskId", taskId)
                .getResultList();

        return lst.size() > 0 ? lst.get(0) : null;
    }

    /**
     * Return the entity manager used internatlly
     *
     * @return
     */
    private EntityManager getEntityManager() {
        return (EntityManager) Component.getInstance("entityManager");
    }

    /**
     * Return the list of tasks under execution
     *
     * @return
     */
    public Set<AsyncTask> getTasks() {
        return tasks.keySet();
    }


    /**
     * Cancel a task under execution
     *
     * @param id
     * @return true if task was found and canceled
     */
    public boolean cancelTask(String id) {
        AsyncTask task = findTaskById(id);
        if (task == null) {
            return false;
        }

        task.cancel();
        return true;
    }


    /**
     * Find a task by its id
     *
     * @param id
     * @return
     */
    public AsyncTask findTaskById(String id) {
        for (AsyncTask task : tasks.keySet()) {
            if (task.getId().equals(id)) {
                return task;
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.msh.tb.application.tasks.TaskListener#notifyTaskStarting(org.msh.tb.application.tasks.AsyncTask)
     */
    public void notifyTaskStarting(AsyncTask task) {
        tasks.put(task, new Date());
    }


    /* (non-Javadoc)
     * @see org.msh.tb.application.tasks.TaskListener#notifyTaskEnding(org.msh.tb.application.tasks.AsyncTask)
     */
    public void notifyTaskFinished(AsyncTask task) {
        try {
            if (task.isLogged()) {
                logTask(task);
            }
        } finally {
            tasks.remove(task);
        }
    }


    public void taskStatusChangeHandler(AsyncTask task) {
        switch (task.getStatus()) {
            case STARTING:
                notifyTaskStarting(task);
                break;
            case FINISHED:
            case ERROR:
            case CANCELED:
                notifyTaskFinished(task);
                break;
        }
    }

}