/*
 * 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.
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 */
package org.jvnet.glassfish.comms.clb.admin;

import org.jvnet.glassfish.comms.clb.core.CLBRuntimeException;
import org.jvnet.glassfish.comms.clb.core.Controller;
import org.jvnet.glassfish.comms.clb.core.ControllerInitializer;
import org.jvnet.glassfish.comms.util.LogUtil;

import org.w3c.dom.DOMException;

import org.xml.sax.SAXException;

import java.io.*;

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

import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;


/**
 * This class is responsible for monitoring for configuration changes both through DAS changes and
 * by polling and periodic checking for changes to converged-loadbalancer.xml.
 */
public class CLBConfigurator {
    private static final LogUtil logger = new LogUtil(CLBConfigurator.class);
    static CLBConfigurator sigletonCLBConfigurator = null;
    File lbConfig = null;
    Date lastModifiedDate = null;
    Loadbalancer lb;
    ObjectFactory configFactory;
    ControllerInitializer sipControllerInitializer;
    ControllerInitializer httpControllerInitializer;

    /**
     * public constructor to create a new CLBConfigurator instance
     *
     */
    private CLBConfigurator() {
        configFactory = new ObjectFactory();
    }

    public void init(File lbConfig,
        ControllerInitializer sipControllerInitializer,
        ControllerInitializer httpControllerInitializer) {
        logger.infoMsg(
            "Starting the initialization. ConveredLoadbalancer.xml location ... " +
            lbConfig.getName());
        this.sipControllerInitializer = sipControllerInitializer;
        this.httpControllerInitializer = httpControllerInitializer;
        this.lbConfig = lbConfig;

        FileInputStream fin = null;

        try {
            fin = new FileInputStream(lbConfig);
            load(fin);
            reconfig();
            logger.infoMsg("Initialization completed sucessfully");
        } catch (FileNotFoundException e) {
            logger.logMsg(Level.SEVERE, e.getMessage());
        } catch (Exception e) {
            logger.logMsg(Level.SEVERE,
                "Exception occured while trying to load the xml file. " +
                e.getMessage());
        } finally {
            try {
                if (fin != null) {
                    fin.close();
                }
            } catch (IOException e) {
                //no op
            }
        }
    }

    /**
      * Method to load the configration from the inputStream
     */
    public void load(InputStream in)
        throws JAXBException, DOMException, ParserConfigurationException,
            IOException, SAXException {
        lb = configFactory.load(in);
    }

    /**
     * Method to force a reload of the configration from the XML file
     */
    public void reload() {
        InputStream in = null;

        try {
            in = new FileInputStream(lbConfig);
            load(in);
            reconfig();
        } catch (FileNotFoundException e) {
            logger.logMsg(Level.SEVERE, e.getMessage());
        } catch (Exception e) {
            logger.logMsg(Level.SEVERE,
                "Exception occured while trying to reload the xml file. " +
                e.getMessage());
        }
    }

    private void reconfig() {
        assert (sipControllerInitializer != null) &&
        (httpControllerInitializer != null);

        try {
            if (lb != null) {
                List clusters = lb.getCluster();

                if ((clusters == null) || (clusters.size() == 0)) {
                    logger.logMsg(Level.WARNING,
                        "There are no clusters defined in this configuration ... Exiting reconfig");

                    return;
                }

                Controller sipController = sipControllerInitializer.createController();
                Controller httpController = httpControllerInitializer.createController();
                logger.infoMsg("Adding cluster info ... There are " +
                    clusters.size() + " clusters in the configuration");

                for (int i = 0; i < clusters.size(); i++) {
                    Cluster cluster = (Cluster) clusters.get(i);
                    addCluster(cluster.getName(), cluster.getSelfLoadbalance(),
                        sipController, httpController);

                    List instances = cluster.getInstance();
                    logger.infoMsg("Adding Instance info ... There are " +
                        instances.size() + " instances in the configuration");
                    addInstances(cluster.getName(), instances, sipController,
                        httpController);

                    List webModules = cluster.getWebModule();
                    logger.infoMsg("Adding webModules info ... There are " +
                        webModules.size() + " webModules in the configuration");
                    addWebModules(cluster.getName(), webModules, sipController,
                        httpController);

                    logger.infoMsg("Setting the Policies ... HTTP Policy " +
                        lb.getHttpPolicy() + " SIP Policy " +
                        lb.getSipPolicy());
                    setPolicies(lb.getHttpPolicy(), lb.getSipPolicy(),
                        sipController, httpController);

                    logger.infoMsg("Setting the DCR filename " +
                        lb.getDcrFile());
                    setDCRFileName(lb.getDcrFile(), sipController,
                        httpController);

                    logger.infoMsg(
                        "Configuration Successful. Setting the SIP and HTTP Controllers");
                    sipControllerInitializer.setController(sipController);
                    httpControllerInitializer.setController(httpController);
                }
            }
        } catch (CLBRuntimeException e) {
            logger.logMsg(Level.SEVERE, e,
                "Configuration failed. Exiting without applying changes");
        }
    }

    private void addCluster(String clusterName, String selfLoadbalance,
        Controller sipController, Controller httpController)
        throws CLBRuntimeException {
        boolean bselfLoadbalance = Boolean.parseBoolean(selfLoadbalance);
        logger.infoMsg("Adding Cluster " + clusterName +
            " with selfloadbalance option " + selfLoadbalance);

        try {
            sipController.addCluster(clusterName, bselfLoadbalance);
            httpController.addCluster(clusterName, bselfLoadbalance);
        } catch (CLBRuntimeException e) {
            logger.logMsg(Level.SEVERE,
                "CLBRuntimeException occured while trying to add cluster " +
                clusterName + " " + e.getMessage());
            throw e;
        }
    }

    private void addInstances(String clusterName, List instances,
        Controller sipController, Controller httpController)
        throws CLBRuntimeException {
        if (instances == null) {
            return;
        }

        for (int i = 0; i < instances.size(); i++) {
            Instance instance = (Instance) instances.get(i);
            logger.infoMsg("Parsing instance " + instance.getName());

            String strListeners = instance.getListeners();
            String[] listeners = parseListeners(strListeners);
            addInstance(clusterName, instance.getName(), instance.enabled,
                instance.getDisableTimeoutInMinutes(), listeners,
                sipController, httpController);
        }
    }

    private String[] parseListeners(String listeners) {
        if (listeners == null) {
            return new String[0];
        }

        StringTokenizer tokenizer = new StringTokenizer(listeners, " ");
        List<String> listenersList = new ArrayList<String>();

        while (tokenizer.hasMoreTokens()) {
            String listener = tokenizer.nextToken();
            logger.infoMsg("Parsing listener " + listener);
            listenersList.add(listener);
        }

        return listenersList.toArray(new String[listenersList.size()]);
    }

    private void addInstance(String clusterName, String instanceName,
        String strEnabled, String strTimeoutInMinutes, String[] listeners,
        Controller sipController, Controller httpController)
        throws CLBRuntimeException {
        boolean enabled = Boolean.parseBoolean(strEnabled);
        int timeoutInMinutes = Integer.parseInt(strTimeoutInMinutes);
        logger.infoMsg("Adding Instance - " + instanceName + " to Cluster " +
            clusterName);

        try {
            sipController.addInstance(clusterName, instanceName, enabled,
                timeoutInMinutes, listeners);
            httpController.addInstance(clusterName, instanceName, enabled,
                timeoutInMinutes, listeners);
        } catch (CLBRuntimeException e) {
            logger.logMsg(Level.SEVERE,
                "CLBRuntimeException occured while trying to add instance " +
                instanceName + " " + e.getMessage());
            throw e;
        }
    }

    private void addWebModules(String clusterName, List webModules,
        Controller sipController, Controller httpController)
        throws CLBRuntimeException {
        assert webModules != null;

        for (int i = 0; i < webModules.size(); i++) {
            WebModule webModule = (WebModule) webModules.get(i);
            addWebModule(clusterName, webModule.getContextRoot(),
                sipController, httpController);
        }
    }

    private void addWebModule(String clusterName, String contextRoot,
        Controller sipController, Controller httpController)
        throws CLBRuntimeException {
        try {
            logger.infoMsg("Adding web module with context root " +
                contextRoot + " to cluster " + clusterName);
            httpController.addWebModule(clusterName, contextRoot);
        } catch (CLBRuntimeException e) {
            logger.logMsg(Level.SEVERE,
                "CLBRuntimeException occured while trying to add web module " +
                contextRoot + " " + e.getMessage());
            throw e;
        }
    }

    private void setPolicies(String HTTPPolicy, String SIPPolicy,
        Controller sipController, Controller httpController) {
        httpController.setPolicy(HTTPPolicy);
        sipController.setPolicy(SIPPolicy);
    }

    private void setDCRFileName(String name, Controller sipController,
        Controller httpController) {
        httpController.setDCRFileName(name);
        sipController.setDCRFileName(name);
    }

    public String getDCRFileName() {
        if (lb == null) {
            return null;
        }

        return lb.getDcrFile();
    }

    public boolean isSelfLoadbalanced() {
        if ((lb != null) && (lb.getCluster() != null) &&
                (lb.getCluster().get(0) != null)) {
            return new Boolean(lb.getCluster().get(0).getSelfLoadbalance()).booleanValue();
        }

        return false;
    }

    /**
     * Method to check if the converged-loadbalancer.xml has changed
     *
     * @return a boolean indicating whether the configuration has changed
     */
    protected boolean checkIfConfigChanged() {
        if (lastModifiedDate.before(new Date(this.lbConfig.lastModified()))) {
            return true;
        }

        return false;
    }

    public static CLBConfigurator getInstance() {
        if (sigletonCLBConfigurator == null) {
            sigletonCLBConfigurator = new CLBConfigurator();
        }

        return sigletonCLBConfigurator;
    }
}
