# Copyright (C) 2013, 2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

from gi.repository import GObject
from gi.repository import Gtk

# pylint: disable=arguments-differ
# Newer pylint can detect, but warns that overridden arguments are wrong

BASECOLOR = Gtk.StyleContext().lookup_color("theme_base_color")[1]


def rect_print(name, rect):  # pragma: no cover
    # For debugging
    print("%s: height=%d, width=%d, x=%d, y=%d" % (name, rect.height, rect.width, rect.x, rect.y))


def _line_helper(cairo_ct, bottom_baseline, points, for_fill=False):
    last_was_zero = False
    last_point = None

    for index, (x, y) in enumerate(points):

        # If stats value == 0, we don't want to draw a line
        is_zero = bool(y == bottom_baseline)

        # If the line is for filling, alter the coords so that fill covers
        # the same area as the parent sparkline: fill is one pixel short
        # to not overwrite the spark line
        if for_fill:
            if index == 0:
                x -= 1
            elif index == (len(points) - 1):
                x += 1
            elif last_was_zero and is_zero:
                y += 1

        if index == 0:
            cairo_ct.move_to(x, y)
        elif last_was_zero and is_zero and not for_fill:
            cairo_ct.move_to(x, y)
        else:
            cairo_ct.line_to(x, y)
            last_point = (x, y)

        last_was_zero = is_zero

    return last_point


def draw_line(cairo_ct, y, h, points):
    if not len(points):
        return  # pragma: no cover

    last_point = _line_helper(cairo_ct, y + h, points)
    if not last_point:
        # Nothing to draw
        return

    # Paint the line
    cairo_ct.stroke()


def draw_fill(cairo_ct, x, y, w, h, points, taper=False):
    if not len(points):
        return  # pragma: no cover

    _line_helper(cairo_ct, y + h, points, for_fill=True)

    baseline_y = h + y + 1
    if taper:
        start_x = w + x
    else:
        start_x = points[-1][0]

    # Box out the area to fill
    cairo_ct.line_to(start_x + 1, baseline_y)
    cairo_ct.line_to(x - 1, baseline_y)

    # Paint the fill
    cairo_ct.fill()


