/*
 * Copyright 2016 Google Inc.
 *
 * Licensed 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.
 */

import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest;
import com.google.api.services.monitoring.v3.model.LabelDescriptor;
import com.google.api.services.monitoring.v3.model.ListMetricDescriptorsResponse;
import com.google.api.services.monitoring.v3.model.ListTimeSeriesResponse;
import com.google.api.services.monitoring.v3.model.Metric;
import com.google.api.services.monitoring.v3.model.MetricDescriptor;
import com.google.api.services.monitoring.v3.model.MonitoredResource;
import com.google.api.services.monitoring.v3.model.Point;
import com.google.api.services.monitoring.v3.model.TimeInterval;
import com.google.api.services.monitoring.v3.model.TimeSeries;
import com.google.api.services.monitoring.v3.model.TypedValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import org.joda.time.DateTime;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;


/**
 * Class to demonstrate creating a custom metric with Cloud Monitoring.
 * <p/>
 * <p>This class provides a few functions that create a custom GAUGE metric, writes a timeseries
 * value to it, then reads that metric's value back within the last 5 minutes to see the value
 * written.
 */
public class CreateCustomMetric {

  /**
   * Cloud Monitoring v3 REST client.
   */
  private Monitoring monitoringService;

  private static SimpleDateFormat rfc3339 =
      new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");

  static {
    rfc3339.setTimeZone(TimeZone.getTimeZone("UTC"));
  }

  /**
   * Identifier for project resource, in format 'projects/your-project-id'.
   */
  private String projectResource;

  /**
   * All custom metrics should use this domain as their prefix.
   */
  static final String CUSTOM_METRIC_DOMAIN = "custom.googleapis.com";

  /**
   * Name of our custom metric to create.
   */
  static final String DEFAULT_METRIC_TYPE = "custom_measurement";

  /**
   * The specific metric type for the instance of this class. Defaults to DEFAULT_METRIC_TYPE.
   */
  private String metricType;

  /**
   * The specific metric name, which is based on the project resource and the type.
   */
  private String metricName;


  /**
   * GAUGE metrics measure a value at a point in time.
   */
  static final String METRIC_KIND = "GAUGE";

  /**
   * Upper bound for random number to write to metric, defaults to 10.
   */
  private int bound = 10;

  /**
   * Constructs an instance of the class using the default metric name.
   */
  public CreateCustomMetric(Monitoring monitoringService, String projectResource) {
    this.monitoringService = monitoringService;
    this.projectResource = projectResource;
    this.metricType = CUSTOM_METRIC_DOMAIN + "/" + DEFAULT_METRIC_TYPE;
    this.metricName = projectResource + "/metricDescriptors/" + metricType;

  }

  /**
   * Constructs an instance of the class using the default metric name, and takes in a random
   * number generaotr (used for test purposes).
   * <p/>
   * <p>Package-private to be accessible to tests.
   */
  CreateCustomMetric(Monitoring monitoringService, String projectResource,
                     String metricName, int bound) {
    this.monitoringService = monitoringService;
    this.projectResource = projectResource;
    this.metricType = CUSTOM_METRIC_DOMAIN + "/" + DEFAULT_METRIC_TYPE;
    this.metricName = projectResource + "/metricDescriptors/" + metricType;
    this.bound = bound;
  }

  /**
   * Constructs an instance of the class with the metric name specified.
   */
  public CreateCustomMetric(Monitoring monitoringService, String projectResource,
                            String metricName) {
    this.monitoringService = monitoringService;
    this.projectResource = projectResource;
    this.metricType = CUSTOM_METRIC_DOMAIN + "/" + metricName;
    this.metricName = projectResource + "/metricDescriptors/" + metricType;
  }

  /**
   * Returns now in RFC3339 format. This is the end-time of the window
   * this example views the TimeSeries in.
   */
  private static String getNow() {
    DateTime dt = new DateTime();
    return rfc3339.format(dt.toDate());
  }

  /**
   * Returns 5 minutes before now to create a window to view timeseries in.
   */
  private static String getStartTime() {
    DateTime dt = new DateTime().minusMinutes(5);
    return rfc3339.format(dt.toDate());
  }

  /**
   * Dummy method to get an arbitrary data point.
   */
  private long getRandomPoint() {
    long value = new Random().nextInt(bound);
    System.out.println("Returning value " + value);
    return value;
  }

  /**
   * This method creates a custom metric with arbitrary names, description,
   * and units.
   * <p/>
   * <p>Package-private to be accessible to tests.
   */
  MetricDescriptor createCustomMetric() throws IOException {
    MetricDescriptor metricDescriptor = new MetricDescriptor();


    metricDescriptor.setName(metricName);
    metricDescriptor.setType(metricType);

    LabelDescriptor labelDescriptor = new LabelDescriptor();
    labelDescriptor.setKey("environment");
    labelDescriptor.setValueType("STRING");
    labelDescriptor.setDescription("An arbitrary measurement.");
    labelDescriptor.setDescription("Custom Metric");
    List<LabelDescriptor> labelDescriptorList = new ArrayList<LabelDescriptor>();
    labelDescriptorList.add(labelDescriptor);
    metricDescriptor.setLabels(labelDescriptorList);

    metricDescriptor.setMetricKind(METRIC_KIND);
    metricDescriptor.setValueType("INT64");
    // Fake custom metric with unit 'items'
    metricDescriptor.setUnit("items");


    MetricDescriptor descriptorResponse = this.monitoringService.projects()
        .metricDescriptors()
        .create(projectResource, metricDescriptor)
        .execute();
    System.out.println("create response" + descriptorResponse.toPrettyString());
    return descriptorResponse;

  }

