# Copyright 2015 The Chromium OS 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 logging
import re

from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error


def get_histogram_text(tab, histogram_name):
     """
     This returns contents of the given histogram.

     @param tab: object, Chrome tab instance
     @param histogram_name: string, name of the histogram
     @returns string: contents of the histogram
     """
     docEle = 'document.documentElement'
     tab.Navigate('chrome://histograms/%s' % histogram_name)
     tab.WaitForDocumentReadyStateToBeComplete()
     raw_text = tab.EvaluateJavaScript(
          '{0} && {0}.innerText'.format(docEle))
     # extract the contents of the histogram
     histogram = raw_text[raw_text.find('Histogram:'):].strip()
     if histogram:
          logging.debug('chrome://histograms/%s:\n%s', histogram_name,
                        histogram)
     else:
          logging.debug('No histogram is shown in chrome://histograms/%s',
                        histogram_name)
     return histogram


def loaded(tab, histogram_name, pattern):
     """
     Checks if the histogram page has been fully loaded.

     @param tab: object, Chrome tab instance
     @param histogram_name: string, name of the histogram
     @param pattern: string, required text to look for
     @returns re.MatchObject if the given pattern is found in the text
              None otherwise

     """
     return re.search(pattern, get_histogram_text(tab, histogram_name))


def  verify(cr, histogram_name, histogram_bucket_value):
     """
     Verifies histogram string and success rate in a parsed histogram bucket.
     The histogram buckets are outputted in debug log regardless of the
     verification result.

     Full histogram URL is used to load histogram. Example Histogram URL is :
     chrome://histograms/Media.GpuVideoDecoderInitializeStatus

     @param cr: object, the Chrome instance
     @param histogram_name: string, name of the histogram
     @param histogram_bucket_value: int, required bucket number to look for
     @raises error.TestError if histogram is not successful

     """
     bucket_pattern = '\n'+ str(histogram_bucket_value) +'.*100\.0%.*'
     error_msg_format = ('{} not loaded or histogram bucket not found '
                         'or histogram bucket found at < 100%')
     tab = cr.browser.tabs.New()
     msg = error_msg_format.format(histogram_name)
     utils.poll_for_condition(lambda : loaded(tab, histogram_name,
                                              bucket_pattern),
                              exception=error.TestError(msg),
                              sleep_interval=1)


def is_bucket_present(cr,histogram_name, histogram_bucket_value):
     """
     This returns histogram succes or fail to called function

     @param cr: object, the Chrome instance
     @param histogram_name: string, name of the histogram
     @param histogram_bucket_value: int, required bucket number to look for
     @returns True if histogram page was loaded and the bucket was found.
              False otherwise

     """
     try:
          verify(cr,histogram_name, histogram_bucket_value)
     except error.TestError:
          return False
     else:
          return True


def is_histogram_present(cr, histogram_name):
     """
     This checks if the given histogram is present and non-zero.

     @param cr: object, the Chrome instance
     @param histogram_name: string, name of the histogram
     @returns True if histogram page was loaded and the histogram is present
              False otherwise

     """
     histogram_pattern = 'Histogram: '+ histogram_name + ' recorded ' + \
                         r'[1-9][0-9]*' + ' samples'
     tab = cr.browser.tabs.New()
     try:
          utils.poll_for_condition(lambda : loaded(tab, histogram_name,
                                                   histogram_pattern),
                                   timeout=2,
                                   sleep_interval=0.1)
          return True
     except utils.TimeoutError:
          # the histogram is not present, and then returns false
          return False


def get_histogram(cr, histogram_name):
     """
     This returns contents of the given histogram.

     @param cr: object, the Chrome instance
     @param histogram_name: string, name of the histogram
     @returns string: contents of the histogram

     """
     tab = cr.browser.tabs.New()
     return get_histogram_text(tab, histogram_name)


def parse_histogram(histogram_text):
     """
     Parses histogram text into bucket structure.

     @param histogram_text: histogram raw text.
     @returns dict(bucket_value, bucket_count)
     """
     # Match separator line, e.g. "1   ..."
     RE_SEPEARTOR = re.compile(r'\d+\s+\.\.\.')
     # Match bucket line, e.g. "2  --O  (46 = 1.5%) {46.1%}"
     RE_BUCKET = re.compile(
          r'(\d+)\s+\-*O\s+\((\d+) = (\d+\.\d+)%\).*')
     result = {}
     for line in histogram_text.splitlines():
          if RE_SEPEARTOR.match(line):
               continue
          m = RE_BUCKET.match(line)
          if m:
               result[int(m.group(1))] = int(m.group(2))
     return result


def subtract_histogram(minuend, subtrahend):
     """
     Subtracts histogram: minuend - subtrahend

     @param minuend: histogram bucket dict from which another is to be
                     subtracted.
     @param subtrahend: histogram bucket dict to be subtracted from another.
     @result difference of the two histograms in bucket dict. Note that
             zero-counted buckets are removed.
     """
     result = collections.defaultdict(int, minuend)
     for k, v in subtrahend.iteritems():
          result[k] -= v

     # Remove zero counted buckets.
     return {k: v for k, v in result.iteritems() if v}


def expect_sole_bucket(histogram_differ, bucket, bucket_name, timeout=10,
                       sleep_interval=1):
     """
     Returns true if the given bucket solely exists in histogram differ.

     @param histogram_differ: a HistogramDiffer instance used to get histogram
            name and histogram diff multiple times.
     @param bucket: bucket value.
     @param bucket_name: bucket name to be shown on error message.
     @param timeout: timeout in seconds.
     @param sleep_interval: interval in seconds between getting diff.
     @returns True if the given bucket solely exists in histogram.
     @raises TestError if bucket doesn't exist or other buckets exist.
     """
     timer = utils.Timer(timeout)
     histogram = {}
     histogram_name = histogram_differ.histogram_name
     while timer.sleep(sleep_interval):
          histogram = histogram_differ.end()
          if histogram:
               break

     if bucket not in histogram:
          raise error.TestError('Expect %s has %s. Histogram: %r' %
                                (histogram_name, bucket_name, histogram))
     if len(histogram) > 1:
          raise error.TestError('%s has bucket other than %s. Histogram: %r' %
                                (histogram_name, bucket_name, histogram))
     return True


def poll_histogram_grow(histogram_differ, timeout=2, sleep_interval=0.1):
     """
     Polls histogram to see if it grows within |timeout| seconds.

     @param histogram_differ: a HistogramDiffer instance used to get histogram
            name and histogram diff multiple times.
     @param timeout: observation timeout in seconds.
     @param sleep_interval: interval in seconds between getting diff.
     @returns (True, histogram_diff) if the histogram grows.
              (False, {}) if it does not grow in |timeout| seconds.
     """
     timer = utils.Timer(timeout)
     while timer.sleep(sleep_interval):
          histogram_diff = histogram_differ.end()
          if histogram_diff:
               return (True, histogram_diff)
     return (False, {})


class HistogramDiffer(object):
     """
     Calculates a histogram's progress between begin() and end().

     Usage:
       differ = HistogramDiffer(cr, 'Media.GpuVideoDecoderError')
       ....
       diff_gvd_error = differ.end()
     """
     def __init__(self, cr, histogram_name, begin=True):
          """
          Constructor.

          @param: cr: object, the Chrome instance
          @param: histogram_name: string, name of the histogram
          @param: begin: if set, calls begin().
          """
          self.cr = cr
          self.histogram_name = histogram_name
          self.begin_histogram_text = ''
          self.end_histogram_text = ''
          self.begin_histogram = {}
          self.end_histogram = {}
          if begin:
               self.begin()

     def _get_histogram(self):
          """
          Gets current histogram bucket.

          @returns (dict(bucket_value, bucket_count), histogram_text)
          """
          tab = self.cr.browser.tabs.New()
          text = get_histogram_text(tab, self.histogram_name)
          tab.Close()
          return (parse_histogram(text), text)

     def begin(self):
          """
          Takes a histogram snapshot as begin_histogram.
          """
          (self.begin_histogram,
           self.begin_histogram_text) = self._get_histogram()
          logging.debug('begin histograms/%s: %r\nraw_text: %s',
                        self.histogram_name, self.begin_histogram,
                        self.begin_histogram_text)

     def end(self):
          """
          Takes a histogram snapshot as end_histogram.

          @returns self.diff()
          """
          self.end_histogram, self.end_histogram_text = self._get_histogram()
          logging.debug('end histograms/%s: %r\nraw_text: %s',
                        self.histogram_name, self.end_histogram,
                        self.end_histogram_text)
          diff = subtract_histogram(self.end_histogram, self.begin_histogram)
          logging.debug('histogram diff: %r', diff)
          return diff