#########################################################################
# This file is part of Lyntin.
#
# Lyntin 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.
#
# Lyntin 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/>.
#
# copyright (c) Free Software Foundation 2001-2007
#
# $Id: alias.py,v 1.9 2007/07/24 00:39:03 willhelm Exp $
#########################################################################
"""
This module defines the AliasManager which manages aliases, creating new
aliases, removing aliases, checking user input for aliases, expanding
aliases, and other such things.

The AliasManager has an AliasData object for every session that has
aliases.

An alias consists of an "alias" and an "expansion".  So if there is
an alias:

   l3k -> #ses a localhost 3000

the "alias" is "l3k" and the "expansion" is "#ses a localhost 3000".
Whenever the user types "l3k" this module expands it to 
"#ses a localhost 3000" which then (after going through other user_filter
hook functions) gets executed.

Aliases are currently handled via string finding and not regular
expressions.  At some point in the future, this will be changed to
regular expressions to better handle a wider variety of aliases.
"""
from lyntin import manager, utils, exported
from lyntin.modules import modutils

class AliasData:
  """ Manages aliases."""
  def __init__(self):
    self._aliases = {}

  def addAlias(self, name, expansion):
    """
    Adds an alias to the dict.

    @param name: the alias name
    @type  name: string

    @param expansion: the string the alias expands to
    @type  expansion: string

    @raises ValueError: when the name is the same as the expansion
    """
    if name == expansion:
      raise ValueError("name cannot equal expansion.")
    self._aliases[name] = expansion

  def clear(self):
    """
    Removes all the aliases.
    """
    self._aliases.clear()

  def removeAliases(self, text):
    """
    Removes aliases from the list.

    Returns a list of tuples of alias name/expansion that
    were removed.

    @param text: the text which when run through util.expand
        gives us the aliases to remove
    @type  text: string

    @return: the list of alias/expansions that match the text
    @rtype: list of (string, string)
    """
    badaliases = utils.expand_text(text, list(self._aliases.keys()))

    ret = []
    for mem in badaliases:
      ret.append((mem, self._aliases[mem]))
      del self._aliases[mem]

    return ret

  def getAliases(self):
    """
    Returns the keys of the alias dict.

    @return: all the aliases we're managing (but not the expansions)
    @rtype: list of strings
    """
    listing = sorted(self._aliases.keys())
    return listing

  def expand(self, input):
    """
    Looks at user input and expands any aliases involved.

    It'll return the expansion if there is one.  Otherwise
    it returns None.

    @param input: the user input to expand
    @type  input: string

    @return: the alias expansion for the given input if it's an alias
        or None
    @rtype: string
    """
    if len(input) > 0:
      # pull out the first word of the input
      firstword = input.split(' ', 1)[0]

      # if we match an alias, we return the expansion
      if firstword in list(self._aliases.keys()):
        return self._aliases[firstword]            

    return None

  def getStatus(self):
    """
    Returns the one-line status of this manager.

    @return: the one line status
    @rtype: string
    """
    return "%d alias(es)." % len(self._aliases)
    
  def getInfo(self, text=""):
    """
    Returns information about the aliases in here.

    This is used by #alias to tell all the aliases involved
    as well as #write which takes this information and dumps
    it to the file.

    @param text: the text to expand to find aliases the user
        wants information about.
    @type  text: string

    @return: a list of strings where each string represents an alias
    @rtype: list of strings
    """
    if len(list(self._aliases)) == 0:
      return []

    listing = list(self._aliases.keys())
    if text:
      listing = utils.expand_text(text, listing)

    data = []
    for mem in listing:
      data.append("alias {%s} {%s}" % (mem, utils.escape(self._aliases[mem])))

    return data

  def getInfoMappings(self):
    l = []
    for m in list(self._aliases.keys()):
      l.append( { "alias": m, "expansion": self._aliases[m] } )

    return l


