/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

package monitoring.web.controls.popup;

import ilog.cpl.IlpNetwork;
import ilog.cpl.datasource.structure.IlpChild;
import ilog.cpl.graph.IlpGraphView;
import ilog.cpl.model.IlpAttribute;
import ilog.cpl.model.IlpObject;
import ilog.cpl.model.IlpRepresentationObject;
import ilog.tgo.model.IltAlarm;
import ilog.tgo.model.IltObjectState;
import ilog.views.IlvRect;
import ilog.views.faces.dhtml.IlvDHTMLConstants;
import ilog.views.faces.dhtml.event.FacesMenuActionEvent;
import ilog.views.faces.dhtml.event.FacesViewActionListener;
import ilog.views.util.servlet.model.IlvMenu;
import ilog.views.util.servlet.model.IlvMenuSeparator;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.EventObject;
import java.util.List;
import java.util.logging.Level;

import monitoring.shared.LoggingUtils;
import monitoring.shared.drilldown.NetworkDrillDownManager;
import monitoring.web.AbstractSampleContext;
import monitoring.web.action.NetworkActionProvider;
import monitoring.web.controls.NetworkControls;
import monitoring.web.utils.WebUtils;
import shared.ResourceUtils;

/**
 * The contextual menu factory that is used in the network module's network view.
 */
public class NetworkContextualMenuFactory extends AbstractContextualMenuFactory {

  /**
   * Entity in charge of handing the changes in the alarm state of an object
   * given a contextual menu interaction.
   */
  protected AlarmStateActionListener alarmStateHandler;

  /**
   * Identifier that signals showing the details.
   */
  public static final String SHOW_DETAILS = "SHOW_DETAILS";

  /**
   * Identifier that signals showing associated services.
   */
  public static final String SHOW_SERVICES = "SHOW_SERVICES";

  //Listeners in charge of showing details, services and the enclosing network
  protected FacesViewActionListener showDetailsActionListener;
  protected FacesViewActionListener showServicesActionListener;

  /**
   * Creates its internal structures.
   */
  public NetworkContextualMenuFactory() {

    //Create alarm state handler
    alarmStateHandler = new AlarmStateActionListener();

    //Action listeners for the contextual menu  
    showDetailsActionListener = new ShowDetailsActionListener();
    showServicesActionListener = new ShowServicesActionListener();
  }

  /**
   * Initializes internal structures.
   * 
   * @see monitoring.web.controls.popup.AbstractContextualMenuFactory#initialize(monitoring.web.AbstractSampleContext)
   */
  public void initialize(AbstractSampleContext sampleContext) {

    super.initialize(sampleContext);

    //Sets the identifier of the actual contextual menu instance
    setContextualMenuFactoryIdentifier("sampleContext.controls.networkControls.networkContextualMenuFactory");

    alarmStateHandler.setApplicationContext(sampleContext);
  }

  /**
   * Network implementation.
   * 
   * @see monitoring.web.controls.popup.AbstractContextualMenuFactory#createMenu(java.lang.Object, java.lang.Object, java.lang.String)
   */
  public IlvMenu createMenu(Object graphicComponent, Object activeObject, String menuModelId) {

    IlvMenu menu = super.createMenu(graphicComponent, activeObject, menuModelId);

    //Create the menu to move up the hierarchy
    createHierarchicalNavigationMenuItems(menu);

    return menu;
  }

  /**
   * Returns the server side action binding that handles alarm state updates.
   */
  protected String getAlarmStateHandlerAction() {
    return "#{" + contextualMenuFactoryIdentifier + ".alarmStateHandler.processAlarmStateUpdate}";
  }

  /**
   * Network implementation.
   * 
   * @see monitoring.web.controls.popup.AbstractContextualMenuFactory#createViewSpecificMenuItems(ilog.views.util.servlet.model.IlvMenu, ilog.cpl.model.IlpObject, java.lang.String, ilog.cpl.graph.IlpGraphView)
   */
  protected void createViewSpecificMenuItems(IlvMenu root, IlpObject businessObject, 
      String menuModelId, IlpGraphView graphView) {

    //1- Create the "show" related menu items
    createShowMenuItems(root, businessObject, menuModelId, graphView);

    //2- Create the equipment menu items
    createAlarmMenuItems(root, businessObject, menuModelId, graphView);
  }

