/** 
 * 
 * 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.activemq.service.boundedvm;

import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import javax.jms.JMSException;
import org.activemq.broker.BrokerClient;
import org.activemq.filter.AndFilter;
import org.activemq.filter.DestinationMap;
import org.activemq.filter.Filter;
import org.activemq.filter.FilterFactory;
import org.activemq.filter.FilterFactoryImpl;
import org.activemq.filter.NoLocalFilter;
import org.activemq.io.util.MemoryBoundedQueue;
import org.activemq.io.util.MemoryBoundedQueueManager;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ConsumerInfo;
import org.activemq.message.MessageAck;
import org.activemq.service.DeadLetterPolicy;
import org.activemq.service.MessageContainer;
import org.activemq.service.MessageContainerManager;
import org.activemq.service.TransactionManager;
import org.activemq.service.TransactionTask;
import org.activemq.service.impl.AutoCommitTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;

/**
 * A MessageContainerManager for transient topics
 * 
 * @version $Revision: 1.1.1.1 $
 */

/**
 * A manager of MessageContainer instances
 */
public class TransientTopicBoundedMessageManager implements MessageContainerManager {
    private static final Log log = LogFactory.getLog(TransientTopicBoundedMessageManager.class);
    private MemoryBoundedQueueManager queueManager;
    private ConcurrentHashMap containers;
    private ConcurrentHashMap subscriptions;
    private DestinationMap destinationMap;
    private FilterFactory filterFactory;
    private SynchronizedBoolean started;
    private Map destinations;
    private DeadLetterPolicy deadLetterPolicy;
    private boolean decoupledDispatch = false;

    /**
     * Constructor for TransientTopicBoundedMessageManager
     *
     * @param mgr
     */
    public TransientTopicBoundedMessageManager(MemoryBoundedQueueManager mgr) {
        this.queueManager = mgr;
        this.containers = new ConcurrentHashMap();
        this.subscriptions = new ConcurrentHashMap();
        this.destinationMap = new DestinationMap();
        this.destinations = new ConcurrentHashMap();
        this.filterFactory = new FilterFactoryImpl();
        this.started = new SynchronizedBoolean(false);
    }

