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

import logging
import os
import re
import tempfile
import time

from devil.utils import cmd_helper
from pylib import constants
from pylib.constants import host_paths
from .expensive_line_transformer import ExpensiveLineTransformer
from .expensive_line_transformer import ExpensiveLineTransformerPool

_STACK_TOOL = os.path.join(
    host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, 'stack'
)
_MINIMUM_TIMEOUT = 10.0
_PER_LINE_TIMEOUT = 0.005  # Should be able to process 200 lines per second.
_PROCESS_START_TIMEOUT = 20.0
_MAX_RESTARTS = 4  # Should be plenty unless tool is crashing on start-up.
_POOL_SIZE = 1
_PASSTHROUH_ON_FAILURE = True
ABI_REG = re.compile('ABI: \'(.+?)\'')


def _DeviceAbiToArch(device_abi):
    # The order of this list is significant to find the more specific match
    # (e.g., arm64) before the less specific (e.g., arm).
    arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips']
    for arch in arches:
        if arch in device_abi:
            return arch
    raise RuntimeError('Unknown device ABI: %s' % device_abi)


class Symbolizer:
    """A helper class to symbolize stack."""

    def __init__(self, apk_under_test=None):
        self._apk_under_test = apk_under_test
        self._time_spent_symbolizing = 0

    def __del__(self):
        self.CleanUp()

    def CleanUp(self):
        """Clean up the temporary directory of apk libs."""
        if self._time_spent_symbolizing > 0:
            logging.info(
                'Total time spent symbolizing: %.2fs',
                self._time_spent_symbolizing,
            )

    def ExtractAndResolveNativeStackTraces(
        self, data_to_symbolize, device_abi, include_stack=True
    ):
        """Run the stack tool for given input.

        Args:
          data_to_symbolize: a list of strings to symbolize.
          include_stack: boolean whether to include stack data in output.
          device_abi: the default ABI of the device which generated the tombstone.

        Yields:
          A string for each line of resolved stack output.
        """
        if not os.path.exists(_STACK_TOOL):
            logging.warning(
                '%s missing. Unable to resolve native stack traces.',
                _STACK_TOOL,
            )
            return

        arch = _DeviceAbiToArch(device_abi)
        if not arch:
            logging.warning('No device_abi can be found.')
            return

        cmd = [
            _STACK_TOOL,
            '--arch',
            arch,
            '--output-directory',
            constants.GetOutDirectory(),
            '--more-info',
        ]
        if self._apk_under_test:
            cmd.extend(
                ['--apks-directory', os.path.dirname(self._apk_under_test)]
            )
        env = dict(os.environ)
        env['PYTHONDONTWRITEBYTECODE'] = '1'
        with tempfile.NamedTemporaryFile(mode='w') as f:
            f.write('\n'.join(data_to_symbolize))
            f.flush()
            start = time.time()
            try:
                _, output = cmd_helper.GetCmdStatusAndOutput(
                    cmd + [f.name], env=env
                )
            finally:
                self._time_spent_symbolizing += time.time() - start
        for line in output.splitlines():
            if not include_stack and 'Stack Data:' in line:
                break
            yield line


class PassThroughSymbolizer(ExpensiveLineTransformer):
    def __init__(self, device_abi, apks_directory=None):
        self._command = None
        super().__init__(
            _PROCESS_START_TIMEOUT, _MINIMUM_TIMEOUT, _PER_LINE_TIMEOUT
        )
        if not os.path.exists(_STACK_TOOL):
            logging.warning(
                '%s: %s missing. Unable to resolve native stack traces.',
                PassThroughSymbolizer.name,
                _STACK_TOOL,
            )
            return
        arch = _DeviceAbiToArch(device_abi)
        if not arch:
            logging.warning(
                '%s: No device_abi can be found.', PassThroughSymbolizer.name
            )
            return
        self._command = [
            _STACK_TOOL,
            '--arch',
            arch,
            '--output-directory',
            constants.GetOutDirectory(),
            '--more-info',
            '--pass-through',
            '--flush',
            '--quiet',
        ]
        if apks_directory:
            self._command.extend(['--apks-directory', apks_directory])
        self._command.append('-')
        self.start()

    @property
    def name(self):
        return "symbolizer"

    @property
    def command(self):
        return self._command


class PassThroughSymbolizerPool(ExpensiveLineTransformerPool):
    def __init__(self, device_abi, apks_directory=None):
        self._device_abi = device_abi
        self._apks_directory = apks_directory
        super().__init__(_MAX_RESTARTS, _POOL_SIZE, _PASSTHROUH_ON_FAILURE)

    def CreateTransformer(self):
        return PassThroughSymbolizer(self._device_abi, self._apks_directory)

    @property
    def name(self):
        return "symbolizer-pool"
