/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © 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;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;

import org.apache.myfaces.trinidad.component.core.layout.CorePanelGroupLayout;
import org.apache.myfaces.trinidad.component.core.nav.CoreNavigationPane;

import ilog.cpl.IlpNetwork;
import ilog.cpl.network.IlpDefaultNetworkSelectionModel;
import ilog.cpl.network.IlpGeographicPositionConverter;
import ilog.cpl.network.NetworkSelectionEvent;
import ilog.cpl.network.NetworkSelectionListener;
import ilog.cpl.service.IlpContext;
import ilog.tgo.faces.network.dhtml.component.IltFacesDHTMLNetworkView;
import ilog.views.IlvRect;
import ilog.views.faces.dhtml.component.IlvFacesDHTMLOverview;
import ilog.views.maps.format.IlvMapDataPathManager;
import ilog.views.maps.srs.coordsys.IlvGeographicCoordinateSystem;
import ilog.views.maps.srs.coordtrans.IlvCoordinateTransformation;
import monitoring.shared.LoggingUtils;
import monitoring.shared.drilldown.AbstractDrillDownManager;
import monitoring.shared.drilldown.NetworkDrillDownManager;
import monitoring.web.AbstractSampleContext;
import monitoring.web.SampleConstants;
import monitoring.web.controls.popup.NetworkContextualMenuFactory;
import monitoring.web.controls.popup.ServiceContextualMenuFactory;
import monitoring.web.utils.MapsPathResolver;
import monitoring.web.utils.WebUtils;
import shared.ResourceUtils;

/**
 * Contains all the network related controls in the sample.
 */
public class NetworkControls {

  private static final Map<String, IlvRect> visibleAreas = new HashMap<String, IlvRect>();
  public static final String NETWORK_ROOT_VISIBLE_AREA = "NETWORK_ROOT";
  public static final String SERVICE_DETAIL_ROOT_VISIBLE_AREA = "SERVICE_DETAIL_ROOT";

  static {

    // This only needs to be done once at the static level
    // Ensure that we can find all maps under the resources/backgrounds
    // directory
    try {
      ServletContext servletContext = WebUtils.getServletContext();
      String backgroundsDirectory = servletContext.getRealPath("/resources/backgrounds");
      IlvMapDataPathManager.AddDataPathResolver(new MapsPathResolver(backgroundsDirectory));
    } catch (Exception e) {
      LoggingUtils.getSampleLogger().log(Level.SEVERE,
          "Could not properly backgrounds with this exception: " + e.getLocalizedMessage());
      e.printStackTrace();
    }

    // Setup the visible areas

    // Network Module
    visibleAreas.put(NETWORK_ROOT_VISIBLE_AREA, new IlvRect(-11, -54.1, 26.4, 16.5));
    // Portugal
    visibleAreas.put("NetA", new IlvRect(-11.09, -42.35, 6.07, 3.79));
    visibleAreas.put("NetA_NS1", new IlvRect(-10.03, -43.06, 4.96, 3.10));
    // France
    visibleAreas.put("NetB", new IlvRect(-2.04, -50.19, 12.4, 7.75));
    visibleAreas.put("NetB_NET2", new IlvRect(4.23, -44.92, 4.96, 3.10));
    visibleAreas.put("NetB_NS2", new IlvRect(0.53, -51.01, 4.96, 3.10));
    // UK
    visibleAreas.put("NetC", new IlvRect(-6.82, -55.81, 11.16, 6.97));

    // Service Module
    visibleAreas.put(SERVICE_DETAIL_ROOT_VISIBLE_AREA, new IlvRect(-14.77, -59.80, 33.06, 24.80));
    visibleAreas.put("Service1", new IlvRect(-1.79, -49.50, 9.60, 7.20));
    visibleAreas.put("Service2", new IlvRect(-1.62, -50.54, 10.49, 7.87));
    visibleAreas.put("Service3", new IlvRect(6.74, -53.60, 8.26, 6.20));
    visibleAreas.put("Service4", new IlvRect(-8.45, -43.34, 9.24, 6.93));
    visibleAreas.put("Service5", new IlvRect(-9.93, -42.04, 4.96, 3.72));
    visibleAreas.put("Service6", new IlvRect(-10.49, -44.27, 9.92, 7.44));
    visibleAreas.put("Service7", new IlvRect(-11.09, -48.44, 16.53, 12.40));
    visibleAreas.put("Service8", new IlvRect(-11.23, -42.63, 6.2, 4.65));
    visibleAreas.put("Service9", new IlvRect(-11.40, -41.13, 4.96, 3.72));
    visibleAreas.put("Service10", new IlvRect(-11.68, -41.44, 4.96, 3.72));
  }

