#!/usr/bin/env python

from collections import defaultdict
from random import choice, randint, random

from evennia import CmdSet
from evennia.commands.default.muxcommand import MuxCommand
from evennia.prototypes.spawner import spawn
from evennia.utils import delay, iter_to_str, logger

from commands.command import Command
from typeclasses.objects import Object
from typeclasses.scripts import Script, Spell
from typeclasses.drinkables import Container
from utils.scoring import Scores
from utils.word_list import choices


class CmdEmpty(Command):
    """
    Empty the cauldron.

    Usage:

      empty [ cauldron ]

    Empty the cauldron before you start working on making a potion.
    This destroys either the ingredients or the potion that it contains.

    Next, you can start |gadd|ning the potion's components.
    """
    key = "empty"

    def func(self):
        self.obj.do_empty(self.caller)


class CmdAdd(Command):
    """
    Add an item from your inventory to the cauldron.

    Usage:

      add <item> [ to cauldron ]

    The item can be a single item you picked up on your travels, like
    a bunch of tickleweed, or can be the contents of a container, like
    a bottle or teacup.

    Keep in mind that potions may require |wenough|n of an item.
    For instance, a bottle can contain more liquid than a teacup.
    (You can get an empty bottle from the shelf.)

    Once all ingredients have been added, you can |gcreate|n a potion.
    """
    priority = 2
    key = "add"
    aliases = "give"

    def func(self):
        maker = self.caller
        item_str = self.args.strip().split(" to ")
        if len(item_str) == 0:
            maker.msg(f"What do you want to add to the {self.obj.name}?")
            return

        item = maker.search(item_str[0], location=maker)
        if item:
            self.obj.do_add(self.caller, item)


class CmdCreate(Command):
    """
    Create a potion from the contents of the cauldron.

    Usage:

      create [ potion ]

    After you have |gadd|ned all the ingredients for a potion, cast
    this. If you have the correct components, the cauldron will
    contain a potion ready for you to |gbottle|n.
    """
    key = "create"
    aliases = ["make", "brew", "cook"]

    def func(self):
        self.obj.do_create(self.caller)


class CmdBottle(Command):
    """Bottle the contents of the cauldron.

    Usage:

      bottle [ potion ]

    After you have |gcreate|nd a potion, use this command to put it
    into a vial you can consume later (or give to a friend).

    If you |gdrop|n the vial, you can always get another potion from
    the cauldron (at least, until some one calls |gempty|n).
    """
    key = "bottle"
    aliases = ["fill", "get vial"]

    def func(self):
        self.obj.do_bottle(self.caller)


class CmdSetCauldron(CmdSet):
    """
    The commands available to people next to a cauldron.
    """
    def at_cmdset_creation(self):
        self.add(CmdEmpty)
        self.add(CmdAdd)
        self.add(CmdCreate)
        self.add(CmdBottle)


class Cauldron(Object):
    """
    A cauldron where we can collect and mix ingredients.

      - empty
      - add
      - mix
      - bottle
    """
    def at_object_creation(self):
        """
        Associate the commands with this instance.
        """
        self.cmdset.add_default(CmdSetCauldron)

    def return_appearance(self, looker):
        """
        Return the full description and contents of this object.

        Along with a description of this object, we want to return the
        ingredients and items in this object. For that, we duplicate a
        lot of already implemented code.
        """
        full_desc = self.db.desc
        num_items = len(self.contents)
        if num_items == 0:
            return full_desc + "|/It is empty, and ready for you to |gadd|n ingredients."

        # Had to copy the following code from the objects.objects'
        # get_display_things so that I could reformat it:
        grouped_things = defaultdict(list)
        for thing in self.contents:
            grouped_things[thing.get_display_name(looker)].append(thing)

        thing_names = []
        for thingname, thinglist in sorted(grouped_things.items()):
            nthings = len(thinglist)
            thing = thinglist[0]
            singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
            thing_names.append(singular if nthings == 1 else plural)
        thing_names = iter_to_str(thing_names)

        return full_desc + \
            f"|/It contains {thing_names}." if thing_names else ""

    def do_empty(self, maker=None):
        """
        Delete the contents of the cauldron.

        Also randomly return a funny emptying message.
        """
        if self.contents == []:
            if maker:
                maker.msg(f"The {self.name} is already empty.")
            return

        msg = choice([
            f"The imp <<climbs ^ flies >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew, then <<downs ^ swallows ^ drinks >> it, emptying the cauldron. Lethargically, it << hoists itself ^ climbs back >> to its branch.",
            "The imp produces a long metal straw and slurps up the brew from the cauldron.",
            "The imp pushes down a lever, flushing the contents of cauldron.",
            "The imp << stokes the ^ breathes >> fire, quickly boiling the brew until it evaporates. It licks clean the remaining sludge, emptying the cauldron."
        ])
        if maker:
            maker.announce_action(msg)
        for item in self.contents:
            item.delete()

    def do_add_liquid(self, maker, obj):
        """
        Like do_add, but doesn't delete the container.

        Instead this creates a new "object" based on the contents of
        the container. Makes it safe to delete.
        """
        name, amount, desc = obj.do_empty()
        if name:
            for cup in range(int(amount / 4)):
                liquid = spawn({
                    "typeclass": "typeclasses.objects.Object",
                    "key": f"cup of {name}"
                })[0]
                liquid.location = self

            maker.announce_action(f"$You() $conj(pour) the {name} from $pron(your) {obj.name} into the {self.name}.")
        else:
            maker.msg(f"The {obj.name} is empty. You can |gfill|n it first.")

    def do_add(self, maker, item):
        """
        Moves an item to the cauldron.

        If the item is a container, we call do_add_liquid to add its contents.
        We limit this to Herbs and other consumables.
        """
        if self.has_potion():
            maker.msg("The cauldron already contains a potion. You need to |gempty|n it first.")
            return

        if item.has_method("do_empty"):
            self.do_add_liquid(maker, item)
        elif item.is_typeclass("typeclasses.consumables.Consumable") or \
             item.is_typeclass("typeclasses.consumables.Herb"):
            item.move_to(self, quiet=True)
            maker.announce_action(f"$You() $conj(add) {item.name} to {self.name}.")
        else:
            maker.msg("Adding that to a cauldron for brewing potions doesn't make sense.")

    def do_create(self, maker):
        """
        Does this make a viable concoction?
        """
        good = choice([
            "The imp <<climbs ^ flies ^ jumps >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew. Before returning to its roost, it << gives $you() a tiny thumbs up ^ nods ^ smiles ^ nods >>.",
        ])
        bad = choice([
            "The imp <<climbs ^ flies ^ jumps >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew. Before returning to its roost, it << shakes its head ^ grimaces ^ retches a bit >>.",
            "As soon as $you() $conj(grab) a wooden spoon to stir, the <<brew ^ concoction>> in the cauldron farts a black cloud. This must not be right."
        ])

        if self.contents == []:
            maker.msg(f"The {self.name} is empty. First, |gadd|n ingredients.")
            return

        for seq, brew_func in [
                self.can_create_laughter(),
                self.can_create_drugtrip()
        ]:
            if seq:
                maker.score(Scores.make_potion)
                maker.announce_action(good)
                for idx, msg in enumerate(seq):
                    delay(idx * 3 + 2, maker.announce_action, msg)
                self.do_empty()
                return brew_func()

        maker.announce_action(bad)

    def has_potion(self):
        """
        Return reference to the potion in the cauldron.
        None otherwise.
        """
        if len(self.contents) == 1 and self.has(Potion):
            return self.contents[0]

    def do_bottle(self, maker):
        """
        If the contents are viable, let's create a vial.
        """
        potion = self.has_potion()
        if potion:
            self.do_make(maker, potion)
        else:
            maker.msg("The cauldron doesn't have a potion to bottle. Perhaps, you need to |gcreate|n one first?")

    def do_make(self, maker, potion):
        vial = spawn({
            "typeclass": "typeclasses.alchemy.Vial",
            "key": potion.name,
            "aliases": ["potion", "vial"],
            "desc": potion.db.desc
        })[0]
        vial.db.amount = 1
        vial.db.spell = potion.db.spell
        vial.location = maker
        maker.announce_action(f"$You() $conj(fill) a small vial with $pron(your) << stew ^ elixir ^ potion ^ concoction >>.")

    # ----------------------------------------------------------------------
    #  Potions:

    def can_create_laughter(self):
        """
        Return true if the cauldron can make a laughter potion.
        """
        mushrooms = self.search("gigglecap mushroom", location=self, quiet=True)
        weeds = self.search("tickleweed", location=self, quiet=True)
        water = self.search("fizzy water", location=self, quiet=True)

        if len(mushrooms) > 0 and len(weeds) > 0 and len(water) > 1:
            return ([
                "$You() $conj(stir) the cauldron with a wooden spoon, watching the liquid change to a deep purple, releasing a fragrant aroma.",
                "The imp climbs down from its perch and mutters, \"Risus ignis, laetitiae flamma, in corde nostro, gaudium humourous.\"",
                "Sparks of vibrant octarine pop over the elixir, making it ready to |gbottle|n."
            ], self.create_laughter)
        else:
            return (None, None)

    def create_laughter(self):
        """
        Spawn a Potion with the spell function for the cauldron's contents.
        """
        potion = spawn({
            "typeclass": "typeclasses.alchemy.Potion",
            "key": "potion",
            "desc": "Small glass vial containing a purple liquid, labeled: |mElixir Risorium|n"
        })[0]
        potion.location = self
        potion.db.spell = LaughterSpell

    def can_create_drugtrip(self):
        """
        Return true if the cauldron can make the drug trip potion.
        """
        mushrooms = self.search("dreamshade mushroom", location=self, quiet=True)
        berries = self.search("moonberries", location=self, quiet=True)
        dust = self.search("pixie dust", location=self, quiet=True)
        water = self.search("still water", location=self, quiet=True)

        if len(mushrooms) > 0 and len(berries) > 0 and len(dust) > 0 and len(water) > 1:
            return ([
                "$You() $conj(stir) the cauldron with a wooden spoon, watching the liquid change to a deep purple, releasing a fragrant aroma.",
                "The imp climbs down from its perch and mutters, \"Risus ignis, laetitiae flamma, in corde nostro, gaudium humourous.\"",
                "Sparks of vibrant octarine pop over the elixir, making it ready to |gbottle|n."
            ], self.create_drugtrip)
        else:
            return (None, None)

    def create_drugtrip(self):
        """
        Spawn a Potion with the spell function for the cauldron's contents.
        """
        potion = spawn({
            "typeclass": "typeclasses.alchemy.Potion",
            "key": "potion",
            "desc": "Small glass vial containing a glowing blue liquid, labeled: |mSomnium Illusorium|n"
        })[0]
        potion.location = self
        potion.db.spell = DrugtripSpell

