# Copyright (C) 2009 Canonical Ltd
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

"""Mapping of logical commands to actual commands.

The primary purpose of this mapping is to enable the gui tools in QBzr or
bzr-gtk to be selected. Users may wish to have custom suites as well to
mix and match across plugins or when developing their own gui plugins.

In the future, suites might also be added for more GUI toolkits like:

 * .NET (Windows)
 * Swing (Java)
 * SWT (Eclipse)
 * Sugar (OLPC)
 * etc.

Note: Qt language bindings are available for .NET and Java so many of these
suites probably aren't required in the short term.
"""

from bzrlib import registry
from bzrlib.cmdline import split

from bzrlib.plugins.explorer.lib.qbzr import get_qbzr_ignore_mapping


_PERMITTED_PLACEHOLDERS = [
    "root",
    "selected",
    "filename",
    "dirname",
    "basename",
    "EDITOR",
    "BZR_LOG",
    "branch_root",
    "wt_selected",
    ]


class ApplicationSuite(object):
    """A mapping from logical commands to actual commands.

    The lookup() method is used to find an actual command. Different
    mappings may be registered for local vs remote locations. A context is
    required so that placeholders in the command templates can be expanded.
    The following placeholder names are permitted:

    * root - the root of the location (branch, checkout or repository)
    * selected - a space separated list of paths (relative to root)
    * filename - first selected item filename (relative to root)
    * dirname - first selected item directory name (relative to root)
    * basename - first selected item basename (relative to dirname)
    * EDITOR - preferred editor path and options
    * BZR_LOG - path to .bzr.log file
    * branch_root - the root of the branch (for a checkout or branch)
    * wt_selected - space separated list of paths selected in working tree    

    Placeholder are defined in templates using the format %(name)s.

    Before running local commands, the current working directory should
    be set to the root of the location. No assumptions should be made
    about the current working directory before running commands on
    remote locations.
    """
    def __init__(self, name, stateless_mapping, local_mapping,
        remote_mapping, fallbacks=[]):
        """Create an application suite.

        :param stateless_mapping: map of logical commands to commands to
          use regardless of whether a location is active or not.
        :param local_mapping: map of logical commands to commands to
          execute on local locations.
        :param remote_mapping: map of logical commands to commands to
          execute on remote locations.
        :param fallbacks: an ordered list of other suites to check
          if a command isn't found in this one.
        """
        self._name = name
        self._stateless_mapping = stateless_mapping
        self._local_mapping = local_mapping
        self._remote_mapping = remote_mapping
        self._fallbacks = [app_suite_registry.get(f) for f in fallbacks]
        # Remember the keys
        keys = set()
        for map in [stateless_mapping, local_mapping, remote_mapping]:
            keys.update(map.keys())
        for fallback in self._fallbacks:
            keys.update(fallback.keys())
        self._keys = keys

    def name(self):
        return self._name

    def keys(self):
        return self._keys

    def lookup(self, cmd_id, context, location_type=False):
        """Get the command & args for a cmd-id or return None.
 
        :param cmd_id: the logical command identifier
        :param content: dictionary mapping placeholders to strings.
          If a permitted placeholder is missing, it is replaced
          with an empty string. If an unknown placeholder is found,
          an exception is thrown?
        :param location_type: either "local", "remote" or None.
          If None, only the stateless mapping is checked.
        :return: the list of arguments (first is command name) or
          None if not found.
        """
        result = self._lookup_here(cmd_id, context, location_type)
        if result is None and self._fallbacks:
            for fallback in self._fallbacks:
                result = fallback._lookup_here(cmd_id, context, location_type)
                if result:
                    break
        return result

    def _lookup_here(self, cmd_id, context, location_type):
        map = {}
        map.update(self._stateless_mapping)
        if location_type == "remote":
            map.update(self._remote_mapping)
        else:
            map.update(self._local_mapping)
        result = map.get(cmd_id, None)
        if not result:
            return None
        return command_to_args(result, context)


def command_to_expanded(template, context):
    """Convert a command template and context to expanded form."""
    params = dict([(k, '') for k in _PERMITTED_PLACEHOLDERS])
    params.update(context)
    return unicode(template) % params


def command_to_args(template, context):
    """Convert a command template and context to a list of arguments."""
    return split(command_to_expanded(template, context))


# The registry of suites
# Note: it's best to name suites the same as the plugin module
app_suite_registry = registry.Registry()


# XXX: These mappings really ought to be stored in the QBzr plugin so that
# they can updated as new features are added there
_QBZR_STATELESS_MAPPING = {
    "advanced":           "bzr qrun --ui-mode",
    "branch":             "bzr qbranch --ui-mode %(branch_root)s",
    # qgetnew uses the location arg as the target, not source, so don't pass it
    "checkout":           "bzr qgetnew --ui-mode",
    "init":               "bzr qinit --ui-mode",
    "plugins":            "bzr qplugins",
    "version":            "bzr qversion",
    "config:bazaar.conf": "bzr qconfig",
    # Note: We need to quote BZR_LOG here as shlex_split_unicode() is buggy
    # on Windows
    ".bzr.log":           "bzr qviewer '%(BZR_LOG)s'",
    }
