#!/usr/bin/env python

from random import choice
from re import match, findall

from evennia import CmdSet, create_script
from evennia.utils import delay, logger
from evennia.commands.default.muxcommand import MuxCommand
from evennia.contrib.rpg.rpsystem import send_emote
from evennia.prototypes.spawner import spawn
from evennia.utils.utils import int2str

from .command import Command
from typeclasses.scripts import DonkeyHeadSpell
from typeclasses.drinkables import Cocktail
from utils.word_list import routput, pluralize


class CmdFly(Command):
    """Cast the 'fly' spell.

    Make sure you set the following properties, for instance:

    @set self/disappear_msg = "The wizard disappears in a puff of smoke."
    @set self/reappear_msg = "A plume of <<white ^ light blue ^ gray ^ >>
       smoke appears... ;; When the smoke clears, a
       wizard <<emerges ^ materializes>>."
    @set self/appear_delay = 3

    The last setting is the number of seconds between message segments
    (those are separated by double semicolons).

    """
    key = "fly"
    locks = "cmd:holds()"

    def func(self):
        """
        Call the 'do_fly' method on the caller.
        """
        self.caller.do_fly(self.args.strip())


class CmdMagic(Command):
    """
    Cast a generic 'magic' spell.

    Usage:

      wave [ effect(s) ]

    Where 'effects' is a statement of what happens after the magic erupts.
    Note that you can have multiple effects separated by two semicolons.

    You can tailor the effects to your character with the following:

      - |y$you()|n: Replaced by "you" and your name for others, like "old gnome"
      - |y$your()|n: Replaced by "your" and your possessive name for others, like "old gnome's"
      - |y$conj(verb)|n: Replaced by "verb" for you, and "verbs" for others.
      - |y$pron(you)|n: Replaced by "you" for you, but "he" or "she" for others.
      - |y$pron(your)|n: Replaced by "your" for you, but "his" or "her" for others.

    While flexible, you would use this complex replacement in either
    |gnicks|n or as part of a standard magical prefix, by setting the
    property:

      @set self/magic_msg = "$You() $conj(shake) $pron(your) necklace of bones!"
    """
    key = "magic"
    aliases = ["wave"]
    locks = "cmd:holds()"

    def func(self):
        """
        Call the 'do_magic' method on the caller.
        """
        wizard = self.caller
        msgs = wizard.db.magic_msg
        if msgs:
            msgs = msgs.split(';;')
        else:
            msgs = [
                "$You() $conj(wave) $pron(your) " + self.obj.key + ".",
                "<< Sparks ^ Colored lights ^ Flashes ^ Flashes >> of |yoctarine|n << appear ^ emerge ^ materialize >> as << the ^ >> magic << coalesces into an amorphous show of power ^ blends into swirling patterns ^ weaves together>>."
            ]

        if self.args:
            msgs = msgs + self.args.strip().split(';;')

        wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3)


class CmdMakeItem(Command):
    """
    Create one or more items from thin air.

    Usage:

      make [ number ] name [ description ]

    Where 'effects' is a statement of what happens after the magic erupts.
    Note that you can have multiple effects separated by two semicolons.

    You can tailor the effects to your character with the following:

      - |y$you()|n: Replaced by "you" and your name for others, like "old gnome"
      - |y$your()|n: Replaced by "your" and your possessive name for others, like "old gnome's"
      - |y$conj(verb)|n: Replaced by "verb" for you, and "verbs" for others.
      - |y$pron(you)|n: Replaced by "you" for you, but "he" or "she" for others.
      - |y$pron(your)|n: Replaced by "your" for you, but "his" or "her" for others.

    While flexible, you would use this complex replacement in either
    |gnicks|n or as part of a standard magical prefix, by setting the
    property:

      @set self/magic_msg = "$You() $conj(shake) $pron(your) necklace of bones!"
    """
    key = "make"
    locks = "cmd:holds()"

    def parse(self):
        """
        Allows the following phrases:

        candy
        2 candy Piece of Turkish Delight.
        candy Piece of Turkish Delight.
        "turkish delight" Piece of Turkish Delight.
        4 "turkish delight" Piece of Turkish Delight.
        """
        pattern = r'"(.*?)"|(\S+)'
        matches = findall(pattern, self.args.strip())

        try:
            self.item_number = int(matches[0][0] or matches[0][1])
            self.item_name = matches[1][0] or matches[1][1]
            start = 2
        except ValueError:
            self.item_number = 1
            self.item_name = matches[0][0] or matches[0][1]
            start = 1

        words = [m[0] or m[1] for m in matches[start:]]
        self.item_desc = " ".join(words)

    def func(self):
        """
        Call the 'do_magic' method on the caller.
        """
        wizard = self.caller
        msgs = wizard.db.make_msg or wizard.db.magic_msg
        if msgs:
            msgs = msgs.split(';;')
        else:
            msgs = [
                "$You() $conj(snap) $pron(your) fingers.",
            ]

        if self.item_number == 1:
            if match(r"^[aeiou]", self.item_name):
                name = f"an {self.item_name}"
            else:
                name = f"a {self.item_name}"
        else:
            name = int2str(self.item_number) + " " + \
                pluralize(self.item_name)

        # from evennia.utils.utils import int2str ; print(int2str("2"))
        msgs = msgs + [ name + " appear in $pron(your) hand." ]

        wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3)

        # FIXME So that the cat will eat it and it can't be littered:
        if not self.item_desc or self.item_desc == "":
            self.item_desc = f"Conjured by {wizard.get_name()}."

        for _ in range(self.item_number):
            item = spawn({
                "typeclass": "typeclasses.consumables.Litterable",
                "key": self.item_name,
                "desc": self.item_desc
            })[0]
            item.location = wizard

