/*
 * 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.jclouds.cloudwatch.domain;

import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Date;
import java.util.Set;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

/**
 * @see <a href=
 *      "http://docs.amazonwebservices.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html"
 *      />
 */
public class MetricDatum {

   private final Set<Dimension> dimensions;
   private final String metricName;
   private final Optional<StatisticValues> statisticValues;
   private final Optional<Date> timestamp;
   private final Unit unit;
   private final Optional<Double> value;

   /**
    * Private constructor to enforce using {@link Builder}.
    */
   protected MetricDatum(Iterable<Dimension> dimensions, String metricName, Optional<StatisticValues> statisticValues,
            Optional<Date> timestamp, Unit unit, Optional<Double> value) {
      this.dimensions = ImmutableSet.<Dimension> copyOf(checkNotNull(dimensions, "dimensions"));
      this.metricName = checkNotNull(metricName, "metricName");
      this.statisticValues = checkNotNull(statisticValues, "statisticValues");
      this.timestamp = checkNotNull(timestamp, "timestamp");
      this.unit = checkNotNull(unit, "unit");
      this.value = checkNotNull(value, "value");
   }

   /**
    * return the list of dimensions describing the the metric.
    */
   public Set<Dimension> getDimensions() {
      return dimensions;
   }

   /**
    * return the metric name for the metric.
    */
   public String getMetricName() {
      return metricName;
   }

   /**
    * return the object describing the set of statistical values for the metric
    */
   public Optional<StatisticValues> getStatisticValues() {
      return statisticValues;
   }

   /**
    * return the time stamp used for the metric
    */
   public Optional<Date> getTimestamp() {
      return timestamp;
   }

   /**
    * return Standard unit used for the metric.
    */
   public Unit getUnit() {
      return unit;
   }

   /**
    * return the actual value of the metric
    */
   public Optional<Double> getValue() {
      return value;
   }

   /**
    * Returns a new builder. The generated builder is equivalent to the builder created by the
    * {@link Builder} constructor.
    */
   public static Builder builder() {
      return new Builder();
   }

   public static class Builder {

      // this builder is set to be additive on dimension calls, so make this mutable
      private ImmutableList.Builder<Dimension> dimensions = ImmutableList.<Dimension> builder();
      private String metricName;
      private Optional<StatisticValues> statisticValues = Optional.absent();
      private Optional<Date> timestamp = Optional.absent();
      private Unit unit = Unit.NONE;
      private Optional<Double> value = Optional.absent();

      /**
       * Creates a new builder. The returned builder is equivalent to the builder generated by
       * {@link org.jclouds.cloudwatch.domain.MetricDatum#builder}.
       */
      public Builder() {
      }

      /**
       * A list of dimensions describing qualities of the metric.
       * 
       * @param dimensions
       *           the dimensions describing the qualities of the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder dimensions(Iterable<Dimension> dimensions) {
         this.dimensions.addAll(checkNotNull(dimensions, "dimensions"));
         return this;
      }

      /**
       * A dimension describing qualities of the metric.
       * 
       * @param dimension
       *           the dimension describing the qualities of the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder dimension(Dimension dimension) {
         this.dimensions.add(checkNotNull(dimension, "dimension"));
         return this;
      }

      /**
       * The name of the metric.
       * 
       * @param metricName
       *           the metric name
       * 
       * @return this {@code Builder} object
       */
      public Builder metricName(String metricName) {
         this.metricName = metricName;
         return this;
      }

      /**
       * The object describing the set of statistical values describing the metric.
       * 
       * @param statisticValues
       *           the object describing the set of statistical values for the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder statisticValues(StatisticValues statisticValues) {
         this.statisticValues = Optional.fromNullable(statisticValues);
         return this;
      }

      /**
       * The time stamp used for the metric. If not specified, the default value is set to the time
       * the metric data was received.
       * 
       * @param timestamp
       *           the time stamp used for the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder timestamp(Date timestamp) {
         this.timestamp = Optional.fromNullable(timestamp);
         return this;
      }

      /**
       * The unit for the metric.
       * 
       * @param unit
       *           the unit for the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder unit(Unit unit) {
         this.unit = unit;
         return this;
      }

      /**
       * The value for the metric.
       * 
       * @param value
       *           the value for the metric
       * 
       * @return this {@code Builder} object
       */
      public Builder value(Double value) {
         this.value = Optional.fromNullable(value);
         return this;
      }

      /**
       * Returns a newly-created {@code MetricDatum} based on the contents of the {@code Builder}.
       */
      public MetricDatum build() {
         return new MetricDatum(dimensions.build(), metricName, statisticValues, timestamp, unit, value);
      }

   }

   @Override
   public boolean equals(Object o) {
      if (this == o)
         return true;
      if (o == null || getClass() != o.getClass())
         return false;
      MetricDatum that = MetricDatum.class.cast(o);
      return equal(this.dimensions, that.dimensions) && equal(this.metricName, that.metricName)
               && equal(this.statisticValues, that.statisticValues) && equal(this.timestamp, that.timestamp)
               && equal(this.unit, that.unit) && equal(this.value, that.value);
   }

   @Override
   public int hashCode() {
      return Objects.hashCode(dimensions, metricName, statisticValues, timestamp, unit, value);
   }

   @Override
   public String toString() {
      return string().toString();
   }

   protected ToStringHelper string() {
      return MoreObjects.toStringHelper("").omitNullValues().add("dimensions", dimensions).add("metricName", metricName)
               .add("statisticValues", statisticValues.orNull()).add("timestamp", timestamp.orNull()).add("unit", unit)
               .add("value", value.orNull());
   }
}