class Vial(Container):
    """
    A vial with a single quaff, but cast a spell.
    """
    fill_amount = 1

    def at_object_creation(self):
        """
        Set up the database.
        """
        self.db.amount = 0
        self.db.empty_name = "empty vial"
        self.db.empty_desc = "Small crystal vial. May have contained a potion."

    def do_drink(self, drinker):
        """
        Called when owner calls the 'drink' command.
        """
        if not self.do_empty():
            drinker.msg("The vial is empty.")
        else:
            drinker.announce_action("$You() $conj(<< down ^ quaff ^ imbibe >>) a small vial.")
            self.do_empty()
            drinker.scripts.add(self.db.spell, autostart=True)


class Potion(Object):
    """
    A marker-type object for a potion in the cauldron.
    """
    pass


# ----------------------------------------------------------------------
#   Scripts that emulate the effects of Potions
# ----------------------------------------------------------------------

class LaughterSpell(Spell):
    """
    This class defines the script itself
    """

    def at_script_creation(self):
        self.key = "laughter-spell"
        self.desc = "Adds various timed events to a character."
        self.interval = 20  # seconds
        self.repeats = 15  # repeat only a certain number of times
        self.start_delay = True  # wait self.interval until first call

    def at_stop(self, **kwargs):
        self.delete()

    def at_repeat(self, **kwargs):
        """
        This gets called every self.interval seconds. We make
        a random check here so as to only return 33% of the time.
        """
        if random() < 0.66:
            # no message this time
            return
        self.send_random_message()

    def send_random_message(self):
        if not self.obj:
            return

        self.obj.announce_action(
            choice([
            "$You() $conj(erupt) into laughter, a deep, rolling sound that fills the room and makes everyone turn to see what’s so funny.",
            "$You() $conj(let) out a series of high-pitched giggles, each one bubbling up like a fizzy drink, light and infectious.",
            "$You() $conj(burst) into cackling laughter, the sharp, gleeful sound that echo off the walls.",
            "$You() $conj(find) yourself wheezing with laughter, gasping for air as the hilarity overwhelms you, tears streaming down your cheeks.",
            "$You() $conj(break) into a fit of snorting giggles; $pron(you) $conj(try) to suppress them to no avail.",
            "$You() $conj(let) out a tinkling laugh, a melodic sound that dances through the air, bringing a sense of whimsy to the moment.",
            "$You() $conj(roar) with laughter, a booming sound that resonates with joy.",
            "$You() $conj(chuckle) softly, a warm and genuine sound that reflects $pron(your) delight.",
            "$You() $conj(giggle) uncontrollably, making it hard for $pron(you) to catch $pron(your) breath.",
            "$You() $conj(let) a stifled chuckle, unsure what became so humorous.",
        ]))