_QBZR_LOCAL_MAPPING = {
    "add":      "bzr qadd --ui-mode %(selected)s",
    "annotate": "bzr qannotate --ui-mode %(filename)s",
    "bind":     "bzr qbind --ui-mode",
    "browse":   "bzr qbrowse -r-1",
    "commit":   "bzr qcommit --ui-mode %(selected)s",
    "conflicts":"bzr qconflicts",
    "diff":     "bzr qdiff %(filename)s",
    "diff(-rsubmit:..-1)":     "bzr qdiff -rsubmit:..-1 %(filename)s",
    "export":   "bzr qexport --ui-mode",
    "ignore":   get_qbzr_ignore_mapping(),
    "info":     "bzr qinfo",
    "list":     "bzr qbrowse",
    "log":      "bzr qlog %(selected)s",
    "merge":    "bzr qmerge --ui-mode",
    "push":     "bzr qpush --ui-mode",
    "pull":     "bzr qpull --ui-mode",
    "revert":   "bzr qrevert --ui-mode %(selected)s",
    "send":     "bzr qsend --ui-mode",
    "shelve":   "bzr qshelve --ui-mode",
    "switch":   "bzr qswitch --ui-mode",
    "tag":      "bzr qtag --ui-mode",
    "unbind":   "bzr qunbind --ui-mode --execute",
    "uncommit": "bzr quncommit --ui-mode",
    "unshelve": "bzr qunshelve --ui-mode",
    "update":   "bzr qupdate --ui-mode --execute",
    }
_QBZR_REMOTE_MAPPING = {
    "browse":   "bzr qbrowse %(root)s -r-1",
    "list":     "bzr qbrowse %(root)s",
    "log":      "bzr qlog %(root)s",
    # These aren't supported but ought to be
    #"info":     "bzr qinfo %(root)s",
    #"push":     "bzr qpush -d%(root)s",
    #"pull":     "bzr qpull -d%(root)s",
    #"tag":      "bzr qtag %(root)s",
    }
app_suite_registry.register("qbzr", ApplicationSuite("qbzr",
    _QBZR_STATELESS_MAPPING, _QBZR_LOCAL_MAPPING, _QBZR_REMOTE_MAPPING),
    help="Applets from the QBzr plugin")
app_suite_registry.default_key = "qbzr"


# XXX: These mappings really ought to be stored in the bzr-gtk plugin so that
# they can updated as new features are added there. That plugin should
# also take responsibility for registering it so that it only becomes
# available if the plugin is installed.
_GTK_STATELESS_MAPPING = {
    "branch":             "bzr gbranch %(filename)s",
    "checkout":           "bzr gcheckout %(filename)s",
    "init":               "bzr ginit",
    "config:bazaar.conf": "bzr gpreferences",
    # Note: We need to quote BZR_LOG here as shlex_split_unicode() is buggy
    # on Windows
    ".bzr.log":           "bzr qviewer '%(BZR_LOG)s'",
    # These aren't supported but ought to be
    #"version":            "bzr gversion",
    }
_GTK_LOCAL_MAPPING = {
    "annotate": "bzr gannotate %(filename)s",
    "commit":   "bzr gcommit %(selected)s",
    "conflicts":"bzr gconflicts",
    "diff":     "bzr gdiff %(filename)s",
    "info":     "bzr ginfo",
    "log":      "bzr visualise %(selected)s",
    "merge":    "bzr gmerge",
    "push":     "bzr gpush",
    "send":     "bzr gsend",
    "status":   "bzr gstatus",
    "tag":      "bzr gtags",
    # These aren't supported but ought to be
    #"add":      "bzr gadd %(selected)s",
    #"list":     "bzr gbrowse",
    #"pull":     "bzr gpull",
    #"revert":   "bzr grevert %(selected)s",
    #"update":   "bzr gupdate",
    }
_GTK_REMOTE_MAPPING = {
    "log":      "bzr visualise %(root)s",
    # These aren't supported but ought to be
    #"info":     "bzr ginfo %(root)s",
    #"push":     "bzr gpush -d %(root)s",
    #"pull":     "bzr gpull -d %(root)s",
    #"tag":      "bzr gtags %(root)s",
    }
try:
    import bzrlib.plugins.gtk
    app_suite_registry.register("gtk", ApplicationSuite("gtk",
        _GTK_STATELESS_MAPPING, _GTK_LOCAL_MAPPING, _GTK_REMOTE_MAPPING,
        fallbacks=['qbzr']),
        help="Applets from the bzr-gtk plugin")
except ImportError:
    pass
