/*
 * Copyright (C) 2011 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 com.android.graphics.tests;

import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.NullOutputReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;

import org.junit.Assert;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Runs the user action framerate benchmark test. This test capture use the scripted monkey to
 * inject the keyevent to mimic the real user action, capture the average framerate and save the
 * result file to the sdcard.
 *
 * <p>Note that this test will not run properly unless /sdcard is mounted and writable.
 */
public class UserActionBenchmark implements IDeviceTest, IRemoteTest {
    private static final String LOG_TAG = "UserActionBenchmark";

    ITestDevice mTestDevice = null;

    private static final long START_TIMER = 2 * 60 * 1000; // 2 minutes

    @Option(name = "test-output-filename", description = "The test output filename.")
    private String mDeviceTestOutputFilename = "avgFrameRateOut.txt";

    // The time in ms to wait the scripted monkey finish.
    private static final int CMD_TIMEOUT = 60 * 60 * 1000;

    private static final Pattern AVERAGE_FPS = Pattern.compile("(.*):(\\d+.\\d+)");

    @Option(name = "test-case", description = "The name of test-cases  to run. May be repeated.")
    private Collection<String> mTestCases = new ArrayList<>();

    @Option(name = "iteration", description = "Test run iteration")
    private int mIteration = 1;

    @Option(name = "throttle", description = "Scripted monkey throttle time")
    private int mThrottle = 500; // in milliseconds

    @Option(name = "script-path", description = "Test script path")
    private String mScriptPath = "userActionFPSScript";

    @Option(name = "test-label", description = "Test label")
    private String mTestLabel = "UserActionFramerateBenchmark";

    @Override
    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
        Assert.assertNotNull(mTestDevice);

        // Start the test after device is fully booted and stable
        // FIXME: add option in TF to wait until device is booted and stable
        RunUtil.getDefault().sleep(START_TIMER);

        String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
        String scriptFullPath =
                String.format("%s/%s/%s", extStore, mScriptPath, mTestDevice.getProductType());
        for (String testCase : mTestCases) {
            // Start the scripted monkey command
            mTestDevice.executeShellCommand(
                    String.format(
                            "monkey -f /%s/%s.txt --throttle %d %d",
                            scriptFullPath, testCase, mThrottle, mIteration),
                    new NullOutputReceiver(),
                    CMD_TIMEOUT,
                    TimeUnit.MILLISECONDS,
                    2);
            logOutputFiles(listener);
            cleanResultFile();
        }
    }

    /** Clean up the test result file from test run */
    private void cleanResultFile() throws DeviceNotAvailableException {
        String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
        mTestDevice.executeShellCommand(
                String.format("rm %s/%s", extStore, mDeviceTestOutputFilename));
    }

    /**
     * Pull the output files from the device, add it to the logs, and also parse out the relevant
     * test metrics and report them.
     */
    private void logOutputFiles(ITestInvocationListener listener)
            throws DeviceNotAvailableException {
        File outputFile = null;
        InputStreamSource outputSource = null;
        try {
            outputFile = mTestDevice.pullFileFromExternal(mDeviceTestOutputFilename);

            if (outputFile == null) {
                return;
            }

            // Upload a verbatim copy of the output file
            Log.d(
                    LOG_TAG,
                    String.format(
                            "Sending %d byte file %s into the logosphere!",
                            outputFile.length(), outputFile));
            outputSource = new FileInputStreamSource(outputFile);
            listener.testLog(mDeviceTestOutputFilename, LogDataType.TEXT, outputSource);

            // Parse the output file to upload aggregated metrics
            parseOutputFile(new FileInputStream(outputFile), listener);
        } catch (IOException e) {
            Log.e(
                    LOG_TAG,
                    String.format("IOException while reading or parsing output file: %s", e));
        } finally {
            FileUtil.deleteFile(outputFile);
            StreamUtil.cancel(outputSource);
        }
    }

    /**
     * Parse the test result, calculate the average and parse the metrics from the scripted monkey
     * test output file
     */
    private void parseOutputFile(InputStream dataStream, ITestInvocationListener listener) {

        Map<String, String> runMetrics = new HashMap<>();

        // try to parse it
        String contents;
        try {
            contents = StreamUtil.getStringFromStream(dataStream);
        } catch (IOException e) {
            Log.e(LOG_TAG, String.format("Got IOException during test processing: %s", e));
            return;
        }

        List<String> lines = Arrays.asList(contents.split("\n"));
        String key = null;
        float averageResult;
        float totalResult = 0;
        int counter = 0;

        // collect the result and calculate the average
        for (String line : lines) {
            Matcher m = AVERAGE_FPS.matcher(line);
            if (m.matches()) {
                key = m.group(1);
                totalResult += Float.parseFloat(m.group(2));
                counter++;
            }
        }
        averageResult = totalResult / counter;
        Log.i(LOG_TAG, String.format("averageResult = %s\n", averageResult));
        runMetrics.put(key, Float.toString(averageResult));
        reportMetrics(listener, runMetrics);
    }

    /**
     * Report run metrics by creating an empty test run to stick them in
     *
     * <p>Exposed for unit testing
     */
    void reportMetrics(ITestInvocationListener listener, Map<String, String> metrics) {
        Log.d(LOG_TAG, String.format("About to report metrics: %s", metrics));
        listener.testRunStarted(mTestLabel, 0);
        listener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(metrics));
    }

    @Override
    public void setDevice(ITestDevice device) {
        mTestDevice = device;
    }

    @Override
    public ITestDevice getDevice() {
        return mTestDevice;
    }
}
