/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.services.ejb;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.services.Action;
import com.queplix.core.modules.services.jxb.Script;
import com.queplix.core.modules.services.jxb.Task;
import com.queplix.core.modules.services.jxb.types.DelayunitSType;
import com.queplix.core.modules.services.jxb.types.StatusSType;
import com.queplix.core.modules.services.utils.ServicesPropertyFactory;
import com.queplix.core.utils.DateHelper;
import com.queplix.core.utils.cache.Cache;
import com.queplix.core.utils.ejb.AbstractSessionEJB;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * Script manager session EJB.
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.4 $ $Date: 2005/12/05 08:59:59 $
 */

public class ScriptManagerEJB
        extends AbstractSessionEJB {

    // ----------------------------------------------------- public methods

    /**
     * Initializes bean.
     */
    public void ejbCreate() {
        INFO("ScriptManager EJB created - " + hashCode());
    }

    /**
     * Gets the script by its ID.
     *
     * @param script_id script id
     * @return Script object
     */
    public Script getScript(int script_id) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get Script" +
                    ". Id: " + script_id + ".");
        }

        // Try to find out in cache.
        Integer key = new Integer(script_id);
        Cache cache = ServicesPropertyFactory.getInstance().getScriptCache();
        Script script = null;

        if(!cache.containsKey(key)) {
            // Load Script from database.
            script = ServicesPropertyFactory.getInstance().getScriptDAO()
                    .loadScriptVO(script_id);

            // Store in cache.
            updateCache(script);

        } else {
            // Found in cache!
            if(getLogger().isDebugEnabled()) {
                DEBUG("Script #" + script_id + " found in cache.");
            }
            script = (Script) cache.get(key);
        }

        return script;
    }

    /**
     * Gets the script by its name.
     *
     * @param scriptName script name
     * @return Script object
     */
    public Script getScript(String scriptName) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get Script" +
                    ". Name: " + scriptName + ".");
        }

        // Load Script from database.
        Script script = ServicesPropertyFactory.getInstance().getScriptDAO()
                .loadScriptVO(scriptName);

        // Store in cache.
        updateCache(script);

        return script;
    }

    /**
     * Gets the task by its ID.
     *
     * @param task_id task id
     * @return Task object
     */
    public Task getTask(long task_id) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get Task" +
                    ". Id: " + task_id + ".");
        }

        // Try to find out in cache.
        Long key = new Long(task_id);
        Cache cache = ServicesPropertyFactory.getInstance().getTaskCache();
        Task task = null;

        if(!cache.containsKey(key)) {
            // Load Task from database.
            task = ServicesPropertyFactory.getInstance().getTaskDAO()
                    .loadTaskVO(task_id);

            // Store in cache.
            updateCache(task);

        } else {
            // Found in cache!
            if(getLogger().isDebugEnabled()) {
                DEBUG("Task #" + task_id + " found in cache.");
            }
            task = (Task) cache.get(key);
        }

        return task;
    }

    /**
     * Gets the task by its name.
     *
     * @param taskName task name
     * @return Task object
     */
    public Task getTask(String taskName) {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to get Task" +
                    ". Name: " + taskName + ".");
        }

        // Load Task from database.
        Task task = ServicesPropertyFactory.getInstance().getTaskDAO()
                .loadTaskVO(taskName);

        // Store in cache.
        updateCache(task);

        return task;
    }

    /**
     * Get collection of Task value objects
     *
     * @return collection of Task objects
     */
    public Collection getTasks() {

        Collection tasks;
        long time = System.currentTimeMillis();

        DEBUG("Try to get All tasks");

        Cache cache = ServicesPropertyFactory.getInstance().getTaskCache();
        if(cache.isOpen()) {
            // Load entities.
            tasks = ServicesPropertyFactory.getInstance().getTaskDAO()
                    .loadAllTaskVO();

            synchronized(cache) {
                // Clear cache.
                cache.clear();

                // Store tasks in cache.
                if(tasks != null) {
                    for(Iterator it = tasks.iterator(); it.hasNext();) {
                        Task task = (Task) it.next();
                        cache.put(task.getId(), task);
                    }
                }

                // Close cache.
                cache.close();
            }

        } else {
            // Take entire cache.
            tasks = cache.values();
        }

        if(getLogger().isDebugEnabled()) {
            DEBUG("Get All tasks - ok. Time (ms): " + (
                    System.currentTimeMillis() - time));
        }

        return tasks;
    }

    /**
     * Updates cache with new VO.
     *
     * @param script Script VO
     */
    public void updateCache(Script script) {

        Integer key = script.getId();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to update Script cache" +
                    ". Id: " + key + ".");
        }

        Cache cache = ServicesPropertyFactory.getInstance().getScriptCache();
        synchronized(cache) {
            boolean isOpen = cache.open();
            cache.put(key, script);
            if(!isOpen) {
                cache.close();
            }
        }
    }

    /**
     * Updates cache with new VO.
     *
     * @param task Task VO
     */
    public void updateCache(Task task) {

        Long key = task.getId();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to update Task cache" +
                    ". Id: " + key + ".");
        }

        Cache cache = ServicesPropertyFactory.getInstance().getTaskCache();
        synchronized(cache) {
            boolean isOpen = cache.open();
            cache.put(key, task);
            if(!isOpen) {
                cache.close();
            }
        }
    }

    /**
     * Flushes key <code>script_id</code> from Script cache.
     *
     * @param script_id script id
     */
    public void flushScriptCache(int script_id) {

        Integer key = new Integer(script_id);

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to flush Script cache" +
                    ". Id: " + key + ".");
        }

        Cache cache = ServicesPropertyFactory.getInstance().getScriptCache();
        // Open cache and don't close it!
        cache.open();
        cache.remove(key);
    }

    /**
     * Flushes key <code>task_id</code> from Task cache.
     *
     * @param task_id task id
     */
    public void flushTaskCache(long task_id) {

        Long key = new Long(task_id);

        if(getLogger().isDebugEnabled()) {
            DEBUG("Try to flush Task cache" +
                    ". Id: " + key + ".");
        }

        Cache cache = ServicesPropertyFactory.getInstance().getTaskCache();
        // Open cache and don't close it!
        cache.open();
        cache.remove(key);
    }

    /**
     * Get tasks with flag "auto start"
     *
     * @return Collection of Task objects or null
     */
    public Collection getAutostartedTasks() {

        DEBUG("Try to get autostarted tasks");

        Collection tasks = getTasks();
        if(tasks == null || tasks.size() == 0) {
            return null;
        }

        Collection ret = new ArrayList();

        for(Iterator it = tasks.iterator(); it.hasNext();) {
            Task task = (Task) it.next();
            if(task.getAutoStart().booleanValue()) {
                ret.add(task);
            }
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Got [" + ret.size() + "] autostarted tasks...");
        }

        return ret;
    }

    /**
     * Get running tasks
     *
     * @return Collection of Task objects or null
     */
    public Collection getRunTasks() {

        DEBUG("Try to get running tasks");

        Collection tasks = getTasks();
        if(tasks == null || tasks.size() == 0) {
            return null;
        }

        Collection ret = new ArrayList();

        for(Iterator it = tasks.iterator(); it.hasNext();) {
            Task task = (Task) it.next();
            int status = task.getStatus().getType();
            if(status == StatusSType.RUN_TYPE ||
                    status == StatusSType.WAITED_TYPE) {
                ret.add(task);
            }
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Got [" + ret.size() + "] running tasks...");
        }

        return ret;
    }

    /**
     * Get matured (ready to run) tasks
     *
     * @return Collection of Task objects or null
     */
    public Collection getMaturedTasks() {

        DEBUG("Try to get matured tasks");

        Collection tasks = getTasks();
        if(tasks == null || tasks.size() == 0) {
            return null;
        }

        long nowMillis = DateHelper.currentTimeMillis();
        Collection ret = new ArrayList();

        for(Iterator it = tasks.iterator(); it.hasNext();) {
            Task task = (Task) it.next();
            int status = task.getStatus().getType();
            if(status == StatusSType.READY_TYPE) {
                Long nextStart = task.getNextStart();
                if(nextStart != null && nextStart.longValue() < nowMillis) {
                    ret.add(task);
                }
            }
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Got [" + ret.size() + "] matured tasks...");
        }

        return ret;
    }

    /**
     * Perform action
     *
     * @param action given Action object
     * @return Action result
     */
    public Serializable startAction(java.io.Serializable action) {
        if(!(action instanceof Action)) {
            throw new IllegalStateException("Only Action classes supported!");
        }
        return ((Action) action).perform();
    }

    // ----------------------------------------------------- task management

    //
    // Set task status to READY
    //

    public void readyTask(long task_id, boolean ignoreStatus)
            throws EQLException {

        long time = System.currentTimeMillis();

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        if(!ignoreStatus) {
            // Check task status
            int task_status = task.getStatus().getType();
            switch(task_status) {
                case StatusSType.READY_TYPE:
                case StatusSType.RUN_TYPE:
                case StatusSType.WAITED_TYPE:
                    throw new EQLException("Task #" + task_id + " already run");
            }
        }

        // Set READY status
        task.setStatus(StatusSType.READY);

        // Set next start
        long next_start = getNextStart(task);
        task.setNextStart(new Long(next_start));

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task ready (ID = " + task_id + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
    }

    //
    // Set task status to RUN
    //
    public void runTask(long task_id, boolean ignoreStatus)
            throws EQLException {

        long time = System.currentTimeMillis();

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        if(!ignoreStatus) {
            // Check task status
            int task_status = task.getStatus().getType();
            switch(task_status) {
                case StatusSType.NEW_TYPE:
                case StatusSType.COMPLETED_TYPE:
                case StatusSType.INTERRUPTED_TYPE:
                    throw new EQLException("Can't run task #" + task_id);
                case StatusSType.RUN_TYPE:
                    throw new EQLException("Task #" + task_id + " already run");
            }
        }

        // Set RUN status
        task.setStatus(StatusSType.RUN);

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task run (ID = " + task_id + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
    }

    //
    // Set task status to WAITED
    //
    public long waitTask(long task_id, boolean ignoreStatus)
            throws EQLException {

        long time = System.currentTimeMillis();
        long timeout = 0;

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        // Get previos start date.
        long prev_start = getNextStart(task);

        // Calculate next start date.
        timeout = getTimeout(task);
        long now = DateHelper.currentTimeMillis();
        long next_start = prev_start;
        if(next_start < now) {
            next_start += timeout;
        }

        if(!ignoreStatus) {
            // Check task status
            int task_status = task.getStatus().getType();
            switch(task_status) {
                case StatusSType.NEW_TYPE:
                case StatusSType.READY_TYPE:
                case StatusSType.COMPLETED_TYPE:
                case StatusSType.INTERRUPTED_TYPE:
                    throw new EQLException("Can't wait task #" + task_id);
                case StatusSType.WAITED_TYPE:
                    throw new EQLException(
                            "Task #" + task_id + " already waited");
            }
        }

        // Set WAITED status
        task.setStatus(StatusSType.WAITED);

        // Set next start
        task.setNextStart(new Long(next_start));

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task waited (ID = " + task_id + ") for " + timeout + "(ms).");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
        return timeout;
    }

    //
    // Set task status to COMPLETED
    //
    public void completeTask(long task_id, boolean ignoreStatus)
            throws EQLException {

        long time = System.currentTimeMillis();

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        if(!ignoreStatus) {
            // Check task status
            int task_status = task.getStatus().getType();
            switch(task_status) {
                case StatusSType.NEW_TYPE:
                case StatusSType.READY_TYPE:
                case StatusSType.COMPLETED_TYPE:
                case StatusSType.INTERRUPTED_TYPE:
                    throw new EQLException(
                            "Task #" + task_id + " already stopped");
            }
        }

        // Set COMPLETED status
        task.setStatus(StatusSType.COMPLETED);

        // Set next start to NULL
        task.setNextStart(null);

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task completed (ID = " + task_id + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
    }

    //
    // Set task status to INTERRUPTED
    //
    public void interruptTask(long task_id, boolean ignoreStatus)
            throws EQLException {

        long time = System.currentTimeMillis();

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        if(!ignoreStatus) {
            // Check task status
            int task_status = task.getStatus().getType();
            switch(task_status) {
                case StatusSType.NEW_TYPE:
                case StatusSType.COMPLETED_TYPE:
                case StatusSType.INTERRUPTED_TYPE:
                    throw new EQLException(
                            "Task #" + task_id + " already stopped");
            }
        }

        // Set INTERRUPTED status
        task.setStatus(StatusSType.INTERRUPTED);

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task interrupted (ID = " + task_id + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
    }

    //
    // Reanimate task and set status to READY
    //
    public void reanimateTask(long task_id)
            throws EQLException {

        long time = System.currentTimeMillis();

        Task task = getTask(task_id);
        if(task == null) {
            throw new NullPointerException("Task #" + task_id + " not found");
        }

        // Set READY status
        task.setStatus(StatusSType.READY);

        // Set next start
        long next_start = getNextStart(task);
        task.setNextStart(new Long(next_start));

        // Update Task VO.
        update(task);

        // Ok.
        if(getLogger().isInfoEnabled()) {
            INFO("Task renimated (ID = " + task_id + ").");
            INFO("Time (ms) - " + (System.currentTimeMillis() - time));
        }
    }

    // ----------------------------------------------------- private methods

    /**
     * Updates Task VO
     *
     * @param task Task
     */
    private void update(Task task) {
        // Update database.
        ServicesPropertyFactory.getInstance().getTaskDAO().updateTaskVO(task);
        // Update cache.
        updateCache(task);
    }

    /**
     * Calculate task next start
     *
     * @param task Task VO
     * @return millis
     */
    private long getNextStart(Task task) {

        long now = DateHelper.currentTimeMillis();
        long next_start;
        if(task.getFirstStart() != null) {
            next_start = task.getFirstStart().longValue();
        } else {
            next_start = now;
        }

        if(next_start < now) {
            // bring next start up
            if(task.getDelayunit() != null) {
                long timout = getTimeout(task);
                do {
                    next_start += timout;
                } while(next_start < now);
            } else {
                next_start = now;
            }
        }

        return next_start;
    }

    /**
     * Calculate timeout period for task
     *
     * @param task Task VO
     * @return timeout in ms or 0
     */
    private long getTimeout(Task task) {

        if(task.getDelayunit() == null) {
            throw new NullPointerException("Delay unit is NULL");
        }

        if(task.getDelay() == null) {
            throw new NullPointerException("Delay is NULL");
        }

        int delay = task.getDelay().intValue();
        int delayunit = task.getDelayunit().getType();

        switch(delayunit) {
            case DelayunitSType.MIN_TYPE:
                return 60 * 1000 * delay;
            case DelayunitSType.HOUR_TYPE:
                return 60 * 60 * 1000 * delay;
            case DelayunitSType.DAY_TYPE:
                return 24 * 60 * 60 * 1000 * delay;
            case DelayunitSType.MONTH_TYPE:
                return 30 * 24 * 60 * 60 * 1000 * delay;
            default:
                throw new GenericSystemException(
                        "Unsupported task delayunit type: " + delayunit);
        }
    }

} // end of class
