package org.activemq.ws.notification.impl;

import java.io.IOException;
import java.util.Calendar;

import javax.jms.JMSException;
import javax.jms.Topic;

import org.activemq.ActiveMQConnection;
import org.activemq.ActiveMQConnectionFactory;
import org.activemq.message.ActiveMQTopic;
import org.activemq.ws.notification.NotificationBroker;
import org.activemq.ws.notification.NotificationConsumer;
import org.activemq.ws.notification.PublisherRegistrationManager;
import org.activemq.ws.notification.SubscriptionManager;
import org.activemq.ws.xmlbeans.addressing.v2003_03.EndpointReferenceType;
import org.activemq.ws.xmlbeans.notification.base.GetCurrentMessageDocument;
import org.activemq.ws.xmlbeans.notification.base.GetCurrentMessageResponseDocument;
import org.activemq.ws.xmlbeans.notification.base.NotificationMessageHolderType;
import org.activemq.ws.xmlbeans.notification.base.NotifyDocument;
import org.activemq.ws.xmlbeans.notification.base.SubscribeDocument;
import org.activemq.ws.xmlbeans.notification.base.SubscribeResponseDocument;
import org.activemq.ws.xmlbeans.notification.base.NotifyDocument.Notify;
import org.activemq.ws.xmlbeans.notification.base.SubscribeDocument.Subscribe;
import org.activemq.ws.xmlbeans.notification.brokered.NotificationBrokerRPDocument;
import org.activemq.ws.xmlbeans.notification.brokered.RegisterPublisherDocument;
import org.activemq.ws.xmlbeans.notification.brokered.RegisterPublisherResponseDocument;
import org.activemq.ws.xmlbeans.notification.brokered.NotificationBrokerRPDocument.NotificationBrokerRP;
import org.activemq.ws.xmlbeans.notification.brokered.RegisterPublisherDocument.RegisterPublisher;
import org.activemq.ws.xmlbeans.notification.brokered.RegisterPublisherResponseDocument.RegisterPublisherResponse;
import org.activemq.ws.xmlbeans.resource.properties.GetResourcePropertyDocument;
import org.activemq.ws.xmlbeans.resource.properties.GetResourcePropertyResponseDocument;

public class ActiveMQNotificationBroker implements NotificationBroker {
    
    private ActiveMQPublisherRegistrationManager publisherManager = new ActiveMQPublisherRegistrationManager();
    private ActiveMQSubscriptionManager subscriptionManager = new ActiveMQSubscriptionManager();
    
    private ActiveMQConnectionFactory factory;
    private ActiveMQConnection connection;
    private ActiveMQPublisherRegistration anonymousPublisher;
    private NotificationBrokerRP resourceProperties;
    private XmlObjectResourceProperties xmlResourceProperties;
    
    
    public ActiveMQNotificationBroker() throws JMSException {
        this("vm://localhost");
    }

    public ActiveMQNotificationBroker(String url) throws JMSException {
        this(new ActiveMQConnectionFactory(url));
    }

    public ActiveMQNotificationBroker(ActiveMQConnectionFactory factory) throws JMSException {
        this.factory = factory;
        connection = (ActiveMQConnection) factory.createConnection();
        connection.start();
        
        anonymousPublisher = new ActiveMQPublisherRegistration(connection);
        NotificationBrokerRPDocument document = NotificationBrokerRPDocument.Factory.newInstance();
        resourceProperties = document.addNewNotificationBrokerRP();
        resourceProperties.setFixedTopicSet(false);
        resourceProperties.setRequiresRegistration(false);
        resourceProperties.setTopicExpressionDialectsArray(new String[]{"ActiveMQ"});
        xmlResourceProperties = new XmlObjectResourceProperties(resourceProperties);
    }
    
    public ActiveMQConnection getConnection() {
        return connection;
    }
    

