#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2011 S2S Network Consultoria e Tecnologia da Informacao LTDA
#
# Authors: Guilherme Polo <ggpolo@gmail.com>
#          Tianwei Liu <liutainweidlut@gmail.com> 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""
Timeline graph Display Bar: show the statistics about the services test
"""
import gtk
import cairo
import pango
import gobject

from umit.icm.agent.I18N import _
from umit.icm.agent.test import service_name_by_id
from umit.icm.agent.Global import *
from umit.icm.agent.logger import g_logger

services_list = service_name_by_id.keys()
services_list.append("nothing")

PI = 3.1415926535897931
GOLDENRATIO = 1.618033989
GOLDENRADIUS = GOLDENRATIO ** 4 # my magical number

EMPTY_COLOR =  ((0.3, 0.3, 0.3), (0, 0, 0))
NOSELECTION = _("No Selection")
NOCHANGES = _("No Changes")
STATISTICS = _("Success Statistics")

colors_from_file_bar = {
                        "FTP":[0.188, 0.659, 0.282],
                        "SMTP":[0.847, 0.016, 0.016],
                        "POP3":[0.988, 0.82, 0.157],
                        "IMAP":[0.369, 0.008, 0.353],
                        "IRC":[0.133, 0.361, 0.706],
                        "SSH":[0.545, 0.502, 0.514],
                        "nothing":[0.645, 0.602, 0.614],
                        }
gradient_colors_from_file_bar = {
                        "FTP":[0.69, 0.859, 0.722],
                        "SMTP":[0.847, 0.57, 0.57],
                        "POP3":[0.988, 0.949, 0.604],
                        "IMAP":[0.588, 0.008, 0.549],
                        "IRC":[0.624, 0.737, 0.906],
                        "SSH":[0.784, 0.784, 0.784],    
                        "nothing":[0.645, 0.602, 0.614],                             
                                 }

class TimeLineDisplayBar(gtk.Widget):
    """
    A widget that shows statics based on Timeline Graph selection
    """
    
    def __init__(self,capacity_box):
        """
        """
        gtk.Widget.__init__(self)
        
        self.capacity_box = capacity_box
        
        self.__create_widgets()
        self.__connected_widgets()

        
    def __create_widgets(self):
        """
        """
        #Title
        self.title = self.create_pango_layout('')
        self.title.set_alignment(pango.ALIGN_CENTER)
        self.second_title = self.create_pango_layout('')
        self.second_title.set_alignment(pango.ALIGN_CENTER)
        
        #Empty Selection/colors
        self.percent, self.currselection = self.emptygraph()
        not_used, self.newselection = self.emptygraph() 
        self.currcolor = (list(EMPTY_COLOR[0]), list(EMPTY_COLOR[1]))
        self.newcolor = EMPTY_COLOR

        self.total = 0 # number of changes in selection
        self.multiplier = { }
        
        # animation effect control
        self.in_anim = False # bar drawing effect
        self.in_transition = False # color transition effect
        self.piece_cut = 12.0 # how many divisions will be done in each bar
                              # graph to do animation effect.
        self.trans_progress = 0.04 # color transition increments/decrements
        
        # bar drawing constants
        self.bar_draw = { }
        self._calculate_bar_sizes()   
        
        # Displaybar
        self.area =  []            
    
    def __connected_widgets(self):
        """
        """
        self.capacity_box.refresh_btn.connect('clicked', self._update_graph)
                
    def emptygraph(self):
        """
        Return a dict where each category has 0 changes.
        """
        empty = { }
        percent = { }

        for item in services_list:
            empty[item] = 0
            percent[item] = 0.0

        return empty, percent
    
    def colors_diff(self):
        """
        Check if colors are different.
        """
        for indx, color in enumerate(self.currcolor):
            for c_indx, cvalue in enumerate(color):
                nvalue = self.newcolor[indx][c_indx]

                if nvalue != cvalue:
                    return True

        return False
    
    def _update_graph(self,widget):
        """
        """
        self.draw_logical()
    
    
    def _update_graph_data(self):
        """
        click refresh button  
        """       
        bars_height = { }
        self.percent = { }
        self.multiplier = { }
        
        for key in services_list:
            (success_cnt,total_cnt) = g_db_helper.service_choice_count(key)
            #calculate bar height and percent based on their amount of task success
            if total_cnt == 0:
                bars_height[key] = 0
                self.percent[key] = 0.0
            else:
                bars_height[key] = ((success_cnt * self.bar_draw["bars_y_area"]) / float(total_cnt))
                self.percent[key] = (float(success_cnt)/float(total_cnt)) * 100
                
            # calculate increment needed to complete this bar animation
            self.multiplier[key] = abs(bars_height[key] - self.currselection[key] ) / self.piece_cut              
        
        
        # get new color
        more_changes = self._color_max_changes(bars_height)
        if more_changes.values()[0] == 0: # no changes in current selection
            newcolor = EMPTY_COLOR
            color_from = newcolor[0]
            color_to = newcolor[1]
        else:
            color_name = more_changes.keys()[0]
            color_from = colors_from_file_bar[color_name]
            color_to = gradient_colors_from_file_bar[color_name]

        self.newcolor = (color_from, color_to)
        self.newselection = bars_height # set new selection, this will start
                                        # animation effect.
                                        
        #print "bars_height",bars_height
        #print "percent",self.percent
        #print "multiplier",self.multiplier
        #print "newselection",self.newselection
        
    def _color_max_changes(self, datad):
        """
        Return {category: value} with more changes.
        """
        max_v = -1
        max_d = {"invalid": max_v}

        for key, value in datad.items():
            if key == "nothing": # discarding this category for now
                continue

            if value > max_v:
                max_d = {key: value}
                max_v = value

        if max_v == 0:
            return {"nothing": datad["nothing"]}

        return max_d    
   
    def _write_text(self, cr, text, bold):
        """
        Write centered text on widget.
        """
        cr.save()

        if bold:
            height = 1
            self.title.set_markup("<b>%s</b>" % text)
            cr.update_layout(self.title)
            pango_layout = self.title

        else:
            width, height = self.title.get_size()
            height = (height / pango.SCALE) + 2
            self.second_title.set_text(text)
            cr.update_layout(self.second_title)
            pango_layout = self.second_title

        cr.move_to(1, height + 1)
        cr.set_source_rgb(0, 0, 0)
        cr.show_layout(pango_layout)

        cr.move_to(0, height)
        cr.set_source_rgb(1, 1, 1)
        cr.show_layout(pango_layout)

        cr.restore()    
    
    def _write_title(self, cr):
        """
        Tells _write_text to write widget title.
        """
        self._write_text(cr, "Service Tests Results",True)
        self._write_text(cr, STATISTICS, False)   
   
    def _draw_base(self, cr):
        """
        Draws __________
        """
        cr.save()

        cr.move_to(self.bar_draw["start_x"], self.bar_draw["start_y"])
        cr.line_to(self.bar_draw["end_x"], self.bar_draw["start_y"])

        cr.set_source_rgba(1, 1, 1, 0.3)
        cr.stroke()

        cr.restore()
    
    def _paint_background(self, cr):
        """
        Draw and paint widget background with a vertical gradient.
        """
        cr.save()

        cr.new_path()

        # draws a round rectangle, pure Art ;)
        cr.arc(GOLDENRADIUS, self.allocation[3] - GOLDENRADIUS,
               GOLDENRADIUS, PI / 2.0, PI)
        cr.arc(GOLDENRADIUS, GOLDENRADIUS,
               GOLDENRADIUS, PI, - PI / 2.0)
        cr.arc(self.allocation[2] - GOLDENRADIUS, GOLDENRADIUS,
               GOLDENRADIUS, PI + (PI / 2.0), 0)
        cr.arc(self.allocation[2] - GOLDENRADIUS,
               self.allocation[3] - GOLDENRADIUS, GOLDENRADIUS, 0, PI/2.0)

        cr.close_path()

        pat = cairo.LinearGradient(0, 0, 0, self.allocation[3])

        color_from = self.currcolor[0]
        color_to = self.currcolor[1]

        pat.add_color_stop_rgb(0, *color_from)
        pat.add_color_stop_rgb(1, *color_to)

        cr.set_source(pat)
        cr.fill()

        cr.restore()    

    def _draw_bar(self, cr, value, color, x_pos):
        """
        Draws a vertical graph bar.
        """
        cr.save()

        cr.rectangle(self.bar_draw["start_x"] + x_pos,
                     self.bar_draw["start_y"], self.bar_draw["bar_width"],
                     - value - 1)

        cr.set_source_rgb(*color)
        cr.fill()

        cr.restore()

        return self.bar_draw["start_y"] - value
    
    def _write_bar_text(self, cr, text, bar_height, x_pos):
        """
        Write text above or inside bar.
        """
        cr.save()
        cr.select_font_face("Sans", cairo.FONT_SLANT_NORMAL,
            cairo.FONT_WEIGHT_BOLD)

        _, _, width, height, _, _ = cr.text_extents(text)
        center = (self.bar_draw["bar_width"] / 2.0) + (height / 2.0)

        if self.bar_draw["start_y"] - bar_height != 0:
            if self.bar_draw["start_y"] - bar_height - width - 3 == 0:
                h_pos = bar_height + width
            elif self.bar_draw["start_y"] - bar_height - width - 3 >= 3:
                h_pos = bar_height + width + 3
            else:
                h_pos = self.bar_draw["start_y"] - 3
        else:
            h_pos = bar_height - 3

        cr.move_to(self.bar_draw["start_x"] + x_pos + center + 1, h_pos)

        cr.rotate(- PI/2)
        cr.set_source_rgb(0, 0, 0)
        cr.show_text(text)
        cr.rotate(PI/2)

        cr.move_to(self.bar_draw["start_x"] + x_pos + center, h_pos - 1)

        cr.rotate(- PI/2)
        cr.set_source_rgb(1, 1, 1)
        cr.show_text(text)
        cr.rotate(PI/2)

        cr.restore()    

    def _color_transition(self):
        """
        Color transition.
        """
        continue_trans = False # if this remains False, transition is complete

        # check values to perform color transition
        for indx, color in enumerate(self.currcolor):
            for c_indx, cvalue in enumerate(color):
                nvalue = self.newcolor[indx][c_indx]

                # doing this so in some cases (float point with many digits)
                # we don't stay here forever.
                if abs(nvalue - cvalue) <= self.trans_progress and \
                    nvalue != cvalue:

                    self.currcolor[indx][c_indx] = nvalue
                    continue_trans = True
                    continue

                if cvalue > nvalue:
                    self.currcolor[indx][c_indx] = cvalue - self.trans_progress

                    continue_trans = True

                elif cvalue < nvalue:
                    self.currcolor[indx][c_indx] = cvalue + self.trans_progress

                    continue_trans = True

        if not self.in_anim:
            # This timer starts right after bar drawing starts, so
            # queue_draw is already being called by _update_bars().
            # But, this may end after _update_bars finishes, so it
            # is needed to call queue_draw now.
            self.queue_draw()

        if not continue_trans:
            self.in_transition = False
            return False

        return True
    
    def _update_bars(self):
        """
        Increment/decrement bar sizes.
        """
        continue_anim = False

        # increment/decrement bars height
        for key, value in self.newselection.items():
            if value > self.currselection[key]:
                self.currselection[key] += self.multiplier[key]
                continue_anim = True

            elif value < self.currselection[key]:
                self.currselection[key] -= self.multiplier[key]
                continue_anim = True

            # doing this so in some cases (float point with many digits)
            # we don't stay here forever.
            if abs(self.currselection[key] - self.newselection[key]) <= 0.5 \
                and self.currselection[key] != self.newselection[key]:

                self.currselection[key] = self.newselection[key]
                continue_anim = True

        if not continue_anim:
            self.in_anim = False
            return False

        self.queue_draw()
        return True
    
    def _pre_bar_draw(self, cr):
        """
        Calculate where each bar will be draw, and then call _draw_bar to
        draw it.
        """
        for multiplier, item in enumerate(services_list):
            curr_x_pos = self.bar_draw["bars_distance"] + \
                         (self.bar_draw["bars_distance"] * multiplier) + \
                         (self.bar_draw["bar_width"] * multiplier)

            bar_height = self._draw_bar(cr, self.currselection[item],
                colors_from_file_bar[item], curr_x_pos)

            self._write_bar_text(cr, "%s    %.1f%%" % (item,self.percent[item]), bar_height,
                curr_x_pos)

    def _calculate_bar_sizes(self):
        """
        Calculates everything needed during bars drawing.
        """
        
        self.bar_draw["start_x"] = 12
        self.bar_draw["end_x"] = self.allocation[2] - 12
        self.bar_draw["start_y"] = self.allocation[3] - 6
        self.bar_draw["bars_distance"] = 5
        self.bar_draw["bars_y_area"] = self.bar_draw["start_y"] - 60

        bars_space = self.allocation[2] - (2 * self.bar_draw["start_x"]) - \
                     self.bar_draw["bars_distance"]
        bar_width = (bars_space / float(len(services_list))) - \
                    self.bar_draw["bars_distance"]

        self.bar_draw["bar_width"] = bar_width    
    
    def do_realize(self):
        """
        Realizes widget with necessary event_mask and calculate initial
        bar drawing constants.
        """
        self.set_flags(self.flags() | gtk.REALIZED | gtk.CAN_FOCUS)

        self.window = gtk.gdk.Window(self.get_parent_window(),
            width=self.allocation.width, height=self.allocation.height,
            window_type=gtk.gdk.WINDOW_CHILD, wclass=gtk.gdk.INPUT_OUTPUT,
            event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK)

        self.window.set_user_data(self)
        self.style.attach(self.window)
        self.style.set_background(self.window, gtk.STATE_NORMAL)
        self.window.move_resize(*self.allocation)

        self._calculate_bar_sizes()    
    
    def do_unrealize(self):
        """
        Widget cleanup.
        """
        self.window.set_user_data(None)    
    
    def do_size_request(self, requisition):
        """
        Sets "optimal" minimal size for widget.
        """
        requisition.width = 190
        requisition.height = 130    
    
    def do_size_allocate(self, allocation):
        """
        Handles size allocation and recalculate bar drawing constants.
        """
        self.allocation = allocation

        if self.flags() & gtk.REALIZED:
            self.window.move_resize(*self.allocation)

        self._calculate_bar_sizes()

        self.title.set_width(allocation[2] * pango.SCALE)
        self.second_title.set_width(allocation[2] * pango.SCALE)    
      
    def do_expose_event(self, event):
        """
        """
        if self.area == []:
            self.area = event.area
        self.draw_logical()
 
    def draw_logical(self):
        """
        Controls widget drawing.
        """
        self._update_graph_data()
        
        cr = self.window.cairo_create()
        cr.rectangle(*self.area)
        cr.clip()

        #if self.currselection != self.newselection and not self.in_anim:
        if not self.in_anim:
            self.in_anim = True
            gobject.timeout_add(20, self._update_bars)

        if self.colors_diff() and not self.in_transition:
            self.in_transition = True
            gobject.timeout_add(20, self._color_transition)

        self._paint_background(cr)
        self._write_title(cr)
        self._draw_base(cr)
        self._pre_bar_draw(cr)        
gobject.type_register(TimeLineDisplayBar)