/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2001 - 2025 ScalAgent Distributed Technologies
 * Copyright (C) 1996 - 2000 Dyade
 *
 * 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.
 *
 * Initial developer(s): ScalAgent Distributed Technologies
 */
package org.ow2.joram.jakarta.jms;

import jakarta.jms.JMSException;
import jakarta.jms.MessageListener;
import jakarta.jms.ServerSession;
import jakarta.jms.ServerSessionPool;

import org.objectweb.joram.shared.client.ConsumerMessages;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.joram.jakarta.jms.connection.RequestMultiplexer;

import fr.dyade.aaa.common.Daemon;
import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.common.Queue;

/**
 * The MultiSessionConsumer is threaded (see MessageDispatcher) because the session pool
 * can hang if there is no more available ServerSession.
 */
public final class MultiSessionConsumer extends MessageConsumerListener implements jakarta.jms.ConnectionConsumer{
  private static final Logger logger = Debug.getLogger(MultiSessionConsumer.class.getName());
  private static final boolean DEBUG = logger.isLoggable(BasicLevel.DEBUG);
  
  private ServerSessionPool serverSessionPool;
  
  private Connection cnx;
  
  private int maxMsgs;
  
  private Queue repliesIn;
  
  /**
   * Number of simultaneously activated
   * listeners.
   */
  private int nbActivatedListeners;
  
  private MessageDispatcher msgDispatcher;

  /**
   * @param consumer
   * @param listener
   * @param ackMode
   * @param queueMessageReadMax
   * @param topicActivationThreshold
   * @param topicPassivationThreshold
   * @param topicAckBufferMax
   * @param reqMultiplexer
   */
  MultiSessionConsumer(
      boolean queueMode,
      boolean durable,
      String selector,
      String destName,
      String targetName,
      ServerSessionPool sessionPool,
      int queueMessageReadMax, 
      int topicActivationThreshold, int topicPassivationThreshold, 
      int topicAckBufferMax, 
      RequestMultiplexer reqMultiplexer,
      Connection connection,
      int maxMessages) {
    super(queueMode, durable, selector, destName, targetName, 
        null, queueMessageReadMax,
        topicActivationThreshold,
        topicPassivationThreshold, topicAckBufferMax,
        reqMultiplexer);
    if (DEBUG)
      logger.log(BasicLevel.DEBUG,
                 "MultiSessionConsumer.<init>(" + queueMode + ',' + durable + ',' + selector + ',' +  destName + ',' + targetName + ',' + sessionPool + ',' + queueMessageReadMax + ',' +
                 topicActivationThreshold + ',' + topicPassivationThreshold + ',' + topicAckBufferMax + ',' + reqMultiplexer + ',' + maxMessages + ')');
    serverSessionPool = sessionPool;
    cnx = connection;
    maxMsgs = maxMessages;
    if (maxMsgs < 1) maxMsgs = 1;
    msgDispatcher = new MessageDispatcher("MessageDispatcher[" + reqMultiplexer.getDemultiplexerDaemonName() + ']');
    repliesIn = new Queue();
    msgDispatcher.setDaemon(true);
    msgDispatcher.start();
  }

  /* (non-Javadoc)
   * @see org.ow2.joram.jakarta.jms.MessageConsumerListener#pushMessages(org.objectweb.joram.shared.client.ConsumerMessages)
   */
  public void pushMessages(ConsumerMessages cm) throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer.pushMessages(" + cm + ')');
    repliesIn.push(cm);
  }  

  /* (non-Javadoc)
   * @see jakarta.jms.ConnectionConsumer#getServerSessionPool()
   */
  public ServerSessionPool getServerSessionPool() throws JMSException {
    return serverSessionPool;
  }
  
  public void close() throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer.close()");
    
    msgDispatcher.stop();
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer -> dispatcher stopped");
    
    super.close();
    
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer -> close connection consumer");
    cnx.closeConnectionConsumer(this);
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer -> connection consumer closed");
  }
  
  public void onMessage(Message msg, MessageListener listener, int ackMode) throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "MultiSessionConsumer.onMessage(" + msg + ')');
    try {
      synchronized (this) {
        if (getStatus() == Status.CLOSE)
          throw new jakarta.jms.IllegalStateException("Message listener closed");

        if (nbActivatedListeners == 0) {
          setStatus(Status.ON_MSG);
        }
        nbActivatedListeners++;
      }
      activateListener(msg, listener, ackMode);
    } finally {
      synchronized (this) {
        nbActivatedListeners--;
        if (nbActivatedListeners == 0) {
          setStatus(Status.RUN);
          // Notify threads trying to close the 
          // MessageConsumerListener.
          notifyAll();
        }        
      }
    }
  }
  
  class MessageDispatcher extends Daemon {
    
    MessageDispatcher(String name) {
      super(name, logger);
    }

    /* (non-Javadoc)
     * @see fr.dyade.aaa.util.Daemon#close()
     */
    protected void close() {
      // TODO Auto-generated method stub
      
    }

    /* (non-Javadoc)
     * @see fr.dyade.aaa.util.Daemon#shutdown()
     */
    protected void shutdown() {
      // TODO Auto-generated method stub
      
    }
    
    /**
     * Enables the daemon to stop itself.
     */
    public void stop() {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, "MessageDispatcher.stop()");
      if (isCurrentThread()) { 
        finish();
      } else {
        super.stop();
      }
    }

    private Session getSession(ServerSession serverSession) throws JMSException {
      Session session = null;
      jakarta.jms.Session obj = serverSession.getSession();
      if (obj instanceof Session) {
        session = (Session) obj;
      } else if (obj instanceof XASession) {
        session = ((XASession) obj).sess;
      } else {
        throw new Error("Unexpected session type: " + obj);
      }
      return session;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    public void run() {
      try {
        while (running) {
          canStop = true;
          ConsumerMessages cm = null;
          try {
            cm = (ConsumerMessages) repliesIn.get();
          } catch (InterruptedException exc) {}
          if (! running) break;
          if ((cm == null) || cm.getMessages().isEmpty())
            continue;
          canStop = false;
          
          ServerSession serverSession = serverSessionPool.getServerSession();
          Session session = getSession(serverSession);
          session.setMessageConsumerListener(MultiSessionConsumer.this);
          int sessionMsgCounter = 0;
          
          for (org.objectweb.joram.shared.messages.Message msg : cm.getMessages()) {
            if (sessionMsgCounter >= maxMsgs) {
              // Starts the previous ServerSession..
              serverSession.start();
              // .. then gets a new one.
              serverSession = serverSessionPool.getServerSession();
              session = getSession(serverSession);
              session.setMessageConsumerListener(MultiSessionConsumer.this);
              sessionMsgCounter = 0;
            }
            session.onMessage(msg);
            sessionMsgCounter++;
          }
          // Starts the last ServerSession
          serverSession.start();
          // Removes the handled messages
          repliesIn.pop();
        }
      } catch (JMSException exc) {
        if (DEBUG)
          logger.log(BasicLevel.WARN, "MultiSessionConsumer exits", exc);
        else
          logger.log(BasicLevel.WARN, "MultiSessionConsumer exits: " + exc.getMessage());
      } finally {
        try {
          MultiSessionConsumer.this.close();
        } catch (JMSException exc2) {}
        finish();
      }
    } 
  }

  @Override
  protected boolean checkSessionThread() {
    return false;
  }
}