class DrugtripSpell(Spell):
    """
    This class defines the script itself
    """
    def at_script_creation(self):
        self.key = "drug-trip-spell"
        self.desc = "Adds various timed events to a character."
        self.interval = 120  # seconds
        self.repeats = 4  # repeat only a certain number of times
        self.start_delay = True  # wait self.interval until first call

    def at_stop(self):
        self.delete()

    def at_repeat(self, **kwargs):
        """
        This gets called every self.interval seconds. We make
        a random check here so as to only return 33% of the time.
        """
        if random() < 0.56:
            self.send_random_message()

    def send_random_message(self):
        """
        Send a random message to the 'victim' of this spell,
        and another message to the other people in the room.
        """
        you_msg, other_msg = choice([
            (
                """You catch a glimpse of a monstrous creature hovering ominously into view, its spherical body covered in a mottled, iridescent skin that shifts colors like a living kaleidoscope! Its eyestalks, each ending in a glimmering orb, swivel independently, scan the surroundings with a predatory gaze, while its central eyes stares at you... ;;
It unleashes its disintegration ray, a brilliant beam of energy erupting from one of its eyes, slicing through the air with a crackling intensity that seems to warp reality itself, leaving a trail of shimmering particles in its wake! ;;
The ray engulfs you in a radiant light, dissolving the boundaries of individuality and merging your essence with the cosmos, revealing a profound interconnectedness that transcends existence itself.
""",
                """$You() suddenly $conj(crouch) down, staring wide-eyed. ;; $You() screams and falls to the ground! ;; Pitiful whimpering sounds $conj(escape) from $you(), as $pron(you) $conj(lay) in a fetal ball."""
            ),
            ("""
            The suddenly find the air thick with the scent of blooming flowers, as a soft, melodic whisper beckons you. You want to follow, but not sure where the whispers come from... ;;

You see a luminescent creature flit by...a butterfly with wings like stained glass!

The colorful butterfly lands near your feet and grows as large as you. It then removes its mask revealing the face of an old man. \"Vezof is jēda, se vestri est vestri,\" it says before flying off.
            """,
             """$You() $conj(spin) around in circles as if looking for something. ;;
             Following an invisible trail through the air, $you() $conj(stare) wide eye.;;
             The look of confusion and horror on $pron(your) face, as $you() stare straight ahead...unblinking.
             """),
            ("""You hear a languid, sleepy voice say, \"Skoros nūm?\" Turning around, you see a large caterpillar smoking a hookah on a mushroom cap. ;;
The caterpillar shakes its head and continues smoking, blowing large smoke rings into the air. ;;
The caterpillar yawns, and then asks, \"Vezof?\" ;;
As the caterpillar crawls off, it says, \"Er haer indóme care-tye alta. Bo haer indóme care-tye ince.\"
            """,
            """$You() quickly $conj(turn) around to stare at empty space. ;; Trying to catch invisible eddies in the air, $you() $conj(jump) around. ;; $You() $conj(stretch) and $conj(yawn). ;; $You() $conj(say) to nothing in particular, "Er haer indóme care-tye alta. Bo haer indóme care-tye ince." """
            ),
            ("You feel your bones turn to hollow reeds. A warm breeze blows through your ribs, playing a hauntingly beautiful flute melody that tastes like honey.",
             "You see the $you() stand perfectly still, emitting a loud, melodic whistling sound from $pron(your,sp) nostrils that may attract a confused songbird."),

            ("You are standing on the ceiling of the sky. The fluffy clouds are surprisingly firm, like soft islands, you must hop across to avoid falling. \"Be quiet,\" you whisper to yourself, \"Don't wake the cloud people.\"",
             "You see the $you() frantically \"climbing\" on everything around...though $pron(you,sp) is doing it upside down and backward with terrifying, twitchy agility. \"Be quiet,\" $pron(you,sp) says, \"Don't wake the cloud people.\""),

            ("Your shadow stretches and detaches itself. It seems to stare at you with its hands on its hips. ;; It grows a mouth, and begins whispering to you, \"The trees once had names, but they've forgotten them. Now they desperately want to steal your name, so never use your real name in the woods.\"",
             "You see the $you() staring intently at the floor...$pron(your,sp) mouth agape. ;; You see the $you() engaged in a heated, whispered argument with the ground at $pron(your,sp) feet, eventually pointing a finger and ordering $pron(your,sp) own shadow to \"stay.\""),

            ("You feel yourself stretch and grow, breaking out of the area you knew to reach for the sky. You are a gargantuan giant! Every step feels like it should crush a mountain; you move slowly to avoid destroying the trees.",
             "You see the $you() moving in extreme slow-motion, lifting $pron(your,sp) feet two feet high for every step and looking down at something on the ground with intense pity."),

            ("With loud squawk, you see your hands turn into a pair of bickering, multicolored pheasants! They painfully start to fly away in opposite directions.",
             "You see the $you() shove $pron(your) hands deep into $pron(your) armpits, hunched over and making muffled, frantic clucking noises while $pron(your) elbows flap wildly."),

            ("Thousands of tiny, glowing yellow spiders start weaving a suit of |wGlimmer-Silk|n onto your body. You feel the itch of a thousand needles. Ah, but this silk suit will be wonderful when they are done.",
             "You see the $you() begin frantically stripping off $pron(your,sp) clothes, shouting that $pron(your,sp) clothes are \"smothering the velvet.\""),

            ("The ground turns into a liquid mosaic of memories. Walking feels like treading water in a pool of your own childhood dreams.",
             "You see the $you() drop and begin \"swimming\" across the floor, performing a remarkably proficient breaststroke."),

            ("Your mind is clear, as you realize you've been playing a character in some play this whole time...wearing green tights and a tunic. You also know the |waudience|n has been watching you from behind the trees, and you feel the need to give them a performance of a lifetime. ",
             "You see the $you() break into a booming, theatrical monologue, \"If we shadows have offended, think but this, and all is mended! That you have but slumbered here while these visions did appear. And this weak and idle theme, no more yielding but a dream! Gentles, do not reprehend: if you pardon, we will mend!\" $pron(You,sp) then takes a big bow."),
        ])
        you_msg += ";;" + choice([
            "Your << world ^ reality ^ clarity >> returns, and you feel << back to ^ >> normal.",
            "You << snap back ^ re-fade back >> to your << old ^ original >> << self ^ mind >>."
        ])
        other_msg += ";; $You() << $conj(blink) ^ $conj(snap) back ^ $conj(come) to >>."

        self.obj.priv_sequence(you_msg, 15)
        self.obj.spell_sequence(None, other_msg, 15, self.obj)
