/*
 * 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.sip;

import com.ericsson.ssa.container.LinkBase;
import com.ericsson.ssa.container.NetworkManager;
import com.ericsson.ssa.container.callflow.CallflowResolver;
import com.ericsson.ssa.container.callflow.Reporter;
import com.ericsson.ssa.sip.Dispatcher;
import com.ericsson.ssa.sip.Layer;
import com.ericsson.ssa.sip.LayerHelper;
import com.ericsson.ssa.sip.SipFactoryImpl;
import com.ericsson.ssa.sip.SipServletMessageImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.dns.DnsResolver;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;

import com.sun.enterprise.config.ConfigException;

import org.jvnet.glassfish.comms.clb.core.ConvergedLoadBalancerFactory;
import org.jvnet.glassfish.comms.clb.core.common.chr.CompositeHashKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.common.chr.DefaultHashKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrConfigurableHashKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrRulesException;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrUtils;
import org.jvnet.glassfish.comms.clb.core.util.LoadbalancerUtil;
import org.jvnet.glassfish.comms.util.LogUtil;

import java.io.File;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;


/**
 * This is the LoadBalancingManagerBackEnd that together with
 * {@link SipLoadBalancerManager} is the entry point of the Converged Load
 * Balancer.
 * <p>
 * This part of the CLB is responsible for handling outgoing requests/responses
 * on a back-end.
 *
 * It is a singleton and will be inserted into the SIP chain by the
 * {@link org.jvnet.glassfish.comms.startup.SipServiceListener} and
 * {@link com.ericsson.ssa.config.LayerHandler} at start-up. Note, it should be
 * placed "above" the {@link SipLoadBalancerManager} to achieve the best
 * performance.
 *
 * It is this class that is responsible for reading the configuration and
 * creating all the runtime objects for the back-end parts.
 * <p>
 * TODO (qbinjoe) Ensure that we share runtime objects (ServerCluster,
 * ServerInstance, etc.) between front-end and back-end layer.
 */
public class SipLoadBalancerManagerBackEnd implements Layer {
    private static LogUtil logger = new LogUtil(LogUtil.CLB_LOG_DOMAIN);
    private static SipLoadBalancerManagerBackEnd instance; // The singleton
                                                           // instance
    private Layer nextLayer; // The next layer above
    private SipLoadBalancerBackend sipLoadBalancerBackend;
    private Reporter _reporter;

    /**
     * Create the instance
     */
    private SipLoadBalancerManagerBackEnd() {
    }

    /**
     * Gets the singleton instance. Used by the
     * {@link com.ericsson.ssa.config.LayerHandler} (via reflection) when
     * setting up the SIP and HTTP processing chains at start-up.
     *
     * @return the singleton instance
     */
    public static SipLoadBalancerManagerBackEnd getInstance() {
        if (instance == null) {
            instance = new SipLoadBalancerManagerBackEnd();
        }

        return instance;
    }

    /**
     * Called by {@link org.jvnet.glassfish.comms.startup.SipServiceListener} at
     * Lifecycle.AFTER_START_EVENT.
     */
    public void start() {
        logger.info("Starting SIP CLB back-end...");

        // Create default HKE
        CompositeHashKeyExtractor hashKeyExtractor = new CompositeHashKeyExtractor();
        DefaultHashKeyExtractor defaultHashKeyExtractor = new DefaultHashKeyExtractor();
        hashKeyExtractor.push(defaultHashKeyExtractor);

        // Then DCR hash key extractor...
        DcrUtils.setup(DnsResolver.getInstance(), SipFactoryImpl.getInstance());

        String dcrFileName;

        try {
            dcrFileName = LoadbalancerUtil.getDcrFileNameFromConfig();

            if (dcrFileName != null) {
                try {
                    File dcrFile = new File(dcrFileName);
                    DcrConfigurableHashKeyExtractor dcrHashKeyExtractor = new DcrConfigurableHashKeyExtractor(dcrFile.getAbsolutePath());
                    hashKeyExtractor.push(dcrHashKeyExtractor);
                } catch (DcrRulesException e) {
                    logger.severe("Failed to read DCR configuration: " + e +
                        " ; will only use default hash key extraction.");
                }
            }
        } catch (ConfigException e1) {
            logger.severe("Failed to configuration to find DCR file: " + e1 +
                " ; will only use default hash key extraction.");
        }

        sipLoadBalancerBackend = new SipLoadBalancerBackend(hashKeyExtractor,
                LoadbalancerUtil.getLocalSocket(SipTransports.TCP_PROT));

        logger.info("SIP CLB back-end started successfully.");
    }