class AliasManager(manager.Manager):
  """
  Extends the manager.Manager base class to implement alias
  functionality.  The AliasManager holds session -> AliasData objects
  which handle the alias functionality.
  """
  def __init__(self):
    # session -> AliasData objects
    self._aliasdata = {}

  def getAliasData(self, ses):
    if ses not in self._aliasdata:
      self._aliasdata[ses] = AliasData()
    return self._aliasdata[ses]

  def clear(self, ses):
    if ses not in self._aliasdata:
      return
    self._aliasdata[ses].clear()

  def getAlias(self, ses, text):
    return self.getAliasData(ses).expand(text)

  def getStatus(self, ses):
    return self.getAliasData(ses).getStatus()

  def getInfo(self, ses, text=""):
    return self.getAliasData(ses).getInfo(text)

  def getInfoMappings(self, item, ses):
    if item != "alias":
      raise ValueError("%s is not a valid item for this manager." % item)

    if ses in self._aliasdata:
      return self._aliasdata[ses].getInfoMappings()
    return []
    
  def getItems(self):
    return [ "alias" ]

  def getParameters(self, item):
    if item != "alias":
      raise ValueError("%s is not a valid item for this manager." % item)

    return [ ("alias", "The alias name."),
             ("expansion", "The alias expansion.") ]

  def persist(self, args):
    """
    write_hook function for persisting the state of our session.
    """
    ses = args["session"]
    quiet = args["quiet"]

    data = self.getInfo(ses)
    if quiet == 1:
      data = [m + " quiet={true}" for m in data]
        
    return data

  def userfilter(self, args):
    """ 
    user_filter_hook.
    """
    # we check for aliases here--and if we find some, we
    # do the variable expansion and then recurse over the result
    ses = args["session"]
    internal = args["internal"]
    verbatim = args["verbatim"]
    text = args["dataadj"]
  
    if ses not in self._aliasdata or verbatim == 1:
      return text

    aliasexpansion = self._aliasdata[ses].expand(text)

    if not aliasexpansion:
      return text
    else:
      aliasexpansion = utils.expand_placement_vars(text, aliasexpansion)
      exported.lyntin_command(aliasexpansion, 1, ses)
      return None

  def addSession(self, newsession, basesession=None):
    if basesession:
      if basesession in list(self._aliasdata):
        bdata = self.getAliasData(basesession)
        ndata = self.getAliasData(newsession)

        for mem in list(bdata._aliases.keys()):
          ndata.addAlias(mem, bdata._aliases[mem])

  def removeSession(self, ses):
    if ses in self._aliasdata:
      del self._aliasdata[ses]


commands_dict = {}

def alias_cmd(ses, args, input):
  """
  With no arguments, prints all aliases.
  With one argument, prints all aliases which match the arg.
  With multiple arguments, creates an alias.

  You can use pattern variables which look like % and a number.  %0 
  will be all the arguments passed in.

  Ranges can be used by using python colon-syntax, specifying a
  half-open slice of the input items, so %0:3 is the alias name, first,
  and second arguments of the input.

  Negative numbers count back from the end of the list.  So %-1 is the
  last item in the list, %:-1 is everything but the last item in the
  list. 

  examples:
    #alias {k*}                    - prints out aliases that start with k
    #alias {k} {kill %1}           - builds a new alias
    #alias {gg} {put %1: in chest} - builds a new alias

  category: commands
  """
  name = args["alias"]
  command = args["expansion"]
  quiet = args["quiet"]

  am = exported.get_manager("alias")
  ad = am.getAliasData(ses)

  # they typed '#alias' or '#alias x' so we print the relevant aliases
  if not command:
    data = ad.getInfo(name)
    if not data:
      data = ["alias: no aliases defined."]

    exported.write_message("aliases:\n" + "\n".join(data), ses)
    return

  # they're creating an alias
  try:
    ad.addAlias(name, command)
  except ValueError as e:
    exported.write_error("alias: %s" % e, ses)

  if not quiet:
    exported.write_message("alias: {%s} {%s} added." % (name, command), ses)

commands_dict["alias"] = (alias_cmd, "alias= expansion= quiet:boolean=false")


def unalias_cmd(ses, args, input):
  """
  Allows you to remove aliases.

  category: commands
  """
  func = exported.get_manager("alias").getAliasData(ses).removeAliases
  modutils.unsomething_helper(args, func, None, "alias", "aliases")

commands_dict["unalias"] = (unalias_cmd, "str= quiet:boolean=false")

am = None

def load():
  """ Initializes the module by binding all the commands."""
  global am
  modutils.load_commands(commands_dict)
  am = AliasManager()
  exported.add_manager("alias", am)

  exported.hook_register("user_filter_hook", am.userfilter, 20)
  exported.hook_register("write_hook", am.persist)

def unload():
  """ Unloads the module by calling any unload/unbind functions."""
  global am
  modutils.unload_commands(list(commands_dict.keys()))
  exported.remove_manager("alias")

  exported.hook_register("user_filter_hook", am.userfilter)
  exported.hook_register("write_hook", am.persist)

# Local variables:
# mode:python
# py-indent-offset:2
# tab-width:2
# End:
