/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.component.sip.listener;

import javax.sip.Dialog;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.Transaction;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.apache.camel.CamelException;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.component.sip.SipSubscriber;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SipSubscriptionListener implements SipListener {
    private static final transient Log LOG = LogFactory.getLog(SipSubscriptionListener.class);
    private SipSubscriber sipSubscriber;
    private Dialog subscriberDialog;
    private Dialog forkedDialog;

    public SipSubscriptionListener(SipSubscriber sipSubscriber) {
        this.setSipSubscriber(sipSubscriber);
    }

    private void dispatchExchange(Object response) throws CamelException {
        Exchange exchange;
        
        if (LOG.isDebugEnabled()) {
            LOG.debug("Consumer Dispatching the received notification along the route");
        }
        exchange = sipSubscriber.getEndpoint().createExchange(ExchangePattern.InOnly);
        exchange.getIn().setBody(response);
        try {
            sipSubscriber.getProcessor().process(exchange);
        } catch (Exception e) {
            throw new CamelException("Error in consumer while dispatching exchange", e);
        }
    }
    
    public void processRequest(RequestEvent requestReceivedEvent) {
        Request request = requestReceivedEvent.getRequest();
        ServerTransaction serverTransactionId = requestReceivedEvent
                .getServerTransaction();
        String viaBranch = ((ViaHeader)(request.getHeaders(ViaHeader.NAME).next())).getParameter("branch");
        if (LOG.isDebugEnabled()) {
            LOG.debug("Request: " + request.getMethod()); 
            LOG.debug("Server Transaction Id:" + serverTransactionId);
            LOG.debug("Received From Branch:" + viaBranch);
        }

        if (request.getMethod().equals(Request.NOTIFY)) {
            processNotify(requestReceivedEvent, serverTransactionId);
        } 
    }

    public synchronized void processNotify(RequestEvent requestEvent,
            ServerTransaction serverTransactionId) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Notification received at Subscriber");
        }
        SipProvider provider = (SipProvider) requestEvent.getSource();
        Request notify = requestEvent.getRequest();
        try {
            if (serverTransactionId == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.info("ServerTransaction is null. Creating new Server transaction");
                }
                serverTransactionId = provider.getNewServerTransaction(notify);
            }
            Dialog dialog = serverTransactionId.getDialog();

            if (dialog != subscriberDialog) {
                forkedDialog = dialog;
            }
            //Dispatch the response along the route
            dispatchExchange(notify.getContent());
            
            // Send back an success response
            Response response = sipSubscriber.getConfiguration().getMessageFactory().createResponse(200, notify);            
            response.addHeader(sipSubscriber.getConfiguration().getContactHeader());
            serverTransactionId.sendResponse(response);

            SubscriptionStateHeader subscriptionState = (SubscriptionStateHeader) notify
                    .getHeader(SubscriptionStateHeader.NAME);

            // Subscription is terminated?
            if (subscriptionState.getState().equalsIgnoreCase(SubscriptionStateHeader.TERMINATED)) {
                if (LOG.isDebugEnabled()) {
                    LOG.info("Subscription state is terminated. Deleting the current dialog");
                }
                dialog.delete();
            }
        } catch (Exception e) {
            LOG.error("Exception thrown during Notify processing in the SipSubscriptionListener.", e);
        }
    }
    
    public void processResponse(ResponseEvent responseReceivedEvent) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Response received at Subscriber");
        }
        Response response = responseReceivedEvent.getResponse();
        Transaction clientTransactionId = responseReceivedEvent.getClientTransaction();

        if (LOG.isDebugEnabled()) {
            LOG.debug("Response received with client transaction id " + clientTransactionId + ":" + response.getStatusCode());
        }
        if (clientTransactionId == null) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Stray response -- dropping");
            }
            return;
        }
    }

    public void processIOException(IOExceptionEvent exceptionEvent) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("IOExceptionEvent received at Sip Subscription Listener");
        }
    }

    public void processTransactionTerminated(
            TransactionTerminatedEvent transactionTerminatedEvent) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("TransactionTerminatedEvent received at Sip Subscription Listener");
        }
    }

    public void processDialogTerminated(
            DialogTerminatedEvent dialogTerminatedEvent) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("DialogTerminatedEvent received at Sip Subscription Listener");
        }
    }

    public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("TimeoutEvent received at Sip Subscription Listener");
        }
    }

    public void setSipSubscriber(SipSubscriber sipSubscriber) {
        this.sipSubscriber = sipSubscriber;
    }

    public SipSubscriber getSipSubscriber() {
        return sipSubscriber;
    }

    public Dialog getForkedDialog() {
        return forkedDialog;
    }
}
