#!/usr/bin/env python

from random import random
from re import match, split, sub, MULTILINE

from evennia.contrib.rpg.rpsystem import send_emote
from evennia.utils import logger, delay
from evennia.utils.verb_conjugation.pronouns import PRONOUN_MAPPING

from commands.command import Command
from utils.word_list import routput, paragraph, choices


ADVERBS = [
    "aloud",
    "abruptly", "actually", "angrily", "animatedly", "bitterly", "boldly", "cheerfully", "clearly",
    "confidently", "continuously", "cruelly", "defiantly", "desperately", "earnestly", "elaborately",
    "emphatically", "excitedly", "frantically", "frequently", "gently", "gravely", "hesitantly",
    "impatiently", "insistently", "intelligently", "jokingly", "joyfully", "loudly", "mockingly",
    "nonchalantly", "passionately", "playfully", "quietly", "reassuringly", "reluctantly", "seriously",
    "sharply", "sharply", "simply", "silently", "sincerely", "softly", "suddenly", "surprisingly",
    "thoughtfully", "unexpectedly", "voraciously", "whimsically", "wistfully",
]


def get_adverb(phrase):
    """Return tuple of initial adverb (or None) and the phrase."""
    m = match(r"(\w+)[, ]+(.*)", phrase)
    if m:
        first_word = m.group(1)
        rest_words = m.group(2)
        if first_word in ADVERBS:
            return first_word, rest_words
    return None, phrase


def get_say_direction(phrase):
    """Return tuple of object of communication and the rest of the phrase."""
    m = match(r"to (\w+)[, ]+(.*)", phrase)
    if m:
        first_word = m.group(1)
        rest_words = m.group(2)
        if first_word.endswith("self"):
            return "self", rest_words
        else:
            return first_word, rest_words
    return None, phrase


def speech_effect(speech, verb, target, effects):
    """
    Return speech after applying 'effects'.

    Return a tuple for what a user thinks he said, and what
    others actually here. If no effect, just return speech twice.

    Effects:
      - mute ... can't talk
      - donkied ... target and others hear the value of effect
      - sloshed ... slurred speech

    To administer:

        @set woman/donkied:effect = "Heehaw! ;; Heehaw, heehaw!"
    """
    for effect in effects:
        if effect:
            if effect.db_key == 'donkied':
                msg = choices(effect.value)
                return (msg, msg, "bray")

    return (speech, speech, verb)


