/*
 * Copyright 2016-17, OpenCensus Authors
 *
 * 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.
 */

package io.opencensus.implcore.stats;

import io.opencensus.metrics.data.AttachmentValue;
import io.opencensus.stats.Measure;
import io.opencensus.stats.Measure.MeasureDouble;
import io.opencensus.stats.Measure.MeasureLong;
import io.opencensus.stats.Measurement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

// TODO(songya): consider combining MeasureMapImpl and this class.
/** A map from {@link Measure}'s to measured values. */
final class MeasureMapInternal {

  /** Returns a {@link Builder} for the {@link MeasureMapInternal} class. */
  static Builder builder() {
    return new Builder();
  }

  /**
   * Returns an {@link Iterator} over the measure/value mappings in this {@link MeasureMapInternal}.
   * The {@code Iterator} does not support {@link Iterator#remove()}.
   */
  Iterator<Measurement> iterator() {
    return new MeasureMapInternalIterator();
  }

  // Returns the contextual information associated with an example value.
  Map<String, AttachmentValue> getAttachments() {
    return attachments;
  }

  private final ArrayList<Measurement> measurements;
  private final Map<String, AttachmentValue> attachments;

  private MeasureMapInternal(
      ArrayList<Measurement> measurements, Map<String, AttachmentValue> attachments) {
    this.measurements = measurements;
    this.attachments =
        Collections.unmodifiableMap(new HashMap<String, AttachmentValue>(attachments));
  }

  /** Builder for the {@link MeasureMapInternal} class. */
  static class Builder {
    /**
     * Associates the {@link MeasureDouble} with the given value. Subsequent updates to the same
     * {@link MeasureDouble} will overwrite the previous value.
     *
     * @param measure the {@link MeasureDouble}
     * @param value the value to be associated with {@code measure}
     * @return this
     */
    Builder put(MeasureDouble measure, double value) {
      measurements.add(Measurement.MeasurementDouble.create(measure, value));
      return this;
    }

    /**
     * Associates the {@link MeasureLong} with the given value. Subsequent updates to the same
     * {@link MeasureLong} will overwrite the previous value.
     *
     * @param measure the {@link MeasureLong}
     * @param value the value to be associated with {@code measure}
     * @return this
     */
    Builder put(MeasureLong measure, long value) {
      measurements.add(Measurement.MeasurementLong.create(measure, value));
      return this;
    }

    Builder putAttachment(String key, AttachmentValue value) {
      this.attachments.put(key, value);
      return this;
    }

    /** Constructs a {@link MeasureMapInternal} from the current measurements. */
    MeasureMapInternal build() {
      // Note: this makes adding measurements quadratic but is fastest for the sizes of
      // MeasureMapInternals that we should see. We may want to go to a strategy of sort/eliminate
      // for larger MeasureMapInternals.
      for (int i = measurements.size() - 1; i >= 0; i--) {
        for (int j = i - 1; j >= 0; j--) {
          if (measurements.get(i).getMeasure() == measurements.get(j).getMeasure()) {
            measurements.remove(j);
            j--;
          }
        }
      }
      return new MeasureMapInternal(measurements, attachments);
    }

    private final ArrayList<Measurement> measurements = new ArrayList<Measurement>();
    private final Map<String, AttachmentValue> attachments = new HashMap<String, AttachmentValue>();

    private Builder() {}
  }

  // Provides an unmodifiable Iterator over this instance's measurements.
  private final class MeasureMapInternalIterator implements Iterator<Measurement> {
    @Override
    public boolean hasNext() {
      return position < length;
    }

    @Override
    public Measurement next() {
      if (position >= measurements.size()) {
        throw new NoSuchElementException();
      }
      return measurements.get(position++);
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }

    private final int length = measurements.size();
    private int position = 0;
  }
}
