"""
General Character commands usually available to all characters
"""
import re
from django.conf import settings
from evennia.utils import utils, evtable
from evennia.typeclasses.attributes import NickTemplateInvalid

COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)

# limit symbol import for API
__all__ = (
    # "CmdHome",
    "CmdLook",
    "CmdMove",
    # "CmdNick",
    "CmdInventory",
    # "CmdSetDesc",
    "CmdGet",
    "CmdDrop",
    "CmdGive",
    # "CmdSay",
    # "CmdWhisper",
    # "CmdPose",
    # "CmdAccess",
)


# class CmdHome(COMMAND_DEFAULT_CLASS):
#     """
#     move to your character's home location
#
#     Usage:
#       home
#
#     Teleports you to your home location.
#     """
#
#     key = "home"
#     locks = "cmd:perm(home) or perm(Builder)"
#     arg_regex = r"$"
#
#     def func(self):
#         """Implement the command"""
#         caller = self.caller
#         home = caller.home
#         if not home:
#             caller.msg("You have no home!")
#         elif home == caller.location:
#             caller.msg("You are already home!")
#         else:
#             caller.msg("There's no place like home ...")
#             caller.move_to(home)


class CmdLook(COMMAND_DEFAULT_CLASS):
    """
    look at location or object

    Usage:
      look
      look <obj>
      look *<account>

    Observes your location or objects in your vicinity.
    """

    key = "look"
    aliases = ["l", "ls", "examine", "look at", "talk to", "read", "look at the", "examine the", "read the"]
    locks = "cmd:all()"
    arg_regex = r"\s|$"

    def func(self):
        """
        Handle the looking.
        """
        caller = self.caller
        if not self.args:
            target = caller.location
            if not target:
                caller.msg("You have no location to look at!")
                return
        else:
            target = caller.search(self.args)
            if not target:
                return
        self.msg((caller.at_look(target), {"type": "look"}), options=None)

        if caller.__class__.__name__ == 'Character':
            caller.track_view(target)


class CmdMove(COMMAND_DEFAULT_CLASS):
    """
    move to an adjacent location via exit

    Usage:
      move <room>
      <room>
    """

    key = "move"
    aliases = ["m", "go", "go to"]
    locks = "cmd:all()"
    arg_regex = r"\s|$"

    def func(self):
        """
        Handle the looking.
        """
        caller = self.caller
        if not self.args:
            caller.msg("No location specified to move to!")
        else:
            caller.execute_cmd(self.args)


