/*
 * 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.hadoop.metrics2.util;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.metrics2.AbstractMetric;
import org.apache.hadoop.metrics2.MetricsRecord;
import org.apache.hadoop.metrics2.MetricsTag;
import static org.apache.hadoop.metrics2.lib.Interns.*;

public class TestMetricsCache {
  private static final Log LOG = LogFactory.getLog(TestMetricsCache.class);

  @SuppressWarnings("deprecation")
  @Test public void testUpdate() {
    MetricsCache cache = new MetricsCache();
    MetricsRecord mr = makeRecord("r",
        Arrays.asList(makeTag("t", "tv")),
        Arrays.asList(makeMetric("m", 0), makeMetric("m1", 1)));

    MetricsCache.Record cr = cache.update(mr);
    verify(mr).name();
    verify(mr).tags();
    verify(mr).metrics();
    assertEquals("same record size", cr.metrics().size(),
                 ((Collection<AbstractMetric>)mr.metrics()).size());
    assertEquals("same metric value", 0, cr.getMetric("m"));

    MetricsRecord mr2 = makeRecord("r",
        Arrays.asList(makeTag("t", "tv")),
        Arrays.asList(makeMetric("m", 2), makeMetric("m2", 42)));
    cr = cache.update(mr2);
    assertEquals("contains 3 metric", 3, cr.metrics().size());
    checkMetricValue("updated metric value", cr, "m", 2);
    checkMetricValue("old metric value", cr, "m1", 1);
    checkMetricValue("new metric value", cr, "m2", 42);

    MetricsRecord mr3 = makeRecord("r",
        Arrays.asList(makeTag("t", "tv3")), // different tag value
        Arrays.asList(makeMetric("m3", 3)));
    cr = cache.update(mr3); // should get a new record
    assertEquals("contains 1 metric", 1, cr.metrics().size());
    checkMetricValue("updated metric value", cr, "m3", 3);
    // tags cache should be empty so far
    assertEquals("no tags", 0, cr.tags().size());
    // until now
    cr = cache.update(mr3, true);
    assertEquals("Got 1 tag", 1, cr.tags().size());
    assertEquals("Tag value", "tv3", cr.getTag("t"));
    checkMetricValue("Metric value", cr, "m3", 3);
  }

  @SuppressWarnings("deprecation")
  @Test public void testGet() {
    MetricsCache cache = new MetricsCache();
    assertNull("empty", cache.get("r", Arrays.asList(makeTag("t", "t"))));
    MetricsRecord mr = makeRecord("r",
        Arrays.asList(makeTag("t", "t")),
        Arrays.asList(makeMetric("m", 1)));
    cache.update(mr);
    MetricsCache.Record cr = cache.get("r", mr.tags());
    LOG.debug("tags="+ mr.tags() +" cr="+ cr);

    assertNotNull("Got record", cr);
    assertEquals("contains 1 metric", 1, cr.metrics().size());
    checkMetricValue("new metric value", cr, "m", 1);
  }

  /**
   * Make sure metrics tag has a sane hashCode impl
   */
  @Test public void testNullTag() {
    MetricsCache cache = new MetricsCache();
    MetricsRecord mr = makeRecord("r",
        Arrays.asList(makeTag("t", null)),
        Arrays.asList(makeMetric("m", 0), makeMetric("m1", 1)));

    MetricsCache.Record cr = cache.update(mr);
    assertTrue("t value should be null", null == cr.getTag("t"));
  }

  @Test public void testOverflow() {
    MetricsCache cache = new MetricsCache();
    MetricsCache.Record cr;
    Collection<MetricsTag> t0 = Arrays.asList(makeTag("t0", "0"));
    for (int i = 0; i < MetricsCache.MAX_RECS_PER_NAME_DEFAULT + 1; ++i) {
      cr = cache.update(makeRecord("r",
          Arrays.asList(makeTag("t"+ i, ""+ i)),
          Arrays.asList(makeMetric("m", i))));
      checkMetricValue("new metric value", cr, "m", i);
      if (i < MetricsCache.MAX_RECS_PER_NAME_DEFAULT) {
        assertNotNull("t0 is still there", cache.get("r", t0));
      }
    }
    assertNull("t0 is gone", cache.get("r", t0));
  }

  private void checkMetricValue(String description, MetricsCache.Record cr,
      String key, Number val) {
    assertEquals(description, val, cr.getMetric(key));
    assertNotNull("metric not null", cr.getMetricInstance(key));
    assertEquals(description, val, cr.getMetricInstance(key).value());
  }

  private MetricsRecord makeRecord(String name, Collection<MetricsTag> tags,
                                   Collection<AbstractMetric> metrics) {
    MetricsRecord mr = mock(MetricsRecord.class);
    when(mr.name()).thenReturn(name);
    when(mr.tags()).thenReturn(tags);
    when(mr.metrics()).thenReturn(metrics);
    return mr;
  }

  private MetricsTag makeTag(String name, String value) {
    return new MetricsTag(info(name, ""), value);
  }

  private AbstractMetric makeMetric(String name, Number value) {
    AbstractMetric metric = mock(AbstractMetric.class);
    when(metric.name()).thenReturn(name);
    when(metric.value()).thenReturn(value);
    return metric;
  }
}