    // --------------- Layer interface ----------------
    /**
     * No handling of incoming requests (handled by
     * {@link SipLoadBalancerManager}).
     *
     * @param req the received request
     * @see com.ericsson.ssa.sip.Layer#next(com.ericsson.ssa.sip.SipServletRequestImpl)
     */
    public void next(SipServletRequestImpl request) {
        sipLoadBalancerBackend.handleIncomingRequest(request);
        request.pushTransactionDispatcher(this);
        request.pushApplicationDispatcher(this);
        LayerHelper.next(request, this, nextLayer);
    }

    /**
     * No handling of incoming responses (handled by
     * {@link SipLoadBalancerManager}).
     *
     * @param resp the received request
     * @see com.ericsson.ssa.sip.Layer#next(com.ericsson.ssa.sip.SipServletResponseImpl)
     */
    public void next(SipServletResponseImpl resp) {
        if (logger.isLoggable(Level.FINE)) {
            logger.logMsg(Level.FINE,
                "An incoming response, pass it on. response: " + resp);
        }

        LayerHelper.next(resp, this, nextLayer);
    }

    /**
     * Handle an outgoing request.
     *
     * @param req the request to be sent
     * @see com.ericsson.ssa.sip.Dispatcher#dispatch(com.ericsson.ssa.sip.SipServletRequestImpl)
     */
    public void dispatch(SipServletRequestImpl req) {
        Connection connection;

        try {
            connection = sipLoadBalancerBackend.handleOutgoingRequest(req);

            if (connection != null) {
                assert false : "Should never happen!!! Original connection set by ResolverManager shall always be used";
            } else {
                // Dispatch the request
                req.popDispatcher().dispatch(req);
            }
        } catch (SipRoutingException e) {
            logger.warning("Failed to route outgoing request: " + e);

            SipServletResponseImpl errorResponse = req.createTerminatingResponse(503);

            if (errorResponse != null) {
                LayerHelper.next(errorResponse, this, nextLayer);
            }
        }
    }

    /**
     * Handle an outgoing response.
     *
     * @param resp the response to be sent
     * @see com.ericsson.ssa.sip.Dispatcher#dispatch(com.ericsson.ssa.sip.SipServletResponseImpl)
     */
    public void dispatch(SipServletResponseImpl response) {
        Connection connection;

        try {
            connection = sipLoadBalancerBackend.handleOutgoingResponse(response);

            if (connection != null) {
                // Alter connection and dispatch
                if (logger.isLoggable(Level.FINE)) {
                    logger.logMsg(Level.FINE, "Alter connection: " +
                        connection);
                }

                resetNetworkManager(response);
                response.setRemote(new TargetTuple(connection.getTransport(),
                        connection.getRemoteEndpoint().getHostName(),
                        connection.getRemoteEndpoint().getPort()));
            }

            response.popDispatcher().dispatch(response);
        } catch (SipRoutingException e) {
            logger.warning("Failed to route outgoing response: " + e);

            // Drop the response
        }
    }

    static void resetNetworkManager(SipServletRequestImpl resp) {
        List<Dispatcher> dispStack = rebuildStack(resp);
        pushBackStack(dispStack, resp);
    }

    static void pushBackStack(List<Dispatcher> dispStack,
        SipServletRequestImpl req) {
        for (Dispatcher d : dispStack) {
            req.pushApplicationDispatcher(d);
        }
    }

    static void resetNetworkManager(SipServletResponseImpl resp) {
        List<Dispatcher> dispStack = rebuildStack(resp);
        pushBackStack(dispStack, resp);
    }

    static void pushBackStack(List<Dispatcher> dispStack,
        SipServletResponseImpl resp) {
        for (Dispatcher d : dispStack) {
            resp.pushTransactionDispatcher(d);
        }
    }

    static List<Dispatcher> rebuildStack(SipServletMessageImpl msg) {
        List<Dispatcher> dispStack = new ArrayList<Dispatcher>();

        while (true) {
            Dispatcher dispatcher = msg.popDispatcher();

            if (dispatcher != null) {
                dispStack.add(0, dispatcher);
            } else {
                break;
            }
        }

        // Remove first item in stack (shall be the Link or NetworkManager) tied
        // to the incoming connection...
        if (dispStack.size() > 0) {
            Dispatcher nm = dispStack.remove(0);
            assert nm instanceof NetworkManager || nm instanceof LinkBase : "Just check that we removed the right class";
        }

        // ...and add a fresh one (will force the NM to use msg.getRemote())
        dispStack.add(0, NetworkManager.getInstance());

        return dispStack;
    }

    /**
     * Register the next layer (used by
     * {@link com.ericsson.ssa.config.LayerHandler})
     *
     * @see com.ericsson.ssa.sip.Layer#registerNext(com.ericsson.ssa.sip.Layer)
     */
    public void registerNext(Layer layer) {
        nextLayer = layer;
    }

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

    public Reporter getReporter() {
        return _reporter;
    }
}
