# Copyright (C) 2015, Juan Riquelme González
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

import datetime
import math
import subprocess
from functools import partial

from libqtile import pangocffi
from libqtile.widget import base


def format_time(time_seconds_string):
    """Format time in seconds as [h:]mm:ss."""
    return str(datetime.timedelta(seconds=float(time_seconds_string))).lstrip("0").lstrip(":")


class Cmus(base.ThreadPoolText):
    """A simple Cmus widget.

    Show the metadata of now listening song and allow basic mouse
    control from the bar:

        - toggle pause (or play if stopped) on left click;
        - skip forward in playlist on scroll up;
        - skip backward in playlist on scroll down.

    The following fields (extracted from ``cmus-remote -C status``) are available in the `format`
    string:

    - ``status``: cmus playback status, one of "playing", "paused" or "stopped".
    - ``file``
    - ``position``: Current position in [h:]mm:ss.
    - ``position_percent``: Current position in percent.
    - ``remaining``: Remaining time in [h:]mm:ss.
    - ``remaining_percent``: Remaining time in percent.
    - ``duration``: Total length in [h:]mm:ss.
    - ``artist``
    - ``album``
    - ``albumartist``
    - ``composer``
    - ``comment``
    - ``date``
    - ``discnumber``
    - ``genre``
    - ``title``: Title or filename if no title is available.
    - ``tracknumber``
    - ``stream``
    - ``status_text``: Text indicating the playback status, corresponds to one of `playing_text`,
      `paused_text` or `stopped_text`.

    Cmus (https://cmus.github.io) should be installed.
    """

    defaults = [
        ("format", "{status_text}{artist} - {title}", "Format of playback info."),
        ("stream_format", "{status_text}{stream}", "Format of playback info for streams."),
        (
            "no_artist_format",
            "{status_text}{title}",
            "Format of playback info if no artist available.",
        ),
        ("playing_text", "♫ ", "Text to display when playing, if chosen."),
        ("playing_color", "00ff00", "Text colour when playing."),
        ("paused_text", "♫ ", "Text to display when paused, if chosen."),
        ("paused_color", "cecece", "Text color when paused."),
        ("stopped_text", "♫ ", "Text to display when stopped, if chosen."),
        ("stopped_color", "cecece", "Text color when stopped."),
        ("update_interval", 0.5, "Update Time in seconds."),
        (
            "play_icon",
            "♫ ",
            "DEPRECATED Text to display when playing, paused, and stopped, if chosen.",
        ),
        ("play_color", "", "DEPRECATED Text colour when playing."),
        ("noplay_color", "", "DEPRECATED Text colour when paused or stopped."),
    ]

    def __init__(self, **config):
        base.ThreadPoolText.__init__(self, "", **config)
        self.add_defaults(Cmus.defaults)
        self.status = ""
        self.local = None

        self.add_callbacks(
            {
                "Button1": self.play,
                "Button4": partial(subprocess.Popen, ["cmus-remote", "-n"]),
                "Button5": partial(subprocess.Popen, ["cmus-remote", "-r"]),
            }
        )

    def _configure(self, qtile, parent_bar):
        base.ThreadPoolText._configure(self, qtile, parent_bar)
        # Backwards compatibility
        if self.play_color:
            self.playing_color = self.play_color
            self.paused_color = self.play_color
        if self.noplay_color:
            self.stopped_color = self.noplay_color

    def get_info(self):
        """Return a dictionary with info about the current cmus status."""
        try:
            output = self.call_process(["cmus-remote", "-C", "status"])
        except subprocess.CalledProcessError as err:
            output = err.output
        if output.startswith("status"):
            output = output.splitlines()
            info = {
                "status": "",
                "file": "",
                "position": "",
                "position_percent": "",
                "remaining": "",
                "remaining_percent": "",
                "duration": "",
                "artist": "",
                "album": "",
                "albumartist": "",
                "composer": "",
                "comment": "",
                "date": "",
                "discnumber": "",
                "genre": "",
                "title": "",
                "tracknumber": "",
                "stream": "",
                "status_text": "",
                "play_icon": self.play_icon,
            }

            for line in output:
                if line.startswith("set"):
                    break
                for data in info:
                    match = data + " "
                    if match in line:
                        index = line.index(data)
                        if index < 5:
                            info[data] = line[len(data) + index :].strip()
                            break

            # Set status text
            status = info["status"]
            info["status_text"] = getattr(self, f"{status}_text", self.stopped_text)

            # Format and process duration and position
            if info["position"] != "" and info["duration"] != "" and int(info["duration"]) > 0:
                info["position_percent"] = (
                    str(math.floor(int(info["position"]) / int(info["duration"]) * 100)) + "%"
                )
                info["remaining_percent"] = (
                    str(
                        math.ceil(
                            (int(info["duration"]) - int(info["position"]))
                            / int(info["duration"])
                            * 100
                        )
                    )
                    + "%"
                )
                info["remaining"] = format_time(int(info["duration"]) - int(info["position"]))
                info["position"] = format_time(info["position"])
                info["duration"] = format_time(info["duration"])
            else:
                info["duration"] = ""
                info["position"] = ""

            return info

    def now_playing(self):
        """Return a string with the now playing info."""
        info = self.get_info()
        now_playing = ""
        if info:
            display_format = self.format
            status = info["status"]
            if self.status != status:
                self.status = status
                if self.status == "playing":
                    self.layout.colour = self.playing_color
                elif self.status == "paused":
                    self.layout.colour = self.paused_color
                else:
                    self.layout.colour = self.stopped_color
            self.local = info["file"].startswith("/")
            if self.local:
                if not info["title"]:
                    info["title"] = info["file"].split("/")[-1]
                if not info["artist"]:
                    display_format = self.no_artist_format
            elif info["stream"]:
                display_format = self.stream_format
            # Handle case if cmus was started and no file is selected yet
            elif not info["file"]:
                display_format = ""
            now_playing = display_format.format(**info)
            if now_playing.strip() == info["status_text"].strip():
                now_playing = ""

        return pangocffi.markup_escape_text(now_playing)

    def play(self):
        """Play music if stopped, else toggle pause."""
        if self.status in ("playing", "paused"):
            subprocess.Popen(["cmus-remote", "-u"])
        elif self.status == "stopped":
            subprocess.Popen(["cmus-remote", "-p"])

    def poll(self):
        """Poll content for the text box."""
        return self.now_playing()