  /**
   * Creates the "show" menu items.
   *
   * @param root        The root pop-up menu
   * @param businessObject   The object under the pop-up menu
   * @param menuModelId The interactor currently selected
   */
  protected void createShowMenuItems(IlvMenu root, IlpObject businessObject,
      String menuModelId, IlpGraphView graphView) {

    boolean hasDetails = hasDetails(businessObject);

    if (hasDetails) {
      
      NetworkActionProvider networkActions = sampleContext.getActionProviders().getNetworkActions();
      
      if(networkActions.needToSwitchToInventoryDuringShowDetails(businessObject)) {
        //If it needs to switch modules then we need a full refresh
        this.createServerMenuItem(root, 
            ResourceUtils.getString("labelMenuItemShowEquipmentDetails"),
            ResourceUtils.getString("iconMenuItemShowDetails"),
            showDetailsActionListener, true);
      } else {
        //It will just do a local drilldown - no need for a full refresh
        this.createServerClientMenuItem(
            root,
            ResourceUtils.getString("labelMenuItemShowNetworkDetails"),
            ResourceUtils.getString("iconMenuItemShowDetails"),
            "#{sampleContext.actionProviders.networkActions.showDetails}",
            getUpdateComponentsJavaScriptAction(false, true, true, 
                getNetworkVisibleAreaForObject(businessObject.getIdentifier())),
            "SHOW_DETAILS", true,
            IlvDHTMLConstants.IMAGE_SERVLET_CONTEXT);
      }
    }

    boolean hasServices = hasServices(businessObject);
    if (hasServices) {

      this.createServerMenuItem(root, 
          ResourceUtils.getString("labelMenuItemShowServices"), 
          ResourceUtils.getString("iconMenuItemShowServices"),
          showServicesActionListener, true);
    }

    if (hasDetails || hasServices) {
      // Add a menu separator
      root.addChild(new IlvMenuSeparator());
    }
  }

  private String getNetworkVisibleAreaForObject(Object identifier) {
    IlvRect visibleArea = 
      getSampleContext().getActionProviders().getNetworkActions().getNetworkNetworkDisplayArea(identifier);
    
    return WebUtils.getStringForRect(visibleArea); 
  }
  
  /**
   * Network implementation.
   * 
   * @see monitoring.web.controls.popup.AbstractContextualMenuFactory#createModuleNavigationMenuItems(ilog.views.util.servlet.model.IlvMenu, java.lang.String, ilog.cpl.graph.IlpGraphView)
   */
  protected void createModuleNavigationMenuItems(IlvMenu root,
      String menuModelId, IlpGraphView graphView) {

    this.createServerMenuItem(root, 
        ResourceUtils.getString("labelMenuItemShowInventoryModule"), 
        ResourceUtils.getString("iconMenuItemShowInventoryModule"),
        showInventoryModuleActionListener, true);

    this.createServerMenuItem(root, 
        ResourceUtils.getString("labelMenuItemShowServiceModule"), 
        ResourceUtils.getString("iconMenuItemShowServiceModule"),
        showServiceModuleActionListener, true);
  }

  /**
   * Creates menu items related to hierarchical navigation. 
   */
  protected void createHierarchicalNavigationMenuItems(IlvMenu root) {

    if (!isShowingRoot()) {
      
      //Find the parent of the current root
      Object id = getIdOfCurrentRootParent(); 
      
      // Add a menu separator
      root.addChild(new IlvMenuSeparator());

      this.createServerClientMenuItem(
          root,
          ResourceUtils.getString("labelMenuItemShowEnclosingNetwork"),
          ResourceUtils.getString("iconMenuItemShowEnclosingNetwork"),
          "#{sampleContext.actionProviders.networkActions.showEnclosingNetwork}",
          getUpdateComponentsJavaScriptAction(false, true, true, 
              getNetworkVisibleAreaForObject(id)),
          "SHOW_ENCLOSING_NETWORK", true,
          IlvDHTMLConstants.IMAGE_SERVLET_CONTEXT);    
    }
  }