# class CmdNick(COMMAND_DEFAULT_CLASS):
#     """
#     define a personal alias/nick by defining a string to
#     match and replace it with another on the fly
#
#     Usage:
#       nick[/switches] <string> [= [replacement_string]]
#       nick[/switches] <template> = <replacement_template>
#       nick/delete <string> or number
#       nicks
#
#     Switches:
#       inputline - replace on the inputline (default)
#       object    - replace on object-lookup
#       account   - replace on account-lookup
#       list      - show all defined aliases (also "nicks" works)
#       delete    - remove nick by index in /list
#       clearall  - clear all nicks
#
#     Examples:
#       nick hi = say Hello, I'm Sarah!
#       nick/object tom = the tall man
#       nick build $1 $2 = create/drop $1;$2
#       nick tell $1 $2=page $1=$2
#       nick tm?$1=page tallman=$1
#       nick tm\=$1=page tallman=$1
#
#     A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
#     Put the last $-marker without an ending space to catch all remaining text. You
#     can also use unix-glob matching for the left-hand side <string>:
#
#         * - matches everything
#         ? - matches 0 or 1 single characters
#         [abcd] - matches these chars in any order
#         [!abcd] - matches everything not among these chars
#         \= - escape literal '=' you want in your <string>
#
#     Note that no objects are actually renamed or changed by this command - your nicks
#     are only available to you. If you want to permanently add keywords to an object
#     for everyone to use, you need build privileges and the alias command.
#
#     """
#
#     key = "nick"
#     switch_options = ("inputline", "object", "account", "list", "delete", "clearall")
#     aliases = ["nickname", "nicks"]
#     locks = "cmd:all()"
#
#     def parse(self):
#         """
#         Support escaping of = with \=
#         """
#         super(CmdNick, self).parse()
#         args = (self.lhs or "") + (" = %s" % self.rhs if self.rhs else "")
#         parts = re.split(r"(?<!\\)=", args, 1)
#         self.rhs = None
#         if len(parts) < 2:
#             self.lhs = parts[0].strip()
#         else:
#             self.lhs, self.rhs = [part.strip() for part in parts]
#         self.lhs = self.lhs.replace("\=", "=")
#
#     def func(self):
#         """Create the nickname"""
#
#         def _cy(string):
#             "add color to the special markers"
#             return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string)
#
#         caller = self.caller
#         switches = self.switches
#         nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")]
#         specified_nicktype = bool(nicktypes)
#         nicktypes = nicktypes if specified_nicktype else ["inputline"]
#
#         nicklist = (
#             utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or [])
#             + utils.make_iter(caller.nicks.get(category="object", return_obj=True) or [])
#             + utils.make_iter(caller.nicks.get(category="account", return_obj=True) or [])
#         )
#
#         if "list" in switches or self.cmdstring in ("nicks",):
#
#             if not nicklist:
#                 string = "|wNo nicks defined.|n"
#             else:
#                 table = self.styled_table("#", "Type", "Nick match", "Replacement")
#                 for inum, nickobj in enumerate(nicklist):
#                     _, _, nickvalue, replacement = nickobj.value
#                     table.add_row(
#                         str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement)
#                     )
#                 string = "|wDefined Nicks:|n\n%s" % table
#             caller.msg(string)
#             return
#
#         if "clearall" in switches:
#             caller.nicks.clear()
#             caller.account.nicks.clear()
#             caller.msg("Cleared all nicks.")
#             return
#
#         if "delete" in switches or "del" in switches:
#             if not self.args or not self.lhs:
#                 caller.msg("usage nick/delete <nick> or <#num> ('nicks' for list)")
#                 return
#             # see if a number was given
#             arg = self.args.lstrip("#")
#             oldnicks = []
#             if arg.isdigit():
#                 # we are given a index in nicklist
#                 delindex = int(arg)
#                 if 0 < delindex <= len(nicklist):
#                     oldnicks.append(nicklist[delindex - 1])
#                 else:
#                     caller.msg("Not a valid nick index. See 'nicks' for a list.")
#                     return
#             else:
#                 if not specified_nicktype:
#                     nicktypes = ("object", "account", "inputline")
#                 for nicktype in nicktypes:
#                     oldnicks.append(caller.nicks.get(arg, category=nicktype, return_obj=True))
#
#             oldnicks = [oldnick for oldnick in oldnicks if oldnick]
#             if oldnicks:
#                 for oldnick in oldnicks:
#                     nicktype = oldnick.category
#                     nicktypestr = "%s-nick" % nicktype.capitalize()
#                     _, _, old_nickstring, old_replstring = oldnick.value
#                     caller.nicks.remove(old_nickstring, category=nicktype)
#                     caller.msg(
#                         "%s removed: '|w%s|n' -> |w%s|n."
#                         % (nicktypestr, old_nickstring, old_replstring)
#                     )
#             else:
#                 caller.msg("No matching nicks to remove.")
#             return
#
#         if not self.rhs and self.lhs:
#             # check what a nick is set to
#             strings = []
#             if not specified_nicktype:
#                 nicktypes = ("object", "account", "inputline")
#             for nicktype in nicktypes:
#                 nicks = [
#                     nick
#                     for nick in utils.make_iter(
#                         caller.nicks.get(category=nicktype, return_obj=True)
#                     )
#                     if nick
#                 ]
#                 for nick in nicks:
#                     _, _, nick, repl = nick.value
#                     if nick.startswith(self.lhs):
#                         strings.append(
#                             "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
#                         )
#             if strings:
#                 caller.msg("\n".join(strings))
#             else:
#                 caller.msg("No nicks found matching '{}'".format(self.lhs))
#             return
#
#         if not self.rhs and self.lhs:
#             # check what a nick is set to
#             strings = []
#             if not specified_nicktype:
#                 nicktypes = ("object", "account", "inputline")
#             for nicktype in nicktypes:
#                 if nicktype == "account":
#                     obj = account
#                 else:
#                     obj = caller
#                 nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True))
#                 for nick in nicks:
#                     _, _, nick, repl = nick.value
#                     if nick.startswith(self.lhs):
#                         strings.append(
#                             "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
#                         )
#             if strings:
#                 caller.msg("\n".join(strings))
#             else:
#                 caller.msg("No nicks found matching '{}'".format(self.lhs))
#             return
#
#         if not self.rhs and self.lhs:
#             # check what a nick is set to
#             strings = []
#             if not specified_nicktype:
#                 nicktypes = ("object", "account", "inputline")
#             for nicktype in nicktypes:
#                 if nicktype == "account":
#                     obj = account
#                 else:
#                     obj = caller
#                 nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True))
#                 for nick in nicks:
#                     _, _, nick, repl = nick.value
#                     if nick.startswith(self.lhs):
#                         strings.append(
#                             "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
#                         )
#             if strings:
#                 caller.msg("\n".join(strings))
#             else:
#                 caller.msg("No nicks found matching '{}'".format(self.lhs))
#             return
#
#         if not self.args or not self.lhs:
#             caller.msg("Usage: nick[/switches] nickname = [realname]")
#             return
#
#         # setting new nicks
#
#         nickstring = self.lhs
#         replstring = self.rhs
#
#         if replstring == nickstring:
#             caller.msg("No point in setting nick same as the string to replace...")
#             return
#
#         # check so we have a suitable nick type
#         errstring = ""
#         string = ""
#         for nicktype in nicktypes:
#             nicktypestr = "%s-nick" % nicktype.capitalize()
#             old_nickstring = None
#             old_replstring = None
#
#             oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True)
#             if oldnick:
#                 _, _, old_nickstring, old_replstring = oldnick.value
#             if replstring:
#                 # creating new nick
#                 errstring = ""
#                 if oldnick:
#                     if replstring == old_replstring:
#                         string += "\nIdentical %s already set." % nicktypestr.lower()
#                     else:
#                         string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % (
#                             nicktypestr,
#                             old_nickstring,
#                             replstring,
#                         )
#                 else:
#                     string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (
#                         nicktypestr,
#                         nickstring,
#                         replstring,
#                     )
#                 try:
#                     caller.nicks.add(nickstring, replstring, category=nicktype)
#                 except NickTemplateInvalid:
#                     caller.msg(
#                         "You must use the same $-markers both in the nick and in the replacement."
#                     )
#                     return
#             elif old_nickstring and old_replstring:
#                 # just looking at the nick
#                 string += "\n%s '|w%s|n' maps to '|w%s|n'." % (
#                     nicktypestr,
#                     old_nickstring,
#                     old_replstring,
#                 )
#                 errstring = ""
#         string = errstring if errstring else string
#         caller.msg(_cy(string))


