'''
Created on May 19, 2011

@author: bungeman
'''

import re
import math

class BenchDataPoint:
    """A single data point produced by bench.
    
    (str, str, str, float, {str:str})"""
    def __init__(self, bench, config, time_type, time, settings):
        self.bench = bench
        self.config = config
        self.time_type = time_type
        self.time = time
        self.settings = settings
    
    def __repr__(self):
        return "BenchDataPoint(%s, %s, %s, %s, %s)" % (
                   str(self.bench),
                   str(self.config),
                   str(self.time_type),
                   str(self.time),
                   str(self.settings),
               )
    
class _ExtremeType(object):
    """Instances of this class compare greater or less than other objects."""
    def __init__(self, cmpr, rep):
        object.__init__(self)
        self._cmpr = cmpr
        self._rep = rep
    
    def __cmp__(self, other):
        if isinstance(other, self.__class__) and other._cmpr == self._cmpr:
            return 0
        return self._cmpr
    
    def __repr__(self):
        return self._rep

Max = _ExtremeType(1, "Max")
Min = _ExtremeType(-1, "Min")

def parse(settings, lines):
    """Parses bench output into a useful data structure.
    
    ({str:str}, __iter__ -> str) -> [BenchDataPoint]"""
    
    benches = []
    current_bench = None
    setting_re = '([^\s=]+)(?:=(\S+))?'
    settings_re = 'skia bench:((?:\s+' + setting_re + ')*)'
    bench_re = 'running bench (?:\[\d+ \d+\] )?\s*(\S+)'
    time_re = '(?:(\w*)msecs = )?\s*(\d+\.\d+)'
    config_re = '(\S+): ((?:' + time_re + '\s+)+)'
    
    for line in lines:
        
        #see if this line is a settings line
        settingsMatch = re.search(settings_re, line)
        if (settingsMatch):
            settings = dict(settings)
            for settingMatch in re.finditer(setting_re, settingsMatch.group(1)):
                if (settingMatch.group(2)):
                    settings[settingMatch.group(1)] = settingMatch.group(2)
                else:
                    settings[settingMatch.group(1)] = True
                
        #see if this line starts a new bench
        new_bench = re.search(bench_re, line)
        if new_bench:
            current_bench = new_bench.group(1)
        
        #add configs on this line to the current bench
        if current_bench:
            for new_config in re.finditer(config_re, line):
                current_config = new_config.group(1)
                times = new_config.group(2)
                for new_time in re.finditer(time_re, times):
                    current_time_type = new_time.group(1)
                    current_time = float(new_time.group(2))
                    benches.append(BenchDataPoint(
                            current_bench
                            , current_config
                            , current_time_type
                            , current_time
                            , settings))
    
    return benches
    
class LinearRegression:
    """Linear regression data based on a set of data points.
    
    ([(Number,Number)])
    There must be at least two points for this to make sense."""
    def __init__(self, points):
        n = len(points)
        max_x = Min
        min_x = Max
        
        Sx = 0.0
        Sy = 0.0
        Sxx = 0.0
        Sxy = 0.0
        Syy = 0.0
        for point in points:
            x = point[0]
            y = point[1]
            max_x = max(max_x, x)
            min_x = min(min_x, x)
            
            Sx += x
            Sy += y
            Sxx += x*x
            Sxy += x*y
            Syy += y*y
        
        B = (n*Sxy - Sx*Sy) / (n*Sxx - Sx*Sx)
        a = (1.0/n)*(Sy - B*Sx)
        
        se2 = 0
        sB2 = 0
        sa2 = 0
        if (n >= 3):
            se2 = (1.0/(n*(n-2)) * (n*Syy - Sy*Sy - B*B*(n*Sxx - Sx*Sx)))
            sB2 = (n*se2) / (n*Sxx - Sx*Sx)
            sa2 = sB2 * (1.0/n) * Sxx
        
        
        self.slope = B
        self.intercept = a
        self.serror = math.sqrt(max(0, se2))
        self.serror_slope = math.sqrt(max(0, sB2))
        self.serror_intercept = math.sqrt(max(0, sa2))
        self.max_x = max_x
        self.min_x = min_x
        
    def __repr__(self):
        return "LinearRegression(%s, %s, %s, %s, %s)" % (
                   str(self.slope),
                   str(self.intercept),
                   str(self.serror),
                   str(self.serror_slope),
                   str(self.serror_intercept),
               )
    
    def find_min_slope(self):
        """Finds the minimal slope given one standard deviation."""
        slope = self.slope
        intercept = self.intercept
        error = self.serror
        regr_start = self.min_x
        regr_end = self.max_x
        regr_width = regr_end - regr_start
        
        if slope < 0:
            lower_left_y = slope*regr_start + intercept - error
            upper_right_y = slope*regr_end + intercept + error
            return min(0, (upper_right_y - lower_left_y) / regr_width)
        
        elif slope > 0:
            upper_left_y = slope*regr_start + intercept + error
            lower_right_y = slope*regr_end + intercept - error
            return max(0, (lower_right_y - upper_left_y) / regr_width)
        
        return 0

def CreateRevisionLink(revision_number):
    """Returns HTML displaying the given revision number and linking to
    that revision's change page at code.google.com, e.g.
    http://code.google.com/p/skia/source/detail?r=2056
    """
    return '<a href="http://code.google.com/p/skia/source/detail?r=%s">%s</a>'%(
        revision_number, revision_number)