/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 android.cts.statsd.metric;

import static com.google.common.truth.Truth.assertThat;

import android.cts.statsd.atom.DeviceAtomTestCase;

import com.android.internal.os.StatsdConfigProto;
import com.android.internal.os.StatsdConfigProto.ActivationType;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.EventActivation;
import com.android.internal.os.StatsdConfigProto.EventMetric;
import com.android.internal.os.StatsdConfigProto.GaugeMetric;
import com.android.internal.os.StatsdConfigProto.MetricActivation;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.AtomsProto.AppBreadcrumbReported;
import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.EventMetricData;
import com.android.os.StatsLog.StatsLogReport;
import com.android.tradefed.log.LogUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * Test Statsd Metric activations and deactivations
 */
public class MetricActivationTests extends DeviceAtomTestCase {
    private final long metric1Id = 1L;
    private final int metric1MatcherId = 1;

    private final long metric2Id = 2L;
    private final int metric2MatcherId = 2;

    private final long metric3Id = 3L;
    private final int metric3MatcherId = 3;

    private final int act1MatcherId = 10;
    private final int act1CancelMatcherId = -10;

    private final int act2MatcherId = 20;
    private final int act2CancelMatcherId = -20;


    private StatsdConfig.Builder createConfig(final int act1TtlSecs, final int act2TtlSecs) {
        AtomMatcher metric1Matcher =
                MetricsUtils.simpleAtomMatcher(metric1MatcherId, metric1MatcherId);
        AtomMatcher metric2Matcher =
                MetricsUtils.simpleAtomMatcher(metric2MatcherId, metric2MatcherId);
        AtomMatcher metric3Matcher =
                MetricsUtils.simpleAtomMatcher(metric3MatcherId, metric3MatcherId);
        AtomMatcher act1Matcher =
                MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
        AtomMatcher act1CancelMatcher =
                MetricsUtils.simpleAtomMatcher(act1CancelMatcherId, act1CancelMatcherId);
        AtomMatcher act2Matcher =
                MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
        AtomMatcher act2CancelMatcher =
                MetricsUtils.simpleAtomMatcher(act2CancelMatcherId, act2CancelMatcherId);

        EventMetric metric1 = EventMetric.newBuilder()
                .setId(metric1Id)
                .setWhat(metric1MatcherId)
                .build();

        EventMetric metric2 = EventMetric.newBuilder()
                .setId(metric2Id)
                .setWhat(metric2MatcherId)
                .build();

        EventMetric metric3 = EventMetric.newBuilder()
                .setId(metric3Id)
                .setWhat(metric3MatcherId)
                .build();

        EventActivation metric1Act1 =
                MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
                    .build();

        EventActivation metric1Act2 =
                MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
                    .build();

        EventActivation metric2Act1 =
                MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
                    .build();

        EventActivation metric2Act2 =
                MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
                    .build();

        MetricActivation metric1Activation = MetricActivation.newBuilder()
                .setMetricId(metric1Id)
                .addEventActivation(metric1Act1)
                .addEventActivation(metric1Act2)
                .build();

        MetricActivation metric2Activation = MetricActivation.newBuilder()
                .setMetricId(metric2Id)
                .addEventActivation(metric2Act1)
                .addEventActivation(metric2Act2)
                .build();

        return createConfigBuilder()
                .addAtomMatcher(metric1Matcher)
                .addAtomMatcher(metric2Matcher)
                .addAtomMatcher(metric3Matcher)
                .addAtomMatcher(act1Matcher)
                .addAtomMatcher(act1CancelMatcher)
                .addAtomMatcher(act2Matcher)
                .addAtomMatcher(act2CancelMatcher)
                .addEventMetric(metric1)
                .addEventMetric(metric2)
                .addEventMetric(metric3)
                .addMetricActivation(metric1Activation)
                .addMetricActivation(metric2Activation);
    }

