# tsuserver3, an Attorney Online server
#
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import asyncio
import random
import time
from enum import Enum

import yaml

from server import database
from server.evidence import EvidenceList
from server.exceptions import AreaError


class AreaManager:
    """Holds the list of all areas."""
    class Area:
        """Represents a single instance of an area."""
        def __init__(self,
                     area_id,
                     server,
                     name,
                     background,
                     bg_lock,
                     evidence_mod='FFA',
                     locking_allowed=False,
                     iniswap_allowed=True,
                     showname_changes_allowed=True,
                     shouts_allowed=True,
                     jukebox=False,
                     abbreviation='',
                     non_int_pres_only=False):
            self.iniswap_allowed = iniswap_allowed
            self.clients = set()
            self.invite_list = {}
            self.id = area_id
            self.name = name
            self.background = background
            self.bg_lock = bg_lock
            self.server = server
            self.music_looper = None
            self.next_message_time = 0
            self.hp_def = 10
            self.hp_pro = 10
            self.doc = 'No document.'
            self.status = 'IDLE'
            self.judgelog = []
            self.current_music = ''
            self.current_music_player = ''
            self.current_music_player_ipid = -1
            self.evi_list = EvidenceList()
            self.is_recording = False
            self.recorded_messages = []
            self.evidence_mod = evidence_mod
            self.locking_allowed = locking_allowed
            self.showname_changes_allowed = showname_changes_allowed
            self.shouts_allowed = shouts_allowed
            self.abbreviation = abbreviation
            self.cards = dict()
            """
            #debug
            self.evidence_list.append(Evidence("WOW", "desc", "1.png"))
            self.evidence_list.append(Evidence("wewz", "desc2", "2.png"))
            self.evidence_list.append(Evidence("weeeeeew", "desc3", "3.png"))
            """

            self.is_locked = self.Locked.FREE
            self.blankposting_allowed = True
            self.non_int_pres_only = non_int_pres_only
            self.jukebox = jukebox
            self.jukebox_votes = []
            self.jukebox_prev_char_id = -1

            self.owners = []
            self.afkers = []
            self.last_ic_message = None

        class Locked(Enum):
            """Lock state of an area."""
            FREE = 1,
            SPECTATABLE = 2,
            LOCKED = 3

        def new_client(self, client):
            """Add a client to the area."""
            self.clients.add(client)
            self.server.area_manager.send_arup_players()
            if client.char_id != -1:
                database.log_room('area.join', client, self)

        def remove_client(self, client):
            """Remove a disconnected client from the area."""
            self.clients.remove(client)
            if client in self.afkers:
                self.afkers.remove(client)
            if len(self.clients) == 0:
                self.change_status('IDLE')
            if client.char_id != -1:
                database.log_room('area.leave', client, self)

        def unlock(self):
            """Mark the area as unlocked."""
            self.is_locked = self.Locked.FREE
            self.blankposting_allowed = True
            self.invite_list = {}
            self.server.area_manager.send_arup_lock()
            self.broadcast_ooc('This area is open now.')

        def spectator(self):
            """Mark the area as spectator-only."""
            self.is_locked = self.Locked.SPECTATABLE
            for i in self.clients:
                self.invite_list[i.id] = None
            for i in self.owners:
                self.invite_list[i.id] = None
            self.server.area_manager.send_arup_lock()
            self.broadcast_ooc('This area is spectatable now.')

        def lock(self):
            """Mark the area as locked."""
            self.is_locked = self.Locked.LOCKED
            for i in self.clients:
                self.invite_list[i.id] = None
            for i in self.owners:
                self.invite_list[i.id] = None
            self.server.area_manager.send_ar
up_lock()
            self.broadcast_ooc('This area is locked now.')

        def is_char_available(self, char_id):
            """
            Check if a character is available for use.
      
      :param char_id: character ID
            """
            return char_id not in [x.char_id for x in self.clients]

        def get_rand_avail_char_id(self):
            """Get a random available character ID."""
            avail_set = set(range(len(
                self.server.char_list))) - {x.char_id
                                            for x in self.clients}
            if len(avail_set) == 0:
                raise AreaError('No available characters.')
            return random.choice(tuple(avail_set))

        def send_command(self, cmd, *args):
            """
            Broadcast an AO-compatible command to all clients in the area.
            """
            for c in self.clients:
                c.send_command(cmd, *args)

        def send_owner_command(self, cmd, *args):
            """
            Send an AO-compatible command to all owners of the area
            that are not currently in the area.
            """
            for c in self.owners:
                if c not in self.clients:
                    c.send_command(cmd, *args)

        def broadcast_ooc(self, msg):
            """
            Broadcast an OOC message to all clients in the area.
            :param msg: message
            """
            self.send_command('CT', self.server.config['hostname'], msg, '1')
            self.send_owner_command(
                'CT',
                '[' + self.abbreviation + ']' + self.server.config['hostname'],
                msg, '1')

        def set_next_msg_delay(self, msg_length):
            """
            Set the delay when the next IC message can be send by any client.
            :param msg_length: estimated length of message (ms)
            """
            delay = min(3000, 100 + 60 * msg_length)
            self.next_message_time = round(time.time() * 1000.0 + delay)

        def is_iniswap(self, client, preanim, anim, char, sfx):
            """
            Determine if a client is performing an INI swap.
            :param client: client attempting the INI swap.
            :param preanim: name of preanimation
            :param anim: name of idle/talking animation
            :param char: name of character

            """
            if self.iniswap_allowed:
                return False
            if '..' in preanim or '..' in anim or '..' in char:
                # Prohibit relative paths
                return True
            if char.lower() != client.char_name.lower():
                for char_link in self.server.allowed_iniswaps:
                    # Only allow if both the original character and the
                    # target character are in the allowed INI swap list
                    if client.char_name in char_link and char in char_link:
                        return False
            return not self.server.char_emotes[char]