/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2012-2014 ForgeRock Inc.
 */
package org.opends.server.snmp;


import javax.management.MBeanServer;

import com.sun.management.snmp.agent.SnmpMib;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.forgerock.i18n.slf4j.LocalizedLogger;

/**
 * The class represents the "DsMIB" group implementation.
 * The group is defined with the following oid: 1.3.6.1.2.1.66.
 */
public class DsMIBImpl extends DsMIB implements NotificationListener {

   /**
   * The serial version identifier required to satisfy the compiler because
   * this class implements the <CODE>java.io.Serializable</CODE> interface.
   * This value was generated using the <CODE>serialver</CODE> command-line
   * utility included with the Java SDK.
   */
  private static final long serialVersionUID = 6787374593664749374L;

  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /**
   * Directory Server MIB access.
   */
  private SnmpMib mib;

  /**
   * Register or not the SNMP MBean.
   */
  private boolean registeredSnmpMBean = false;

  /**
   * List of DsTableEntries.
   * SNMP dsTable contains the list of Directory Servers
   */
  private Hashtable<ObjectName, DsEntry> dsTableEntries =
          new Hashtable<ObjectName, DsEntry>();

  /**
   * List of DsApplIfOpsTableEntries.
   * SNMP dsAppIfOpsTable provides summary statistics on the accesses
   * operations and errors for each application protocol interface
   * of a Directory Server
   */
  private Hashtable<ObjectName, DsEntry> dsApplIfOpsTableEntries =
          new Hashtable<ObjectName, DsEntry>();

  /**
   * List of DsIntTableEntries.
   * SNMP dsIntTable provides some useful information on the
   * interactions of the monitored DS with peer DS
   */
  private Hashtable<ObjectName, DsEntry> dsIntTableEntries =
          new Hashtable<ObjectName, DsEntry>();

  /**
   * Directory Server MBeanServer.
   */
  private MBeanServer server;

  /**
   * cn=monitor Mapping Class SNMP->MBean.
   */
  private SNMPMonitor monitor;

  /**
   * Start Table indexes.
   */
  private int applIndex = 1;
  private int applIfOpsIndex = 1;
  private int intIndex = 1;

  /**
   * Constructor for the "DsMIB" group.
   * If the group contains a table, the entries created through an SNMP SET
   * will not be registered in Java DMK.
   * @param myMib snmp mib
   * @param server where the MBeans are registered
   * @param registered if the MBeans should be registered in server
   */
  public DsMIBImpl(SnmpMib myMib, MBeanServer server, boolean registered) {
    super(myMib);
    this.mib = myMib;
    this.server = server;
    this.monitor = SNMPMonitor.getMonitor(server);
    this.registeredSnmpMBean = registered;
    this.dsTableEntries.clear();
    this.dsApplIfOpsTableEntries.clear();
    this.dsIntTableEntries.clear();
    // Initialize the MIB
    initDsTables();

    logger.trace("DsMIB Group Created");
  }

  /**
   * Returns the Set of ObjectName of all the created entries in all the Table.
   * @return The Set of ObjectName
   */
  @SuppressWarnings("unchecked")
  public Set<ObjectName> getEntriesObjectNames() {
    Set<ObjectName> results = new HashSet<ObjectName>();
    results.addAll(this.dsTableEntries.keySet());
    results.addAll(this.dsApplIfOpsTableEntries.keySet());
    results.addAll(this.dsIntTableEntries.keySet());
    return results;
  }

  /**
   * Returns the list of Created Entries.
   * @return Set of created entries
   */
  @SuppressWarnings("unchecked")
  public Set<ObjectName> getEntries() {
    Set results = new HashSet();
    results.addAll(this.dsTableEntries.values());
    results.addAll(this.dsApplIfOpsTableEntries.values());
    results.addAll(this.dsIntTableEntries.values());
    return results;
  }