    public RegisterPublisherResponseDocument registerPublisher(RegisterPublisherDocument requestDoc) {

        // Check request.
        RegisterPublisher publisherInfo = requestDoc.getRegisterPublisher();
        ActiveMQTopic topic = TopicExpressionConverter.toActiveMQTopic(publisherInfo.getTopicArray());
        Calendar terminationTime = publisherInfo.getInitialTerminationTime();
        if( terminationTime!=null ) {
            // Termination time cannot be in the past
            if( terminationTime.before( Calendar.getInstance() ) ) {
                // Is this the right way to fault??
                throw new RuntimeException("Termination time cannot be in the past.");
            }            
            // We could fault here if the time is too far in the future.
        } else {
            // We could default a sensible timeout here.
        }
        
        // Create publisher and assoicate an EndpointReference with it.
        EndpointReferenceType registrationEndpointReference;
        try {
            
            ActiveMQPublisherRegistration publisher = new ActiveMQPublisherRegistration(connection);
            registrationEndpointReference = publisherManager.register(publisher);
            publisher.setEndpointReference(registrationEndpointReference);
            publisher.setTerminationTime(terminationTime);
            publisher.setDemand(publisherInfo.getDemand());
            
            if( publisherInfo.getDemand() ) {
                publisher.setPublisherReference(publisherInfo.getPublisherReference());            
            }

            publisher.setTopic( topic );
            publisher.start();
            
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
        
        // Send response.
        RegisterPublisherResponseDocument responseDoc = RegisterPublisherResponseDocument.Factory.newInstance();
        RegisterPublisherResponse response = responseDoc.addNewRegisterPublisherResponse();        
        response.setPublisherRegistrationReference( registrationEndpointReference );        
        return responseDoc;
    }

    public void notify(NotifyDocument requestDoc) {
        try {
            Notify notify = requestDoc.getNotify();
            NotificationMessageHolderType[] messageHolders = notify.getNotificationMessageArray();
            for (int i = 0; i < messageHolders.length; i++) {
                NotificationMessageHolderType messageHolder = messageHolders[i];
                Topic topic = TopicExpressionConverter.toActiveMQTopic(messageHolder.getTopic());            
                EndpointReferenceType producerReference = messageHolder.getProducerReference();
                if( producerReference != null ) {
                    ActiveMQPublisherRegistration publisher = publisherManager.getProducer(producerReference);
                    publisher.notify( topic, messageHolder.getMessage() );
                } else {
                    anonymousPublisher.notify( topic, messageHolder.getMessage() );
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
    }

    public SubscribeResponseDocument Subscribe(SubscribeDocument requestDoc) {
        Subscribe subscribe = requestDoc.getSubscribe();
        
        Calendar terminationTime = subscribe.getInitialTerminationTime();
        if( terminationTime!=null ) {
            // Termination time cannot be in the past
            if( terminationTime.before( Calendar.getInstance() ) ) {
                // Is this the right way to fault??
                throw new RuntimeException("Termination time cannot be in the past.");
            }            
            // We could fault here if the time is too far in the future.
        } else {
            // We could default a sensible timeout here.
        }

        ActiveMQSubscription subscription;
        try {
            
            subscription = new ActiveMQSubscription(connection);
            subscription.setTopicExpression(subscribe.getTopicExpression());
            subscription.setConsumerReference(subscribe.getConsumerReference());
            subscription.setPrecondition(subscribe.getPrecondition());
            subscription.setSelector(subscribe.getSelector());
            subscription.setSubscriptionPolicy(subscribe.getSubscriptionPolicy());
            subscription.setTopicExpression(subscribe.getTopicExpression());
            subscription.setUseNotify(subscribe.getUseNotify());
            subscription.setConsumer( createNotificationConsumer(subscribe.getConsumerReference()) );
            
            subscription.start();
            
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
        
        EndpointReferenceType endpointReference = subscriptionManager.register( subscription );
        subscription.setEndpointReference(endpointReference);
        
        // Send response.
        SubscribeResponseDocument responseDoc = SubscribeResponseDocument.Factory.newInstance();
        responseDoc.addNewSubscribeResponse().setSubscriptionReference(endpointReference);
        return responseDoc;
    }

    protected NotificationConsumer createNotificationConsumer(final EndpointReferenceType consumerReference) {
        return new NotificationConsumer() {
            public void notify(NotifyDocument notify) {
                System.out.println("WS invoke not yet implemented");
                System.out.println("Target: "+consumerReference);
                System.out.println("Notify Message: "+notify);
            }
        };
    }

    public GetCurrentMessageResponseDocument getCurrentMessage(GetCurrentMessageDocument request) {
        throw new RuntimeException("Not supported");
    }

    public GetResourcePropertyResponseDocument getResourceProperty(EndpointReferenceType resource, GetResourcePropertyDocument request) {
        return xmlResourceProperties.getResourceProperty(null, request);
    }

    public PublisherRegistrationManager getPublisherManager() {
        return publisherManager;
    }
    

    public SubscriptionManager getSubscriptionManager() {
        return subscriptionManager;
    }    

}
