/*
 * 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, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip.dns;

import com.ericsson.ssa.sip.AddressImpl;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.ViaImpl;

import java.util.logging.Level;

// inserted by hockey (automatic)
import java.util.logging.Logger;

import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;


/**
 * @reviewed ejoelbi 2007-jan-17
 * @reviewed epetstr 2007-feb-20
 */
public class TargetResolver {
    private static TargetResolver myInstance = new TargetResolver();
    private static DnsResolver myDnsResolver = DnsResolver.getInstance();
    private static final int UNDEFINED_PORTNO = -1;
    public static final int DEFAULT_RETRYTIME = 30;
    private final int MAX_MTU_SIZE_EXCL_VIA = UDPProtocol.MAX_MTU_SIZE - 50;
    private Logger logger = (Logger) Logger.getLogger("SipContainer");

    private TargetResolver() {
    }

    public static TargetResolver getInstance() {
        return myInstance;
    }

    // TODO remove when TargetResolverTest is updated
    public void setFailed(TargetTuple target) {
        setTargetFailed(target, DEFAULT_RETRYTIME);
    }

    public void setTargetFailed(TargetTuple target, int expireValue) {
        myDnsResolver.setTargetFailed(target, expireValue);

        // A/AAAA/SRV:
        // The failure of a protocol/ip/port is stored for a while
        // to avoid unnecesary attempts to connect to a faulty host
        // a fail state is cleared after a configurable period
    }

    public TargetTuple resolveResponse(SipServletResponseImpl resp)
        throws Exception {
        ViaImpl via = new ViaImpl(resp.getHeader(Header.VIA));
        SipTransports transport = SipTransports.getTransport(via.getTransport());

        if (transport == SipTransports.UDP_PROT) {
            return resolveRespUDP(resp, via);
        } else {
            return resolveRespTCP(resp, via, transport);
        }
    }

    public TargetTuple resolveRespUDP(SipServletResponseImpl resp, ViaImpl via)
        throws Exception {
        SipTransports transport = SipTransports.UDP_PROT;

        // Check if maddr is present
        // 3261 18.2.2 bullet 2)
        String maddr = null;

        if ((maddr = via.getParameter(ViaImpl.PARAM_MADDR)) != null) {
            return resolveRespMaddr(maddr, via, transport);
        }

        // Check if "received" is present
        // 3261 18.2.2 bullet 3)
        String received = via.getParameter(ViaImpl.PARAM_RECEIVED);

        if (received != null) {
            return resolveRespReceived(received, via, transport);
        }

        // 3261 18.2.2 bullet 4)
        return resolveResp3263_5(via);
    }

    /**
     * 1) use already opened tcp channel found by transactionId 2) reopen the tcp
     * connection by means of (received, maddr, sent-by) 3) if reopen fails then
     * try alternate according to 3263 Chapter5
     */
    public TargetTuple resolveRespTCP(SipServletResponseImpl resp, ViaImpl via,
        SipTransports transport) throws Exception {
        // Check if "received" is present
        // 3261 18.2.2 bullet 1), second part
        String received = via.getParameter(ViaImpl.PARAM_RECEIVED);

        if (received != null) {
            return resolveRespReceived(received, via, transport);
        }

        // 3261 18.2.2 bullet 4)
        return resolveResp3263_5(via);
    }

    /**
     * Use this method if: - udp/received failed - tcp/received failed
     */
    public TargetTuple resolveRespAlternate(SipServletResponseImpl resp)
        throws Exception {
        ViaImpl via = new ViaImpl(resp.getHeader(Header.VIA));

        return resolveResp3263_5(via);
    }

    private TargetTuple resolveRespReceived(String received_ip, ViaImpl via,
        SipTransports transport) throws Exception {
        int port = via.getPort();
        String rport = null;

        if ((rport = via.getParameter(ViaImpl.PARAM_RPORT)) != null) {
            port = Integer.parseInt(rport);
        } else if (port == -1) {
            // use default
            port = transport.getDefaultPort();
        }

        // "received" MUST be numeric ip:
        // via-received = "received" EQUAL (IPv4address / IPv6address)
        // TODO see comments in OLDresolveUDPResponse()
        return new TargetTuple(transport, received_ip, port);
    }