    /**
     * start the manager
     *
     * @throws JMSException
     */
    public void start() throws JMSException {
        if (started.commit(false, true)) {
            for (Iterator i = containers.values().iterator(); i.hasNext();) {
                TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) i.next();
                container.start();
            }
        }
    }

    /**
     * stop the manager
     *
     * @throws JMSException
     */
    public void stop() throws JMSException {
        if (started.commit(true, false)) {
            for (Iterator i = containers.values().iterator(); i.hasNext();) {
                TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) i.next();
                container.stop();
            }
        }
    }

    /**
     * Add a consumer if appropiate
     *
     * @param client
     * @param info
     * @throws JMSException
     */
    public synchronized void addMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
        ActiveMQDestination destination = info.getDestination();
        if (destination.isTopic()) {
            TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) containers
                    .get(client);
            if (container == null) {
                MemoryBoundedQueue queue = queueManager.getMemoryBoundedQueue(client.toString());
                container = new TransientTopicBoundedMessageContainer(this, client, queue);
                containers.put(client, container);
                if (started.get()) {
                    container.start();
                }
            }
            if (log.isDebugEnabled()) {
                log.debug("Adding consumer: " + info);
            }

            TransientTopicSubscription ts = container.addConsumer(createFilter(info), info);
            if (ts != null) {
                subscriptions.put(info.getConsumerId(), ts);
            }

            destinationMap.put(destination,container);
            String name = destination.getPhysicalName();
            //As the destinations are used for generating
            //subscriptions for NetworkConnectors etc.,
            //we should not generate duplicates by adding in 
            //durable topic subscribers
            if (!info.isDurableTopic() && !destinations.containsKey(name)) {
                destinations.put(name, destination);
            }
        }
    }

    /**
     * @param client
     * @param info
     * @throws JMSException
     */
    public synchronized void removeMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
        ActiveMQDestination destination = info.getDestination();
        if (destination.isTopic()) {
            TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) containers
                    .get(client);
            if (container != null) {
                container.removeConsumer(info);
                if (container.isInactive()) {
                    containers.remove(client);
                    container.close();
                    destinationMap.remove(destination, container);
                }

                // lets check if we've no more consumers for this destination
                //As the destinations are used for generating
                //subscriptions for NetworkConnectors etc.,
                //we should not count durable topic subscribers
                if (!info.isDurableTopic() && !hasConsumerFor(destination)) {
                    destinations.remove(destination.getPhysicalName());
                }
            }
            subscriptions.remove(info.getConsumerId());
        }
    }

    /**
     * Delete a durable subscriber
     *
     * @param clientId
     * @param subscriberName
     * @throws JMSException if the subscriber doesn't exist or is still active
     */
    public void deleteSubscription(String clientId, String subscriberName) throws JMSException {
    }

    /**
     * @param client
     * @param message
     * @throws JMSException
     */
    public void sendMessage(final BrokerClient client, final ActiveMQMessage message) throws JMSException {
        if (TransactionManager.getContexTransaction()==AutoCommitTransaction.AUTO_COMMIT_TRANSACTION){
            doSendMessage(client, message);
        }else {
            // If there is no transaction.. then this executes directly.
            TransactionManager.getContexTransaction().addPostCommitTask(new TransactionTask() {
                public void execute() throws Throwable {
                    doSendMessage(client, message);
                }
            });
        }
    }

    /**
     * @param client
     * @param message
     * @throws JMSException
     */
    private void doSendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
        Set set = destinationMap.get(message.getJMSActiveMQDestination());
        if (!set.isEmpty()){
            for (Iterator i = set.iterator(); i.hasNext();) {
                TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) i.next();
                container.targetAndDispatch(client,message);
            }       
        }
    }

    /**
     * @param client
     * @param ack
     * @throws JMSException
     * 
     */
    public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
    }
    
    /**
     * @throws JMSException
     * 
     */

    public void poll() throws JMSException {
    }

    /**
     * For Transient topics - a MessageContainer maps on to the messages
     * to be dispatched through a BrokerClient, not a destination
     * @param physicalName
     * @return the MessageContainer used for dispatching - always returns null
     * @throws JMSException
     */
    public MessageContainer getContainer(String physicalName) throws JMSException {
        return null; 
    }

    /**
     * @return a map of all the destinations
     */
    public Map getDestinations() {
        return Collections.unmodifiableMap(destinations);
    }

    /**
     * Returns an unmodifiable map, indexed by String name, of all the {@link javax.jms.Destination}
     * objects used by non-broker consumers directly connected to this container
     *
     * @return
     */
    public Map getLocalDestinations() {
        Map localDestinations = new HashMap();
        for (Iterator iter = subscriptions.values().iterator(); iter.hasNext();) {
            TransientTopicSubscription sub = (TransientTopicSubscription) iter.next();
            if (sub.isLocalSubscription() && !sub.isDurableTopic()) {
                final ActiveMQDestination dest = sub.getDestination();
                localDestinations.put(dest.getPhysicalName(), dest);
            }
        }
        return Collections.unmodifiableMap(localDestinations);
    }
    
    /**
     * @return the DeadLetterPolicy for this Container Manager
     */
    public DeadLetterPolicy getDeadLetterPolicy(){
        return deadLetterPolicy;
    }
    
    /**
     * Set the DeadLetterPolicy for this Container Manager
     * @param policy
     */
    public void setDeadLetterPolicy(DeadLetterPolicy policy){
        this.deadLetterPolicy = policy;
    }

    /**
     * @return Returns the decoupledDispatch.
     */
    public boolean isDecoupledDispatch() {
        return decoupledDispatch;
    }
    /**
     * @param decoupledDispatch The decoupledDispatch to set.
     */
    public void setDecoupledDispatch(boolean decoupledDispatch) {
        this.decoupledDispatch = decoupledDispatch;
    }
    /**
     * Create filter for a Consumer
     *
     * @param info
     * @return the Fitler
     * @throws javax.jms.JMSException
     */
    protected Filter createFilter(ConsumerInfo info) throws JMSException {
        Filter filter = filterFactory.createFilter(info.getDestination(), info.getSelector());
        if (info.isNoLocal()) {
            filter = new AndFilter(filter, new NoLocalFilter(info.getClientId()));
        }
        return filter;
    }

    protected boolean hasConsumerFor(ActiveMQDestination destination) {
        for (Iterator i = containers.values().iterator(); i.hasNext();) {
            TransientTopicBoundedMessageContainer container = (TransientTopicBoundedMessageContainer) i.next();
            if (container.hasConsumerFor(destination)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @see org.activemq.service.MessageContainerManager#createMessageContainer(org.activemq.message.ActiveMQDestination)
     */
    public void createMessageContainer(ActiveMQDestination dest) throws JMSException {
    }
    
    /**
     * @see org.activemq.service.MessageContainerManager#destroyMessageContainer(org.activemq.message.ActiveMQDestination)
     */
    public void destroyMessageContainer(ActiveMQDestination dest) throws JMSException {
        containers.remove(dest);
        destinationMap.removeAll(dest);
    }
    
    /**
     * @see org.activemq.service.MessageContainerManager#getMessageContainerAdmins()
     */
    public Map getMessageContainerAdmins() throws JMSException {
        return Collections.EMPTY_MAP;
    }
    
}