/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * 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.codehaus.activemq.service.impl;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.broker.BrokerClient;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.service.MessageContainerManager;
import org.codehaus.activemq.service.Service;
import org.codehaus.activemq.service.Subscription;

import javax.jms.JMSException;
import java.util.Map;
import java.util.Iterator;

/**
 * A Dispatcher that polls for updates for active Message Consumers
 *
 * @version $Revision: 1.1 $
 */
public class DispatchWorker implements Runnable, Service {
    private static final Log log = LogFactory.getLog(DispatchWorker.class);
    private static final int POLL_TIMEOUT = 250;

    private Map subscriptions = new ConcurrentHashMap(1000, 0.75f);
    private Object lock = new Object();
    private boolean active = true;
    private boolean started = false;
    private MessageContainerManager containerManager;

    /**
     * Register the MessageContainerManager for the Dispatcher
     *
     * @param mcm
     */
    public void register(MessageContainerManager mcm) {
        this.containerManager = mcm;
    }

    /**
     * Called to indicate that there is work to do on a Subscription this will wake up a Dispatch Worker if it is
     * waiting for messages to dispatch
     */
    public void wakeup() {
        synchronized (lock) {
            active = true;
            lock.notifyAll();
        }
    }

    /**
     * Add an active subscription
     *
     * @param client
     * @param sub
     */
    public void addActiveSubscription(BrokerClient client, Subscription sub) {
        if (log.isDebugEnabled()) {
            log.info("Adding subscription: " + sub + " to client: " + client);
        }
        subscriptions.put(sub, client);
    }

    /**
     * remove an active subscription
     *
     * @param client
     * @param sub
     */
    public void removeActiveSubscription(BrokerClient client, Subscription sub) {
        if (log.isDebugEnabled()) {
            log.info("Removing subscription: " + sub + " from client: " + client);
        }
        subscriptions.remove(sub);
    }

    /**
     * dispatch messages to active Consumers
     *
     * @see java.lang.Runnable#run()
     */
    public void run() {
        while (started) {
            doPoll();
            boolean dispatched = false;
            try {
                // our collection will not throw concurrent modification exception
                for (Iterator iter = subscriptions.keySet().iterator(); iter.hasNext();) {
                    Subscription sub = (Subscription) iter.next();
                    if (sub != null && sub.isReadyToDispatch()) {
                        dispatched = dispatchMessages(sub, dispatched);
                    }
                }
            }
            catch (JMSException jmsEx) {
                log.error("Could not dispatch to Subscription: " + jmsEx, jmsEx);
            }
            if (!dispatched) {
                synchronized (lock) {
                    active = false;
                    if (!active && started) {
                        try {
                            lock.wait(POLL_TIMEOUT);
                        }
                        catch (InterruptedException e) {
                        }
                    }
                }
            }
        }
    }


    /**
     * start the DispatchWorker
     *
     * @see org.codehaus.activemq.service.Service#start()
     */
    public void start() {
        started = true;
    }

    /**
     * stop the DispatchWorker
     *
     * @see org.codehaus.activemq.service.Service#stop()
     */
    public void stop() {
        started = false;
    }


    // Implementation methods
    //-------------------------------------------------------------------------

    protected boolean dispatchMessages(Subscription subscription, boolean dispatched) throws JMSException {
        ActiveMQMessage[] msgs = subscription.getMessagesToDispatch();
        if (msgs != null && msgs.length > 0) {
            BrokerClient client = (BrokerClient) subscriptions.get(subscription);
            if (client == null) {
                log.warn("Null client for subscription: " + subscription);
            }
            else {
                for (int i = 0; i < msgs.length; i++) {
                    ActiveMQMessage msg = msgs[i].shallowCopy();

                    if (log.isDebugEnabled()) {
                        log.debug("Dispatching message: " + msg);
                    }
                    int[] consumerNos = new int[1];
                    consumerNos[0] = subscription.getConsumerNumber();
                    msg.setConsumerNos(consumerNos);
                    client.dispatch(msg);
                    dispatched = true;
                }
            }
        }
        return dispatched;
    }

    protected void doPoll() {
        if (containerManager != null && started) {
            try {
                containerManager.poll();
            }
            catch (JMSException e) {
                log.error("Error polling from the ContainerManager: ", e);
            }
        }
    }
}