class CmdSetWand(CmdSet):
    """
    All wizard spells are tied to a 'wand' that might be flavored.
    """
    def at_cmdset_creation(self):
        super().at_cmdset_creation()
        self.add(CmdFly)
        self.add(CmdMagic)
        self.add(CmdMakeItem)


class CmdScry(Command):
    """Cast the 'scry' spell to view a room."""
    key = "scry"
    locks = "cmd:holds()"

    def func(self):
        """
        Call the 'do_show_room' method on the object.
        """
        self.obj.do_show_room(self.caller, self.args.strip())


class CmdSetScry(CmdSet):
    """
    The set containing the 'scry' command.
    """
    def at_cmdset_creation(self):
        super().at_cmdset_creation()
        self.add(CmdScry)


class CmdMakeCocktail(MuxCommand):
    """
    For the 'Bartender' especially.

    Usage:

      shake |wcocktail|n = |wpatron|n

    If patron is not given or not found, the drink will be in your
    inventory, and you can call |ggive|n to pass it along.

    If cocktail name isn't given (or matches anything), a random
    one will be created.
    """
    key = 'shake'
    locks = "cmd:perm(gm) or perm(Admin)"

    def func(self):
        dest = self.caller
        if self.rhs:
            dest = self.caller.search(self.rhs)
        Cocktail.make(dest, self.caller, self.lhs)


class CmdGift(MuxCommand):
    """
    Give a special gift to a character.

    Usage:

      gift <gift> to <char> [ : name : desc ]
    """
    key = "gift"
    locks = "cmd:perm(gm) or perm(Admin)"

    def func(self):
        m = match(r"([A-z]+) *?( to|=)? *(.+)( *: *[A-z]+( *: *[A-z]+)?)?",
                  self.args.strip())
        if m:
            # logger.info(f"Gift: {m.group(1)} to {m.group(3)}")
            self.caller.do_gift(m.group(3), m.group(1), m.group(4), m.group(5))
        else:
            self.caller.msg("Usage: gift <gift> to <char> [ : name : desc ]")


class CmdGM(MuxCommand):
    """
    The gm command allows anything to be emoted into a room.

    Usage:

      gm A bat flies into the room!
      gm/gnome You hear a distant ringing
    """
    key = "gm"
    aliases = ["#"]
    locks = "cmd:perm(gm) or perm(Admin)"

    def func(self):
        from typeclasses.things import Scribe
        send_to = []
        for switch in self.switches:
            o = self.caller.search(switch, global_search=True)
            if o:
                send_to = send_to + [o]

        if not send_to:
            send_to = [self.caller.location]

        me = self.caller

        msg = routput(self.args)
        for o in send_to:
            if o.is_typeclass('typeclasses.rooms.Room'):
                # Send the message to all characters and any recording
                # scribes in attendance:
                chars = o.contents_get(None, 'character') + [o.has(Scribe)]
                send_emote(me, chars, msg, 'say', None)
            elif o.is_typeclass('typeclasses.characters.Character'):
                o.msg(msg)