    private TargetTuple resolveRespMaddr(String maddr, ViaImpl via,
        SipTransports transport) throws Exception {
        int port = via.getPort();

        if (port == -1) {
            // use default
            port = transport.getDefaultPort();
        }

        // maddr may be an domainname:
        // maddr-param = "maddr=" host
        if (isNumericIp(maddr) == false) {
            // A, AAAA lookup
            String ip = myDnsResolver.lookupName(maddr);

            if (ip == null) {
                throw new DnsLookupFailedException(
                    "Dns A lookup failed for host:" + maddr);
            }

            maddr = ip;
        }

        return new TargetTuple(transport, maddr, port);
    }

    public TargetTuple resolveResp3263_5(ViaImpl via) throws Exception {
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE,
                "TargetResolver.resolve():\n VIA:" + via.toString() + "\n");
        }

        //
        SipTransports transport = SipTransports.getTransport(via.getTransport());

        // should we check MAX_MTU for resonses?
        // No where in 3261 it says so!! LETS NOT for time beeing
        if (transport.isSupported() == false) {
            throw new Exception("This transport is disabled in configuration: " +
                via.getTransport());
        }

        int port = via.getPort();
        String host = via.getHost();

        /*
         * Maddr SHOULD NOT be here? // rfc3261 18.2.2 handling // maddr handling
         * String maddr=via.getParameter(ViaImpl.PARAM_MADDR); if (maddr != null)
         * host = maddr;
         */
        if (isNumericIp(host)) {
            if (port == -1) {
                port = transport.getDefaultPort();
            }
        } else/* host is a domainname */
         {
            if (port != -1) {
                // if port is defined then
                // A, AAAA lookup
                String ip = myDnsResolver.lookupName(host);

                if (ip == null) {
                    throw new DnsLookupFailedException(
                        "Dns A lookup failed for host:" + host);
                }

                host = ip;
            } else {
                // if port is not defined in VIA
                // we should perform an SRV query
                TargetTuple target = null;

                if ((target = myDnsResolver.lookupSRVDirect(transport, host)) != null) {
                    return target;
                }

                throw new Exception(
                    "Failed to find a valid SRV record for via:" + via);
            }
        }

        return new TargetTuple(transport, host, port);
    }

    public TargetTuple resolveRequest(SipServletRequestImpl req, int msgSize)
        throws Exception {
        URI uri = null;
        String uriStr = null;

        if ((uriStr = req.getHeader(Header.ROUTE)) != null) {
            AddressImpl address = new AddressImpl(uriStr);
            uri = address.getURI();
        } else {
            uri = req.getRequestURI();
        }

        if (myDnsResolver.isTelephoneNumber(uri)) {
            SipURI sipuri = myDnsResolver.doLookupSipURI(uri);

            if (sipuri == null) {
                return null;
            }

            updateDefaultTransportParameter(sipuri);

            return resolveReq3263_4(sipuri, msgSize);
        } else if (uri.isSipURI()) {
            SipURI sipuri = (SipURI) uri;
            updateDefaultTransportParameter(sipuri);

            return resolveReq3263_4(sipuri, msgSize);
        } else {
            throw new Exception("Unknown type of uri :" + uri);
        }
    }

    /**
     * If {@link ResolverManager#isDefaultTCPTransport()} returns true then the
     * transport "transport=tcp" will be added to the URI if it is a SipURI.<br>
     *
     * @param uri
     *            This URI may be modified.
     */
    public void updateDefaultTransportParameter(URI uri) {
        if (!uri.isSipURI()) {
            return;
        }

        SipURI sipURI = (SipURI) uri;

        if (sipURI.isSecure() || (sipURI.getTransportParam() != null)) {
            return; //Already right transport
        }

        boolean isDefaultTCPTransport = ResolverManager.getInstance()
                                                       .isDefaultTCPTransport();

        if (isDefaultTCPTransport) {
            sipURI.setTransportParam(SipTransports.TCP_PROT.name());
        }
    }

    public TargetTuple resolveReq3263_4(SipURI sipuri, int msgSize)
        throws Exception {
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE,
                "TargetResolver.resolve():\n URI:" + sipuri.toString() + "\n");
        }

        String host = sipuri.getHost();

        // maddr overides host 3263.4
        String maddr = sipuri.getMAddrParam();

        if (maddr != null) {
            host = maddr;
        }

        /*
         * if (sipuri.isSecure()) { throw new Exception("ASSERT Currently sips:
         * uri is not supported"); }
         */
        SipTransports theTransport = SipTransports.UNDEFINED_PROT;
        int thePort = sipuri.getPort();
        boolean isUdpMTUExceeded = false;

        if (msgSize > MAX_MTU_SIZE_EXCL_VIA) {
            isUdpMTUExceeded = true;
        }

        String transport = sipuri.getTransportParam();

        TargetTuple target = null;

        if (transport != null) {
            theTransport = SipTransports.getTransport(transport);
        } else if (isNumericIp(host) &&
                (theTransport == SipTransports.UNDEFINED_PROT)) {
            // if ("sips: uri")
            if (sipuri.isSecure()) {
                theTransport = SipTransports.TLS_PROT;
            } else {
                theTransport = SipTransports.UDP_PROT;
            }
        } else if (thePort != UNDEFINED_PORTNO) {
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE,
                    "port specified in URI:" + sipuri.getPort());
            }

            if (theTransport == SipTransports.UNDEFINED_PROT) {
                theTransport = SipTransports.UDP_PROT;
            }
        } else if ((target = myDnsResolver.lookupNAPTRRecord(host,
                        isUdpMTUExceeded)) != null) {
            return target;
        } else if ((target = myDnsResolver.lookupSRVDirect(theTransport, host,
                        isUdpMTUExceeded)) != null) {
            return target;
        } else {
            // transport == null && port == null && no naptr && no srv
            // ---> SIP Default
            theTransport = SipTransports.UDP_PROT;
        }

        if (thePort == UNDEFINED_PORTNO) {
            thePort = theTransport.getDefaultPort();
        }

        if (isNumericIp(host) == false) {
            // A, AAAA lookup
            String ip = myDnsResolver.lookupName(host);

            if (ip == null) {
                return null;
            }

            host = ip;
        }

        if ((theTransport == SipTransports.UDP_PROT) &&
                (isUdpMTUExceeded == true)) {
            theTransport = SipTransports.TCP_PROT;

            // RFC 3261 18.1.1 states that the caller must change "transport"
            // parameter accordingly
            // 8.1.1.7 says that a via is generated after the 3263 procedures
            // this means that it is not necesary to update the via according to
            // MTUExceeded protocol switch
            // TODO assign new "transport" value to
            // my URI...
            // to be used in the via header
            // as described in
            // RFC 3261 18.1.1
        }

        TargetTuple tuple = new TargetTuple(theTransport, host, thePort);

        if (myDnsResolver.isTargetFailed(tuple)) {
            return null;
        } else {
            return tuple;
        }
    }

    public static boolean isIPV6(String ipAddress) {
        // example URI with IPV6
        // <sip:ppessi@[3ffe::200:86ff:fe46:9616]:5066;maddr=[3ffe::200:86ff:fe46:9616]>
        return (ipAddress.charAt(0) == '[') && (ipAddress.indexOf(']') != -1);
    }

    /**
     * Checks whether "host" is an ipadress (either ipv4 or ipv6) or a domainname
     *
     * @param host
     *            String
     * @return boolean
     */
    public static boolean isNumericIp(String host) {
        // TODO fix this.....
        return Character.isDigit(host.charAt(0)) || (host.charAt(0) == '[');
    }
}
