/*
 *  Copyright 2004-2006 Stefan Reuter
 *
 *  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.asteriskjava.fastagi;

import org.asteriskjava.util.Log;
import org.asteriskjava.util.LogFactory;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Runs an AgiServer in a separate Thread.
 * <p>
 * You can use this class to run an AgiServer in the background of your
 * application or run it in your servlet container or application server.
 * <p>
 * By default the thread used by this class is marked as daemon thread, that
 * means it will be destroyed when the last user thread has finished.
 *
 * @author srt
 * @version $Id$
 * @since 0.2
 */
public class AgiServerThread {
    private final Log logger = LogFactory.getLog(getClass());
    private static AtomicLong idCounter = new AtomicLong();
    private AgiServer agiServer;
    private Thread thread;
    private boolean daemon = true;

    /**
     * Creates a new AgiServerThread.
     * <p>
     * Before you can run this thread you must set an {@link AgiServer} using
     * {@link #setAgiServer(AgiServer)}.
     * <p>
     * This constructor is mainly intended for use with setter based dependency
     * injection.
     */
    public AgiServerThread() {
        super();
    }

    /**
     * Creates a new AgiServerThread that runs the given {@link AgiServer}.
     *
     * @param agiServer the AgiServer to run.
     */
    public AgiServerThread(AgiServer agiServer) {
        super();
        this.agiServer = agiServer;
    }

    /**
     * Sets the AgiServer to run.
     * <p>
     * This property must be set before starting the AgiServerThread by calling
     * startup.
     *
     * @param agiServer the AgiServer to run.
     */
    public synchronized void setAgiServer(AgiServer agiServer) {
        this.agiServer = agiServer;
    }

    /**
     * Marks the thread as either a daemon thread or a user thread.
     * <p>
     * Default is <code>true</code>.
     *
     * @param daemon if <code>false</code>, marks the thread as a user
     *               thread.
     * @see Thread#setDaemon(boolean)
     * @since 0.3
     */
    public void setDaemon(boolean daemon) {
        this.daemon = daemon;
    }

    /**
     * Starts the AgiServer in its own thread.
     * <p>
     * Note: The AgiServerThread is designed to handle one AgiServer instance at
     * a time so calling this method twice without stopping the AgiServer in
     * between will result in a RuntimeException.
     *
     * @throws IllegalStateException if the mandatory property agiServer has not
     *                               been set or the AgiServer had already been started.
     * @throws RuntimeException      if the AgiServer can't be started due to IO
     *                               problems, for example because the socket has already been
     *                               bound by another process.
     */
    public synchronized void startup() throws IllegalStateException, RuntimeException {
        if (agiServer == null) {
            throw new IllegalStateException("Mandatory property agiServer is not set.");
        }

        if (thread != null) {
            throw new IllegalStateException("AgiServer is already started");
        }

        thread = createThread();
        thread.start();
    }

    protected Thread createThread() {
        Thread t;

        t = new Thread(new Runnable() {
            public void run() {
                try {
                    agiServer.startup();
                } catch (Throwable e) {
                    throw new RuntimeException("Exception running AgiServer.", e);
                }
            }
        });
        t.setName("Asterisk-Java AgiServer-" + idCounter.getAndIncrement());
        t.setDaemon(daemon);
        t.setUncaughtExceptionHandler(new AgiThreadUncaughtExceptionHanlder());

        return t;
    }

    /**
     * Stops the {@link AgiServer}.
     * <p>
     * The AgiServer must have been started by calling {@link #startup()} before
     * you can stop it.
     *
     * @throws IllegalStateException if the mandatory property agiServer has not
     *                               been set or the AgiServer had already been shut down.
     * @see AgiServer#shutdown()
     */
    public synchronized void shutdown() throws IllegalStateException {
        if (agiServer == null) {
            throw new IllegalStateException("Mandatory property agiServer is not set.");
        }

        agiServer.shutdown();

        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                logger.warn("Interrupted while waiting for AgiServer to shutdown.");
                Thread.currentThread().interrupt();
            }
            thread = null; // NOPMD by srt on 7/5/06 11:23 PM
        }
    }

    class AgiThreadUncaughtExceptionHanlder implements UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            logger.error("Uncaught exception in AgiServerThread", e);
        }
    }
}
