#!/usr/bin/env python

from os.path import exists
from datetime import datetime
from re import split, match, sub, IGNORECASE
from shutil import copyfile

from evennia import CmdSet
from evennia.utils import logger

from commands.command import Command
from typeclasses.characters import Character
from typeclasses.objects import Listener
from utils.word_list import routput


class Puppet(Character, Listener):
    """
    Special character that if not puppetable, stays put.
    Perhaps responding or logging information to the GM.
    """
    def at_post_puppet(self, **kwargs):
        """
        Called just after puppeting has been completed and all
        Account<->Object links have been established.

        Args:
            **kwargs (dict): Arbitrary, optional arguments for users
                overriding the call (unused by default).
        Notes:

            You can use `self.account` and `self.sessions.get()` to get
            account and sessions at this point; the last entry in the
            list from `self.sessions.get()` is the latest Session
            puppeting this Object.
        """
        self.msg(f"\nYou are puppeting |c{self.key}|n.")
        self.execute_cmd("pose reset")
        self.msg((self.at_look(self.location), {"type": "look"}), options=None)

    def at_pre_unpuppet(self, **kwargs):
        """
        Set our 'sleeping' pose.
        """
        super().at_pre_unpuppet()
        sleep_pose = self.attributes.get("pose_sleep")
        if sleep_pose:
            self.execute_cmd(f"pose {sleep_pose}")

    def at_post_unpuppet(self, account=None, session=None, **kwargs):
        """
        Make the character object remain in the room.

        Args:
            account (DefaultAccount): The account object that just disconnected
                from this object.
            session (Session): Session controlling the connection that
                just disconnected.
        Keyword Args:
            reason (str): If given, adds a reason for the unpuppet. This
                is set when the user is auto-unpuppeted due to being link-dead.
            **kwargs: Arbitrary, optional arguments for users
                overriding the call (unused by default).

        """
        self.msg(f"\nNo longer puppeting |c{self.key}|n.\n")

    def get_display_desc(self, _, **kwargs):
        """
        Return one of two appearances based on if it is being puppeted or not.

        Set either or both:

        @set me/desc_puppeted = "A clown bouncing up and down from a box."
        @set me/desc_uppuppeted = "A colorful box with a closed lid."

        If either is not specified, it uses the standard description.
        """
        if self.tags.get(key='puppeted', category='account'):
            return self.db.desc_puppeted if self.db.desc_puppeted else self.db.desc
        else:
            return self.db.desc_unpuppeted if self.db.desc_unpuppeted else self.db.desc

class CmdShrubSay(Command):
    """Erase and write on the shrub's chalkboard.
    """
    key = "say"
    aliases = ["write"]

    def func(self):
        self.obj.write(self.args.strip())


class CmdSetShrubSay(CmdSet):
    """
    The shrub's version of the 'say' command.
    """
    def at_cmdset_creation(self):
        super().at_cmdset_creation()
        self.add(CmdShrubSay)


