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

import collections
import re
import urllib

from common.buildbot import network


StackTraceLine = collections.namedtuple(
    'StackTraceLine', ('file', 'function', 'line', 'source'))


class Step(object):

  def __init__(self, data, build_url):
    self._name = data['name']
    self._status = data['results'][0]
    self._start_time, self._end_time = data['times']
    self._url = '%s/steps/%s' % (build_url, urllib.quote(self._name))

    self._log_link = None
    self._results_link = None
    for link_name, link_url in data['logs']:
      if link_name == 'stdio':
        self._log_link = link_url + '/text'
      elif link_name == 'json.output':
        self._results_link = link_url + '/text'

    # Property caches.
    self._log = None
    self._results = None
    self._stack_trace = None

  def __str__(self):
    return self.name

  @property
  def name(self):
    return self._name

  @property
  def url(self):
    return self._url

  @property
  def status(self):
    return self._status

  @property
  def start_time(self):
    return self._start_time

  @property
  def end_time(self):
    return self._end_time

  @property
  def log_link(self):
    return self._log_link

  @property
  def results_link(self):
    return self._results_link

  @property
  def log(self):
    if self._log is None:
      if not self.log_link:
        return None

      self._log = network.FetchText(self.log_link)
    return self._log

  @property
  def results(self):
    if self._results is None:
      if not self.results_link:
        return None

      self._results = network.FetchData(self.results_link)
    return self._results

  @property
  def stack_trace(self):
    if self._stack_trace is None:
      self._stack_trace = _ParseTraceFromLog(self.log)
    return self._stack_trace


def _ParseTraceFromLog(log):
  """Searches the log for a stack trace and returns a structured representation.

  This function supports both default Python-style stacks and Telemetry-style
  stacks. It returns the first stack trace found in the log - sometimes a bug
  leads to a cascade of failures, so the first one is usually the root cause.

  Args:
    log: A string containing Python or Telemetry stack traces.

  Returns:
    Two values, or (None, None) if no stack trace was found.
    The first is a tuple of StackTraceLine objects, most recent call last.
    The second is a string with the type and description of the exception.
  """
  log_iterator = iter(log.splitlines())
  for line in log_iterator:
    if line == 'Traceback (most recent call last):':
      break
  else:
    return (None, None)

  stack_trace = []
  while True:
    line = log_iterator.next()
    match_python = re.match(r'\s*File "(?P<file>.+)", line (?P<line>[0-9]+), '
                            r'in (?P<function>.+)', line)
    match_telemetry = re.match(r'\s*(?P<function>.+) at '
                               r'(?P<file>.+):(?P<line>[0-9]+)', line)
    match = match_python or match_telemetry
    if not match:
      exception = line
      break
    trace_line = match.groupdict()
    # Use the base name, because the path will be different
    # across platforms and configurations.
    file_base_name = trace_line['file'].split('/')[-1].split('\\')[-1]
    source = log_iterator.next().strip()
    stack_trace.append(StackTraceLine(
        file_base_name, trace_line['function'], trace_line['line'], source))

  return tuple(stack_trace), exception