/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2007. All rights reserved.
 */
package com.ericsson.ssa.sip;

import com.ericsson.ssa.container.SipBindingCtx;
import com.ericsson.ssa.container.SipBindingResolver;
import com.ericsson.ssa.container.callflow.CallflowResolver;
import com.ericsson.ssa.container.callflow.Reporter;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;

import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.URI;


/**
 * Singleton class that manages local routes added via the
 * {@link javax.servlet.sip.SipServletRequest#pushRoute(javax.servlet.sip.Address) SipServletRequest.pushRoute(Address)}
 * or
 * {@link javax.servlet.sip.SipServletRequest#pushRoute(javax.servlet.sip.SipURI) SipServletRequest.pushRoute(SipURI)}
 * methods.
 *
 * This Layer checks if the top Route header on outgoing requests has the form
 * sip:user@localhost;lr or sip:user@127.0.0.1;lr and in that case stops
 * downwards traversal in the stack, pops the Route header and sends the request
 * upwards in the stack again.
 *
 * @author eraknhm
 */
public class LocalRouteManager implements Layer {
    private static final String LOCALHOST = "localhost";
    private static final String LOCALIP = "127.0.0.1";
    private static final LocalRouteManager singletonInstance = new LocalRouteManager();
    private Logger m_logger = Logger.getLogger("SipContainer");
    private Layer _nextLayer = null;
    private Reporter _reporter;

    // Enforce Singleton pattern
    private LocalRouteManager() {
        // Empty
    }

    public void setReporters(String reporters) {
        _reporter = CallflowResolver.getInstance().getReporter(reporters);
    }

    public Reporter getReporter() {
        return _reporter;
    }

    public static LocalRouteManager getInstance() {
        return singletonInstance;
    }

    /**
     * Delegate to next layer
     */
    public void next(SipServletRequestImpl req) {
        if (req.isInitial()) {
            req.pushApplicationDispatcher(this);
        }

        // Send onwards
        LayerHelper.next(req, this, _nextLayer);
    }

    /**
     * Delegate to next layer
     */
    public void next(SipServletResponseImpl resp) {
        // Send onwards
        LayerHelper.next(resp, this, _nextLayer);
    }

    public void registerNext(Layer layer) {
        _nextLayer = layer;
    }

    /**
     * Delegate to next layer
     */
    public void dispatch(SipServletRequestImpl req) {
        // Check request
        if (req.isInitial()) {
            if (isLocalRoute(req)) {
                // Let request stay in container...
                if (m_logger.isLoggable(Level.FINE)) {
                    m_logger.log(Level.FINE, "Request should stay in container.");
                }

                // Pop route header
                if (popRouteHeader(req)) {
                    // Add this layer as dispatcher
                    req.pushApplicationDispatcher(this);
                    // Send onwards
                    LayerHelper.next(req, this, _nextLayer);

                    return;
                }
            }
        }

        // Send onwards
        req.popDispatcher().dispatch(req);
    }

    /**
     * Delegate to next layer
     */
    public void dispatch(SipServletResponseImpl resp) {
        // Send onwards
        resp.popDispatcher().dispatch(resp);
    }

    /**
     * Examines the Route header of a request to determine if the request could
     * stay inside the container
     *
     * @param req
     *            the request to examine
     * @return true if route header exists and host part is "localhost" or
     *         "127.0.0.1", false otherwise
     */
    private boolean isLocalRoute(SipServletRequestImpl req) {
        // Check top Route header, if any
        boolean local = false;

        try {
            Address route = req.getAddressHeader(Header.ROUTE);

            if (route != null) {
                if (!route.isWildcard()) {
                    URI uri = route.getURI();

                    // Check if the URI contains localhost or 127.0.0.1
                    if (uri.isSipURI()) {
                        SipURIImpl sipUri = (SipURIImpl) uri;
                        String host = sipUri.getHost();

                        if (host.equalsIgnoreCase(LOCALHOST) ||
                                host.equals(LOCALIP)) {
                            // Fetch port from request (use 5060 if port is missing)
                            int port = sipUri.getPort();

                            if (port == -1) {
                                port = 5060;
                            }

                            // Fetch valid ports
                            SipBindingCtx sipBindingCtx = SipBindingResolver.instance()
                                                                            .getContext(SipBindingResolver.PUBLIC_BINDING_CTX);

                            TargetTuple[] tt = sipBindingCtx.getTargetTuples();

                            // Check if port in request is one of the valid ones 
                            for (int i = 0; i < tt.length; i++) {
                                if (port == tt[i].getPort()) {
                                    local = true;

                                    break;
                                }
                            }
                        }
                    }
                }
            }
        } catch (ServletParseException e) {
            // Should never occur.
        }

        return local;
    }

    /**
     * Pop the topmost Route header to simulate that request have exited and
     * re-entered.
     *
     * @param req
     *            the request to manipulate
     * @return true if route is popped, false otherwise
     */
    private boolean popRouteHeader(SipServletRequestImpl req) {
        // loose route support: remove first route header
        boolean status = false;
        Header r = req.getRawHeader(Header.ROUTE);

        if (r != null) {
            r.setReadOnly(false);

            ListIterator<Address> it_a = null;

            try {
                it_a = r.getAddressValues();

                Address a = it_a.next();
                URIImpl uri = (URIImpl) a.getURI();

                if (uri.getLrParam()) {
                    if (m_logger.isLoggable(Level.FINE)) {
                        m_logger.log(Level.FINE, "Removing route = " + a);
                    }

                    it_a.remove();

                    // support of popped route added.
                    req.setPoppedRoute(a);
                    req.setRecordRouteURI(uri);

                    status = true;
                }
            } catch (ServletParseException e) {
                // Should not happen
            }

            r.setReadOnly(true);
        }

        return status;
    }
}
