/*
 * 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 org.jvnet.glassfish.comms.clb.core.util;

import com.ericsson.ssa.container.SipBindingCtx;
import com.ericsson.ssa.container.SipBindingResolver;
import com.ericsson.ssa.sip.SipURIDecoder;
import com.ericsson.ssa.sip.SipURIEncoder;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;

import com.sun.enterprise.admin.server.core.AdminService;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.Cluster;
import com.sun.enterprise.config.serverbeans.ClusterHelper;
import com.sun.enterprise.config.serverbeans.ConvergedLbClusterRef;
import com.sun.enterprise.config.serverbeans.ConvergedLbConfig;
import com.sun.enterprise.config.serverbeans.ConvergedLbConfigs;
import com.sun.enterprise.config.serverbeans.ConvergedLbPolicy;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.ServerHelper;
import com.sun.enterprise.config.serverbeans.ServerRef;
import com.sun.enterprise.server.ServerContext;

import org.jvnet.glassfish.comms.clb.core.sip.Socket;
import org.jvnet.glassfish.comms.util.LogUtil;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.IOException;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;

import java.util.logging.Level;


/**
 * Misc. utility methods.
 */
public class LoadbalancerUtil {
    private static LogUtil logger = new LogUtil(LogUtil.CLB_LOG_DOMAIN);
    private static BASE64Decoder decoder;
    private static BASE64Encoder encoder;
    private static SipURIDecoder sipURIDecoder;
    private static SipURIEncoder sipURIEncoder;

    private LoadbalancerUtil() {
    } // Disable creation

    /**
     * Encodes the specific string to a string that only contains valid
     * characters for a SIP parameter.
     *
     * @param raparamm the parameter to encode
     * @return the encoded parameter
     */
    public static String encodeParameter(String str) {
        return encodeParameter(str.getBytes(), false);
    }

    /**
     * Decodes a parameter that have been encoded using
     * {@link #encodeParameter(String)}.
     *
     * @param encodedParam the encoded parameter
     * @return the decoded parameter
     * @throws IOException thrown in case decoding fails
     */
    public static String decodeParameter(String encodedParam)
        throws IOException {
        if (sipURIDecoder == null) {
            sipURIDecoder = new SipURIDecoder();
        }

        String uriDecoded = sipURIDecoder.decode(encodedParam);

        return uriDecoded;
    }

    /**
     * Encodes the specific byte array to a string that only contains valid
     * characters for a SIP parameter.
     *
     * @param bytes the bytes to encode
     * @param doBase64 TODO
     * @return the encoded string
     */
    public static String encodeParameter(byte[] bytes, boolean doBase64) {
        if (encoder == null) {
            encoder = new BASE64Encoder();
            sipURIEncoder = new SipURIEncoder();
        }

        String str;

        if (doBase64) {
            str = encoder.encode(bytes);
        } else {
            str = new String(bytes);
        }

        return sipURIEncoder.encodeParameter(str);
    }

    /**
     * Decodes a parameter that have been encoded using
     * {@link #encodeParameter(String)}.
     *
     * @param encodedParam the encoded parameter
     * @param doBase64 TODO
     * @return the decoded parameter
     * @throws IOException thrown in case decoding fails
     */
    public static byte[] decodeParameterToBytes(String encodedParam,
        boolean doBase64) throws IOException {
        if (decoder == null) {
            decoder = new BASE64Decoder();
            sipURIDecoder = new SipURIDecoder();
        }

        String uriDecoded = sipURIDecoder.decode(encodedParam);

        if (doBase64) {
            return decoder.decodeBuffer(uriDecoded);
        } else {
            return uriDecoded.getBytes();
        }
    }

    /**
     * Gets the numeric IP address of the specified InetAddress.
     *
     * @param inetAddress the address
     * @return the numeric IP address of the specified InetAddress.
     */
    public static String getNumericIpAddress(InetAddress inetAddress) {
        byte[] addressBytes = inetAddress.getAddress();

        return unsigned(addressBytes[0]) + "." + unsigned(addressBytes[1]) +
        "." + unsigned(addressBytes[2]) + "." + unsigned(addressBytes[3]);
    }

    private static int unsigned(byte b) {
        if (b >= 0) {
            return b;
        }

        int i = b;

        return i & 0xff;
    }

