// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base.jank_tracker;

import android.os.Handler;

import org.chromium.base.TraceEvent;

/**
 * This runnable receives a FrameMetricsStore instance and starts/stops tracking a given scenario.
 * When a scenario stops it takes its metrics and sends them to native to be recorded in UMA.
 * This is executed by JankReportingScheduler on its own thread.
 */
class JankReportingRunnable implements Runnable {
    private final FrameMetricsStore mMetricsStore;
    private final @JankScenario int mScenario;
    private final boolean mIsStartingTracking;
    // This must be the same handler that this is running on.
    private final Handler mHandler;
    // If metrics should be collected based on the state (scrolling) specify a
    // JankEndScenarioTime.
    private final JankEndScenarioTime mJankEndScenarioTime;

    // When a JankEndScenarioTime is specified we don't immediately collect the metrics but instead
    // post a task (this runnable). However to keep code reuse the same between delay/no-delay we
    // use the same runnable but don't post it when there is no delay.
    private class FinalReportingRunnable implements Runnable {
        @Override
        public void run() {
            try (TraceEvent e = TraceEvent.scoped("ReportingCUJScenarioData", mScenario)) {
                JankMetrics metrics;
                if (mJankEndScenarioTime == null) {
                    metrics = mMetricsStore.stopTrackingScenario(mScenario);
                } else {
                    // Since this is after the timeout we just unconditionally get the metrics.
                    metrics =
                            mMetricsStore.stopTrackingScenario(
                                    mScenario, mJankEndScenarioTime.endScenarioTimeNs);
                }

                if (metrics == null || metrics.timestampsNs.length == 0) {
                    TraceEvent.instant("no metrics");
                    return;
                }

                long startTime = metrics.timestampsNs[0] / 1000000;
                long lastTime = metrics.timestampsNs[metrics.timestampsNs.length - 1] / 1000000;
                long lastDuration = metrics.durationsNs[metrics.durationsNs.length - 1] / 1000000;
                // The time that we have metrics covering is from the first VSYNC_TIMESTAMP
                // (startTime) to the last frame has finished (lastTime + lastDuration).
                long endTime = lastTime - startTime + lastDuration;

                // Confirm that the current call context is valid.
                // Debug builds will assert and fail; release builds will optimize this out.
                JankMetricUMARecorderJni.get();
                // TODO(salg@): Cache metrics in case native takes >30s to initialize.
                JankMetricUMARecorder.recordJankMetricsToUMA(
                        metrics, startTime, endTime, mScenario);
            }
        }
    }

    JankReportingRunnable(
            FrameMetricsStore metricsStore,
            @JankScenario int scenario,
            boolean isStartingTracking,
            Handler handler,
            JankEndScenarioTime endScenarioTime) {
        mMetricsStore = metricsStore;
        mScenario = scenario;
        mIsStartingTracking = isStartingTracking;
        mHandler = handler;
        mJankEndScenarioTime = endScenarioTime;
    }

    @Override
    public void run() {
        try (TraceEvent e =
                TraceEvent.scoped(
                        "StartingOrStoppingJankScenario",
                        "StartingScenario:"
                                + Boolean.toString(mIsStartingTracking)
                                + ",Scenario:"
                                + Integer.toString(mScenario))) {
            if (mIsStartingTracking) {
                if (mMetricsStore == null) {
                    TraceEvent.instant("StartTrackingScenario metrics store null");
                    return;
                }
                mMetricsStore.startTrackingScenario(mScenario);
                return;
            }
            boolean dataIsReady =
                    mJankEndScenarioTime == null
                            || (mJankEndScenarioTime != null
                                    && mMetricsStore.hasReceivedMetricsPast(
                                            mJankEndScenarioTime.endScenarioTimeNs));

            if (dataIsReady) {
                new FinalReportingRunnable().run();
            } else {
                mHandler.postDelayed(
                        new FinalReportingRunnable(), mJankEndScenarioTime.timeoutDelayMs);
            }
        }
    }
}