class CellRendererSparkline(Gtk.CellRenderer):
    __gproperties__ = {
        # 'name': (GObject.TYPE_*,
        #           nickname, long desc, (type related args), mode)
        # Type related args can be min, max for int (etc.), or default value
        # for strings and bool
        "data_array": (
            GObject.TYPE_PYOBJECT,
            "Data Array",
            "Array of data points for the graph",
            GObject.PARAM_READWRITE,
        ),
        "reversed": (
            GObject.TYPE_BOOLEAN,
            "Reverse data",
            "Process data from back to front.",
            0,
            GObject.PARAM_READWRITE,
        ),
    }

    def __init__(self):
        Gtk.CellRenderer.__init__(self)

        self.data_array = []
        self.num_sets = 0
        self.filled = True
        self.reversed = False
        self.rgb = None

    def do_render(self, cr, widget, background_area, cell_area, flags):
        # cr                : Cairo context
        # widget            : GtkWidget instance
        # background_area   : GdkRectangle: entire cell area
        # cell_area         : GdkRectangle: area normally rendered by cell
        # flags             : flags that affect rendering
        # flags = Gtk.CELL_RENDERER_SELECTED, Gtk.CELL_RENDERER_PRELIT,
        #         Gtk.CELL_RENDERER_INSENSITIVE or Gtk.CELL_RENDERER_SORTED
        ignore = widget
        ignore = background_area
        ignore = flags

        # Indent of the gray border around the graph
        BORDER_PADDING = 2
        # Indent of graph from border
        GRAPH_INDENT = 2
        GRAPH_PAD = BORDER_PADDING + GRAPH_INDENT

        # We don't use yalign, since we expand to the entire height
        ignore = self.get_property("yalign")
        xalign = self.get_property("xalign")

        # Set up graphing bounds
        graph_x = cell_area.x + GRAPH_PAD
        graph_y = cell_area.y + GRAPH_PAD
        graph_width = cell_area.width - (GRAPH_PAD * 2)
        graph_height = cell_area.height - (GRAPH_PAD * 2)

        pixels_per_point = graph_width // max(1, len(self.data_array) - 1)

        # Graph width needs to be some multiple of the amount of data points
        # we have
        graph_width = pixels_per_point * max(1, len(self.data_array) - 1)

        # Recalculate border width based on the amount we are graphing
        border_width = graph_width + (GRAPH_INDENT * 2)

        # Align the widget
        empty_space = cell_area.width - border_width - (BORDER_PADDING * 2)
        if empty_space:
            xalign_space = int(empty_space * xalign)
            cell_area.x += xalign_space
            graph_x += xalign_space

        cr.set_line_width(3)
        # 1 == LINE_CAP_ROUND
        cr.set_line_cap(1)

        # Draw gray graph border
        cr.set_source_rgb(0.8828125, 0.8671875, 0.8671875)
        cr.rectangle(
            cell_area.x + BORDER_PADDING,
            cell_area.y + BORDER_PADDING,
            border_width,
            cell_area.height - (BORDER_PADDING * 2),
        )
        cr.stroke()

        # Fill in basecolor box inside graph outline
        cr.set_source_rgb(BASECOLOR.red, BASECOLOR.green, BASECOLOR.blue)
        cr.rectangle(
            cell_area.x + BORDER_PADDING,
            cell_area.y + BORDER_PADDING,
            border_width,
            cell_area.height - (BORDER_PADDING * 2),
        )
        cr.fill()

        def get_y(index):
            baseline_y = graph_y + graph_height

            n = index
            if self.reversed:
                n = len(self.data_array) - index - 1

            val = self.data_array[n]
            y = baseline_y - (graph_height * val)

            y = max(graph_y, y)
            y = min(graph_y + graph_height, y)
            return y

        points = []
        for index in range(0, len(self.data_array)):
            x = int((index * pixels_per_point) + graph_x)
            y = int(get_y(index))

            points.append((x, y))

        cell_area.x = graph_x
        cell_area.y = graph_y
        cell_area.width = graph_width
        cell_area.height = graph_height

        # Set color to dark blue for the actual sparkline
        cr.set_line_width(2)
        cr.set_source_rgb(0.421875, 0.640625, 0.73046875)
        draw_line(cr, cell_area.y, cell_area.height, points)

        # Set color to light blue for the fill
        cr.set_source_rgba(0.71484375, 0.84765625, 0.89453125, 0.5)

        draw_fill(cr, cell_area.x, cell_area.y, cell_area.width, cell_area.height, points)
        return

    def do_get_size(self, widget, cell_area=None):
        ignore = widget
        ignore = cell_area

        FIXED_WIDTH = len(self.data_array)
        FIXED_HEIGHT = 15
        xpad = self.get_property("xpad")
        ypad = self.get_property("ypad")
        xoffset = 0
        yoffset = 0

        width = (xpad * 2) + FIXED_WIDTH
        height = (ypad * 2) + FIXED_HEIGHT

        return (xoffset, yoffset, width, height)

    # Properties are passed to use with "-" in the name, but python
    # variables can't be named like that
    def _sanitize_param_spec_name(self, name):
        return name.replace("-", "_")

    def do_get_property(self, param_spec):  # pragma: no cover
        name = self._sanitize_param_spec_name(param_spec.name)
        return getattr(self, name)

    def do_set_property(self, param_spec, value):
        name = self._sanitize_param_spec_name(param_spec.name)
        setattr(self, name, value)

    def set_property(self, *args, **kwargs):
        # Make pylint happy
        return Gtk.CellRenderer.set_property(self, *args, **kwargs)


