"""
Room

Rooms are simple containers that has no location of their own.

"""

from collections import defaultdict
from datetime import datetime
from re import match

from django.utils.translation import gettext as _

from evennia import utils
from evennia.utils import gametime, logger, delay
from evennia.utils.utils import iter_to_str
from evennia.contrib.grid.extended_room import ExtendedRoom
from evennia.contrib.rpg.rpsystem import ContribRPRoom

from django.conf import settings

from typeclasses.pets import Hunger
from typeclasses.objects import ObjectParent, Listener
from utils.word_list import fix_msg

_SEARCH_AT_RESULT = utils.object_from_module(settings.SEARCH_AT_RESULT)


def articlize_character(character, capitalize=False):
    """
    Return a simple character with article prefix.
    """
    if capitalize:
        article = "A"
    else:
        article = "a"

    # Trim off any trailing periods that show up here.
    if match(r".*\.$", character):
        character = character[:-1]

    # logger.info(f"rooms: articlize_character({character})")
    if character == "" or match(r"^\|[A-z][A-Z]", character):
        return character
    elif match(r"^\|[A-z][aeiou]", character):
        return f"{article}n " + character
    else:
        return f"{article} " + character


def articlize_characters(characters):
    return ", ".join([articlize_character(c) for c in characters])


def captialize_characters(characters):
    """
    Capitalizes and prefixes an article on string of characters.
    Assuming one per line.
    """
    char_list = characters.split("\n")
    updated = [articlize_character(ch, True) for ch in char_list]
    return " " + ", ".join(updated)


class Room(ObjectParent, ExtendedRoom, ContribRPRoom, Listener):
    """
    Rooms are like any Object, except their location is None
    (which is default). They also use basetype_setup() to
    add locks so they cannot be puppeted or picked up.
    (to change that, use at_object_creation instead)

    See mygame/typeclasses/objects.py for a list of
    properties and methods available on all Objects.
    """
    is_dark = False
    has_weather = False
    # Used to keep track of the most recent events:
    short_history = []
    appearance_template = """

{header}
|c{name}{extra_name_info}|n
{desc}
{exits}
{things}
{characters}
{footer}"""

    def puppets_here(self, func_name=None):
        """
        Return a list of puppets in the current location.
        Only used for calling hooks on the animatronic dolls.
        """
        if func_name:
            return [puppet
                    for puppet in self.contents
                    if hasattr(puppet, func_name) and
                    callable(getattr(puppet, func_name))]

        return [puppet
                for puppet in self.contents
                if puppet.is_typeclass("typeclasses.puppets.Puppet")
                or puppet.is_typeclass("typeclasses.puzzles.StoryCube")
                or puppet.is_typeclass("typeclasses.pets.FriendlyPet")
                ]

    def get_time(self):
        """
        Return tuple of current time of this location.
        (Minute, Hour, Time of Day, Season)
        """
        timestamp = gametime.gametime(absolute=True)
        datestamp = datetime.fromtimestamp(timestamp)
        return (datestamp.minute, datestamp.hour,
                self.get_time_of_day(), self.get_season())

    def get_full_appearance(self, looker, **kwargs):
        """
        Return a string containing the full appearance of the
        room, but also the most recent events that have happened.
        """
        logger.info(f"HISTORY: {self.short_history}")
        return self.return_appearance(looker, **kwargs) + "\n\n" + \
            "\n".join(self.short_history)

    def get_display_header(self, looker, **kwargs):
        """
        Possibly send an image to webclients.
        """
        image = None   # self.db.title_image
        if image:
            timed = self.attributes.get("title_time", None)
            if timed:
                # This makes: mellow-marsh-evening
                image = f"{image}-{self.get_time_of_day()}"

            season = self.attributes.get("title_season", None)
            if season:
                # This makes: frog-meadow-winter
                image = f"{image}-{self.get_season()}"

            level = self.attributes.get("title_level", None)
            if level:
                # This makes: cozy-house-2
                image = f"{image}-{level}"

            looker.msg(image=(
                f"https://www.howardabrams.com/cozy-players-guide/{image}.jpg",
                {"type": "background-pane"}
            ))
        return "|n"

    def get_display_characters(self, looker, *args, **kwargs):
        chars = [c for c in self.contents_get(content_type="character")
                 if not c.attributes.get('transformed')]
        vchars = self.filter_visible(chars, looker, **kwargs)
        num_chars = len(vchars)

        char_list = iter_to_str(
            [articlize_character(char.get_display_name(looker, pose=True, **kwargs))
             for char in vchars])

        # We add the word 'also' in case we showed there were objects:
        objs = self.contents_get(content_type="object")
        if len(objs) > 0:
            also = 'also '
        else:
            also = ''

        if num_chars > 1:
            return f"You {also}see the following characters: {char_list}."

        if num_chars > 0:
            character = char_list.strip()
            if character.startswith('|'):
                comp_char = character[2:].lower()
            else:
                comp_char = character.lower()

            if match(r"an? |the ", comp_char):
                return f"You {also}see {character}."
            elif match(r"[aeiou]", comp_char):
                return f"You {also}see an {character}."
            else:
                return f"You {also}see a {character}."
        return ''

    def get_display_things(self, looker, *args, **kwargs):
        """
        Replace
        """
        things = self.filter_visible(self.contents_get(content_type="object"),
                                     looker, **kwargs)
        tchars = [c for c in self.contents_get(content_type="character")
                  if c.attributes.get('transformed')]

        grouped_things = defaultdict(list)
        for thing in things + tchars:
            grouped_things[thing.get_display_name(looker, **kwargs)].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)
            if nthings > 1:
                thing_names.append(plural)
            elif thing.db.plural:
                thing_names.append(thing.get_display_name(looker))
            else:
                thing_names.append(singular)

        thing_names = iter_to_str(thing_names, endsep=_(", and"))
        return _(f"You notice {thing_names}." if thing_names else "")

    def msg_contents(self, text, exclude=None, from_obj=None, mapping=None,
                     raise_funcparse_errors=False, **kwargs):
        self.store_event(fix_msg(text))
        super().msg_contents(text, exclude, from_obj, mapping,
                             raise_funcparse_errors)

    def store_event(self, text, speaker=None):
        """
        Store a text message in the short_history.
        """
        if text and isinstance(text, tuple):
            text = text[0]

        if speaker:
            text = text.replace("/me", speaker.get_name())

        self.short_history.append(text.replace(" /", " "))
        self.short_history = self.short_history[-10:]


