/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package org.apache.geode.tools.pulse.internal.service;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import org.apache.geode.tools.pulse.internal.data.Cluster;
import org.apache.geode.tools.pulse.internal.data.PulseConstants;
import org.apache.geode.tools.pulse.internal.data.Repository;

/**
 * Class ClusterMembersRGraphService
 * 
 * This class contains implementations of getting List of Cluster members and their details
 * 
 * @since GemFire version 7.5
 */
@Component
@Service("ClusterMembersRGraph")
@Scope("singleton")
public class ClusterMembersRGraphService implements PulseService {

  private final ObjectMapper mapper = new ObjectMapper();

  // String constants used for forming a json response
  private final String CLUSTER = "clustor";
  private final String MEMBER_COUNT = "memberCount";
  private final String ID = "id";
  private final String NAME = "name";
  private final String DATA = "data";
  private final String MEMORY_USAGE = "memoryUsage";
  private final String CPU_USAGE = "cpuUsage";
  private final String REGIONS = "regions";
  private final String HOST = "host";
  private final String PORT = "port";
  private final String CLIENTS = "clients";
  private final String GC_PAUSES = "gcPauses";
  private final String GATEWAY_SENDER = "gatewaySender";
  private final String GATEWAY_RECEIVER = "gatewayReceiver";
  private final String LOAD_AVG = "loadAvg";
  private final String SOCKETS = "sockets";
  private final String THREADS = "threads";
  private final String NUM_THREADS = "numThreads";

  private final String MEMBER_NODE_TYPE_NORMAL = "Normal";
  private final String MEMBER_NODE_TYPE_WARNING = "Warning";
  private final String MEMBER_NODE_TYPE_ERROR = "Error";
  private final String MEMBER_NODE_TYPE_SEVERE = "Severe";
  private final String CHILDREN = "children";

  // traversing the alert array list and members which have severe, error or
  // warnings
  // alerts saving them in three different arraylists
  private List<String> severeAlertList;
  private List<String> errorAlertsList;
  private List<String> warningAlertsList;

  public ObjectNode execute(final HttpServletRequest request) throws Exception {

    // Reference to repository
    Repository repository = Repository.get();

    // get cluster object
    Cluster cluster = repository.getCluster();

    // json object to be sent as response
    ObjectNode responseJSON = mapper.createObjectNode();

    // cluster's Members
    responseJSON.put(this.CLUSTER,
        getPhysicalServerJson(cluster, repository.getJmxHost(), repository.getJmxPort()));
    responseJSON.put(this.MEMBER_COUNT, cluster.getMemberCount());

    // Send json response
    return responseJSON;
  }

