/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.jvnet.glassfish.comms.clb.plugin;

import com.sun.enterprise.ee.cms.core.*;
import com.sun.enterprise.ee.cms.impl.client.FailureNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.JoinNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.PlannedShutdownActionFactoryImpl;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.*;
import com.sun.enterprise.ee.cms.core.GroupManagementService;
import com.sun.enterprise.server.ondemand.OnDemandServer;

import java.util.Properties;

/**
 *   This class is a singleton implementation of the cluster configuration monitor plugin. Users would need to register
 *   themselves as listeners by calling the registerListeners method.
 *   Access to the singleton is through the getInstance() method.
 */

public class ClusterConfigurationMonitorPlugin {

    //instance variables
    private String instanceName = null;
    private String clusterName = null;
    private ConfigContext configContext= null;
    private ClusterHealthCheckerThread gmsModuleHealthCheckerThread = null;

    private GroupManagementService gms;
    private boolean startedGMSModule;
    private FailureNotificationActionFactory failureNotificationActionFactory;
    private JoinNotificationActionFactory joinNotificationActionFactory;
    private PlannedShutdownActionFactory plannedShutdownActionFactory;
    private GMSCallBackImpl ipsgmsclientImpl;


    protected static ClusterConfigurationMonitorPlugin singletonPluginInstance = null;

    /**
     * The method to get a reference to the singleton implementation. The singleton instance is lazily initialized the
     * first time it is invoked.
     *
     * @param clbName- Name of the CLB for which this plugin is being configured.
     * @param initProperties- the properties to initialize this plugin
     * @return  the reference to the singleton instance
     * @throws ConfigException - if an exception occurs during initialization
     */
    public static ClusterConfigurationMonitorPlugin getInstance(String clbName, Properties initProperties)throws ConfigException
    {
           if (singletonPluginInstance == null){
              singletonPluginInstance = new  ClusterConfigurationMonitorPlugin(clbName, initProperties);
           }

        return singletonPluginInstance;
    }
    //private consturctor
    private ClusterConfigurationMonitorPlugin(String clbName, Properties initProperties) throws ConfigException
    {
        onInit(clbName, initProperties);
    }

    /**
     * Method to register a new listener.
     * @param listener - the listener that needs to be added
     */
    public void registerListener(ClusterConfigEventListener listener)
    {
        ipsgmsclientImpl.addListener(listener);
    }
      /**
     * Method to remove a particular listener
     * @param listener - the listener that needs to be removed
     */
    public void removeListener (ClusterConfigEventListener listener)
    {
        ipsgmsclientImpl.removeListener(listener);
    }

   /**
     * Method to cleanup all the GMS related threads and listeners during shut-down. 
     */

    public void cleanup()
    {
      if(startedGMSModule)
      {
        gms.removeActionFactory(failureNotificationActionFactory);
        gms.removeActionFactory(joinNotificationActionFactory);
        gms.removeActionFactory(plannedShutdownActionFactory);
        gms.shutdown(GMSConstants.shutdownType.INSTANCE_SHUTDOWN);
      }
    }

    //private helper methods...
    private void onInit(String clbName, Properties props)throws ConfigException
    {
        //Check if the current instance is a part of the specified CLB configuration
        if(isInstanceConfiguredInCLB(clbName))
        {
            startGMSModule();
            ipsgmsclientImpl = new GMSCallBackImpl(props);
            registerCallBackHandlers();
        }
    }

    private ConfigContext getConfigContext()
    {
        if (configContext == null)
            configContext = OnDemandServer.getServerContext().getConfigContext();

        return configContext;
    }

    private Config getConfig()
    {
        try {

            if (configContext == null ) {
                throw new RuntimeException("ConfigContext is null");
            }
            return  ServerHelper.getConfigForServer(configContext,getInstanceName());
        } catch (ConfigException ce) {
            //todo handle the exception;
        }
         return null;
    }

