# -*- coding: utf-8 -*-
# Copyright 2012-2014 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation

from gi.repository import Gtk

import os
import re

import quodlibet
from quodlibet import qltk
from quodlibet import util
from quodlibet.pattern import Pattern
from quodlibet.plugins import PluginConfigMixin
from quodlibet.plugins.songsmenu import SongsMenuPlugin
from quodlibet.qltk.data_editors import JSONBasedEditor
from quodlibet.qltk.x import SeparatorMenuItem
from quodlibet.qltk import ErrorMessage, Icons
from quodlibet.qltk.getstring import GetStringDialog
from quodlibet.util.dprint import print_w, print_d, print_e
from quodlibet.util.json_data import JSONObject, JSONObjectDict
from quodlibet.util import connect_obj

Field = JSONObject.Field


class Command(JSONObject):
    """
    Wraps an arbitrary shell command and its argument pattern.
    Serialises as JSON for some editability
    """

    NAME = _("Command")

    FIELDS = {
        "name": Field(_("name"), _("The name of this command")),

        "command": Field(_("command"), _("The shell command syntax to run")),

        "parameter": Field(_("parameter"),
                           _("If specified, a parameter whose occurrences in "
                             "the command will be substituted with a "
                             "user-supplied value, e.g. by using 'PARAM' "
                             "all instances of '{PARAM}' in your command will "
                             "have the value prompted for when run")),

        "pattern": Field(_("pattern"),
                         _("The QL pattern, e.g. <~filename>, to use to "
                           "compute a value for the command")),

        "unique": Field(_("unique"),
                        _("If set, this will remove duplicate computed values "
                          "of the pattern")),

        "max_args": Field(_("max args"),
                          _("The maximum number of argument to pass to the "
                            "command at one time (like xargs)")),
    }

    def __init__(self, name=None, command=None, pattern="<~filename>",
                 unique=False, parameter=None, max_args=10000,
                 warn_threshold=100):
        JSONObject.__init__(self, name)
        self.command = str(command or "")
        self.pattern = str(pattern)
        self.unique = bool(unique)
        self.max_args = max_args
        self.parameter = str(parameter or "")
        self.__pat = Pattern(self.pattern)
        self.warn_threshold = warn_threshold

    def run(self, songs):
        """
        Runs this command on `songs`,
        splitting into multiple calls if necessary
        """
        args = []
        if self.parameter:
            value = GetStringDialog(None, _("Input value"),
                                    _("Value for %s?") % self.parameter).run()
            self.command = self.command.format(**{self.parameter: value})
            print_d("Actual command=%s" % self.command)
        for song in songs:
            arg = str(self.__pat.format(song))
            if not arg:
                print_w("Couldn't build shell command using \"%s\"."
                        "Check your pattern?" % self.pattern)
                break
            if not self.unique:
                args.append(arg)
            elif arg not in args:
                args.append(arg)
        max = int((self.max_args or 10000))
        com_words = self.command.split(" ")
        while args:
            print_d("Running %s with %d substituted arg(s) (of %d%s total)..."
                    % (self.command, min(max, len(args)), len(args),
                       " unique" if self.unique else ""))
            util.spawn(com_words + args[:max])
            args = args[max:]

    def __str__(self):
        return "Command= {command} {pattern}".format(**dict(self.data))