  /**
   * function used for getting all members details in format of JSON Object array defined under a
   * cluster. This function create json based on the relation of physical host and members related
   * to it.
   * 
   * @param cluster
   * @param host
   * @param port
   * @return Array list of JSON objects for required fields of members in cluster
   */
  private ObjectNode getPhysicalServerJson(Cluster cluster, String host, String port) {
    Map<String, List<Cluster.Member>> physicalToMember = cluster.getPhysicalToMember();

    ObjectNode clusterTopologyJSON = mapper.createObjectNode();

    clusterTopologyJSON.put(this.ID, cluster.getClusterId());
    clusterTopologyJSON.put(this.NAME, cluster.getClusterId());
    ObjectNode data1 = mapper.createObjectNode();
    clusterTopologyJSON.put(this.DATA, data1);
    ArrayNode childHostArray = mapper.createArrayNode();
    DecimalFormat df2 = new DecimalFormat(PulseConstants.DECIMAL_FORMAT_PATTERN);

    updateAlertLists(cluster);

    for (Map.Entry<String, List<Cluster.Member>> physicalToMem : physicalToMember.entrySet()) {
      String hostName = physicalToMem.getKey();
      double hostCpuUsage = 0.0;
      long hostMemoryUsage = 0;
      double hostLoadAvg = 0.0;
      int hostNumThreads = 0;
      long hostSockets = 0;
      boolean hostSevere = false;
      boolean hostError = false;
      boolean hostWarning = false;
      String hostStatus;
      ObjectNode childHostObject = mapper.createObjectNode();
      childHostObject.put(this.ID, hostName);
      childHostObject.put(this.NAME, hostName);

      ArrayNode membersArray = mapper.createArrayNode();

      List<Cluster.Member> memberList = physicalToMem.getValue();
      for (Cluster.Member member : memberList) {
        ObjectNode memberJSONObj = mapper.createObjectNode();

        memberJSONObj.put(this.ID, member.getId());
        memberJSONObj.put(this.NAME, member.getName());

        ObjectNode memberData = mapper.createObjectNode();

        memberData.put("gemfireVersion", member.getGemfireVersion());

        Long currentHeap = member.getCurrentHeapSize();
        Long usedHeapSize = cluster.getUsedHeapSize();

        if (usedHeapSize > 0) {
          double heapUsage = (currentHeap.doubleValue() / usedHeapSize.doubleValue()) * 100;

          memberData.put(this.MEMORY_USAGE, Double.valueOf(df2.format(heapUsage)));
        } else
          memberData.put(this.MEMORY_USAGE, 0);

        double currentCPUUsage = member.getCpuUsage();

        memberData.put(this.CPU_USAGE, Double.valueOf(df2.format(currentCPUUsage)));
        memberData.put(this.REGIONS, member.getMemberRegions().size());
        memberData.put(this.HOST, member.getHost());
        if ((member.getMemberPort() == null) || (member.getMemberPort().equals(""))) {
          memberData.put(this.PORT, "-");
        } else {
          memberData.put(this.PORT, member.getMemberPort());
        }
        memberData.put(this.CLIENTS, member.getMemberClientsHMap().size());
        memberData.put(this.GC_PAUSES, member.getGarbageCollectionCount());
        memberData.put(this.NUM_THREADS, member.getNumThreads());

        // Host CPU Usage is aggregate of all members cpu usage
        // hostCpuUsage = hostCpuUsage + currentCPUUsage;
        hostCpuUsage = member.getHostCpuUsage();
        hostMemoryUsage = hostMemoryUsage + member.getCurrentHeapSize();
        hostLoadAvg = member.getLoadAverage();
        hostNumThreads = member.getNumThreads();
        hostSockets = member.getTotalFileDescriptorOpen();

        // defining the status of Member Icons for R Graph based on the alerts
        // created for that member
        String memberNodeType = "";
        // for severe alert
        if (severeAlertList.contains(member.getName())) {
          memberNodeType = getMemberNodeType(member, this.MEMBER_NODE_TYPE_SEVERE);
          if (!hostSevere) {
            hostSevere = true;
          }
        } else if (errorAlertsList.contains(member.getName())) {
          // for error alerts
          memberNodeType = getMemberNodeType(member, this.MEMBER_NODE_TYPE_ERROR);
          if (!hostError) {
            hostError = true;
          }
        }
        // for warning alerts
        else if (warningAlertsList.contains(member.getName())) {
          memberNodeType = getMemberNodeType(member, this.MEMBER_NODE_TYPE_WARNING);
          if (!hostWarning) {
            hostWarning = true;
          }
        } else {
          memberNodeType = getMemberNodeType(member, this.MEMBER_NODE_TYPE_NORMAL);
        }

        memberData.put("nodeType", memberNodeType);
        memberData.put("$type", memberNodeType);
        memberData.put(this.GATEWAY_SENDER, member.getGatewaySenderList().size());
        if (member.getGatewayReceiver() != null) {
          memberData.put(this.GATEWAY_RECEIVER, 1);
        } else {
          memberData.put(this.GATEWAY_RECEIVER, 0);
        }
        memberJSONObj.put(this.DATA, memberData);
        memberJSONObj.put(this.CHILDREN, mapper.createArrayNode());
        membersArray.add(memberJSONObj);
      }
      ObjectNode data = mapper.createObjectNode();

      data.put(this.LOAD_AVG, Double.valueOf(df2.format(hostLoadAvg)));
      data.put(this.SOCKETS, hostSockets);
      data.put(this.THREADS, hostNumThreads);
      data.put(this.CPU_USAGE, Double.valueOf(df2.format(hostCpuUsage)));
      data.put(this.MEMORY_USAGE, hostMemoryUsage);

      String hostNodeType;
      // setting physical host status
      if (hostSevere) {
        hostStatus = this.MEMBER_NODE_TYPE_SEVERE;
        hostNodeType = "hostSevereNode";
      } else if (hostError) {
        hostStatus = this.MEMBER_NODE_TYPE_ERROR;
        hostNodeType = "hostErrorNode";
      } else if (hostWarning) {
        hostStatus = this.MEMBER_NODE_TYPE_WARNING;
        hostNodeType = "hostWarningNode";
      } else {
        hostStatus = this.MEMBER_NODE_TYPE_NORMAL;
        hostNodeType = "hostNormalNode";
      }
      data.put("hostStatus", hostStatus);
      data.put("$type", hostNodeType);

      childHostObject.put(this.DATA, data);

      childHostObject.put(this.CHILDREN, membersArray);
      childHostArray.add(childHostObject);
    }
    clusterTopologyJSON.put(this.CHILDREN, childHostArray);

    return clusterTopologyJSON;
  }