  private Object getIdOfCurrentRootParent() {
    Object id = null;
    try {
      
      IlpNetwork network = 
        sampleContext.getControls().getNetworkControls().getNetworkNetworkView().getNetwork();

      List<Object> currentOrigins = network.getAdapter().getOrigins();

      if(!currentOrigins.isEmpty()) {
        Object originId = currentOrigins.get(0);
        
        IlpChild childInterface = network.getDataSource().getChildInterface(originId);
        if(childInterface != null) {
          Object parentId = 
            childInterface.getParent(network.getDataSource().getObject(originId));
          
          id = (parentId==null) ? 
              NetworkControls.NETWORK_ROOT_VISIBLE_AREA : 
                parentId;

        } else {
          id = NetworkControls.NETWORK_ROOT_VISIBLE_AREA;
        }
      } else {
        LoggingUtils.getSampleLogger().log(
            Level.WARNING,
        "This method cannot be called while showing the root of the network");
      }
    } catch (Exception e) {
      LoggingUtils.getSampleLogger().log(
          Level.WARNING,
      "Could not find the root's parent with this exception:" + e.getLocalizedMessage());
    }
    return id;
  }
                                             
  /**
   * Checks if the provided object is an equipment detail.
   */
  protected boolean isEquipmentDetails(IlpObject businessObject) {

    NetworkDrillDownManager networkDrillDownManager = 
      sampleContext.getControls().getNetworkControls().getNetworkDrillDownManager();

    //This is a equipment details if we cannot drill in a group and can on a NE
    return !networkDrillDownManager.canDrillDownGroup(businessObject)
    && networkDrillDownManager.canDrillDownNE(businessObject);
  }

  /**
   * Checks if the provided object has details.
   */
  protected boolean hasDetails(IlpObject businessObject) {
    return sampleContext.getControls().getNetworkControls()
    .getNetworkDrillDownManager().canDrillDown(businessObject);
  }

  /**
   * Checks if the provided object has associated services.
   */
  protected boolean hasServices(IlpObject businessObject) {
    return sampleContext.getControls().getNetworkControls()
    .getNetworkDrillDownManager().canShowService(businessObject);
  }

  /**
   * Creates the alarm related menu items.
   *
   * @param root        The root pop-up menu
   * @param activeObj   The object under the pop-up menu
   * @param menuModelId The interactor currently selected
   */
  protected void createAlarmMenuItems(IlvMenu root, IlpObject businessObject,
      String menuModelId, IlpGraphView graphView) {

    int globalNumberOfAddedMenuItems = 0;

    IlvMenu alarmsMenu = new IlvMenu(
        ResourceUtils.getString("labelMenuAlarms"),
        ResourceUtils.getString("iconMenuItemAlarmsIcon"));

    //Only if we have a selected object that has an alarm state
    if ((null != businessObject)
        && (null != getObjectState(businessObject))) {

      IltAlarm.Severity[] severities = {IltAlarm.Severity.Critical,
          IltAlarm.Severity.Major, IltAlarm.Severity.Minor, 
          IltAlarm.Severity.Warning, IltAlarm.Severity.Cleared, 
          IltAlarm.Severity.Unknown};

      String[] alarmMenusResourceKeys = {"labelMenuAlarmsCritical",
          "labelMenuAlarmsMajor", "labelMenuAlarmsMinor", 
          "labelMenuAlarmsWarning", "labelMenuAlarmsCleared", 
      "labelMenuAlarmsUnknown"};

      boolean[] targetAckStates = {true, false};

      String[] ackAndUnAckMenusResourceKeys = {
          "labelMenuAlarmsAcknowledge",
      "labelMenuAlarmsUnAcknowledge"};

      for (int i = 0; i < ackAndUnAckMenusResourceKeys.length; i++) {
        String menuResourceKey = ackAndUnAckMenusResourceKeys[i];
        boolean targetAckState = targetAckStates[i];

        IlvMenu ackMenu = new IlvMenu(
            ResourceUtils.getString(menuResourceKey),
            ResourceUtils
            .getString((targetAckState)
                ? "iconMenuItemAcknowledgeIcon"
                    : "iconMenuItemUnAcknowledgeIcon"));

        int numberOfAckCreatedItems = 0;

        for (int j = 0; j < severities.length; j++) {

          int numberOfMenuItemsForSomeSeverityAlarms = 0;
          IltAlarm.Severity someSeverity = severities[j];

          List<List<String>> someSeverityAlarms = getAlarmsInformation(
              businessObject, someSeverity, targetAckState);

          IlvMenu someSeverityAlarmsMenu = new IlvMenu(
              ResourceUtils.getString(alarmMenusResourceKeys[j]),
              ResourceUtils
              .getString((targetAckState)
                  ? getMenuIconAcknowledgeResourceStringForSeverity(someSeverity)
                      : getMenuIconUnAcknowledgeResourceStringForSeverity(someSeverity))    
          );

          numberOfMenuItemsForSomeSeverityAlarms = createMenuItemsForSeverity(
              someSeverityAlarmsMenu, someSeverityAlarms,
              someSeverity, targetAckState);

          if (numberOfMenuItemsForSomeSeverityAlarms > 0) {

            //Add the sub menu
            ackMenu.addChild(someSeverityAlarmsMenu);

            //Update the counter
            numberOfAckCreatedItems += numberOfMenuItemsForSomeSeverityAlarms;
          }
        }

        if (numberOfAckCreatedItems > 0) {

          alarmsMenu.addChild(ackMenu);

          globalNumberOfAddedMenuItems += numberOfAckCreatedItems;
        }
      }

      if (globalNumberOfAddedMenuItems > 0) {

        //Save the alarms menu
        root.addChild(alarmsMenu);

        //Add a menu separator
        root.addChild(new IlvMenuSeparator());
      }
    }
  }