  /**
   * handleNotification callback called when an MBeansServer Notification is
   * received.
   * @param notification received
   * @param handback The handback
   */
  public void handleNotification(Notification notification, Object handback) {
    if (notification instanceof MBeanServerNotification) {
      MBeanServerNotification notif = (MBeanServerNotification) notification;
      // Process the ConnectionHandler Registration
      if (notif.getType().equals(
              MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
        ObjectName name = notif.getMBeanName();
        if ((name.getKeyProperty("Rdn1").equals("cn-monitor")) &&
                (isAConnectionHandler(name))) {
          addRowInDsApplIfOpsTable(name);
        }
      }
      // Process the ConnectionHandler unregistration
      else if (notif.getType().equals(
              MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
        ObjectName name = notif.getMBeanName();
        if ((name.getKeyProperty("Rdn1").equals("cn-monitor")) &&
                (isAConnectionHandler(name))) {
          removeRowInDsApplIfOpsTable(name);
        }
      }
    }
  }

  /**
   * initDsTables in the dsMib Group.
   */
  private void initDsTables() {

    // Initialize the DSTable with one Entry : Current Directory Server
    initializeDsTable();

    // Initialize the DsApplIfOpsTable with one entry per Connection Handler.
    initializeDsApplIfOpsTable();

    // Register as listener of the MBeanServer Notification to process
    // new Connection Handler MBeans
    try {
      ObjectName name = new ObjectName(
              "JMImplementation:type=MBeanServerDelegate");
      this.server.addNotificationListener(name, this, null, null);
    } catch (Exception ex) {
        logger.traceException(ex);
    }
  }

  /**
   * initializeDsTable with the current Directory server instance.
   * Only one entry is created in this table
   **/
  private void initializeDsTable() {
    // Add the Current Directory Server Instance in the DsTable
    addRowInDsTable();
  }

  /**
   * initializeDsApplIfOpsTable with the already registered Connection
   * Handlers.
   **/
  private void initializeDsApplIfOpsTable() {
    // Get the list of ConnectionHandlers MBeans
    Set connectionHandlers = this.monitor.getConnectionHandlers();
    for (Iterator iter=connectionHandlers.iterator();iter.hasNext();) {
      ObjectName name = (ObjectName)iter.next();
      // Add the ConnectionHandler in the DsApplIfOpsTable
      addRowInDsApplIfOpsTable(name);
    }
  }

 /**
  * addRowInDsTable.
  * @return true if the entry has been added else false
  */
  private boolean addRowInDsTable() {

    try {

      // Create the entry
      DsTableEntryImpl entry = new DsTableEntryImpl(
              this.mib, this.server, this.applIndex);

      // if the entry already exists nothing to do
      if ((this.dsTableEntries.containsKey(entry.getObjectName())) ||
              (entry == null)) {
        return true;
      }

      // Add the entry in the table
      this.DsTable.addEntry(entry, entry.getObjectName());
      this.dsTableEntries.put(entry.getObjectName(), entry);
      if (this.registeredSnmpMBean) {
        // Register the SNMP OID MBean
        this.server.registerMBean(entry, entry.getObjectName());
      }
    } catch (Exception ex) {
        logger.traceException(ex);
        return false;
    }
    return true;
  }

  /**
   * addRowInDsIntTable Not Supported.
   * @return false (Not Supported for this current delivery)
   */
  private boolean addRowInDsIntTable() {
    return false;
  }

  /**
   * addRowInDsApplIfOpsTable.
   * @param connectionHandlerName to add
   * @return true if the entry has been added else false
   */
  private boolean addRowInDsApplIfOpsTable(ObjectName connectionHandlerName) {
    try {

      // Created the entry
      DsApplIfOpsEntryImpl entry = new DsApplIfOpsEntryImpl(
              this.mib, this.server, connectionHandlerName,
              this.applIndex, this.applIfOpsIndex);

      // If the entry already exists then nothing to do
      if ((this.dsApplIfOpsTableEntries.containsKey(entry.getObjectName())) ||
              (entry == null)) {
        return true;
      }
      // Add the entry in the Table
      this.DsApplIfOpsTable.addEntry(entry);
      this.dsApplIfOpsTableEntries.put(entry.getObjectName(), entry);
      this.applIfOpsIndex++;
      if (this.registeredSnmpMBean) {
        // Register the SNMP OID MBean in the MBeanServer
        this.server.registerMBean(entry, entry.getObjectName());
      }
    } catch (Exception ex) {
        logger.traceException(ex);
        return false;
    }
    return true;
  }

  /**
   * removeRowInDsApplIfOpsTable.
   * @param connectionHandlerName
   * @return true if the entry has been removed else false
   */
  private boolean removeRowInDsApplIfOpsTable(ObjectName connectionHandlerName){
    try {
      // Check if the entry is known
      if (!this.dsApplIfOpsTableEntries.containsKey(connectionHandlerName)) {
        return false;
      }
      DsApplIfOpsEntryImpl entry = (DsApplIfOpsEntryImpl)
              this.dsApplIfOpsTableEntries.get(connectionHandlerName);

      this.DsApplIfOpsTable.removeEntry(entry);
      this.dsApplIfOpsTableEntries.remove(connectionHandlerName);
      this.server.unregisterMBean(entry.getObjectName());
      return true;
    } catch (Exception ex) {
      return false;
    }
  }


/**
 * isAConnectionHandler allows to check if the Mbean is a Connection Handler.
 * @param name of the MBean
 * @return true if the MBean is a Connection Handler else false
 */
  private boolean isAConnectionHandler(ObjectName name) {
    // First level
    String Rdn2 = name.getKeyProperty("Rdn2");
    // Second level
    String Rdn3 = name.getKeyProperty("Rdn3");
    if ((Rdn3==null) || (Rdn3.length()==0)) {
      if ((Rdn2.contains("Connection_Handler")) &&
            (!(Rdn2.endsWith("_Statistics")))) {
         return true;
      }
    }
    return false;
  }
}
