/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.ejb21;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

import javax.ejb.EJBException;
import javax.ejb.ScheduleExpression;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerHandle;
import javax.ejb.TimerService;
import javax.transaction.TransactionManager;

import org.ow2.jonas.lib.timer.TraceTimer;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * JOnAS Implementation of the TimerService interface (from EJB 2.1) One such
 * object is created the first time a bean calls getTimerService. Basically
 * manages the list of the Timers for that bean.
 * @author Philippe Durieux
 */
public class JTimerService implements TimerService {

    private ArrayList mytimers = new ArrayList();

    private JFactory bf = null;

    private JEntitySwitch es = null;

    private TransactionManager tm;

    private int timerCount = 0;

    private static final long MAX_DURATION = 5000000000L;

    /**
     * constructor used for MDB or Session beans
     */
    public JTimerService(JFactory bf) {
        this.bf = bf;
        tm = bf.getTransactionManager();
    }

    /**
     * constructor used for Entity beans
     */
    public JTimerService(JEntitySwitch es) {
        this.es = es;
        bf = es.getBeanFactory();
        tm = bf.getTransactionManager();
    }

    /**
     * @return the Transaction Manager
     */
    public TransactionManager getTransactionManager() {
        return tm;
    }

    /**
     * Notify the timer to the listener
     * @param timer The Timer object that will be notified
     */
    public void notify(Timer timer) {
        if (es != null) {
            // Entity Bean
            es.notifyTimeout(timer);
        } else {
            if (bf instanceof JMdbFactory) {
                ((JMdbFactory) bf).notifyTimeout(timer);
            } else if (bf instanceof JMdbEndpointFactory) {
                ((JMdbEndpointFactory) bf).notifyTimeout(timer);
            } else if (bf instanceof JStatelessFactory) {
                ((JStatelessFactory) bf).notifyTimeout(timer);
            } else {
                TraceEjb.logger.log(BasicLevel.ERROR, "Cannot notify this type of bean: " + bf);
                Thread.dumpStack();
            }
        }
    }

    /**
     * Remove the Timer
     * @param timer The Timer object that will be removed
     */
    public void remove(Timer timer) {
        synchronized (this) {
            int index = mytimers.lastIndexOf(timer);
            if (index == -1) {
                TraceTimer.logger.log(BasicLevel.WARN, "try to remove unexisting timer");
            } else {
                mytimers.remove(index);
            }
        }
    }

    /**
     * cancel all timers (when entity bean is removed)
     */
    public void cancelAllTimers() {
        synchronized (this) {
            es = null;
            for (Iterator i = mytimers.iterator(); i.hasNext(); ) {
                JTimer timer = (JTimer) i.next();
                timer.stopTimer();
            }
            mytimers.clear();
        }
    }

    /**
     * get a Timer from the list
     */
    public Timer getTimerByTime(long initialDuration, long intervalDuration, Serializable info) {
        Timer dummy = new JTimer(this, initialDuration, intervalDuration, info);
        synchronized (this) {
            for (Iterator i = mytimers.iterator(); i.hasNext(); ) {
                JTimer timer = (JTimer) i.next();
                if (timer.sameas(dummy)) {
                    return timer;
                }
            }
        }
        return null;
    }

    // ------------------------------------------------------------------------------------
    // TimerService implementation
    // ------------------------------------------------------------------------------------