class CmdInventory(COMMAND_DEFAULT_CLASS):
    """
    view inventory

    Usage:
      inventory
      inv

    Shows your inventory.
    """

    key = "inventory"
    aliases = ["inv", "i"]
    locks = "cmd:all()"
    arg_regex = r"$"

    def func(self):
        """check inventory"""
        items = self.caller.contents
        if not items:
            string = "You are not carrying anything."
        else:
            table = self.styled_table(border="header")
            for item in items:
                table.add_row("|C%s|n" % item.name, item.db.desc or "")
            string = "|wYou are carrying:\n%s" % table
        self.caller.msg(string)


class CmdGet(COMMAND_DEFAULT_CLASS):
    """
    pick up something

    Usage:
      get <obj>

    Picks up an object from your location and puts it in
    your inventory.
    """

    key = "get"
    aliases = "grab"
    locks = "cmd:all()"
    arg_regex = r"\s|$"

    def func(self):
        """implements the command."""

        caller = self.caller

        if not self.args:
            caller.msg("Get what?")
            return
        obj = caller.search(self.args, location=caller.location)
        if not obj:
            return
        if caller == obj:
            caller.msg("You can't get yourself.")
            return
        if not obj.access(caller, "get"):
            if obj.db.get_err_msg:
                caller.msg(obj.db.get_err_msg)
            else:
                caller.msg("You can't get that.")
            return

        # calling at_before_get hook method
        if not obj.at_before_get(caller):
            return

        obj.move_to(caller, quiet=True)
        caller.msg("You pick up %s." % obj.name)
        caller.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller)
        # calling at_get hook method
        obj.at_get(caller)


