# Copyright (c) 2020-21 elParaguayo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from libqtile import bar
from libqtile.log_utils import logger
from libqtile.popup import Popup
from libqtile.widget import base

from qtile_extras.resources.stravadata import get_strava_data


class StravaWidget(base._Widget, base.MarginMixin):
    """
    This module provides a simple widget showing some Strava stats.

    The widget text can be customised using the following keys:

    .. list-table::
        :widths: auto
        :header-rows: 1

        * - prefix
          - suffix
          - value
        * - C
          -
          - Current month
        * - Y
          -
          - Calendar year
        * - A
          -
          - All time
        * -
          - D
          - Distance
        * -
          - C
          - Count
        * -
          - T
          - Time
        * -
          - P
          - Pace
        * -
          - N
          - Name
        * -
          - A
          - Date

    For example, the default text ``{CA:%b} {CD:.1f}km`` displays the
    current date in abbreviated month name format and the distance run
    that month: "Aug 143.1km".

    Extended info is provided by clicking on the widget.

    .. note::

        You will need to follow the instuctions at
        https://developers.strava.com/ to create a new app and authorise it.

        Your id and secret should be put in a json file called ``auth.json``
        in ``~/.cache/stravawidget``

        The token file generated by the authorisation process, strava.json,
        should also be placed in the same folder.
    """

    orientations = base.ORIENTATION_HORIZONTAL
    _experimental = True
    defaults = [
        ("font", "sans", "Default font"),
        ("fontsize", None, "Font size"),
        ("foreground", "ffffff", "Text colour"),
        ("text", "{CA:%b} {CD:.1f}km", "Widget text"),
        ("refresh_interval", 1800, "Time to update data"),
        ("startup_delay", 10, "Time before sending first web request"),
        ("popup_display_timeout", 15, "Time to display extended info"),
        ("warning_colour", "aaaa00", "Highlight when there is an error."),
    ]

    format_map = {
        "CD": ("current", "distance"),
        "CC": ("current", "count"),
        "CT": ("current", "format_time"),
        "CP": ("current", "format_pace"),
        "CN": ("current", "name"),
        "CA": ("current", "date"),
        "YD": ("year", "distance"),
        "YC": ("year", "count"),
        "YT": ("year", "format_time"),
        "YP": ("year", "format_pace"),
        "YN": ("year", "name"),
        "YA": ("year", "date"),
        "AD": ("alltime", "distance"),
        "AC": ("alltime", "count"),
        "AT": ("alltime", "format_time"),
        "AP": ("alltime", "format_pace"),
        "AN": ("alltime", "name"),
        "AA": ("alltime", "date"),
    }

    _screenshots = [
        ("strava_widget.png", ""),
        (
            "strava_widget_detail.png",
            "Extended info. I've blurred out details of my runs for " "privacy reasons.",
        ),
    ]

    _dependencies = ["stravalib", "pint"]

    def __init__(self, **config):
        base._Widget.__init__(self, bar.CALCULATED, **config)
        self.add_defaults(StravaWidget.defaults)
        self.add_defaults(base.MarginMixin.defaults)

        if "font_colour" in config:
            self.foreground = config["font_colour"]
            logger.warning(
                "The use of `font_colour` is deprecated. "
                "Please update your config to use `foreground` instead."
            )

        self.data = None
        self.display_text = ""

    def _configure(self, qtile, bar):
        base._Widget._configure(self, qtile, bar)
        self.timeout_add(self.startup_delay, self.refresh)

    def _get_data(self):
        return get_strava_data()

    def _read_data(self, future):
        results = future.result()

        if results:
            success, data = results

            if not success:
                logger.warning("Error retrieving data: %s.", data)
            else:
                self.data = data
                self.formatted_data = {}
                for k, v in self.format_map.items():
                    obj = self.data
                    for attr in v:
                        obj = getattr(obj, attr)

                    self.formatted_data[k] = obj

                self.timeout_add(1, self.bar.draw)
        self.timeout_add(self.refresh_interval, self.refresh)

    def refresh(self):
        future = self.qtile.run_in_executor(self._get_data)
        future.add_done_callback(self._read_data)

    def calculate_length(self):
        total = 0

        if self.data is not None and self.text:
            text = self.format_text(self.text)

            width, _ = self.drawer.max_layout_size([text], self.font, self.fontsize)

            total += width + 2 * self.margin

        total += self.height

        return total

    def draw_icon(self):
        scale = self.height / 24.0
        self.drawer.set_source_rgb("ffffff")
        self.drawer.ctx.set_line_width(2)
        self.drawer.ctx.move_to(8 * scale, 14 * scale)
        self.drawer.ctx.line_to(12 * scale, 6 * scale)
        self.drawer.ctx.line_to(16 * scale, 14 * scale)
        self.drawer.ctx.stroke()

        self.drawer.ctx.set_line_width(1)
        self.drawer.ctx.move_to(13 * scale, 14 * scale)
        self.drawer.ctx.line_to(16 * scale, 20 * scale)
        self.drawer.ctx.line_to(19 * scale, 14 * scale)
        self.drawer.ctx.stroke()

    def draw_highlight(self, top=False, colour="000000"):
        self.drawer.set_source_rgb(colour)

        y = 0 if top else self.bar.height - 2

        # Draw the bar
        self.drawer.fillrect(0, y, self.width, 2, 2)

    def draw(self):
        # Remove background
        self.drawer.clear(self.background or self.bar.background)

        x_offset = 0

        self.draw_icon()
        x_offset += self.height

        if self.data is None:
            self.draw_highlight(top=True, colour=self.warning_colour)

        else:
            self.display_text = self.format_text(self.text)

            # Create a text box
            layout = self.drawer.textlayout(
                self.display_text, self.foreground, self.font, self.fontsize, None, wrap=False
            )

            # We want to centre this vertically
            y_offset = (self.bar.height - layout.height) / 2

            # Draw it
            layout.draw(x_offset + self.margin_x, y_offset)

        self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)

    def button_press(self, x, y, button):
        self.show_popup_summary()

    def mouse_enter(self, x, y):
        pass

    def format_text(self, text):
        try:
            return text.format(**self.formatted_data)
        except Exception:
            logger.exception("Exception when trying to format text.")
            return "Error"

    def show_popup_summary(self):
        if not self.data:
            return False

        lines = []

        heading = "{:^6} {:^20} {:^8} {:^10} {:^6}".format("Date", "Title", "km", "time", "pace")
        lines.append(heading)

        for act in self.data.current.children:
            line = (
                f"{act.date:%d %b}: {act.name:<20.20} {act.distance:7,.1f} "
                f"{act.format_time:>10} {act.format_pace:>6}"
            )
            lines.append(line)

        sub = (
            f"\n{self.data.current.date:%b %y}: {self.data.current.name:<20.20} {self.data.current.distance:7,.1f} "
            f"{self.data.current.format_time:>10} "
            f"{self.data.current.format_pace:>6}"
        )
        lines.append(sub)

        for month in self.data.previous:
            line = (
                f"{month.groupdate:%b %y}: {month.name:<20.20} {month.distance:7,.1f} "
                f"{month.format_time:>10} {month.format_pace:>6}"
            )
            lines.append(line)

        year = (
            f"\n{self.data.year.groupdate:%Y}  : {self.data.year.name:<20.20} {self.data.year.distance:7,.1f} "
            f"{self.data.year.format_time:>10} "
            f"{self.data.year.format_pace:>6}"
        )
        lines.append(year)

        alltime = (
            f"\nTOTAL : {self.data.alltime.name:<20.20} {self.data.alltime.distance:7,.1f} "
            f"{self.data.alltime.format_time:>10} "
            f"{self.data.alltime.format_pace:>6}"
        )
        lines.append(alltime)

        self.popup = Popup(
            self.qtile,
            y=self.bar.height,
            width=900,
            height=900,
            font="monospace",
            horizontal_padding=10,
            vertical_padding=10,
            opacity=0.8,
        )
        self.popup.text = "\n".join(lines)
        self.popup.height = self.popup.layout.height + (2 * self.popup.vertical_padding)
        self.popup.width = self.popup.layout.width + (2 * self.popup.horizontal_padding)
        self.popup.x = min(self.offsetx, self.bar.width - self.popup.width)
        self.popup.place()
        self.popup.draw_text()
        self.popup.unhide()
        self.popup.draw()
        self.timeout_add(self.popup_display_timeout, self.popup.kill)

    def info(self):
        info = base._Widget.info(self)
        info["display_text"] = self.display_text
        return info
