# -*- coding: utf-8 -*-
#
# piecesbar.py
#
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge 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 deluge.    If not, write to:
#     The Free Software Foundation, Inc.,
#     51 Franklin Street, Fifth Floor
#     Boston, MA  02110-1301, USA.
#
#    In addition, as a special exception, the copyright holders give
#    permission to link the code of portions of this program with the OpenSSL
#    library.
#    You must obey the GNU General Public License in all respects for all of
#    the code used other than OpenSSL. If you modify file(s) with this
#    exception, you may extend this exception to your version of the file(s),
#    but you are not obligated to do so. If you do not wish to do so, delete
#    this exception statement from your version. If you delete this exception
#    statement from all source files in the program, then also delete it here.
#
#

import gtk
import cairo
import pango
import pangocairo
import logging
from math import pi
from deluge.configmanager import ConfigManager

log = logging.getLogger(__name__)


COLOR_STATES = {
    0: "missing",
    1: "waiting",
    2: "downloading",
    3: "completed"
}

class PiecesBar(gtk.DrawingArea):
    # Draw in response to an expose-event
    __gsignals__ = {"expose-event": "override"}

    def __init__(self):
        gtk.DrawingArea.__init__(self)
        # Get progress bar styles, in order to keep font consistency
        pb = gtk.ProgressBar()
        pb_style = pb.get_style()
        self.__text_font = pb_style.font_desc
        self.__text_font.set_weight(pango.WEIGHT_BOLD)
        # Done with the ProgressBar styles, don't keep refs of it
        del pb, pb_style

        self.set_size_request(-1, 25)
        self.gtkui_config = ConfigManager("gtkui.conf")
        self.__width = self.__old_width = 0
        self.__height = self.__old_height = 0
        self.__pieces = self.__old_pieces = ()
        self.__num_pieces = self.__old_num_pieces = None
        self.__text = self.__old_text = ""
        self.__fraction = self.__old_fraction = 0.0
        self.__state = self.__old_state = None
        self.__progress_overlay = self.__text_overlay = self.__pieces_overlay = None
        self.__cr = None

        self.connect('size-allocate', self.do_size_allocate_event)
        self.set_colormap(gtk.gdk.colormap_get_system())
        self.show()

    def do_size_allocate_event(self, widget, size):
        self.__old_width = self.__width
        self.__width = size.width
        self.__old_height = self.__height
        self.__height = size.height

    # Handle the expose-event by drawing
    def do_expose_event(self, event):
        # Create cairo context
        self.__cr = self.window.cairo_create()
        self.__cr.set_line_width(max(self.__cr.device_to_user_distance(0.5, 0.5)))

        # Restrict Cairo to the exposed area; avoid extra work
        self.__roundcorners_clipping()

        if not self.__pieces and self.__num_pieces is not None:
            # Special case. Completed torrents do not send any pieces in their
            # status.
            self.__draw_pieces_completed()
        elif self.__pieces:
            self.__draw_pieces()

        self.__draw_progress_overlay()
        self.__write_text()
        self.__roundcorners_border()

        # Drawn once, update width, eight
        if self.__resized():
            self.__old_width = self.__width
            self.__old_height = self.__height

    def __roundcorners_clipping(self):
        self.__create_roundcorners_subpath(
            self.__cr, 0, 0, self.__width, self.__height
        )
        self.__cr.clip()

    def __roundcorners_border(self):
        self.__create_roundcorners_subpath(
            self.__cr, 0.5, 0.5, self.__width-1, self.__height-1
        )
        self.__cr.set_source_rgba(0.0, 0.0, 0.0, 0.9)
        self.__cr.stroke()

    def __create_roundcorners_subpath(self, ctx, x, y, width, height):
        aspect = 1.0
        corner_radius = height/10.0
        radius = corner_radius/aspect
        degrees = pi/180.0
        ctx.new_sub_path()
        ctx.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
        ctx.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
        ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
        ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
        ctx.close_path()
        return ctx

    def __draw_pieces(self):
        if (self.__resized() or self.__pieces != self.__old_pieces or
                self.__pieces_overlay == None):
            # Need to recreate the cache drawing
            self.__pieces_overlay = cairo.ImageSurface(
                cairo.FORMAT_ARGB32, self.__width, self.__height
            )
            ctx = cairo.Context(self.__pieces_overlay)
            start_pos = 0
            num_pieces = self.__num_pieces and self.__num_pieces or len(self.__pieces)
            piece_width = self.__width*1.0/num_pieces

            for state in self.__pieces:
                color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[state]]
                ctx.set_source_rgb(
                    color[0]/65535.0,
                    color[1]/65535.0,
                    color[2]/65535.0,
                )
                ctx.rectangle(start_pos, 0, piece_width, self.__height)
                ctx.fill()
                start_pos += piece_width

        self.__cr.set_source_surface(self.__pieces_overlay)
        self.__cr.paint()

    def __draw_pieces_completed(self):
        if (self.__resized() or self.__pieces != self.__old_pieces or
                self.__pieces_overlay is None):
            # Need to recreate the cache drawing
            self.__pieces_overlay = cairo.ImageSurface(
                cairo.FORMAT_ARGB32, self.__width, self.__height
            )
            ctx = cairo.Context(self.__pieces_overlay)
            piece_width = self.__width*1.0/self.__num_pieces
            start = 0
            for _ in range(self.__num_pieces):
                # Like this to keep same aspect ratio
                color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[3]]
                ctx.set_source_rgb(
                    color[0]/65535.0,
                    color[1]/65535.0,
                    color[2]/65535.0,
                )
                ctx.rectangle(start, 0, piece_width, self.__height)
                ctx.fill()
                start += piece_width

        self.__cr.set_source_surface(self.__pieces_overlay)
        self.__cr.paint()

    def __draw_progress_overlay(self):
        if not self.__state:
            # Nothing useful to draw, return now!
            return
        if (self.__resized() or self.__fraction != self.__old_fraction) or \
                                                self.__progress_overlay is None:
            # Need to recreate the cache drawing
            self.__progress_overlay = cairo.ImageSurface(
                cairo.FORMAT_ARGB32, self.__width, self.__height
            )
            ctx = cairo.Context(self.__progress_overlay)
            ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent
            ctx.rectangle(0.0, 0.0, self.__width*self.__fraction, self.__height)
            ctx.fill()
        self.__cr.set_source_surface(self.__progress_overlay)
        self.__cr.paint()

    def __write_text(self):
        if not self.__state:
            # Nothing useful to draw, return now!
            return
        if (self.__resized() or self.__text != self.__old_text or
                self.__fraction != self.__old_fraction or
                self.__state != self.__old_state or
                self.__text_overlay is None):
            # Need to recreate the cache drawing
            self.__text_overlay = cairo.ImageSurface(
                cairo.FORMAT_ARGB32, self.__width, self.__height
            )
            ctx = cairo.Context(self.__text_overlay)
            pg = pangocairo.CairoContext(ctx)
            pl = pg.create_layout()
            pl.set_font_description(self.__text_font)
            pl.set_width(-1)    # No text wrapping

            text = ""
            if self.__text:
                text += self.__text
            else:
                if self.__state:
                    text += self.__state + " "
                if self.__fraction == 1.0:
                    format = "%d%%"
                else:
                    format = "%.2f%%"
                text += format % (self.__fraction*100)
            log.trace("PiecesBar text %r", text)
            pl.set_text(text)
            plsize = pl.get_size()
            text_width = plsize[0]/pango.SCALE
            text_height = plsize[1]/pango.SCALE
            area_width_without_text = self.__width - text_width
            area_height_without_text = self.__height - text_height
            ctx.move_to(area_width_without_text/2, area_height_without_text/2)
            ctx.set_source_rgb(1.0, 1.0, 1.0)
            pg.update_layout(pl)
            pg.show_layout(pl)
        self.__cr.set_source_surface(self.__text_overlay)
        self.__cr.paint()

    def __resized(self):
        return (self.__old_width != self.__width or
                self.__old_height != self.__height)

    def set_fraction(self, fraction):
        self.__old_fraction = self.__fraction
        self.__fraction = fraction

    def get_fraction(self):
        return self.__fraction

    def get_text(self):
        return self.__text

    def set_text(self, text):
        self.__old_text = self.__text
        self.__text = text

    def set_pieces(self, pieces, num_pieces):
        self.__old_pieces = self.__pieces
        self.__pieces = pieces
        self.__num_pieces = num_pieces

    def get_pieces(self):
        return self.__pieces

    def set_state(self, state):
        self.__old_state = self.__state
        self.__state = state

    def get_state(self):
        return self.__state

    def update_from_status(self, status):
        log.trace("Updating PiecesBar from status")
        self.set_fraction(status["progress"]/100)
        torrent_state = status["state"]
        self.set_state(torrent_state)
        if torrent_state == "Checking":
            self.update()
            # Skip the pieces assignment
            return

        self.set_pieces(status['pieces'], status['num_pieces'])
        self.update()

    def clear(self):
        self.__pieces = self.__old_pieces = ()
        self.__num_pieces = self.__old_num_pieces = None
        self.__text = self.__old_text = ""
        self.__fraction = self.__old_fraction = 0.0
        self.__state = self.__old_state = None
        self.__progress_overlay = self.__text_overlay = self.__pieces_overlay = None
        self.__cr = None
        self.update()

    def update(self):
        self.queue_draw()