  // Network related controls
  private IltFacesDHTMLNetworkView networkNetworkView;
  private IltFacesDHTMLNetworkView serviceNetworkView;
  private IltFacesDHTMLNetworkView serviceDetailNetworkView;

  private IlvFacesDHTMLOverview serviceOverview;
  private IlvFacesDHTMLOverview networkOverview;

  private CorePanelGroupLayout serviceOverviewPanel;
  private CorePanelGroupLayout networkOverviewPanel;

  private CoreNavigationPane tabs;

  private String networkNetworkDisplayLabel;
  private String networkServiceDisplayLabel;
  private String serviceDetailNetworkWindowTitle;

  private NetworkDrillDownManager networkDrillDownManager;

  private NetworkContextualMenuFactory networkContextualMenuFactory;
  private ServiceContextualMenuFactory serviceContextualMenuFactory;

  private NetworkDisplayLabelUpdater networkDisplayLabelUpdater;
  private ServiceDisplayLabelUpdater serviceDisplayLabelUpdater;

  private IlvRect serviceDetailNetworkDisplayedArea;
  private IlvRect networkNetworkDisplayedArea;

  private static int DEFAULT_SERVICES_DETAIL_VIEW_WIDTH = 450;
  private static int DEFAULT_SERVICES_DETAIL_VIEW_HEIGHT = 200;

  private int serviceDetailViewWidth;
  private int serviceDetailViewHeight;

  private boolean networkNetworkVisibleAreaChanged = false;

  private NetworkViewSelectionPropagator networkViewSelectionPropagator;

  /**
   * Creates all the network related controls.
   */
  public NetworkControls() {

    FacesContext facesContext = FacesContext.getCurrentInstance();

    // Create the network network view
    networkNetworkView = (IltFacesDHTMLNetworkView) facesContext.getApplication()
        .createComponent(IltFacesDHTMLNetworkView.getComponentType());

    // This is so because at the time when the view is being created
    // in this sample, the ID of the created JSF component is not yet in synch
    // with the JSP page, so we need to specify it, to ensure that the framework
    // can properly find its components
    networkNetworkView.setId(SampleConstants.NETWORK_NETWORK_COMPONENT_ID);

    // Create the service network view
    serviceNetworkView = (IltFacesDHTMLNetworkView) facesContext.getApplication()
        .createComponent(IltFacesDHTMLNetworkView.getComponentType());

    // Same as network network view
    serviceNetworkView.setId(SampleConstants.SERVICE_NETWORK_COMPONENT_ID);

    // Create the network network view
    serviceDetailNetworkView = (IltFacesDHTMLNetworkView) facesContext.getApplication()
        .createComponent(IltFacesDHTMLNetworkView.getComponentType());

    // Same as network network view
    serviceDetailNetworkView.setId(SampleConstants.SERVICE_DETAIL_NETWORK_COMPONENT_ID);

    // Create the selection propagators
    networkViewSelectionPropagator = new NetworkViewSelectionPropagator();

    // Create the network drill down manager
    networkDrillDownManager = new NetworkDrillDownManager();

    // Create contextual menus
    serviceContextualMenuFactory = new ServiceContextualMenuFactory();
    createNetworkContextualMenu();

    // Create the label updater
    networkDisplayLabelUpdater = new NetworkDisplayLabelUpdater();
    serviceDisplayLabelUpdater = new ServiceDisplayLabelUpdater();
  }