class Shrub(Puppet):
    """
    The 'Shrub' has its own way of communicating.
    """
    def at_object_creation(self):
        "Called when a character is first created."
        self.cmdset.add(CmdSetShrubSay, persistent=True)

    def intro(self):
        self.record_msg("After tuning their instruments, a quartet of pixies start their first number. \"Quiet evening, eh Mushy?\" asks the elf bartender to a stout myconid. It makes a vaguely <i>shrugging</i> motion with a pair of pseudopods it uses to manipulate its environment. It knew the question was rhetorical, as everyone knew the portal was opening, and that always brought characters.")
        self.execute_cmd("look")
        self.record_msg("The bartender says, \"Fetch that bowl of candies for bar, please, Mushy.\"")
        self.execute_cmd("recog elf as bartender")
        self.note("Not sure how I landed in this bar, but here I am. Don't get me wrong, I enjoy it. The bartender, a fairly haughty elf originally from the <i>Mud World</i>, always supplies me with a nice glass of water.")
        self.execute_cmd("look elf")
        self.record_msg("The mushroom man returns, balancing the bowl on its cap. \"One more thing,\" the bartender says, \"I had a box of oddly shaped, orange crackers. I have a feeling they may come in useful.\"")
        self.note("Helping to keep the place respectable, <i>Mushy</i> also gathers empty cocktail glasses. One of the <i>fungus ones</i>, or mushroom people, it too has always been kind to me.")
        # self.execute_cmd("look mushroom man")
        self.record_msg("\"Any requests?\" asks Lemon of the bartender.")
        self.note("Lemon, Cart, Hairy and Ring, this pixie quartet can't decided on a name for their group; currently calling themselves, the <i>Pixie Stones</i>, provide the entertainment.")
        self.execute_cmd("look pixies")
        self.record_msg("The bartender replies, \"Nah, just remember to play that weird <i>juzz</i> music when the boss arrives later.\"")
        self.note("And here I am, an <i>Awakened Shrub</i>, joting down the events that traspire in this wyld bar somewhere in the <i>Domain of Moss</i>, out of reach from both the Seelie and the Unseelie courts. Of course, that may change.")
        self.note("Now then, let's see what characters may arrive...")
        self.record_msg("<hr/>")

    def note(self, text):
        """
        Record in the transcript log, a special note.
        """
        self.record_msg((text, {'type': 'note'}))

    def write(self, new_text):
        """
        Change the readable message on the chalkboard.

        This will also record the new message in the log.
        """
        here = self.location
        msg = routput("The shrub uses a branch to << brush ^ wipe >> << off ^ >> << the chalk from ^ >> its << small ^ >> chalkboard, and with another branch, << slowly ^ carefully ^ deliberately ^ >> starts to write << something ^ a message ^ >>.")
        here.msg_contents(msg)
        self.db.inside = f"The chalkboard reads: |w{new_text}|n"
        self.record_msg(self.db.inside)

    def find_actor(self, text, default=None):
        """
        Extract the character performing the action from the text.
        """
        if default:
            return default

        m = match(r".*\|b(.*?)\|n.*", text)
        if m:
            return m.group(1)

    def capitalize_msg(self, text, msg_type=None):
        """
        If text is lowercase, capitalize it.

        Maybe prepend a 'The' to the front.
        """
        logger.info(f"capitalize: {text} / {msg_type}")
        if msg_type and msg_type in ('traverse', 'teleport'):
            if match(r"^(\|[A-z])?[aeiou]", text):
                return "An " + text
            else:
                return "A " + text
        # If the line is colored and lowercase, it probably assumes a
        # character's sdesc, so let's add a 'the' to the front:
        # Do we need to only do this on 'say'?
        elif match(r"^\|[mb][a-z]", text):
            return "The " + text
        elif match(r"^(\|[A-z])[a-z]", text) and len(text) > 2:
            return text[0:2] + text[2].upper() + text[3:]
        elif match(r"^[a-z]", text) and len(text) > 2:
            return text[0].upper() + text[1:]
        return text

    def to_html(self, text, msg_type=None):
        """
        Convert the 'text' to an HTML formatted string.
        """
        # Yellow text should be italics:
        text = sub(r"\|y(.*?)\|n", '<i>\\1</i>', text)
        # Bold and tag the titles:
        text = sub(r"\|[cb](.*?)\|n", '<b class="title">\\1</b>', text)
        # Bold anything white:
        text = sub(r"\|w(.*?)\|n", '<b class="heavy">\\1</b>', text)
        # Remove the rest:
        text = sub(r"\|[A-z]", '', text)
        # Remove initial or final carriage returns:
        text = sub(r"^\n", '', text)
        text = sub(r"\n$", '', text)
        # Convert the carriage returns:
        text = sub(r"\n", ' <br/>\n', text)

        if msg_type and msg_type == 'look':
            text = text[0].upper() + text[1:]
            return f"<div class=\"special\"><p>{text}</p></div>\n"
        if msg_type and msg_type == 'note':
            return f"<blockquote>{text}</blockquote>"
        return f"<p>{text}</p>\n"

    def record_msg(self, text, actor=None):
        msg_type = None

        if isinstance(text, tuple):
            if text[1] and isinstance(text[1], dict):
                msg_type = text[1]['type']
            msg = self.capitalize_msg(text[0], msg_type)
        else:
            msg = self.capitalize_msg(str(text))

        actor = self.find_actor(msg, actor)
        msg = self.to_html(msg, msg_type)
        filename = datetime.today().strftime('transcripts/%Y-%m-%d.html')
        if not exists(filename):
            copyfile("transcripts/header-template.html", filename)

        with open(filename, "a") as myfile:
            myfile.write(msg)

        # Follow-up Actions ...
        if match(r".* arrives .*", msg) and actor and \
           msg_type in ('traverse', 'teleport'):
            self.execute_cmd(f"look {actor}")

    def msg(self, text=None, from_obj=None, session=None, **kwargs):
        """
        Record everything that happens in the room for a transcript.

        Some key events may trigger more actions to get more information.
        """
        self.record_msg(text, from_obj)
        super().msg(text, from_obj, session)