class Sparkline(Gtk.DrawingArea):
    __gproperties__ = {
        # 'name': (GObject.TYPE_*,
        #           nickname, long desc, (type related args), mode)
        # Type related args can be min, max for int (etc.), or default value
        # for strings and bool
        "data_array": (
            GObject.TYPE_PYOBJECT,
            "Data Array",
            "Array of data points for the graph",
            GObject.PARAM_READWRITE,
        ),
        "filled": (
            GObject.TYPE_BOOLEAN,
            "Filled",
            "the foo of the object",
            1,
            GObject.PARAM_READWRITE,
        ),
        "num_sets": (
            GObject.TYPE_INT,
            "Number of sets",
            "Number of data sets to graph",
            1,
            2,
            1,
            GObject.PARAM_READWRITE,
        ),
        "reversed": (
            GObject.TYPE_BOOLEAN,
            "Reverse data",
            "Process data from back to front.",
            0,
            GObject.PARAM_READWRITE,
        ),
        "rgb": (GObject.TYPE_PYOBJECT, "rgb array", "List of rgb values", GObject.PARAM_READWRITE),
    }

    def __init__(self):
        Gtk.DrawingArea.__init__(self)

        self._data_array = []
        self.num_sets = 1
        self.filled = True
        self.reversed = False
        self.rgb = []

        ctxt = self.get_style_context()
        ctxt.add_class(Gtk.STYLE_CLASS_ENTRY)

    def set_data_array(self, val):
        self._data_array = val
        self.queue_draw()

    def get_data_array(self):
        return self._data_array

    data_array = property(get_data_array, set_data_array)

    def do_draw(self, cr):
        cr.save()

        window = self.get_window()
        w = window.get_width()
        h = window.get_height()

        points_per_set = len(self.data_array) // self.num_sets
        pixels_per_point = float(w) / (float((points_per_set - 1) or 1))

        widget = self
        ctx = widget.get_style_context()

        # This draws the light gray backing rectangle
        Gtk.render_background(ctx, cr, 0, 0, w - 1, h - 1)

        # This draws the marker ticks
        max_ticks = 4
        for index in range(1, max_ticks):
            Gtk.render_line(ctx, cr, 1, (h // max_ticks) * index, w - 2, (h // max_ticks) * index)

        # Foreground-color graphics context
        # This draws the black border
        Gtk.render_frame(ctx, cr, 0, 0, w - 1, h - 1)

        # Draw the actual sparkline
        def get_y(dataset, index):
            baseline_y = h

            n = dataset * points_per_set
            if self.reversed:
                n += points_per_set - index - 1
            else:
                n += index

            val = self.data_array[n]
            return baseline_y - ((h - 1) * val)

        cr.set_line_width(2)

        for dataset in range(0, self.num_sets):
            if len(self.rgb) == (self.num_sets * 3):
                cr.set_source_rgb(
                    self.rgb[(dataset * 3)],
                    self.rgb[(dataset * 3) + 1],
                    self.rgb[(dataset * 1) + 2],
                )
            points = []
            for index in range(0, points_per_set):
                x = index * pixels_per_point
                y = get_y(dataset, index)

                points.append((int(x), int(y)))

            if self.num_sets == 1:
                pass

            draw_line(cr, 0, h, points)
            if self.filled:
                # Fixes a fully filled graph from having an oddly
                # tapered in end (bug 560913). Need to figure out
                # what's really going on.
                points = [(0, h)] + points
                draw_fill(cr, 0, 0, w, h, points, taper=True)

        cr.restore()

        return 0

    def do_size_request(self, requisition):  # pragma: no cover
        width = len(self.data_array) / self.num_sets
        height = 20

        requisition.width = width
        requisition.height = height

    # Properties are passed to use with "-" in the name, but python
    # variables can't be named like that
    def _sanitize_param_spec_name(self, name):
        return name.replace("-", "_")

    def do_get_property(self, param_spec):  # pragma: no cover
        name = self._sanitize_param_spec_name(param_spec.name)
        return getattr(self, name)

    def do_set_property(self, param_spec, value):
        name = self._sanitize_param_spec_name(param_spec.name)
        setattr(self, name, value)

    # These make pylint happy
    def set_property(self, *args, **kwargs):
        return Gtk.DrawingArea.set_property(self, *args, **kwargs)

    def show(self, *args, **kwargs):
        return Gtk.DrawingArea.show(self, *args, **kwargs)

    def destroy(self, *args, **kwargs):
        return Gtk.DrawingArea.destroy(self, *args, **kwargs)