  /**
   * used for getting member node type based on member's current state
   * 
   * @param member Member
   * @param memberState i.e Severe, Error, Warning or Normal
   * @return
   */
  private String getMemberNodeType(Cluster.Member member, String memberState) {
    String memberNodeType = "";

    if ((member.isLocator()) && (member.isServer()) && (member.isManager())) {
      memberNodeType = "memberLocatorManagerServer" + memberState + "Node";
    } else if ((member.isLocator()) && (member.isServer()) && !(member.isManager())) {
      memberNodeType = "memberLocatorServer" + memberState + "Node";
    } else if ((member.isLocator()) && !(member.isServer()) && (member.isManager())) {
      memberNodeType = "memberLocatorManager" + memberState + "Node";
    } else if ((member.isLocator()) && !(member.isServer()) && !(member.isManager())) {
      memberNodeType = "memberLocator" + memberState + "Node";
    } else if (!(member.isLocator()) && (member.isServer()) && (member.isManager())) {
      memberNodeType = "memberManagerServer" + memberState + "Node";
    } else if (!(member.isLocator()) && (member.isServer()) && !(member.isManager())) {
      memberNodeType = "memberServer" + memberState + "Node";
    } else if (!(member.isLocator()) && !(member.isServer()) && (member.isManager())) {
      memberNodeType = "memberManager" + memberState + "Node";
    } else if (!(member.isLocator()) && !(member.isServer()) && !(member.isManager())) {
      memberNodeType = "member" + memberState + "Node";
    }
    return memberNodeType;
  }

  /**
   * function used for getting list of all the alerts and save the member names in respective error,
   * warning and severe alert lists
   * 
   * @param cluster
   */
  private void updateAlertLists(Cluster cluster) {

    severeAlertList = new ArrayList<String>();
    errorAlertsList = new ArrayList<String>();
    warningAlertsList = new ArrayList<String>();

    Cluster.Alert[] alertsList = cluster.getAlertsList();

    for (Cluster.Alert alert : alertsList) {
      // if alert is severe
      if (alert.getSeverity() == Cluster.Alert.SEVERE) {
        if (errorAlertsList.contains(alert.getMemberName())) {
          errorAlertsList.remove(alert.getMemberName());
        } else if (warningAlertsList.contains(alert.getMemberName())) {
          warningAlertsList.remove(alert.getMemberName());
        }
        if (!severeAlertList.contains(alert.getMemberName())) {
          severeAlertList.add(alert.getMemberName());
        }
      }
      // if alert is error
      else if (alert.getSeverity() == Cluster.Alert.ERROR) {
        if (!severeAlertList.contains(alert.getMemberName())) {
          if (warningAlertsList.contains(alert.getMemberName())) {
            warningAlertsList.remove(alert.getMemberName());
          }
          if (!errorAlertsList.contains(alert.getMemberName())) {
            errorAlertsList.add(alert.getMemberName());
          }
        }
      }
      // if alert is warning
      else if (alert.getSeverity() == Cluster.Alert.WARNING) {
        if (!severeAlertList.contains(alert.getMemberName())) {
          if (!errorAlertsList.contains(alert.getMemberName())) {
            if (!warningAlertsList.contains(alert.getMemberName())) {
              warningAlertsList.add(alert.getMemberName());
            }
          }
        }
      }
    }
  }
}
