/**
   * Acts directly on the supplied agent and populates its metric list with values
   *
   * @param agent the agent for which the latest metric values will be retrieved
   */
  private void getLatestMetricsValuesForJCatascopiaAgent(JCatascopiaAgent agent) {
    URL url = null;
    HttpURLConnection connection = null;
    try {
      url = new URL(this.url + "/metrics");
      connection = (HttpURLConnection) url.openConnection();
      connection.setDoOutput(true);
      connection.setInstanceFollowRedirects(false);
      connection.setRequestMethod("POST");
      connection.setRequestProperty("Content-Type", "text/plain");
      connection.setRequestProperty("Accept", "application/json");

      // write message body
      OutputStream os = connection.getOutputStream();
      BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(os));
      String getMetricsInfoQuerry = "metrics=";
      for (JCatascopiaMetric metric : agent.getAgentMetrics()) {
        getMetricsInfoQuerry += metric.getId() + ",";
      }

      // cut the last ","
      getMetricsInfoQuerry =
          getMetricsInfoQuerry.substring(0, getMetricsInfoQuerry.lastIndexOf(","));

      bufferedWriter.write(getMetricsInfoQuerry);
      bufferedWriter.flush();
      os.flush();
      os.close();

      InputStream errorStream = connection.getErrorStream();
      if (errorStream != null) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
        String line;
        while ((line = reader.readLine()) != null) {
          Logger.getLogger(JCatascopiaDataSource.class.getName()).log(Level.SEVERE, line);
        }
      }

      InputStream inputStream = connection.getInputStream();
      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

      String availableMetrics = "";

      String line = "";
      while ((line = bufferedReader.readLine()) != null) {
        availableMetrics += line;
      }

      JSONObject object = new JSONObject(availableMetrics);
      if (object.has("metrics")) {
        JSONArray metrics = object.getJSONArray("metrics");
        List<JCatascopiaMetric> agentMetrics = agent.getAgentMetrics();

        // map of metric indexed on IDs to find easier the metrics (avoids for in for)
        Map<String, JCatascopiaMetric> metricsMap = new HashMap<String, JCatascopiaMetric>(0);
        for (JCatascopiaMetric jCatascopiaMetric : agentMetrics) {
          metricsMap.put(jCatascopiaMetric.getId(), jCatascopiaMetric);
        }

        // populate the metrics pool
        for (int i = 0; i < metrics.length(); i++) {
          JSONObject metric = metrics.getJSONObject(i);
          String metricId = null;
          String metricValue = null;

          // get agent metricID
          if (metric.has("metricID")) {
            metricId = metric.getString("metricID");
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia metricID not found in {0}", availableMetrics);
          }

          // get metric value
          if (metric.has("value")) {
            metricValue = metric.getString("value");
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia name not found in {0}", availableMetrics);
          }

          if (metricId == null || metricValue == null) {
            continue;
          }

          if (metricsMap.containsKey(metricId)) {
            JCatascopiaMetric jCatascopiaMetric = metricsMap.get(metricId);
            jCatascopiaMetric.setValue(metricValue);
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(
                    Level.SEVERE,
                    "Unrecognized metricId {0} found in {1}",
                    new Object[] {metricId, availableMetrics});
          }
        }

      } else {
        Logger.getLogger(JCatascopiaDataSource.class.getName())
            .log(Level.SEVERE, "No JCatascopia metrics found in {0}", availableMetrics);
      }

    } catch (Exception e) {
      Logger.getLogger(JCatascopiaDataSource.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } finally {
      if (connection != null) {
        connection.disconnect();
      }
    }
  }
  /**
   * Currently the implementation is stupid. It queries for JCatscopia to get All Agents, then it
   * queries for each Agent getAvailableMetrics, and then it asks for all the metrics from each
   * agent. In the future, only metrics that appear in composition rules (filters) should be
   * retrieved, not all in bulk.
   *
   * @return a cluster with monitoring data collected from JCatascopia structured after HostName IP.
   * @throws DataAccessException
   */
  public MonitoringData getMonitoringData() throws DataAccessException {

    MonitoringData monitoringData = new MonitoringData();
    monitoringData.setTimestamp("" + new Date().getTime());
    monitoringData.setSource(url);

    // map for holding the IP, and JCatascopiaAgent, as maybe multiple JCatascopia agents can belong
    // to the same VM, make sure we merge all data
    //        Map<String, JCatascopiaAgent> hostsMap = new HashMap<String, JCatascopiaAgent>();
    //        updateJCatascopiaAgents(poolOfAgents);
    // added to improve query time
    //        if (poolOfAgents.isEmpty()) {
    updateJCatascopiaAgents(poolOfAgents);
    System.err.println("Updating agents");

    for (JCatascopiaAgent agent : poolOfAgents) {
      // if agent is active
      if (agent.getStatus().equalsIgnoreCase("UP")) {
        //                    HostInfo hostInfo = null;
        //                    if (hostsMap.containsKey(agent.getIp())) {
        //                        hostInfo = hostsMap.get(agent.getIp());
        //                    } else {
        //                        hostInfo = new HostInfo();
        //                        hostInfo.setIp(agent.getIp());
        //                        hostInfo.setName(agent.getIp());
        //                        hostsMap.put(hostInfo.getIp(), hostInfo);
        //                    }

        // update metrics using REST API from JCatascopia
        updateMetricsForJCatascopiaAgent(agent);
        getLatestMetricsValuesForJCatascopiaAgent(agent);

        // create monitoring data representation to be returned
        MonitoredElementData elementData = new MonitoredElementData();

        // create representation of monitored element to associate this data in the overall
        // monitored service
        MonitoredElement monitoredElement = new MonitoredElement();

        // for VM level, we use IP as monitored element ID
        monitoredElement.setId(agent.getIp());
        monitoredElement.setName(agent.getId());

        // for the moment we assume all what JCatascopia returns is associated to VM level
        // TODO: consider inserting better level management mechanism in which one data source can
        // return data for multiple levels
        monitoredElement.setLevel(MonitoredElement.MonitoredElementLevel.VM);

        elementData.setMonitoredElement(monitoredElement);

        for (JCatascopiaMetric metric : agent.getAgentMetrics()) {
          if (metric == null || metric.getValue() == null) {
            continue;
          }
          CollectedMetricValue metricInfo = new CollectedMetricValue();
          metricInfo.setName(metric.getName());
          metricInfo.setMonitoredElementLevel(MonitoredElement.MonitoredElementLevel.VM.toString());
          metricInfo.setMonitoredElementID(agent.getIp());
          metricInfo.setType(metric.getType());
          metricInfo.setUnits(metric.getUnit());
          metricInfo.setValue(metric.getValue());
          metricInfo.setTimeSinceCollection("0");
          elementData.addMetric(metricInfo);
        }

        monitoringData.addMonitoredElementData(elementData);

      } else {
        Logger.getLogger(JCatascopiaDataSource.class.getName())
            .log(
                Level.SEVERE,
                "Agent {0} with IP {1} is down",
                new Object[] {agent.getId(), agent.getIp()});
      }
    }
    //        } else {
    //            //added to improve time
    //
    //            for (JCatascopiaAgent agent : poolOfAgents) {
    //                //if agent is active
    //                if (agent.getStatus().equalsIgnoreCase("UP")) {
    ////                    HostInfo hostInfo = null;
    ////                    if (hostsMap.containsKey(agent.getIp())) {
    ////                        hostInfo = hostsMap.get(agent.getIp());
    ////                    } else {
    ////                        hostInfo = new HostInfo();
    ////                        hostInfo.setIp(agent.getIp());
    ////                        hostInfo.setName(agent.getIp());
    ////                        hostsMap.put(hostInfo.getIp(), hostInfo);
    ////                    }
    //
    //                    getLatestMetricsValuesForJCatascopiaAgent(agent);
    //
    //                    //create monitoring data representation to be returned
    //
    //                    MonitoredElementData elementData = new MonitoredElementData();
    //
    //                    //create representation of monitored element to associate this data in the
    // overall monitored service
    //                    MonitoredElement monitoredElement = new MonitoredElement();
    //
    //                    //for VM level, we use IP as monitored element ID
    //                    monitoredElement.setId(agent.getIp());
    //                    monitoredElement.setName(agent.getId());
    //
    //                    //for the moment we assume all what JCatascopia returns is associated to
    // VM level
    //                    //TODO: consider inserting better level management mechanism in which one
    // data source can return data for multiple levels
    //                    monitoredElement.setLevel(MonitoredElement.MonitoredElementLevel.VM);
    //
    //                    elementData.setMonitoredElement(monitoredElement);
    //
    //                    for (JCatascopiaMetric metric : agent.getAgentMetrics()) {
    //                        MetricInfo metricInfo = new MetricInfo();
    //                        metricInfo.setName(metric.getName());
    //                        metricInfo.setType(metric.getType());
    //                        metricInfo.setUnits(metric.getUnit());
    //                        metricInfo.setValue(metric.getValue());
    //                        elementData.addMetric(metricInfo);
    //                    }
    //
    //                    monitoringData.addMonitoredElementData(elementData);
    //
    //                } else {
    //                    Logger.getLogger(JCatascopiaDataSource.class.getName()).log(Level.SEVERE,
    // "Agent {0} with IP {1} is down", new Object[]{agent.getId(), agent.getIp()});
    //                }
    //            }
    //
    //        }

    return monitoringData;
  }
  /**
   * @param agent for which all available metrics will be retrieved. Attention, this does NOT
   *     retrieve metric VALUES
   */
  private void updateMetricsForJCatascopiaAgent(JCatascopiaAgent agent) {
    URL url = null;
    HttpURLConnection connection = null;
    try {
      url = new URL(this.url + "/agents/" + agent.getId() + "/availableMetrics");
      connection = (HttpURLConnection) url.openConnection();
      connection.setRequestMethod("GET");
      connection.setRequestProperty("Content-Type", "application/json");
      connection.setRequestProperty("Accept", "application/json");

      InputStream errorStream = connection.getErrorStream();
      if (errorStream != null) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
        String line;
        while ((line = reader.readLine()) != null) {
          Logger.getLogger(JCatascopiaDataSource.class.getName()).log(Level.SEVERE, line);
        }
      }

      InputStream inputStream = connection.getInputStream();
      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

      String availableMetrics = "";

      String line = "";
      while ((line = bufferedReader.readLine()) != null) {
        availableMetrics += line;
      }

      JSONObject object = new JSONObject(availableMetrics);
      if (object.has("metrics")) {
        JSONArray metrics = object.getJSONArray("metrics");
        List<JCatascopiaMetric> agentMetrics = agent.getAgentMetrics();

        // reuse JCatascopia metric obejcts, to avoid creating new objects all the time
        int nrOfMetricsReportedByJCatascopia = metrics.length();
        int nrOfMetricsInAgent = agentMetrics.size();
        int sizeDifference = nrOfMetricsInAgent - nrOfMetricsReportedByJCatascopia;

        // resize agents metrics list
        if (sizeDifference < 0) {
          // inchrease agents pool
          for (int i = sizeDifference; i < 0; i++) {
            agentMetrics.add(new JCatascopiaMetric());
          }
        } else if (sizeDifference > 0) {
          for (int i = sizeDifference; i > 0; i--) {
            agentMetrics.remove(0);
          }
        }

        // populate the metrics pool
        for (int i = 0; i < metrics.length(); i++) {
          JSONObject metric = metrics.getJSONObject(i);
          JCatascopiaMetric jCatascopiaMetric = agentMetrics.get(i);

          // get agent metricID
          if (metric.has("metricID")) {
            jCatascopiaMetric.setId(metric.getString("metricID"));
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia metricID not found in {0}", availableMetrics);
          }

          // get agent name
          if (metric.has("name")) {
            jCatascopiaMetric.setName(metric.getString("name"));
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia name not found in {0}", availableMetrics);
          }

          // get agent units
          if (metric.has("units")) {
            jCatascopiaMetric.setUnit(metric.getString("units"));
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia units not found in {0}", availableMetrics);
          }

          // get agent type
          if (metric.has("type")) {
            jCatascopiaMetric.setType(metric.getString("type"));
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia type not found in {0}", availableMetrics);
          }

          // get agent group
          if (metric.has("group")) {
            jCatascopiaMetric.setGroup(metric.getString("group"));
          } else {
            Logger.getLogger(JCatascopiaDataSource.class.getName())
                .log(Level.SEVERE, "JCatascopia group not found in {0}", availableMetrics);
          }
        }

      } else {
        Logger.getLogger(JCatascopiaDataSource.class.getName())
            .log(Level.SEVERE, "No JCatascopia metrics found in {0}", availableMetrics);
      }

    } catch (Exception e) {
      Logger.getLogger(JCatascopiaDataSource.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } finally {
      if (connection != null) {
        connection.disconnect();
      }
    }
  }