class CustomCommands(SongsMenuPlugin, PluginConfigMixin):

    PLUGIN_ICON = Icons.DOCUMENT_OPEN
    PLUGIN_ID = "CustomCommands"
    PLUGIN_NAME = _("Custom Commands")
    PLUGIN_DESC = _("Runs custom commands (in batches if required) on songs "
                    "using any of their tags.")

    _TUPLE_DEF = "\s*\('([^']*)'%s\)" % ("(?:,\s*'([^']*)')?" * 5)
    _TUPLE_REGEX = re.compile(_TUPLE_DEF)

    # Here are some starters...
    DEFAULT_COMS = [
        Command("Compress files", "file-roller -d"),

        Command("K3B", "k3b --audiocd"),

        Command("Browse folders (Thunar)", "thunar", "<~dirname>", unique=True,
                max_args=50, warn_threshold=20),

        Command(name="Flash notification",
                command="notify-send"
                    " -i /usr/share/icons/hicolor/scalable/apps/quodlibet.svg",
                pattern="<~rating> \"<title><version| (<version>)>\""
                        "<~people| by <~people>>"
                    "<album|, from <album><discnumber| : disk <discnumber>>"
                    "<~length| (<~length>)>",
                max_args=1,
                warn_threshold=10),

        Command("Fix MP3 VBR with mp3val", "mp3val -f", unique=True,
                max_args=1),
    ]
    COMS_FILE = os.path.join(
        quodlibet.get_user_dir(), 'lists', 'customcommands.json')

    def __set_pat(self, name):
        self.com_index = name

    def get_data(self, key):
        """Gets the pattern for a given key"""
        try:
            return self.commands[key]
        except (KeyError, TypeError):
            print_d("Invalid key %s" % key)
            return None

    @classmethod
    def edit_patterns(cls, button):
        cls.commands = cls._get_saved_searches()
        win = JSONBasedEditor(Command, cls.commands, filename=cls.COMS_FILE,
                              title=_("Edit Custom Commands"))
        win.show()

    @classmethod
    def PluginPreferences(cls, parent):
        hb = Gtk.HBox(spacing=3)
        hb.set_border_width(0)

        button = qltk.Button(_("Edit Custom Commands") + "...", Icons.EDIT)
        button.set_tooltip_markup(util.escape(_("Supports QL patterns\neg "
                                                "<tt><~artist~title></tt>")))
        button.connect("clicked", cls.edit_patterns)
        hb.pack_start(button, True, True, 0)
        hb.show_all()
        return hb

    @classmethod
    def _get_saved_searches(cls):
        filename = cls.COMS_FILE
        print_d("Loading saved commands from '%s'..." % filename)
        coms = None
        try:
            with open(filename) as f:
                coms = JSONObjectDict.from_json(Command, f.read())
        except IOError:
            pass
        except ValueError as e:
            print_w("Couldn't parse saved commands (%s)" % e)

        # Failing all else...
        if not coms:
            print_d("No commands found in %s. Using defaults." % filename)
            coms = dict([(c.name, c) for c in cls.DEFAULT_COMS])
        print_d("Loaded commands: %s" % coms.keys())
        return coms

    def __init__(self, *args, **kwargs):
        super(CustomCommands, self).__init__(*args, **kwargs)
        self.com_index = None
        self.unique_only = False
        self.commands = {}
        submenu = Gtk.Menu()
        self.commands = self._get_saved_searches()
        for (name, c) in self.commands.items():
            item = Gtk.MenuItem(label=name)
            connect_obj(item, 'activate', self.__set_pat, name)
            submenu.append(item)
            # Add link to editor

        self.add_edit_item(submenu)
        if submenu.get_children():
            self.set_submenu(submenu)
        else:
            self.set_sensitive(False)

    @classmethod
    def add_edit_item(cls, submenu):
        config = Gtk.MenuItem(label=_("Edit Custom Commands") + "...")
        connect_obj(config, 'activate', cls.edit_patterns, config)
        config.set_sensitive(not JSONBasedEditor.is_not_unique())
        submenu.append(SeparatorMenuItem())
        submenu.append(config)

    def plugin_songs(self, songs):
        # Check this is a launch, not a configure
        if self.com_index:
            com = self.get_data(self.com_index)
            print_d("Running %s" % com)
            try:
                com.run(songs)
            except Exception, err:
                print_e("Couldn't run command %s: %s %s at"
                        % (com.name, type(err), err))
                ErrorMessage(
                    self.plugin_window,
                    _("Unable to run custom command %s" %
                      util.escape(self.com_index)),
                    util.escape(str(err))).run()