class CmdSpell(Command):
    """
    Cast one of the few spells we've created that affect others.

    Usage:

      spell donkey on lizardman
    """

    key = "spell"
    aliases = ['cast']
    locks = "cmd:perm(gm) or perm(Admin)"

    def parse(self):
        self.spell = None
        self.target = None

        m = match(r"([^ ]+)( +on +(.+))?", self.args.strip())
        if m:
            self.spell = m.group(1)
            self.target = m.group(3)

    def func(self):
        caster = self.caller
        if not self.spell:
            caster.msg('Usage: cast <spell> [on <target>]')
            return

        char = None
        if self.target:
            char = caster.search(self.target)
            if not char:
                return

        if self.spell == 'donkey' and char:
            create_script(key="donkey_head",
                          typeclass=DonkeyHeadSpell,
                          interval=130,
                          start_delay=True,
                          attributes=[("target", char)])
            caster.msg(f"You cast |wHead of Donkey|n on {char}")
        else:
            caster.msg(f"You fail to cast {self.spell}")

class CmdGMTrigger(Command):
    """The trigger command kicks off a series of named events.

    Usage:

      trigger trigger-name
      trigger :game trigger-name
      trigger/character trigger-name
      trigger/character:game trigger-name

    Where 'game' defaults to the value previously set:

      @set npc/currentgame = "session1"

    Triggers are typically set on the NPC (which would be in the room
    with the PCs) or the room. Using the command:

      @set npc/triggers:session1 = {"darkness": {"desc": "Make the
      room go black", "timer": 1, "events": [ "The room gets dark",
      "And then pitch-black.", ("You can't help it, but scream!", "You
      hear a scream!")]}}

    The 'set' command, as a complicated data structure, should be set
    in a batchcommand.

    """
    key = "trigger"
    aliases = ["trig"]
    locks = "cmd:perm(gm) or perm(Admin)"

    def parse(self):
        m = match(r" *(/[^: ]+)?(:[^ ]+)? *(.*)", self.args)
        if m:
            self.name = m.group(3)
            if m.group(2):
                self.game = m.group(2)[1:]
            else:
                self.game = None
            if m.group(1):
                self.switches = m.group(1)[1:].split(',')
            else:
                self.switches = []
        else:
            self.caller.msg("Usage trigger/dest:game trigger")
            return False

    def func(self):
        npc = self.caller
        self.send_to = []

        for switch in self.switches:
            o = npc.search(switch)
            if o:
                self.send_to = self.send_to + [o]
            else:
                return

        game = self.game or npc.db.currentgame
        if not game:
            npc.msg("Specify the game, or set a default with |g@set self/currentgame = <game>")
            return

        triggers = dict(npc.attributes.get(key='triggers', category=game))

        if not self.name:
            npc.msg("What event do you want to trigger?")
            for name, details in triggers.items():
                npc.msg(f" - {name} : {details['desc']}")
            return

        trigger = triggers[self.name]
        if trigger:
            npc.msg(f"Triggering: |w{trigger['desc']}")
            self.trigger(trigger['events'],
                         trigger.get('timer', 5),
                         self.send_to)
        else:
            npc.msg(f"Didn't find '{self.name}' to trigger.")

    def trigger(self, events, time_delay, dests=[]):
        """
        Given a list of events, send each events at an interval.
        If an event is a tuple instead of a string, the first
        element goes to 'dests' and the second goes to the room
        (based on the caller's location).
        """
        target = None
        if len(self.send_to) > 0:
            target = self.send_to[0]
        else:
            targets = self.caller.characters_here()
            if targets:
                target = choice(targets)

        if target:
            name = target.db._sdesc or target.key
        else:
            name = ''

        for idx, event in enumerate(events):
            if isinstance(event, str):
                if event.startswith("@"):
                    self.caller.execute_cmd(event[1:])
                else:
                    if target:
                        msg = target.gendered_text(routput(event, name))
                    else:
                        msg = routput(event)

                    delay(time_delay * idx,
                          self.caller.location.msg_contents,
                          "\n" + msg)
            else:
                char = target.gendered_text(routput(event[0], name))
                room = target.gendered_text(routput(event[1], name))

                if room:
                    delay(time_delay * idx,
                          self.caller.location.msg_contents,
                          "\n" + routput(room),
                          exclude=dests)
                if char:
                    for dest in dests:
                        delay(time_delay * idx,
                              dest.msg,
                              "\n" + routput(char))