class CmdSay(Command):
    """Say something to the characters in the same area.

    Usage:

      |gsay [[<adverb>] [to <character>],] <message>|n

    This command has a couple optional strings. as you see, the text
    within brackets, [...] are optional, while text in <...> are to be
    replaced, and this technical jargon probably means little, so
    perhaps examples would be better:

      |gsay Good evening.|n
      |wYou say, "Good evening."|n

    Or:

      |gsay loudly, Good evening!|n
      |wYou loudly exclaim, "Good evening!"|n

    In the second example, you gave an |wadverb|n that ends in
    |w-ly|n. Also, the exclamation point changes the 'say' to an
    'exclaim'. A question mark changes 'say' to 'ask'.

    If you start your message with an adverb, but want that adverb
    |win|n the message and not |waffecting|n the message, surround it
    with quotes. For instance:

      |gsay actually, it is pronounced gah-nome.
      |wYou say actually, "it is pronounced gah-nome."
      |gsay "actually, it is pronounced gah-nome."
      |wYou say, "actually, it is pronounced gah-nome."|n

    Of course, adverbs that affect the message are lowercase, and
    messages usually begin with an uppercase, as in:

      |gsay Actually, it is pronounced Gah-nome.
      |wYou say, "Actually, it is pronounced Gah-nome."|n

    Because this command is so common, you can also use ", as in:

      |g"What's going on?|n

    You can also direct your message to a particular person (or
    thing), as in:

      |gsay to heron, How do you do?
      |wYou ask the purple heron, "How do you do?"|n
      "What can I do for you?" asks the purple heron.

    Since you might be having a conversation with a character, you can
    use the |gsayto|n to target the same person. For instance:

      |gsay to pet Who's a widdle cutie?
      |wYou ask wee beastie, "Who's a widdle cutie?"
      |gsayto You are!
      |wYou exclaim to wee beastie, "You are!"|n

    You can replace the 'say' command with any of the following:

      - shout
      - scream
      - respond
      - reply
      - yell
      - ask

    """

    key = "say"
    verb = "say"
    aliases = ["says", "speak", "shout", "yell", "exclaim", "scream",
               "ask", "reply", "respond", '"', "'", "sayto"]
    locks = "cmd:all()"
    # don't require a space after `say/'/"`
    arg_regex = None
    adverb = None
    target = None

    def parse(self):
        """Parse the input into speech parts."""
        self.phrase = self.args.strip()
        self.reverse = False

        m = match(r".+\?.*", self.phrase)
        if m:
            self.verb = "ask"
        m = match(r".+!.*", self.phrase)
        if m:
            self.verb = "exclaim"
        m = match(r'.+\w$', self.phrase)
        if m:
            self.reverse = True

        m = match(r'.*".+".*', self.phrase)
        if not m:
            # No overriding quotes? Process adverbs...
            self.adverb, self.phrase = get_adverb(self.phrase)
            target, self.phrase = get_say_direction(self.phrase)

            if target:
                if target == "self":
                    self.target = self.caller
                else:
                    self.target = self.caller.search(target)
                self.caller.db.say_target = self.target

        # Final quotes can be trimmed:
        self.phrase = self.phrase.strip('"').strip("'")

    def func(self):
        """Implement the new 'say' command with switches."""
        speaker = self.caller
        if not self.phrase or self.phrase == "":
            speaker.msg("Say what?")
            return

        # If speech is empty, stop here
        if not speaker.at_pre_say(self.phrase):
            return

        if self.cmdstring == 'sayto':
            self.target = self.caller.db.say_target

        if self.cmdstring == 'scream':
            self.verb = 'scream'
        elif self.cmdstring == 'shout':
            self.verb = 'shout'
        elif self.cmdstring == 'respond':
            self.verb = 'respond'
        elif self.cmdstring == 'reply':
            self.verb = 'reply'
        elif self.cmdstring == 'yell':
            self.verb = 'yell'
        elif self.cmdstring == 'ask':
            self.verb = 'ask'

        for_me, for_others, verb = \
            speech_effect(self.phrase, self.verb, speaker,
                          speaker.attributes.get(category="effect",
                                                 return_obj=True,
                                                 return_list=True))
        self.to_puppets(speaker, for_others, self.target)
        self.to_you(speaker, verb, for_me)
        self.to_others(speaker, verb, for_others)

    def to_you(self, speaker, verb, phrase):
        """Send 'say' message to the speaker.

        The `send_emote` is _global_, so if we want to say to 'Bob',
        'You say ...', we have to both send him a message with the
        'You' as well as everyone else with the 'send_emote'.
        """
        to_who = ""
        if self.target:
            if self.target == self.caller:
                who = "yourself"
            else:
                who = self.target.get_display_name(speaker)

            if verb == "ask":
                to_who = " " + who
            else:
                to_who = " to " + who

        verb = " " + verb
        adverb = " " + self.adverb if self.adverb else ""

        if self.reverse:
            if adverb.endswith("ly") and random() < 0.5:
                full_speech = f"\"{phrase},\" you{adverb}{verb}{to_who}."
            else:
                full_speech = f"\"{phrase},\" you{verb}{adverb}{to_who}."
        else:
            if adverb.endswith("ly") and random() < 0.5:
                full_speech = f"You{adverb}{verb}{to_who}, \"{phrase}\""
            else:
                full_speech = f"You{verb}{adverb}{to_who}, \"{phrase}\""
        speaker.msg(full_speech, from_obj=speaker)

    def to_others(self, speaker, verb, phrase):
        """Use the 'send_emote' to have speaker 'say' something to others.

        We use /me to be replaced by the user's appearance to others.
        We also need to deal with weird English usage.
        """
        to_whom = ""
        if self.target:
            if self.target == self.caller:
                whom = PRONOUN_MAPPING['3rd person']['reflexive pronoun'][self.caller.db.gender]
            else:
                who = self.target.get_display_name(speaker)
                if hasattr(self.target, 'sdesc') and self.target.sdesc.get():

                    whom = f"the /{self.target.sdesc.get()}"
                else:
                    whom = f"the /{self.target.name}"

            if verb == "ask":
                to_whom = " " + whom
            else:
                to_whom = " to " + whom

        # english is weird...
        verb = ' replie' if verb == 'reply' else " " + verb
        adverb = " " + self.adverb if self.adverb else ""
        targets = [item for item in speaker.location.contents
                   if item != speaker]

        if self.reverse:
            if adverb.endswith("ly") and random() < 0.5:
                full_speech = f"\"{phrase},\" /me{adverb}{verb}s{to_whom}."
            else:
                full_speech = f"\"{phrase},\" /me{verb}s{adverb}{to_whom}."
        else:
            if adverb.endswith("ly") and random() < 0.5:
                full_speech = f"/me{adverb}{verb}s{to_whom}, \"{phrase}\""
            else:
                full_speech = f"/me{verb}s{adverb}{to_whom}, \"{phrase}\""
        logger.info(f"Full speech: {full_speech}")
        # Full speech: /me asks /Trampoli, "How are you?"

        speaker.location.store_event(full_speech, speaker)
        send_emote(speaker, targets, full_speech, msg_type="say",
                   anonymous_add=None)

    def to_puppets(self, speaker, phrase, target=None):
        """Send phrase to puppet, target.

        If target is None, then send to all puppets.
        """
        # Speak to everyone in the room:
        if not target:
            # Not sure how this is possible:
            if not speaker or not speaker.location or not speaker.location.contents:
                return

            for char in speaker.location.contents:
                if hasattr(char, 'other_say') and callable(char.other_say):
                    logger.info(f"to_puppets: all: {char}")
                    delay(1, char.other_say, speaker, phrase)
        else:
            if hasattr(target, 'other_sayto') and callable(target.other_sayto):
                logger.info(f"Found {target.key}: {phrase}")
                delay(1, target.other_sayto, speaker, phrase)