class CmdDrop(COMMAND_DEFAULT_CLASS):
    """
    drop something

    Usage:
      drop <obj>

    Lets you drop an object from your inventory into the
    location you are currently in.
    """

    key = "drop"
    locks = "cmd:all()"
    arg_regex = r"\s|$"

    def func(self):
        """Implement command"""

        caller = self.caller
        if not self.args:
            caller.msg("Drop what?")
            return

        # Because the DROP command by definition looks for items
        # in inventory, call the search function using location = caller
        obj = caller.search(
            self.args,
            location=caller,
            nofound_string="You aren't carrying %s." % self.args,
            multimatch_string="You carry more than one %s:" % self.args,
        )
        if not obj:
            return

        # Call the object script's at_before_drop() method.
        if not obj.at_before_drop(caller):
            return

        obj.move_to(caller.location, quiet=True)
        caller.msg("You drop %s." % (obj.name,))
        caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
        # Call the object script's at_drop() method.
        obj.at_drop(caller)


class CmdGive(COMMAND_DEFAULT_CLASS):
    """
    give away something to someone

    Usage:
      give <inventory obj> <to||=> <target>

    Gives an items from your inventory to another character,
    placing it in their inventory.
    """

    key = "give"
    rhs_split = ("=", " to ")  # Prefer = delimiter, but allow " to " usage.
    locks = "cmd:all()"
    arg_regex = r"\s|$"

    def func(self):
        """Implement give"""

        caller = self.caller
        if not self.args or not self.rhs:
            caller.msg("Usage: give <inventory object> = <target>")
            return
        to_give = caller.search(
            self.lhs,
            location=caller,
            nofound_string="You aren't carrying %s." % self.lhs,
            multimatch_string="You carry more than one %s:" % self.lhs,
        )
        target = caller.search(self.rhs)
        if not (to_give and target):
            return
        if target == caller:
            caller.msg("You keep %s to yourself." % to_give.key)
            return
        if not to_give.location == caller:
            caller.msg("You are not holding %s." % to_give.key)
            return

        # calling at_before_give hook method
        if not to_give.at_before_give(caller, target):
            return

        # give object
        caller.msg("You give %s to %s." % (to_give.key, target.key))
        to_give.move_to(target, quiet=True)
        target.msg("%s gives you %s." % (caller.key, to_give.key))
        # Call the object script's at_give() method.
        to_give.at_give(caller, target)


# class CmdSetDesc(COMMAND_DEFAULT_CLASS):
#     """
#     describe yourself
#
#     Usage:
#       setdesc <description>
#
#     Add a description to yourself. This
#     will be visible to people when they
#     look at you.
#     """
#
#     key = "setdesc"
#     locks = "cmd:all()"
#     arg_regex = r"\s|$"
#
#     def func(self):
#         """add the description"""
#
#         if not self.args:
#             self.caller.msg("You must add a description.")
#             return
#
#         self.caller.db.desc = self.args.strip()
#         self.caller.msg("You set your description.")