    /**
     * Gets the local socket on which this instance receives certain traffic for
     * a certain transport.
     *
     * @return the local socket on which this instance receives certain traffic
     *         for a certain transport.
     * @param transport the transport protocol
     * @throws IllegalStateException in case no local socket was found
     */
    public static Socket getLocalSocket(SipTransports transport)
        throws IllegalStateException {
        Socket localAddress = null;

        InetSocketAddress localSAddr = null;

        if (logger.isLoggable(Level.FINE)) {
            logger.logMsg(Level.FINE,
                "Get first suitable local SipBindingCtx for TCP.");
        }

        SipBindingCtx ctx = SipBindingResolver.instance()
                                              .getActiveLocalContext(transport);

        if (ctx != null) {
            TargetTuple tt = ctx.getTargetTupleForProtocol(transport);

            if (tt != null) {
                localSAddr = tt.getSocketAddress();

                if (logger.isLoggable(Level.FINEST)) {
                    logger.logMsg(Level.FINEST,
                        "Suitable local SipBindingCtx found, using: " + ctx +
                        ", resolved address: " + localSAddr);
                }
            }
        }

        if (localSAddr != null) {
            String addr = LoadbalancerUtil.getNumericIpAddress(localSAddr.getAddress());

            if ("0.0.0.0".equals(addr)) {
                try {
                    addr = LoadbalancerUtil.getNumericIpAddress(InetAddress.getLocalHost());
                } catch (UnknownHostException ex) {
                    throw new IllegalStateException("Can't resolve local address.",
                        ex);
                }
            }

            localAddress = new Socket(addr, localSAddr.getPort());

            if (logger.isLoggable(Level.FINE)) {
                logger.logMsg(Level.FINE,
                    "Local address resolved to: " + localAddress);
            }
        } else {
            if (System.getProperty("clb.unittest.localsocket") != null) {
                return new Socket("127.0.0.1", 5060);
            }

            throw new IllegalStateException("Can't resolve local address for '" +
                transport +
                "', there exists no listener for that transport; check your sip-listener configuration!");
        }

        return localAddress;
    }

    /**
     * Gets the name of the local instance.
     * @return the name of the local instance
     */
    public static String getLocalInstanceName() {
        ServerContext sc = com.sun.enterprise.server.ondemand.OnDemandServer.getServerContext();

        return sc.getInstanceName();
    }

    /**
     * Gets the name of the local cluster.
     * @return the name of the local cluster or null if not clustered
     * @throws ConfigException in case the configuration could ne be read.
     */
    public static String getLocalClusterName() throws ConfigException {
        ServerContext sc = com.sun.enterprise.server.ondemand.OnDemandServer.getServerContext();
        ConfigContext instanceConfigContext = sc.getConfigContext();
        String instanceName = sc.getInstanceName();

        if (ServerHelper.isServerClustered(instanceConfigContext, instanceName)) {
            Cluster cluster = ClusterHelper.getClusterForInstance(instanceConfigContext,
                    instanceName);

            return cluster.getName();
        }

        return null;
    }

    /**
     * Gets the DCR file from the configuration.
     * @return the DCR file from the configuration
     * @throws ConfigException in case configuration could not be read
     */
    public static String getDcrFileNameFromConfig() throws ConfigException {
        String instanceName = getLocalInstanceName();
        String clusterName = getLocalClusterName();
        boolean isCluster = clusterName != null;

        ConfigContext ctx = AdminService.getAdminService().getAdminContext()
                                        .getAdminConfigContext();
        Domain domain = (Domain) ctx.getRootConfigBean();
        ConvergedLbConfigs clbConfigs = domain.getConvergedLbConfigs();
        // Null in case of a DAS/developer profile. Hence no CLB frontending
        // this instance.Hence no associated DCR file.
        if(clbConfigs == null ) return null; 
        boolean matchFound = false;
        ConvergedLbConfig[] clbConfigArray = clbConfigs.getConvergedLbConfig();
        
        for (int i = 0; i < clbConfigArray.length; i++) {
            if (isCluster) {
                ConvergedLbClusterRef[] clusterRefs = clbConfigArray[i].getConvergedLbClusterRef();

                for (int j = 0; j < clusterRefs.length; j++) {
                    if (clusterRefs[j].getRef().equals(clusterName)) {
                        matchFound = true;

                        break;
                    }
                }
            } else {
                ServerRef[] serverRefs = clbConfigArray[i].getServerRef();

                for (int j = 0; j < serverRefs.length; j++) {
                    if (serverRefs[j].getRef().equals(instanceName)) {
                        matchFound = true;

                        break;
                    }
                }
            }

            if (matchFound) {
                ConvergedLbPolicy clbPolicy = clbConfigArray[i].getConvergedLbPolicy();

                return clbPolicy.getDcrFile();
            }
        }

        return null;
    }
}
