#!/usr/bin/env python
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utilities for Cronet performance tests."""

import logging
import os
import posixpath
import subprocess
import tempfile
from time import sleep

from cronet.tools import android_rndis_forwarder

# pylint: disable=useless-object-inheritance

REPOSITORY_ROOT = os.path.abspath(
    os.path.join(os.path.dirname(__file__), '..', '..', '..'))
BUILD_TYPE = 'Release'
BUILD_DIR = os.path.join(REPOSITORY_ROOT, 'out', BUILD_TYPE)
QUIC_SERVER = os.path.join(BUILD_DIR, 'quic_server')
CERT_PATH = os.path.join('net', 'data', 'ssl', 'certificates')
QUIC_CERT_DIR = os.path.join(REPOSITORY_ROOT, CERT_PATH)
QUIC_CERT_HOST = 'test.example.com'
QUIC_CERT_FILENAME = 'quic-chain.pem'
QUIC_CERT = os.path.join(QUIC_CERT_DIR, QUIC_CERT_FILENAME)
QUIC_KEY = os.path.join(QUIC_CERT_DIR, 'quic-leaf-cert.key')
APP_APK = os.path.join(BUILD_DIR, 'apks', 'CronetPerfTest.apk')
APP_PACKAGE = 'org.chromium.net'
APP_ACTIVITY = '.CronetPerfTestActivity'
APP_ACTION = 'android.intent.action.MAIN'
HTTP_PORT = None  # Value will be overridden by DEFAULT_BENCHMARK_CONFIG.
# TODO(pauljensen): Consider whether we can avoid loading this
# DEFAULT_BENCHMARK_CONFIG dict into globals.
DEFAULT_BENCHMARK_CONFIG = {
    # Control various metric recording for further investigation.
    'CAPTURE_NETLOG': False,
    'CAPTURE_TRACE': False,
    'CAPTURE_SAMPLED_TRACE': False,
    # While running Cronet Async API benchmarks, indicate if callbacks should be
    # run on network thread rather than posted back to caller thread.  This allows
    # measuring if thread-hopping overhead is significant.
    'CRONET_ASYNC_USE_NETWORK_THREAD': False,
    # A small resource for device to fetch from host.
    'SMALL_RESOURCE': 'small.html',
    'SMALL_RESOURCE_SIZE': 26,
    # Number of times to fetch SMALL_RESOURCE.
    'SMALL_ITERATIONS': 1000,
    # A large resource for device to fetch from host.
    'LARGE_RESOURCE': 'large.html',
    'LARGE_RESOURCE_SIZE': 10000026,
    # Number of times to fetch LARGE_RESOURCE.
    'LARGE_ITERATIONS': 4,
    # Ports of HTTP and QUIC servers on host.
    'HTTP_PORT': 9000,
    'QUIC_PORT': 9001,
    # Maximum read/write buffer size to use.
    'MAX_BUFFER_SIZE': 16384,
    'HOST': QUIC_CERT_HOST,
    'QUIC_CERT_FILE': QUIC_CERT_FILENAME,
}
# Add benchmark config to global state for easy access.
globals().update(DEFAULT_BENCHMARK_CONFIG)
# Pylint doesn't really interpret the file, so it won't find the definitions
# added from DEFAULT_BENCHMARK_CONFIG, so suppress the undefined variable and
# bad string format type warnings.
#pylint: disable=undefined-variable,bad-string-format-type


class NativeDevice(object):

    def GetExternalStoragePath(self):
        return '/tmp'

    def RunShellCommand(self, cmd, check_return=False):
        if check_return:
            subprocess.check_call(cmd)
        else:
            subprocess.call(cmd)

    def WriteFile(self, path, data):
        with open(path, 'w') as f:
            f.write(data)


def GetConfig(device):
    config = DEFAULT_BENCHMARK_CONFIG
    config['HOST_IP'] = GetServersHost(device)
    if isinstance(device, NativeDevice):
        config['RESULTS_FILE'] = '/tmp/cronet_perf_test_results.txt'
        config['DONE_FILE'] = '/tmp/cronet_perf_test_done.txt'
    else:
        # An on-device file containing benchmark timings.  Written by benchmark app.
        config['RESULTS_FILE'] = '/data/data/' + APP_PACKAGE + '/results.txt'
        # An on-device file whose presence indicates benchmark app has terminated.
        config['DONE_FILE'] = '/data/data/' + APP_PACKAGE + '/done.txt'
    return config


def GetAndroidRndisConfig(device):
    return android_rndis_forwarder.AndroidRndisConfigurator(device)


def GetServersHost(device):
    if isinstance(device, NativeDevice):
        return '127.0.0.1'
    return GetAndroidRndisConfig(device).host_ip


def GetHttpServerURL(device, resource):
    return 'http://%s:%d/%s' % (GetServersHost(device), HTTP_PORT, resource)


class QuicServer(object):

    def __init__(self, quic_server_doc_root):
        self._process = None
        self._quic_server_doc_root = quic_server_doc_root

    def StartupQuicServer(self, device):
        cmd = [
            QUIC_SERVER,
            '--quic_response_cache_dir=%s' % self._quic_server_doc_root,
            '--certificate_file=%s' % QUIC_CERT,
            '--key_file=%s' % QUIC_KEY,
            '--port=%d' % QUIC_PORT
        ]
        logging.info("Starting Quic Server: %s", cmd)
        self._process = subprocess.Popen(cmd)
        assert self._process is not None
        # Wait for quic_server to start serving.
        waited_s = 0
        while subprocess.call([
                'lsof', '-i',
                'udp:%d' % QUIC_PORT, '-p',
                '%d' % self._process.pid
        ],
                              stdout=open(os.devnull, 'w')) != 0:
            sleep(0.1)
            waited_s += 0.1
            assert waited_s < 5, "quic_server failed to start after %fs" % waited_s
        # Push certificate to device.
        cert = open(QUIC_CERT, 'r').read()
        device_cert_path = posixpath.join(device.GetExternalStoragePath(),
                                          'chromium_tests_root', CERT_PATH)
        device.RunShellCommand(['mkdir', '-p', device_cert_path],
                               check_return=True)
        device.WriteFile(os.path.join(device_cert_path, QUIC_CERT_FILENAME),
                         cert)

    def ShutdownQuicServer(self):
        if self._process:
            self._process.terminate()


def GenerateHttpTestResources():
    http_server_doc_root = tempfile.mkdtemp()
    # Create a small test file to serve.
    small_file_name = os.path.join(http_server_doc_root, SMALL_RESOURCE)
    small_file = open(small_file_name, 'wb')
    small_file.write('<html><body></body></html>')
    small_file.close()
    assert SMALL_RESOURCE_SIZE == os.path.getsize(small_file_name)
    # Create a large (10MB) test file to serve.
    large_file_name = os.path.join(http_server_doc_root, LARGE_RESOURCE)
    large_file = open(large_file_name, 'wb')
    large_file.write('<html><body>')
    for _ in range(0, 1000000):
        large_file.write('1234567890')
    large_file.write('</body></html>')
    large_file.close()
    assert LARGE_RESOURCE_SIZE == os.path.getsize(large_file_name)
    return http_server_doc_root


def GenerateQuicTestResources(device):
    quic_server_doc_root = tempfile.mkdtemp()
    # Use wget to build up fake QUIC in-memory cache dir for serving.
    # quic_server expects the dir/file layout that wget produces.
    for resource in [SMALL_RESOURCE, LARGE_RESOURCE]:
        assert subprocess.Popen([
            'wget', '-p', '-q', '--save-headers',
            GetHttpServerURL(device, resource)
        ],
                                cwd=quic_server_doc_root).wait() == 0
    # wget places results in host:port directory.  Adjust for QUIC port.
    os.rename(
        os.path.join(quic_server_doc_root,
                     "%s:%d" % (GetServersHost(device), HTTP_PORT)),
        os.path.join(quic_server_doc_root,
                     "%s:%d" % (QUIC_CERT_HOST, QUIC_PORT)))
    return quic_server_doc_root


def GenerateLighttpdConfig(config_file, http_server_doc_root, http_server):
    # Must create customized config file to allow overriding the server.bind
    # setting.
    config_file.write('server.document-root = "%s"\n' % http_server_doc_root)
    config_file.write('server.port = %d\n' % HTTP_PORT)
    # These lines are added so lighttpd_server.py's internal test succeeds.
    config_file.write('server.tag = "%s"\n' % http_server.server_tag)
    config_file.write('server.pid-file = "%s"\n' % http_server.pid_file)
    config_file.write('dir-listing.activate = "enable"\n')
    config_file.flush()