class DabblersRoom(Room):
    """
    Change the description based on the state of the fire.
    """
    def get_display_desc(self, looker):
        fire = self.search("fire")
        full_desc = self.db.initial_desc
        if fire.hunger() == Hunger.RAVENOUS:
            self.db.title_level = 0
            full_desc += " " + self.db.fire_out
        elif fire.hunger() == Hunger.HUNGRY:
            self.db.title_level = 1
            full_desc += " " + self.db.fire_dim
        elif fire.hunger() == Hunger.FULL:
            self.db.title_level = 2
            full_desc += " " + self.db.fire_full
        else:
            self.db.title_level = 3
            full_desc += " " + self.db.fire_on
        full_desc += " " + self.db.final_desc

        return full_desc


class OpenableRoom(Room):
    """
    Rooms that accept the "open" and "close" command.

    This will move an exit in and out of this location.
    Set the follow properties:

      @set here/open_exit = $search(red door)
      @set here/open_exit_msg = "$You() $conj(open) the door!"
      @set here/close_exit_msg = "$You() $conj(close) the door!"
      @set here/close_automatic = 240
    """
    def do_open(self, opener, item):
        """
        Move the 'open_exit' to this location.
        """
        the_exit = self.db.open_exit
        the_item = self.db.open_item
        if the_exit and ((the_item and item.lower() ==the_item.lower()) or
                       the_exit.key.lower().endswith(item)):
            if the_exit.location ==self:
                opener.msg(f"The {item} is already open.")
                return True
            the_exit.location = self
            opener.announce_action(self.db.open_exit_msg or
                                   f"$You() $conj(open) the {the_exit.name}")
            if self.db.close_automatic:
                delay(self.db.close_automatic, self.do_close)
            return True

    def do_close(self, closer=None, item=None):
        """
        Move the 'open_exit' exit to Limbo.
        """
        the_exit = self.db.open_exit

        # If a user is trying to close an item (an automatic close,
        # item will be None), so we just put in this check here:
        the_item = self.db.open_item
        if item and ((the_item and item.lower() ==the_item.lower()) or
                   the_exit.key.lower().endswith(item)):
            pass
        else:
            return None

        if the_exit:
            if the_exit.location == self:  # It is opened and can be closed:
                the_exit.location = None
                if closer:
                    closer.announce_action(self.db.close_exit_msg or
                                           f"$You() $conj(close) the {the_exit.name}")
                else:
                    self.msg_contents(self.db.close_exit_msg)
            elif closer:
                closer.msg(f"The {item} is already closed.")
            return True
