/**
 * 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.yarn.server.applicationhistoryservice.metrics.timeline.aggregators;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.availability.AggregationTaskRunner;
import org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.query.Condition;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicLong;

import static junit.framework.Assert.assertEquals;
import static org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.TimelineMetricConfiguration.AGGREGATOR_CHECKPOINT_DELAY;
import static org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.TimelineMetricConfiguration.RESULTSET_FETCH_SIZE;

public class AbstractTimelineAggregatorTest {

  private AbstractTimelineAggregator agg;

  AtomicLong startTimeInDoWork;
  AtomicLong endTimeInDoWork;
  AtomicLong checkPoint;
  int actualRuns;

  long sleepIntervalMillis;
  int checkpointCutOffMultiplier;

  @Before
  public void setUp() throws Exception {
    sleepIntervalMillis = 5 * 60 * 1000l; //5 minutes
    checkpointCutOffMultiplier = 2;

    Configuration metricsConf = new Configuration();
    metricsConf.setInt(AGGREGATOR_CHECKPOINT_DELAY, 0);
    metricsConf.setInt(RESULTSET_FETCH_SIZE, 2000);

    startTimeInDoWork = new AtomicLong(0);
    endTimeInDoWork = new AtomicLong(0);
    checkPoint = new AtomicLong(-1);
    actualRuns = 0;

    agg = new AbstractTimelineAggregator(AggregationTaskRunner.AGGREGATOR_NAME.METRIC_AGGREGATE_SECOND, null, metricsConf) {
      @Override
      public boolean doWork(long startTime, long endTime) {
        startTimeInDoWork.set(startTime);
        endTimeInDoWork.set(endTime);
        actualRuns++;

        return true;
      }

      @Override
      protected Condition
      prepareMetricQueryCondition(long startTime, long endTime) {
        return null;
      }

      @Override
      protected void aggregate(ResultSet rs, long startTime,
                               long endTime) throws IOException, SQLException {
      }

      @Override
      public Long getSleepIntervalMillis() {
        return sleepIntervalMillis;
      }

      @Override
      protected Integer getCheckpointCutOffMultiplier() {
        return checkpointCutOffMultiplier;
      }

      @Override
      public boolean isDisabled() {
        return false;
      }

      @Override
      protected String getCheckpointLocation() {
        return "dummy_ckptFile";
      }

      protected long readCheckPoint() {
        return checkPoint.get();
      }

      @Override
      protected void saveCheckPoint(long checkpointTime) throws IOException {
        checkPoint.set(checkpointTime);
      }
    };

  }

  @Test
  public void testDoWorkOnZeroDelay() throws Exception {

    long currentTime = System.currentTimeMillis();
    long roundedOffAggregatorTime = AbstractTimelineAggregator.getRoundedCheckPointTimeMillis(currentTime,
      sleepIntervalMillis);

    //Test first run of aggregator with no checkpoint
    checkPoint.set(-1);
    agg.runOnce(sleepIntervalMillis);
    assertEquals("startTime should be zero", 0, startTimeInDoWork.get());
    assertEquals("endTime  should be zero", 0, endTimeInDoWork.get());
    assertEquals(roundedOffAggregatorTime, checkPoint.get());
    assertEquals("Do not aggregate on first run", 0, actualRuns);

//    //Test first run with too "recent" checkpoint
    currentTime = System.currentTimeMillis();
    checkPoint.set(currentTime);
    agg.setSleepIntervalMillis(sleepIntervalMillis);
    agg.runOnce(sleepIntervalMillis);
    assertEquals("startTime should be zero", 0, startTimeInDoWork.get());
    assertEquals("endTime  should be zero", 0, endTimeInDoWork.get());
    assertEquals("Do not aggregate on first run", 0, actualRuns);

    //Test first run with Too Old checkpoint
    currentTime = System.currentTimeMillis();
    checkPoint.set(currentTime - 16*60*1000); //Old checkpoint
    agg.runOnce(sleepIntervalMillis);
    long checkPointTime = AbstractTimelineAggregator.getRoundedAggregateTimeMillis(sleepIntervalMillis);
    assertEquals("startTime should be zero", checkPointTime - sleepIntervalMillis, startTimeInDoWork.get());
    assertEquals("endTime  should be zero", checkPointTime, endTimeInDoWork.get());
    assertEquals(roundedOffAggregatorTime, checkPoint.get());
    assertEquals("Do not aggregate on first run", 1, actualRuns);


//    //Test first run with perfect checkpoint (sleepIntervalMillis back)
    currentTime = System.currentTimeMillis();
    roundedOffAggregatorTime = AbstractTimelineAggregator.getRoundedCheckPointTimeMillis(currentTime,
      sleepIntervalMillis);
    checkPointTime = roundedOffAggregatorTime - sleepIntervalMillis;
    long expectedCheckPoint = AbstractTimelineAggregator.getRoundedCheckPointTimeMillis(checkPointTime, sleepIntervalMillis);
    checkPoint.set(checkPointTime);
    agg.runOnce(sleepIntervalMillis);
    assertEquals("startTime should the lower rounded time of the checkpoint time",
      expectedCheckPoint, startTimeInDoWork.get());
    assertEquals("endTime should the lower rounded time of the checkpoint time + sleepIntervalMillis",
      expectedCheckPoint + sleepIntervalMillis, endTimeInDoWork.get());
    assertEquals(expectedCheckPoint + sleepIntervalMillis,
      checkPoint.get());
    assertEquals("Aggregate on first run", 2, actualRuns);

    //Test edge case for checkpoint (2 x sleepIntervalMillis)
    currentTime = System.currentTimeMillis();
    checkPoint.set(currentTime - 2*sleepIntervalMillis + 5000);
    agg.runOnce(sleepIntervalMillis);
    long expectedStartTime = AbstractTimelineAggregator.getRoundedCheckPointTimeMillis(currentTime - 2*sleepIntervalMillis + 5000, sleepIntervalMillis);
    assertEquals("startTime should the lower rounded time of the checkpoint time",
      expectedStartTime, startTimeInDoWork.get());
    assertEquals("startTime should the lower rounded time of the checkpoint time + sleepIntervalMillis",
      expectedStartTime + sleepIntervalMillis, endTimeInDoWork.get());
    assertEquals(expectedStartTime + sleepIntervalMillis,
      checkPoint.get());
    assertEquals("Aggregate on second run", 3, actualRuns);


 }
}