# Graphite utilities for generating graphs

# generate a graphite compatible time from a unix timestamp, if the value provided is not a valid timestamp, assume that we have a compatible time

from datetime import datetime
import re



# encapsulate graph styles
class GraphStyle(object):
    def __init__(self, width=586, height=308, font_size=8, line_style=None):
        self.width = int(width)
        self.height = int(height)
        self.font_size = int(font_size)
        self.line_style = line_style

    def __str__(self):
        s = 'width={0.width}&height={0.height}&fontSize={0.font_size}'
        if self.line_style is not None:
            s += '&lineMode={0.line_style}'
        return s.format(self)


def graphite_time(timestamp):
    try:
        timestamp = int(timestamp)
        return datetime.fromtimestamp(timestamp).strftime('%H:%M_%Y%m%d')
    except ValueError:
        return timestamp


class GraphiteTarget(object):
    def __init__(self, target='', alias=None, color=None, **kwargs):
        if not target:
            raise ValueError('Target is required')
        self.target = target
        self.alias = alias
        self.color = color

    def __str__(self):
        s = self.target
        if self.alias:
            s = 'alias(%s,"%s")' % (s, self.alias)
        if self.color:
            s = 'color(%s,"%s")' % (s, self.color)
        return s


# programmatic representation of a graphite URL
class GraphiteURL(object):
    def __init__(self, server='', title='', style=GraphStyle(), start=0, end=0, min=None, max=None, targets=None):
        self._start = ''
        self._end = ''
        self.server = server
        self.start = start
        self.end = end
        if targets is not None:
            self.targets = [GraphiteTarget(t) for t in targets]
        else:
            self.targets = []
        self.style = style
        self.title = title
        self.max = max
        self.min = min
        self._targets=''
        pass

    # ensure that the start and end times we are given are in the correct format
    @property
    def start(self):
        return self._start

    @start.setter
    def start(self, value):
        self._start = graphite_time(value)

    @property
    def end(self):
        return self._end

    @end.setter
    def end(self, value):
        self._end = graphite_time(value)

    def add_target(self, target, **kwargs):
        self._targets=''
        self.targets.append(GraphiteTarget(target, **kwargs))

    @property
    def target_string(self):
        if not self._targets:
            self._targets='&'.join(['target=%s' % t for t in self.targets])
        return self._targets

    @classmethod
    def parse(cls, string):
        pass

    def url(self, module='render'):
        if module not in ('render', 'composer'):
            raise ValueError('module must be "render" or "composer" not "%s"' % module)
        s = '{0.server}{1}/?{0.style}&from={0.start}&until={0.end}'
        if self.title:
            s += '&title={0.title}'
        if self.min is not None:
            s += '&yMin={0.min}'
        if self.max is not None:
            s += '&yMax={0.max}'
        if self.target_string:
            s += '&{0.target_string}'

        return s.format(self, module)

    def __str__(self):
        return self.url('render')


class GraphiteMetric(object):
    illegal_char = re.compile(r'[^a-zA-Z0-9_.\-]')

    @staticmethod
    def join(*args):
        return '.'.join([str(a) for a in args])

    @classmethod
    def normalize(cls, metric_name):
        return cls.illegal_char.sub("_", metric_name)

@@ -225,16 +225,31 @@
 port re%0A
+import logging%0A
 %0A%0A%0A# enc
@@ -1110,29 +1110,356 @@
     
-self.target = target%0A
+if target.__class__ is GraphiteTarget:%0A            self.__dict__ = dict(target.__dict__)%0A            if alias is not None:%0A                self.alias=alias%0A            if color is not None:%0A                self.color=color%0A        elif isinstance(target,dict):%0A            self.__init__(**target)%0A        else:%0A            self.target = target%0A    
     
@@ -1477,24 +1477,28 @@
 ias = alias%0A
+    
         self
@@ -1910,24 +1910,33 @@
 targets=None
+,**kwargs
 ):%0A        s
@@ -2053,16 +2053,42 @@
 d = end%0A
+        self.targets = %5B%5D%0A
         
@@ -2127,130 +2127,225 @@
     
-self.targets = %5BGraphiteTarget(t) for t in targets%5D%0A        else:%0A            self.targets = %5B%5D%0A        self.style = style
+for t in targets:%0A                self.add_target(t)%0A        self.style = style%0A        for k in ('height','width','font_size','line_style'):%0A            if k in kwargs:%0A                setattr(self.style,k,kwargs%5Bk%5D)
 %0A   
@@ -2427,33 +2427,35 @@
    self._targets
-=
+ = 
 ''%0A        pass%0A
@@ -2897,17 +2897,19 @@
 _targets
-=
+ = 
 ''%0A     
@@ -3001,32 +3001,49 @@
 t_string(self):%0A
+        try:%0A    
         if not s
@@ -3048,32 +3048,36 @@
  self._targets:%0A
+    
             self
@@ -3085,17 +3085,19 @@
 _targets
-=
+ = 
 '&'.join
@@ -3138,32 +3138,36 @@
 rgets%5D)%0A        
+    
 return self._tar
@@ -3170,16 +3170,156 @@
 _targets
+%0A        except:%0A            logging.exception('Unable to generate target string')%0A            logging.error(self.targets)%0A            raise
 %0A%0A    @c
@@ -4137,16 +4137,21 @@
  in args
+ if a
 %5D)%0A%0A    

correct defeciencies exposed by tests "YAY TESTS!!!"