    /**
     * Metric 1:
     *     Activation 1:
     *         - Ttl: 5 seconds
     *         - Type: IMMEDIATE
     *     Activation 2:
     *         - Ttl: 8 seconds
     *         - Type: ON_BOOT
     *
     * Metric 2:
     *     Activation 1:
     *         - Ttl: 5 seconds
     *         - Type: ON_BOOT
     *     Activation 2:
     *         - Ttl: 8 seconds
     *         - Type: IMMEDIATE
     *
     * Metric 3: No activations; always active
     **/
    public void testCancellation() throws Exception {
        final int act1TtlSecs = 5;
        final int act2TtlSecs = 8;
        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));

        // Ignored, metric not active.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Trigger cancel for already inactive event activation 1.
        doAppBreadcrumbReported(act1CancelMatcherId);
        Thread.sleep(10L);

        // Trigger event activation 1.
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // First logged event.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Second logged event.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Cancel event activation 1.
        doAppBreadcrumbReported(act1CancelMatcherId);
        Thread.sleep(10L);

        // Ignored, metric not active.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Trigger event activation 1.
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // Trigger event activation 2.
        doAppBreadcrumbReported(act2MatcherId);
        Thread.sleep(10L);

        // Third logged event.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Cancel event activation 2.
        doAppBreadcrumbReported(act2CancelMatcherId);
        Thread.sleep(10L);

        // Fourth logged event.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Expire event activation 1
        Thread.sleep(act1TtlSecs * 1000);

        // Ignored, metric 1 not active. Activation 1 expired and Activation 2 was cancelled.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // Trigger event activation 2.
        doAppBreadcrumbReported(act2MatcherId);
        Thread.sleep(10L);

        // Metric 1 log ignored, Activation 1 expired and Activation 2 needs reboot to activate.
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        // First logged event for Metric 3.
        doAppBreadcrumbReported(metric3MatcherId);
        Thread.sleep(10L);

        ConfigMetricsReportList reportList = getReportList();
        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
        ConfigMetricsReport report = reports.get(0);
        verifyMetrics(report, 4, 0, 1);
    }

    /**
     * Metric 1:
     *     Activation 1:
     *         - Ttl: 100 seconds
     *         - Type: IMMEDIATE
     *     Activation 2:
     *         - Ttl: 200 seconds
     *         - Type: ON_BOOT
     *
     * Metric 2:
     *     Activation 1:
     *         - Ttl: 100 seconds
     *         - Type: ON_BOOT
     *     Activation 2:
     *         - Ttl: 200 seconds
     *         - Type: IMMEDIATE
     *
     * Metric 3: No activations; always active
     **/
    public void testRestart() throws Exception {
        final int act1TtlSecs = 200;
        final int act2TtlSecs = 400;
        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));

        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 0 seconds
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // First logged event for Metric 1.
        // Metric 2 event ignored, will activate after boot.
        // First logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 200 seconds
        // Metric 2 Activation 2: 0 seconds
        rebootDeviceAndWaitUntilReady();

        // Second logged event for Metric 1.
        // First logged event for Metric 2.
        // Second logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds
        // Metric 2 Activation 2: 0 seconds
        Thread.sleep(act1TtlSecs * 1000L);

        // Metric 1 event ignored, Activation 1 expired.
        // Metric 2 event ignored, Activation 1 expired.
        // Third logged event for Metric 3.
        logAllMetrics();

        // Trigger Metric 1 Activation 2 and Metric 2 Activation 2.
        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 0 seconds (will activate after boot)
        // Metric 2 Activation 1: 0 seconds
        // Metric 2 Activation 2: 400 seconds
        doAppBreadcrumbReported(act2MatcherId);
        Thread.sleep(10L);

        // Metric 1 event ignored, will activate after boot.
        // Second logged event for Metric 2.
        // Fourth logged event for Metric 3.
        logAllMetrics();

        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds (will activate after boot)
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 400 seconds
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // Third logged event for Metric 1.
        // Third logged event for Metric 2.
        // Fifth logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 100 seconds
        // Metric 1 Activation 2: 0 seconds (will activate after boot)
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 300 seconds
        Thread.sleep(act1TtlSecs * 1000L / 2);

        // Time remaining:
        // Metric 1 Activation 1: 100 seconds
        // Metric 1 Activation 2: 400 seconds
        // Metric 2 Activation 1: 200 seconds
        // Metric 2 Activation 2: 300 seconds
        rebootDeviceAndWaitUntilReady();

        // Fourth logged event for Metric 1.
        // Fourth logged event for Metric 2.
        // Sixth logged event for Metric 3.
        logAllMetrics();

        // Expire Metric 1 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 300 seconds
        // Metric 2 Activation 1: 100 seconds
        // Metric 2 Activation 2: 200 seconds
        Thread.sleep(act1TtlSecs * 1000L / 2);

        // Fifth logged event for Metric 1.
        // Fifth logged event for Metric 2.
        // Seventh logged event for Metric 3.
        logAllMetrics();

        // Expire all activations.
        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds
        // Metric 2 Activation 2: 0 seconds
        Thread.sleep(act2TtlSecs * 1000L);

        // Metric 1 event ignored.
        // Metric 2 event ignored.
        // Eighth logged event for Metric 3.
        logAllMetrics();

        ConfigMetricsReportList reportList = getReportList();
        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
        assertThat(reports).hasSize(3);

        // Report before restart.
        ConfigMetricsReport report = reports.get(0);
        verifyMetrics(report, 1, 0, 1);

        // Report after first restart.
        report = reports.get(1);
        verifyMetrics(report, 2, 3, 4);

        // Report after second restart.
        report = reports.get(2);
        verifyMetrics(report, 2, 2, 3);
    }

    /**
     * Metric 1:
     *     Activation 1:
     *         - Ttl: 100 seconds
     *         - Type: IMMEDIATE
     *     Activation 2:
     *         - Ttl: 200 seconds
     *         - Type: ON_BOOT
     *
     * Metric 2:
     *     Activation 1:
     *         - Ttl: 100 seconds
     *         - Type: ON_BOOT
     *     Activation 2:
     *         - Ttl: 200 seconds
     *         - Type: IMMEDIATE
     *
     * Metric 3: No activations; always active
     **/
    public void testMultipleActivations() throws Exception {
        final int act1TtlSecs = 200;
        final int act2TtlSecs = 400;
        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));

        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 0 seconds
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // First logged event for Metric 1.
        // Metric 2 event ignored, will activate after boot.
        // First logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 100 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 0 seconds
        Thread.sleep(act1TtlSecs * 1000L / 2);

        // Second logged event for Metric 1.
        // Metric 2 event ignored, will activate after boot.
        // Second logged event for Metric 3.
        logAllMetrics();

        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds (will activate after boot)
        // Metric 2 Activation 2: 0 seconds
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // Third logged event for Metric 1.
        // Metric 2 event ignored, will activate after boot.
        // Third logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 200 seconds
        // Metric 2 Activation 2: 0 seconds
        rebootDeviceAndWaitUntilReady();

        // Fourth logged event for Metric 1.
        // First logged event for Metric 2.
        // Fourth logged event for Metric 3.
        logAllMetrics();

        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
        // Time remaining:
        // Metric 1 Activation 1: 200 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 200 seconds
        // Metric 2 Activation 2: 0 seconds
        doAppBreadcrumbReported(act1MatcherId);
        Thread.sleep(10L);

        // Fifth logged event for Metric 1.
        // Second logged event for Metric 2.
        // Fifth logged event for Metric 3.
        logAllMetrics();

        // Expire all activations.
        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds
        // Metric 2 Activation 2: 0 seconds
        Thread.sleep(act1TtlSecs * 1000L);

        // Metric 1 event ignored.
        // Metric 2 event ignored.
        // Sixth logged event for Metric 3.
        logAllMetrics();

        // Time remaining:
        // Metric 1 Activation 1: 0 seconds
        // Metric 1 Activation 2: 0 seconds
        // Metric 2 Activation 1: 0 seconds
        // Metric 2 Activation 2: 0 seconds
        rebootDeviceAndWaitUntilReady();
        Thread.sleep(3_000L);

        // Metric 1 event ignored.
        // Metric 2 event ignored.
        // Seventh logged event for Metric 3.
        logAllMetrics();

        ConfigMetricsReportList reportList = getReportList();
        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
        assertThat(reports).hasSize(3);

        // Report before restart.
        ConfigMetricsReport report = reports.get(0);
        verifyMetrics(report, 3, 0, 3);

        // Report after first restart.
        report = reports.get(1);
        verifyMetrics(report, 2, 2, 3);

        // Report after second restart.
        report = reports.get(2);
        verifyMetrics(report, 0, 0, 1);
    }

    private void logAllMetrics() throws Exception {
        doAppBreadcrumbReported(metric1MatcherId);
        Thread.sleep(10L);

        doAppBreadcrumbReported(metric2MatcherId);
        Thread.sleep(10L);

        doAppBreadcrumbReported(metric3MatcherId);
        Thread.sleep(10L);
    }

    private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
            int metric3Count) throws Exception {
        assertThat(report.getMetricsCount()).isEqualTo(3);

        verifyMetric(
                report.getMetrics(0),   // StatsLogReport
                1,                      // Metric Id
                1,                      // Metric what atom matcher label
                metric1Count            // Data count
        );
        verifyMetric(
                report.getMetrics(1),   // StatsLogReport
                2,                      // Metric Id
                2,                      // Metric what atom matcher label
                metric2Count            // Data count
        );
        verifyMetric(
                report.getMetrics(2),   // StatsLogReport
                3,                      // Metric Id
                3,                      // Metric what atom matcher label
                metric3Count            // Data count
        );
    }

    private void verifyMetric(StatsLogReport metricReport, long metricId, int metricMatcherLabel,
            int dataCount) {
        LogUtil.CLog.d("Got the following event metric data: " + metricReport.toString());
        assertThat(metricReport.getMetricId()).isEqualTo(metricId);
        assertThat(metricReport.hasEventMetrics()).isEqualTo(dataCount > 0);

        StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
        List<EventMetricData> eventMetricDataList = new ArrayList<>();
        for (EventMetricData eventMetricData : eventData.getDataList()) {
            eventMetricDataList.addAll(backfillAggregatedAtomsInEventMetric(eventMetricData));
        }
        assertThat(eventMetricDataList).hasSize(dataCount);
        for (EventMetricData eventMetricData : eventMetricDataList) {
            AppBreadcrumbReported atom = eventMetricData.getAtom().getAppBreadcrumbReported();
            assertThat(atom.getLabel()).isEqualTo(metricMatcherLabel);
        }
    }
}