  /**
   * Retrieve the custom metric created by createCustomMetric.
   * <p/>
   * <p>It can sometimes take a few moments before a new custom metric is ready to have
   * TimeSeries written to it, so this method is used
   * to check when it is ready.
   */
  public MetricDescriptor getCustomMetric() throws IOException {
    Monitoring.Projects.MetricDescriptors.List metrics =
        monitoringService.projects().metricDescriptors()
            .list(projectResource);
    metrics.setFilter("metric.type=\"" + metricType + "\"");
    ListMetricDescriptorsResponse response = metrics.execute();
    List<MetricDescriptor> descriptors = response.getMetricDescriptors();
    System.out.println("reading custom metric");
    if (descriptors == null || descriptors.isEmpty()) {
      System.out.println("No metric descriptor matching that label found.");
      return null;
    } else {
      System.out.println(descriptors.get(0).toPrettyString());
      return descriptors.get(0);
    }
  }

  /**
   * Writes a timeseries value for the custom metric created.
   * <p>The value written is a random integer value for demonstration purposes. It's a GAUGE metric,
   * which means its a measure of a value at a point in time, and thus the start
   * window and end window times are the same.
   *
   * @throws IOException On network error.
   */
  // [START write_timeseries]
  void writeCustomMetricTimeseriesValue() throws IOException {
    Map<String, String> metricLabel = ImmutableMap.of(
        "environment", "STAGING"
    );
    Map<String, String> resourceLabel = ImmutableMap.of(
        "instance_id", "test-instance",
        "zone", "us-central1-f"
    );

    CreateTimeSeriesRequest timeSeriesRequest = new CreateTimeSeriesRequest();
    TimeSeries timeSeries = new TimeSeries();

    Metric metric = new Metric();
    metric.setType(metricType);

    metric.setLabels(metricLabel);
    timeSeries.setMetric(metric);
    MonitoredResource monitoredResource = new MonitoredResource();
    monitoredResource.setType("gce_instance");
    monitoredResource.setLabels(resourceLabel);
    timeSeries.setResource(monitoredResource);
    Point point = new Point();
    TimeInterval ti = new TimeInterval();
    String now = getNow();
    ti.setStartTime(now);
    ti.setEndTime(now);

    point.setInterval(ti);
    point.setValue(new TypedValue().setInt64Value(getRandomPoint()));

    timeSeries.setPoints(Lists.<Point>newArrayList(point));

    timeSeriesRequest.setTimeSeries(Lists.<TimeSeries>newArrayList(timeSeries));
    monitoringService.projects().timeSeries().create(projectResource, timeSeriesRequest).execute();
  }
  // [END write_timeseries]

  /**
   * Read the TimeSeries value for the custom metrics created within a window of the
   * last 5 minutes.
   *
   * @return The TimeSeries response object reflecting the Timeseries of the custom metrics
   *     for the last 5 minutes.
   * @throws IOException On network error.
   */
  ListTimeSeriesResponse readTimeseriesValue() throws IOException {
    ListTimeSeriesResponse response =
        monitoringService.projects().timeSeries().list(projectResource)
            .setFilter("metric.type=\"" + metricType + "\"")
            .setPageSize(3)
            .setIntervalStartTime(getStartTime())
            .setIntervalEndTime(getNow())
            .execute();
    return response;
  }

  /**
   * Use the Google Cloud Monitoring API to create a custom metric.
   *
   * @param args The first arg should be the project name you'd like to inspect.
   * @throws Exception if something goes wrong.
   */
  public static void main(final String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println(String.format("Usage: %s <project-name>",
          CreateCustomMetric.class.getSimpleName()));
      return;
    }

    String project = args[0];
    String projectResource = "projects/" + project;


    // Create an authorized API client
    Monitoring monitoringService = ListResources.authenticate();

    CreateCustomMetric metricWriter =
        new CreateCustomMetric(monitoringService, projectResource);

    MetricDescriptor metricDescriptor = metricWriter.createCustomMetric();

    System.out.println("listMetricDescriptors response");
    System.out.println(metricDescriptor.toPrettyString());

    // wait until custom metric can be read back
    while (metricWriter.getCustomMetric() == null) {
      Thread.sleep(2000);
    }
    metricWriter.writeCustomMetricTimeseriesValue();
    Thread.sleep(3000);
    ListTimeSeriesResponse response = metricWriter.readTimeseriesValue();
    System.out.println("reading custom metric timeseries");
    System.out.println(response.toPrettyString());

  }
}