    /**
     * Create an interval timer whose first expiration occurs at a given point
     * in time and whose subsequent expirations occur after a specified
     * interval.
     * @param initialExpiration The point in time at which the first timer
     *        expiration must occur.
     * @param intervalDuration The number of milliseconds that must elapse
     *        between timer expiration notifications.
     * @param info Application information to be delivered along with the timer
     *        expiration. This can be null.
     * @return the newly created Timer.
     * @throws IllegalArgumentException initialExpiration = 0, or
     *         intervalDuration < 0 or initialExpiration.getTime() < 0.
     * @throws IllegalStateException the instance is in a state that does not
     *         allow access to this method.
     * @throws EJBException If this method could not complete due to a
     *         system-level failure.
     */
    public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        if (initialExpiration == null) {
            throw new IllegalArgumentException("expiration date is null");
        }
        // avoids negative argument here (add a few milliseconds)
        long initialDuration = initialExpiration.getTime() - System.currentTimeMillis() + 20;
        return createTimer(initialDuration, intervalDuration, info);
    }

    /**
     * Create a single-action timer that expires at a given point in time.
     * @param expiration The point in time at which the timer expiration must
     *        occur.
     * @param info Application information to be delivered along with the timer
     *        expiration. This can be null.
     * @return the newly created Timer.
     * @throws IllegalArgumentException expiration = 0, or expiration.getTime() <
     *         0.
     * @throws IllegalStateException the instance is in a state that does not
     *         allow access to this method.
     * @throws EJBException If this method could not complete due to a
     *         system-level failure.
     */
    public Timer createTimer(Date expiration, Serializable info) throws IllegalArgumentException,
            IllegalStateException, EJBException {
        return createTimer(expiration, 0, info);
    }

    /**
     * Create an interval timer whose first expiration occurs after a specified
     * duration, and whose subsequent expirations occur after a specified
     * interval.
     * @param initialDuration The number of milliseconds that must elapse before
     *        the first timer expiration notification.
     * @param intervalDuration The number of milliseconds that must elapse
     *        between timer expiration notifications.
     * @param info Application information to be delivered along with the timer
     *        expiration. This can be null.
     * @return the newly created Timer.
     * @throws IllegalArgumentException initialExpiration = 0, or
     *         intervalDuration < 0.
     * @throws IllegalStateException the instance is in a state that does not
     *         allow access to this method.
     * @throws EJBException If this method could not complete due to a
     *         system-level failure.
     */
    public Timer createTimer(long initialDuration, long intervalDuration, Serializable info)
        throws IllegalArgumentException, IllegalStateException, EJBException {

        // Check that we are not called from entity ejbCreate
        if (es != null) {
            es.getPrimaryKey(); // can throw IllegalStateException
        }

        // Check duration positive
        if (initialDuration < 0 || intervalDuration < 0) {
            throw new IllegalArgumentException("duration is negative");
        }
        // Check duration not too long
        if (initialDuration > MAX_DURATION) {
            TraceTimer.logger.log(BasicLevel.DEBUG, "Too high duration: " + initialDuration);
        }
        if (intervalDuration > MAX_DURATION) {
            TraceTimer.logger.log(BasicLevel.DEBUG, "Too high duration: " + intervalDuration);
        }

        JTimer timer = new JTimer(this, initialDuration, intervalDuration, info);
        File pdir = bf.getPassivationDir();

        // Allocate the Timer Id. Must not reallocate an Id already used.
        int tid;
        File timerfile = null;
        synchronized (this) {
            while (true) {
                tid = timerCount++;
                timerfile = new File(pdir, String.valueOf(tid) + ".tim");
                if (! timerfile.exists()) {
                    break;
                }
            }
        }

        if (mytimers.add(timer)) {
            timer.startTimer();
        } else {
            TraceTimer.logger.log(BasicLevel.WARN, "create a timer already known");
        }

        // Build a TimerHandle and serialized it on storage in case of server restart.
        TimerHandle th = timer.getHandle();
        ObjectOutputStream oos = null;
        FileOutputStream fos = null;
        try {
            TraceTimer.logger.log(BasicLevel.DEBUG, "Writing Timer on disk");
            fos = new FileOutputStream(timerfile);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(th);
            timer.setFile(timerfile);
        } catch (IOException e) {
            TraceTimer.logger.log(BasicLevel.ERROR, "Cannot write Timer on storage");
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (oos != null) {
                    oos.close();
                }
            } catch (Exception e) {
                TraceTimer.logger.log(BasicLevel.WARN, "Cannot close OutputStream:" + e);
            }
        }
        return timer;
    }


    /**
     * Create a single-action timer that expires after a specified duration.
     * @param duration The number of milliseconds that must elapse before the
     *        timer expires.
     * @param info Application information to be delivered along with the timer
     *        expiration. This can be null.
     * @return the newly created Timer.
     * @throws IllegalArgumentException initialExpiration = 0, or
     *         intervalDuration < 0.
     * @throws IllegalStateException the instance is in a state that does not
     *         allow access to this method.
     * @throws EJBException If this method could not complete due to a
     *         system-level failure.
     */
    public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException, IllegalStateException,
            EJBException {
        return createTimer(duration, 0, info);
    }

    /**
     * Get all the active timers associated with this bean.
     * @return A collection of javax.ejb.Timer objects.
     * @throws IllegalStateException the instance is in a state that does not
     *         allow access to this method.
     * @throws EJBException If this method could not complete due to a
     *         system-level failure.
     */
    public Collection getTimers() throws IllegalStateException, EJBException {
        // Check that we are not called from entity ejbCreate
        if (es != null) {
            es.getPrimaryKey(); // can throw IllegalStateException
        }
        ArrayList ret = new ArrayList();
        for (Iterator i = mytimers.iterator(); i.hasNext(); ) {
            JTimer t = (JTimer) i.next();
            if (! t.isCancelled()) {
                ret.add(t);
            }
        }
        return ret;
    }

    @Override
    public Timer createSingleActionTimer(long duration, TimerConfig timerConfig)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    @Override
    public Timer createSingleActionTimer(Date expiration, TimerConfig timerConfig)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    @Override
    public Timer createIntervalTimer(long initialDuration, long intervalDuration, TimerConfig timerConfig)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    @Override
    public Timer createIntervalTimer(Date initialExpiration, long intervalDuration, TimerConfig timerConfig)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    @Override
    public Timer createCalendarTimer(ScheduleExpression schedule) throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    @Override
    public Timer createCalendarTimer(ScheduleExpression schedule, TimerConfig timerConfig)
            throws IllegalArgumentException, IllegalStateException, EJBException {
        throw new UnsupportedOperationException("EJBs 2.1 do not support this operation.");
    }

    /**
     * @return the EjbName used to retrieve the bean factory
     */
    public String getEjbName() {
        if (bf != null) {
            return bf.getEJBName();
        }
        if (es != null) {
            return es.getBeanFactory().getEJBName();
        }
        return null;
    }

    /**
     * @return the encoded PK for entity bean timers
     */
    public Serializable getPK() {
        if (es != null) {
            JEntityFactory ef = (JEntityFactory) es.getBeanFactory();
            Serializable pks = (Serializable) es.getPrimaryKey();
            Serializable pk = (Serializable) ef.encodePK(pks);
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "pk = " + pks);
                TraceEjb.interp.log(BasicLevel.DEBUG, "encoded pk = " + pk);
            }
            return pk;
        } else {
            return null;
        }
    }

    /**
     * @return the Container File Name
     */
    public String getContainer() {
        JContainer cont = bf.getContainer();
        return cont.getExternalFileName();
    }
}