  /**
   * Returns the actual identifier for the provided IltAlarm.
   * <p>
   * This is needed as different OSS may implement its unique identifier 
   * scheme differently. This method gives the chance to for specializations of 
   * this factory for different OSS.  
   * <p>
   * The default implementation simply returns the value of 
   * <code>IltAlarm.getIdentifier()</code>.
   */
  protected String getAlarmIdentifier(IltAlarm alarm) {
    return (String)alarm.getIdentifier();
  }

  //////////////////////////////////////////////////////////////////////////////
  //Action Listeners
  //////////////////////////////////////////////////////////////////////////////

  SuppressWarnings("serial")
  public class ShowDetailsActionListener extends FacesViewActionListener {

    public ShowDetailsActionListener() {
      super(IlvDHTMLConstants.JSF_CONTEXT);
    }

    public void actionPerformed(EventObject event) throws Exception {

      // Event is instance of FacesMenuActionEvent
      FacesMenuActionEvent saEvt = (FacesMenuActionEvent) event;

      // Get active object
      IlpRepresentationObject obj = (IlpRepresentationObject) saEvt.getObject();

      if (obj != null) {
        sampleContext.getActionProviders().getNetworkActions()
        .showDetails(obj.getIlpObject());
      }
    }      
  }

  SuppressWarnings("serial")
  public class ShowServicesActionListener extends FacesViewActionListener {

    public ShowServicesActionListener() {
      super(IlvDHTMLConstants.JSF_CONTEXT);
    }