# class CmdSay(COMMAND_DEFAULT_CLASS):
#     """
#     speak as your character
#
#     Usage:
#       say <message>
#
#     Talk to those in your current location.
#     """
#
#     key = "say"
#     aliases = ['"', "'"]
#     locks = "cmd:all()"
#
#     def func(self):
#         """Run the say command"""
#
#         caller = self.caller
#
#         if not self.args:
#             caller.msg("Say what?")
#             return
#
#         speech = self.args
#
#         # Calling the at_before_say hook on the character
#         speech = caller.at_before_say(speech)
#
#         # If speech is empty, stop here
#         if not speech:
#             return
#
#         # Call the at_after_say hook on the character
#         caller.at_say(speech, msg_self=True)


# class CmdWhisper(COMMAND_DEFAULT_CLASS):
#     """
#     Speak privately as your character to another
#
#     Usage:
#       whisper <character> = <message>
#       whisper <char1>, <char2> = <message>
#
#     Talk privately to one or more characters in your current location, without
#     others in the room being informed.
#     """
#
#     key = "whisper"
#     locks = "cmd:all()"
#
#     def func(self):
#         """Run the whisper command"""
#
#         caller = self.caller
#
#         if not self.lhs or not self.rhs:
#             caller.msg("Usage: whisper <character> = <message>")
#             return
#
#         receivers = [recv.strip() for recv in self.lhs.split(",")]
#
#         receivers = [caller.search(receiver) for receiver in set(receivers)]
#         receivers = [recv for recv in receivers if recv]
#
#         speech = self.rhs
#         # If the speech is empty, abort the command
#         if not speech or not receivers:
#             return
#
#         # Call a hook to change the speech before whispering
#         speech = caller.at_before_say(speech, whisper=True, receivers=receivers)
#
#         # no need for self-message if we are whispering to ourselves (for some reason)
#         msg_self = None if caller in receivers else True
#         caller.at_say(speech, msg_self=msg_self, receivers=receivers, whisper=True)


# class CmdPose(COMMAND_DEFAULT_CLASS):
#     """
#     strike a pose
#
#     Usage:
#       pose <pose text>
#       pose's <pose text>
#
#     Example:
#       pose is standing by the wall, smiling.
#        -> others will see:
#       Tom is standing by the wall, smiling.
#
#     Describe an action being taken. The pose text will
#     automatically begin with your name.
#     """
#
#     key = "pose"
#     aliases = [":", "emote"]
#     locks = "cmd:all()"
#
#     def parse(self):
#         """
#         Custom parse the cases where the emote
#         starts with some special letter, such
#         as 's, at which we don't want to separate
#         the caller's name and the emote with a
#         space.
#         """
#         args = self.args
#         if args and not args[0] in ["'", ",", ":"]:
#             args = " %s" % args.strip()
#         self.args = args
#
#     def func(self):
#         """Hook function"""
#         if not self.args:
#             msg = "What do you want to do?"
#             self.caller.msg(msg)
#         else:
#             msg = "%s%s" % (self.caller.name, self.args)
#             self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller)


# class CmdAccess(COMMAND_DEFAULT_CLASS):
#     """
#     show your current game access
#
#     Usage:
#       access
#
#     This command shows you the permission hierarchy and
#     which permission groups you are a member of.
#     """
#
#     key = "access"
#     aliases = ["groups", "hierarchy"]
#     locks = "cmd:all()"
#     arg_regex = r"$"
#
#     def func(self):
#         """Load the permission groups"""
#
#         caller = self.caller
#         hierarchy_full = settings.PERMISSION_HIERARCHY
#         string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full)
#
#         if self.caller.account.is_superuser:
#             cperms = "<Superuser>"
#             pperms = "<Superuser>"
#         else:
#             cperms = ", ".join(caller.permissions.all())
#             pperms = ", ".join(caller.account.permissions.all())
#
#         string += "\n|wYour access|n:"
#         string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
#         if hasattr(caller, "account"):
#             string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms)
#         caller.msg(string)