  //////////////////////////////////////////////////////////////////////////////
  // Initialization
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Initializes the network related controls.
   */
  public void initialize(AbstractSampleContext sampleContext) {

    // 1- Update the components with the proper TGO context
    IlpContext context = sampleContext.getTgoContext();

    // This is needed as the settings from the JSPf tag have not yet been
    // applied
    // to this faces component
    networkNetworkView.setContext(context);
    serviceNetworkView.setContext(context);
    serviceDetailNetworkView.setContext(context);

    // 2- Apply default stylesheets
    networkNetworkView.setStyleSheets(SampleConstants.NETWORK_NETWORK_STYLESHEETS);
    serviceNetworkView.setStyleSheets(SampleConstants.SERVICE_NETWORK_STYLESHEETS);
    serviceDetailNetworkView.setStyleSheets(SampleConstants.SERVICE_DETAIL_NETWORK_STYLESHEETS);

    // 3- Configure the selection propagator for the network network
    networkViewSelectionPropagator.setApplicationContext(sampleContext);

    // Find the Network network
    IlpNetwork networkNetwork = null;
    IlpNetwork serviceNetwork = null;
    IlpNetwork serviceDetailNetwork = null;
    try {
      networkNetwork = getNetworkNetworkView().getNetwork();
      serviceNetwork = getServiceNetworkView().getNetwork();
      serviceDetailNetwork = getServiceDetailNetworkView().getNetwork();
    } catch (Exception e) {
      LoggingUtils.getSampleLogger().log(Level.SEVERE,
          "Could not find the Network Views with this exception:" + e.getLocalizedMessage());
      e.printStackTrace();
    }

    // Install Geographic position converter
    IlvCoordinateTransformation transformation = IlvCoordinateTransformation
        .CreateTransformation(IlvGeographicCoordinateSystem.KERNEL, IlvGeographicCoordinateSystem.WGS84);
    networkNetwork.setPositionConverter(new IlpGeographicPositionConverter(transformation, true));
    serviceDetailNetwork.setPositionConverter(new IlpGeographicPositionConverter(transformation, true));

    // Install selection model for the Service network
    serviceNetwork.setSelectionModel(new ServiceViewSelectionModel());

    // Install the propagator on the network network
    networkNetwork.getSelectionModel().addNetworkSelectionListener(networkViewSelectionPropagator);
    serviceNetwork.getSelectionModel().addNetworkSelectionListener(networkViewSelectionPropagator);

    // 4- Configure the network drill down manager
    // Set the component
    networkDrillDownManager.setComponent(networkNetwork);
    // Set the tree datasource (which is shared with the network)
    networkDrillDownManager
        .setTreeDataSource(sampleContext.getDataStructures().getDataSources().getNetworkDataSource());

    // This allows the drilldown triggered by the table and tree know
    // when the visible area has changed, and thus re-fit view accordingly
    networkDrillDownManager.addPropertyChangeListener(new PropertyChangeListener() {
      Override
      public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName() == AbstractDrillDownManager.CURRENT_ROOT) {
          setNetworkNetworkVisibleAreaChanged(true);
        }
      }
    });

    // 5- Configure contextual menu

    // Now initialize them...
    networkContextualMenuFactory.initialize(sampleContext);
    serviceContextualMenuFactory.initialize(sampleContext);

    // Network
    networkContextualMenuFactory.setTargetFacesViewIdentifier(SampleConstants.NETWORK_NETWORK_COMPONENT_ID);

    // Service
    serviceContextualMenuFactory.setTargetFacesViewIdentifier(SampleConstants.SERVICE_NETWORK_COMPONENT_ID);

    // 7- Configure and install listener to udpate the network labels
    networkDisplayLabelUpdater.setApplicationContext(sampleContext);
    serviceDisplayLabelUpdater.setApplicationContext(sampleContext);

    networkDrillDownManager.addPropertyChangeListener(AbstractDrillDownManager.CURRENT_ROOT,
        networkDisplayLabelUpdater);

    try {
      serviceNetworkView.getNetwork().getSelectionModel().addNetworkSelectionListener(serviceDisplayLabelUpdater);
    } catch (Exception e) {
      LoggingUtils.getSampleLogger().log(Level.WARNING,
          "Could not configure service display label update mechanism with this excpetion: \n"
              + e.getLocalizedMessage());
      e.printStackTrace();
    }

    setNetworkNetworkDisplayLabel(ResourceUtils.getString("labelNetworkRootLevel"));
    setServiceNetworkDisplayLabel(ResourceUtils.getString("labelServiceRootLevel"));

    // 8- Specify the default size of the service details view
    serviceDetailViewHeight = DEFAULT_SERVICES_DETAIL_VIEW_HEIGHT;
    serviceDetailViewWidth = DEFAULT_SERVICES_DETAIL_VIEW_WIDTH;

    // 9- Set default visible areas
    // Around Europe
    serviceDetailNetworkDisplayedArea = getVisibleAreaForObjectId(SERVICE_DETAIL_ROOT_VISIBLE_AREA);
    networkNetworkDisplayedArea = getVisibleAreaForObjectId(NETWORK_ROOT_VISIBLE_AREA);
  }

  public IlvRect getVisibleAreaForObjectId(Object objectId) {
    return (IlvRect) visibleAreas.get(objectId);
  }

  //////////////////////////////////////////////////////////////////////////////
  // Accessors and Modifiers
  //////////////////////////////////////////////////////////////////////////////
  public IltFacesDHTMLNetworkView getNetworkNetworkView() {
    return networkNetworkView;
  }

  public void setNetworkNetworkView(IltFacesDHTMLNetworkView networkNetworkView) {
    this.networkNetworkView = networkNetworkView;
  }

  public IltFacesDHTMLNetworkView getServiceNetworkView() {
    return serviceNetworkView;
  }

  public void setServiceNetworkView(IltFacesDHTMLNetworkView serviceNetworkView) {
    this.serviceNetworkView = serviceNetworkView;
  }

  public CoreNavigationPane getTabs() {
    return tabs;
  }

  public void setTabs(CoreNavigationPane tabs) {
    this.tabs = tabs;
  }

  public String getNetworkNetworkDisplayLabel() {
    return networkNetworkDisplayLabel;
  }

  public void setNetworkNetworkDisplayLabel(String networkNetworkDisplayLabel) {
    this.networkNetworkDisplayLabel = networkNetworkDisplayLabel;
  }

  public String getServiceNetworkDisplayLabel() {
    return networkServiceDisplayLabel;
  }

  public void setServiceNetworkDisplayLabel(String networkServiceDisplayLabel) {
    this.networkServiceDisplayLabel = networkServiceDisplayLabel;
  }

  public IlvFacesDHTMLOverview getNetworkOverview() {
    return networkOverview;
  }

  public void setNetworkOverview(IlvFacesDHTMLOverview networkOverview) {
    this.networkOverview = networkOverview;
  }

  public IlvFacesDHTMLOverview getServiceOverview() {
    return serviceOverview;
  }

  public void setServiceOverview(IlvFacesDHTMLOverview serviceOverview) {
    this.serviceOverview = serviceOverview;
  }

  public CorePanelGroupLayout getNetworkOverviewPanel() {
    return networkOverviewPanel;
  }

  public void setNetworkOverviewPanel(CorePanelGroupLayout networkOverviewPanel) {
    this.networkOverviewPanel = networkOverviewPanel;
  }

  public CorePanelGroupLayout getServiceOverviewPanel() {
    return serviceOverviewPanel;
  }

  public void setServiceOverviewPanel(CorePanelGroupLayout serviceOverviewPanel) {
    this.serviceOverviewPanel = serviceOverviewPanel;
  }

  public NetworkDrillDownManager getNetworkDrillDownManager() {
    return networkDrillDownManager;
  }

  public NetworkContextualMenuFactory getNetworkContextualMenuFactory() {
    return networkContextualMenuFactory;
  }

  public void setNetworkContextualMenuFactory(NetworkContextualMenuFactory networkContextualMenuFactory) {
    this.networkContextualMenuFactory = networkContextualMenuFactory;
  }

  public ServiceContextualMenuFactory getServiceContextualMenuFactory() {
    return serviceContextualMenuFactory;
  }

  public void setServiceContextualMenuFactory(ServiceContextualMenuFactory serviceContextualMenuFactory) {
    this.serviceContextualMenuFactory = serviceContextualMenuFactory;
  }

  public IltFacesDHTMLNetworkView getServiceDetailNetworkView() {
    return serviceDetailNetworkView;
  }

  public void setServiceDetailNetworkView(IltFacesDHTMLNetworkView serviceDetailNetworkView) {
    this.serviceDetailNetworkView = serviceDetailNetworkView;
  }

  public int getServiceDetailViewHeight() {
    return serviceDetailViewHeight;
  }

  public int getServiceDetailViewWidth() {
    return serviceDetailViewWidth;
  }

  public String getServiceDetailNetworkWindowTitle() {
    return serviceDetailNetworkWindowTitle;
  }

  public void setServiceDetailNetworkWindowTitle(String serviceNetworkWindowTitle) {
    this.serviceDetailNetworkWindowTitle = serviceNetworkWindowTitle;
  }

  public IlvRect getServiceDetailNetworkDisplayedArea() {
    return serviceDetailNetworkDisplayedArea;
  }

  public void setServiceDetailNetworkDisplayedArea(IlvRect serviceDetailNetworkDisplayedArea) {
    this.serviceDetailNetworkDisplayedArea = serviceDetailNetworkDisplayedArea;
  }

  public IlvRect getNetworkNetworkDisplayedArea() {
    return networkNetworkDisplayedArea;
  }

  public void setNetworkNetworkDisplayedArea(IlvRect networkNetworkDisplayedArea) {
    this.networkNetworkDisplayedArea = networkNetworkDisplayedArea;
  }

  public boolean getNetworkNetworkVisibleAreaChanged() {
    return networkNetworkVisibleAreaChanged;
  }

  public void setNetworkNetworkVisibleAreaChanged(boolean networkNetworkVisibleAreadChanged) {
    this.networkNetworkVisibleAreaChanged = networkNetworkVisibleAreadChanged;
  }

  //////////////////////////////////////////////////////////////////////////////
  // Private Methods
  //////////////////////////////////////////////////////////////////////////////
  /**
   * This contextual menu factory needs special care as different
   * implementations need to be used depending on the underlying OSS. So we read
   * it from a settings file.
   */
  private void createNetworkContextualMenu() {
    Exception x = null;
    try {
      String menuClassName = WebUtils.getSampleSetting(WebUtils.NETWORK_NETWORK_CONTEXTUAL_MENU_CLASS_NAME_SETTING);

      if (menuClassName != null) {

        Class<?> menuClass = Class.forName(menuClassName);

        Object menuInstance = menuClass.getDeclaredConstructor().newInstance();

        if (menuInstance instanceof NetworkContextualMenuFactory) {
          networkContextualMenuFactory = (NetworkContextualMenuFactory) menuInstance;
        }
      }
    } catch (Exception e) {
      // Handled below...
      x = e;
    }

    if (networkContextualMenuFactory == null) {
      LoggingUtils.getSampleLogger().log(Level.SEVERE,
          "Could not create contextual menu for network module. Check if the value of the property \" {0} \" is properly set in the \" {1} \" properties file",
          new Object[] { WebUtils.NETWORK_NETWORK_CONTEXTUAL_MENU_CLASS_NAME_SETTING,
              WebUtils.SAMPLE_SETTINGS_RESOURCE_BUNDLE_NAME });
      if (null != x) {
        x.printStackTrace();
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Internal Types
  //////////////////////////////////////////////////////////////////////////////
  /**
   * Custom selection model for the Service network, it does not fire
   * notifications when adding a Collection of objects. This is necessary to
   * prevent that these notifications reach the Trinidad tree.
   */
  static class ServiceViewSelectionModel extends IlpDefaultNetworkSelectionModel {
    private boolean disableNotification = false;

    /**
     * @Override
     */
    Override
    public void fireNetworkSelectionEvent(NetworkSelectionEvent event) {
      if (!disableNotification) {
        super.fireGraphSelectionEvent(event);
      }
    }

    /**
     * @Override
     */
    Override
    SuppressWarnings("rawtypes")
    public void setSelectedObjects(Collection objects) {
      disableNotification = true;
      try {
        super.setSelectedObjects(objects);
      } finally {
        disableNotification = false;
      }
    }

    /**
     * @Override
     */
    Override
    SuppressWarnings("rawtypes")
    public void setSelectedRepresentationObjects(Collection selected) {
      disableNotification = true;
      try {
        super.setSelectedRepresentationObjects(selected);
      } finally {
        disableNotification = false;
      }
    }
  }

  /**
   * Handles the propagation of selections in the network view to the other main
   * components (tree and table).
   */
  static class NetworkViewSelectionPropagator implements NetworkSelectionListener {
    private AbstractSampleContext sampleContext;

    public AbstractSampleContext getSampleContext() {
      return sampleContext;
    }

    public void setApplicationContext(AbstractSampleContext sampleContext) {
      this.sampleContext = sampleContext;
    }

    Override
    public void objectsAdded(NetworkSelectionEvent event) {
      if (WebUtils.isApplicationLive()) {
        sampleContext.getActionProviders().getSharedActions()
            .handleSelectionObjectAddedOnCurrentView(event.getObjects());
      }
    }

    Override
    public void objectsRemoved(NetworkSelectionEvent event) {
      if (WebUtils.isApplicationLive()) {
        sampleContext.getActionProviders().getSharedActions()
            .handleSelectionObjectRemovedOnCurrentView(event.getNetworkSelectionModel());
      }
    }
  }

  /**
   * Ensures that the label of network module's network display label.
   */
  class NetworkDisplayLabelUpdater implements PropertyChangeListener {
    private AbstractSampleContext sampleContext;

    public AbstractSampleContext getSampleContext() {
      return sampleContext;
    }

    public void setApplicationContext(AbstractSampleContext sampleContext) {
      this.sampleContext = sampleContext;
    }

    Override
    public void propertyChange(PropertyChangeEvent evt) {
      sampleContext.getActionProviders().getNetworkActions().updateNetworkNetworkDisplayLabel();
    }
  }

  /**
   * Ensures that the label of service module's network display label.
   */
  class ServiceDisplayLabelUpdater implements NetworkSelectionListener {
    private AbstractSampleContext sampleContext;

    public AbstractSampleContext getSampleContext() {
      return sampleContext;
    }

    public void setApplicationContext(AbstractSampleContext sampleContext) {
      this.sampleContext = sampleContext;
    }

    Override
    public void objectsAdded(NetworkSelectionEvent arg0) {
      if (WebUtils.isApplicationLive()) {
        sampleContext.getActionProviders().getNetworkActions().updateNetworkServiceDisplayLabel();
      }
    }

    Override
    public void objectsRemoved(NetworkSelectionEvent arg0) {
      if (WebUtils.isApplicationLive()) {
        sampleContext.getActionProviders().getNetworkActions().updateNetworkServiceDisplayLabel();
      }
    }
  }
}