    public void actionPerformed(EventObject event) throws Exception {

      // Event is instance of FacesMenuActionEvent
      FacesMenuActionEvent saEvt = (FacesMenuActionEvent) event;

      // Get active object
      IlpRepresentationObject obj = (IlpRepresentationObject) saEvt.getObject();

      if (obj != null) {
        sampleContext.getActionProviders().getNetworkActions()
        .showServices(obj.getIlpObject());
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  //Private Methods
  //////////////////////////////////////////////////////////////////////////////

  private boolean isShowingRoot() {
    try {
      return sampleContext.getControls().getNetworkControls()
      .getNetworkNetworkView().getNetwork().getAdapter().getOrigins().isEmpty();
    } catch (Exception e) {
      LoggingUtils.getSampleLogger().log(
          Level.SEVERE,
          "Could not find if is it is showing the root with this exception:"
          + e.getLocalizedMessage());
      return true;
    }
  }

  private int createMenuItemsForSeverity(IlvMenu root, Collection<List<String>> alarms,
      IltAlarm.Severity severity, boolean targetAckState) {

    int numberOfCreatedItems = 0;

    if (!alarms.isEmpty()) {

      for (List<String> alarmInfo : alarms) {
        String alarmId = alarmInfo.get(0);
        String alarmLabel = alarmInfo.get(1);

        this.createServerClientMenuItem(
            root,
            alarmLabel,
            ResourceUtils.getString("iconMenuItemEmptyIcon"),
            getAlarmStateHandlerAction(),
            getUpdateComponentsJavaScriptAction(false, true, false, null), alarmId,
            true, IlvDHTMLConstants.IMAGE_SERVLET_CONTEXT);

        numberOfCreatedItems++;
      }
    }

    return numberOfCreatedItems;
  }

  private String getMenuIconAcknowledgeResourceStringForSeverity(
      IltAlarm.Severity severity) {
    String resourceString = "iconMenuItemAcknowledge";

    if (IltAlarm.Severity.Critical.equals(severity)) {
      resourceString += "Critical";
    } else if (IltAlarm.Severity.Major.equals(severity)) {
      resourceString += "Major";
    } else if (IltAlarm.Severity.Minor.equals(severity)) {
      resourceString += "Minor";
    } else if (IltAlarm.Severity.Warning.equals(severity)) {
      resourceString += "Warning";
    } else if (IltAlarm.Severity.Unknown.equals(severity)) {
      resourceString += "Unknown";
    } else if (IltAlarm.Severity.Cleared.equals(severity)) {
      resourceString += "Cleared";
    }

    return resourceString;
  }

  private String getMenuIconUnAcknowledgeResourceStringForSeverity(
      IltAlarm.Severity severity) {
    String resourceString = "iconMenuItemUnAcknowledge";

    if (IltAlarm.Severity.Critical.equals(severity)) {
      resourceString += "Critical";
    } else if (IltAlarm.Severity.Major.equals(severity)) {
      resourceString += "Major";
    } else if (IltAlarm.Severity.Minor.equals(severity)) {
      resourceString += "Minor";
    } else if (IltAlarm.Severity.Warning.equals(severity)) {
      resourceString += "Warning";
    } else if (IltAlarm.Severity.Unknown.equals(severity)) {
      resourceString += "Unknown";
    } else if (IltAlarm.Severity.Cleared.equals(severity)) {
      resourceString += "Cleared";
    }

    return resourceString;
  }

  private List<List<String>> getAlarmsInformation(IlpObject businessObject,
      IltAlarm.Severity severity, boolean targetAckState) {

    List<IltAlarm> alarms = sampleContext.getIntegrationProvider()
    .getAlarmProvider().getNetworkAlarms(businessObject.getIdentifier(), severity);

    List<List<String>> alarmsInformation = new ArrayList<List<String>>(alarms.size());

    SimpleDateFormat dateFormat = new SimpleDateFormat(
        ResourceUtils.getString("dateStringFormatAlarmRaised"));

    for (IltAlarm alarm : alarms) {
      if (alarm.getAlarmAckState() != targetAckState) {

        List<String> alarmInformation = new ArrayList<String>(2);

        //Build the menu item identifier so that it can be properly 
        //interpreted in the AlarmStateActionListener
        String idPrefix = ((targetAckState)
            ? AlarmStateActionListener.ACKNOWLEDGE
                : AlarmStateActionListener.UNACKNOWLEDGE)
                + AlarmStateActionListener.SEPARATOR;

        //Save the identifier
        alarmInformation.add(idPrefix + getAlarmIdentifier(alarm));

        //Save the raised date
        Date alarmRaisedDate = 
          (Date) alarm.getAttributeValue(IltAlarm.AlarmRaisedTimeAttribute);
        alarmInformation.add(dateFormat.format(alarmRaisedDate));

        alarmsInformation.add(alarmInformation);
      }
    }

    return alarmsInformation;
  }

  ////////////////////////////////////////////////////////////////////////////
  //ALARM STATE UTILS
  ////////////////////////////////////////////////////////////////////////////

  private IltObjectState getObjectState(IlpObject businessObject) {
    IlpAttribute attrib = 
      businessObject.getAttributeGroup().getAttribute("objectState");

    return (IltObjectState) businessObject.getAttributeValue(attrib);
  }

  //////////////////////////////////////////////////////////////////////////////
  //Accessors
  //////////////////////////////////////////////////////////////////////////////
  public AlarmStateActionListener getAlarmStateHandler() {
    return alarmStateHandler;
  }

}