      private boolean isInstanceConfiguredInCLB(String clbName)
      {
          Config config = getConfig();
          //Check if CLB is configured for this instance

          if (config != null && config.getAvailabilityService() != null &&
                  config.getAvailabilityService().getConvergedLoadBalancer() != null)
          {
            return (config.getAvailabilityService().getConvergedLoadBalancer().getName().equals(clbName));
          }

          return false;
      }

      private String getInstanceName()
      {
          if (instanceName == null)
                 instanceName = OnDemandServer.getServerContext().getInstanceName();

          return instanceName;
      }

      private String getClusterName()
      {
        if(clusterName == null) {
          try{
                ConfigContext context = getConfigContext();
                String instanceName = getInstanceName();

                com.sun.enterprise.config.serverbeans.Cluster cluster =  ClusterHelper.getClusterForInstance(context, instanceName);
                clusterName = cluster != null ? cluster.getName() : null;

          }catch (ConfigException e)
          {
              //todo: add log
              clusterName = null;
          }
         }
          return clusterName;
      }

      private void startGMSModule() {

        Runnable gmsModule = GMSFactory.startGMSModule(getInstanceName(),
                                                       getClusterName(),
                                                       GroupManagementService.MemberType.SPECTATOR,
                                                       getGMSConfigProps());

        gmsModuleHealthCheckerThread = new ClusterHealthCheckerThread(gmsModule);
        gmsModuleHealthCheckerThread.start();

    }
    private Properties getGMSConfigProps()  {
        Properties props = new Properties();
        Config config = getConfig();

        try {
             com.sun.enterprise.config.serverbeans.Cluster cluster = ClusterHelper.getClusterForInstance(getConfigContext(), getInstanceName());

            String configRef = cluster.getConfigRef();
            config = ConfigAPIHelper.getConfigByName(configContext, configRef);

            com.sun.enterprise.config.serverbeans.GroupManagementService gmsConfig =
                config.getGroupManagementService();

            if(!gmsConfig.isEnabled())
               //todo: GMS is not enabled for this cluster. need to throw an exception here

            props.put(ServiceProviderConfigurationKeys.FAILURE_DETECTION_RETRIES.toString(),
                gmsConfig.getFdProtocolMaxTries());
            props.put(ServiceProviderConfigurationKeys.FAILURE_DETECTION_TIMEOUT.toString(),
                gmsConfig.getFdProtocolTimeoutInMillis());
            props.put(ServiceProviderConfigurationKeys.DISCOVERY_TIMEOUT.toString(),
                gmsConfig.getPingProtocolTimeoutInMillis());
            props.put(ServiceProviderConfigurationKeys.FAILURE_VERIFICATION_TIMEOUT.toString(),
                gmsConfig.getVsProtocolTimeoutInMillis());
            props.put(ServiceProviderConfigurationKeys.MULTICASTADDRESS.toString(),
                cluster.getHeartbeatAddress());
            props.put(ServiceProviderConfigurationKeys.MULTICASTPORT.toString(),
                cluster.getHeartbeatPort());
        } catch (ConfigException e) {
            //todo: log exception
        }

        return props;
    }
      private void registerCallBackHandlers() {
        startedGMSModule = false;
            try {
                gms = GMSFactory.getGMSModule(clusterName);
            } catch (GMSNotEnabledException ex) {
                // It means gms module is not started for this cluster

                startGMSModule();
                startedGMSModule = true;

            } catch (GMSException ex) {
                //todo handle this exception;
            }

        failureNotificationActionFactory = new FailureNotificationActionFactoryImpl(ipsgmsclientImpl);
        gms.addActionFactory(failureNotificationActionFactory);

        joinNotificationActionFactory = new JoinNotificationActionFactoryImpl(ipsgmsclientImpl);
        gms.addActionFactory(joinNotificationActionFactory);

        plannedShutdownActionFactory = new PlannedShutdownActionFactoryImpl(ipsgmsclientImpl);
        gms.addActionFactory(plannedShutdownActionFactory);
    }
}
class ClusterHealthCheckerThread extends Thread
{
     ClusterHealthCheckerThread(Runnable gmsModule){
            super(gmsModule);
        }
}