"""
Comsystem command module.

Comm commands are OOC commands and intended to be made available to
the Player at all times (they go into the PlayerCmdSet). So we
make sure to homogenize self.caller to always be the player object
for easy handling.

"""
from django.conf import settings
from src.comms.models import ChannelDB, Msg, PlayerChannelConnection, ExternalChannelConnection
from src.comms import irc, imc2, rss
from src.comms.channelhandler import CHANNELHANDLER
from src.utils import create, utils, prettytable
from src.utils.utils import make_iter
from src.commands.default.muxcommand import MuxCommand, MuxPlayerCommand

# limit symbol import for API
__all__ = ("CmdAddCom", "CmdDelCom", "CmdAllCom",
           "CmdChannels", "CmdCdestroy", "CmdCBoot", "CmdCemit",
           "CmdCWho", "CmdChannelCreate", "CmdClock", "CmdCdesc",
           "CmdPage", "CmdIRC2Chan", "CmdIMC2Chan", "CmdIMCInfo",
           "CmdIMCTell", "CmdRSS2Chan")


def find_channel(caller, channelname, silent=False, noaliases=False):
    """
    Helper function for searching for a single channel with
    some error handling.
    """
    channels = ChannelDB.objects.channel_search(channelname)
    if not channels:
        if not noaliases:
            channels = [chan for chan in ChannelDB.objects.get_all_channels()
                        if channelname in chan.aliases.all()]
        if channels:
            return channels[0]
        if not silent:
            caller.msg("Channel '%s' not found." % channelname)
        return None
    elif len(channels) > 1:
        matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
        if not silent:
            caller.msg("Multiple channels match (be more specific): \n%s" % matches)
        return None
    return channels[0]


class CmdAddCom(MuxPlayerCommand):
    """
    addcom - subscribe to a channel with optional alias

    Usage:
       addcom [alias=] <channel>

    Joins a given channel. If alias is given, this will allow you to
    refer to the channel by this alias rather than the full channel
    name. Subsequent calls of this command can be used to add multiple
    aliases to an already joined channel.
    """

    key = "addcom"
    aliases = ["aliaschan", "chanalias"]
    help_category = "Comms"
    locks = "cmd:not pperm(channel_banned)"

    def func(self):
        "Implement the command"

        caller = self.caller
        args = self.args
        player = caller

        if not args:
            self.msg("Usage: addcom [alias =] channelname.")
            return

        if self.rhs:
            # rhs holds the channelname
            channelname = self.rhs
            alias = self.lhs
        else:
            channelname = self.args
            alias = None

        channel = find_channel(caller, channelname)
        if not channel:
            # we use the custom search method to handle errors.
            return

        # check permissions
        if not channel.access(player, 'listen'):
            self.msg("%s: You are not allowed to listen to this channel." % channel.key)
            return

        string = ""
        if not channel.has_connection(player):
            # we want to connect as well.
            if not channel.connect_to(player):
                # if this would have returned True, the player is connected
                self.msg("%s: You are not allowed to join this channel." % channel.key)
                return
            else:
                string += "You now listen to the channel %s. " % channel.key
        else:
            string += "You are already connected to channel %s." % channel.key

        if alias:
            # create a nick and add it to the caller.
            caller.nicks.add(alias, channel.key, category="channel")
            string += " You can now refer to the channel %s with the alias '%s'."
            self.msg(string % (channel.key, alias))
        else:
            string += " No alias added."
            self.msg(string)


class CmdDelCom(MuxPlayerCommand):
    """
    delcom - unsubscribe from channel or remove channel alias

    Usage:
       delcom <alias or channel>

    If the full channel name is given, unsubscribe from the
    channel. If an alias is given, remove the alias but don't
    unsubscribe.
    """

    key = "delcom"
    aliases = ["delaliaschan, delchanalias"]
    help_category = "Comms"
    locks = "cmd:not perm(channel_banned)"

    def func(self):
        "Implementing the command. "

        caller = self.caller
        player = caller

        if not self.args:
            self.msg("Usage: delcom <alias or channel>")
            return
        ostring = self.args.lower()

        channel = find_channel(caller, ostring, silent=True, noaliases=True)
        if channel:
            # we have given a channel name - unsubscribe
            if not channel.has_connection(player):
                self.msg("You are not listening to that channel.")
                return
            chkey = channel.key.lower()
            # find all nicks linked to this channel and delete them
            for nick in [nick for nick in caller.nicks.get(category="channel")
                         if nick.strvalue.lower() == chkey]:
                nick.delete()
            disconnect = channel.disconnect_from(player)
            if disconnect:
                self.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
            return
        else:
            # we are removing a channel nick
            channame = caller.nicks.get(key=ostring, category="channel")
            channel = find_channel(caller, channame, silent=True)
            if not channel:
                self.msg("No channel with alias '%s' was found." % ostring)
            else:
                if caller.nicks.get(ostring, category="channel"):
                    caller.nicks.remove(ostring, category="channel")
                    self.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key))
                else:
                    self.msg("You had no such alias defined for this channel.")


class CmdAllCom(MuxPlayerCommand):
    """
    allcom - operate on all channels

    Usage:
      allcom [on | off | who | destroy]

    Allows the user to universally turn off or on all channels they are on,
    as well as perform a 'who' for all channels they are on. Destroy deletes
    all channels that you control.

    Without argument, works like comlist.
    """

    key = "allcom"
    locks = "cmd: not pperm(channel_banned)"
    help_category = "Comms"

    def func(self):
        "Runs the function"

        caller = self.caller
        args = self.args
        if not args:
            caller.execute_cmd("@channels")
            self.msg("(Usage: allcom on | off | who | destroy)")
            return

        if args == "on":
            # get names of all channels available to listen to
            # and activate them all
            channels = [chan for chan in ChannelDB.objects.get_all_channels()
                        if chan.access(caller, 'listen')]
            for channel in channels:
                caller.execute_cmd("addcom %s" % channel.key)
        elif args == "off":
             #get names all subscribed channels and disconnect from them all
            channels = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)]
            for channel in channels:
                caller.execute_cmd("delcom %s" % channel.key)
        elif args == "destroy":
            # destroy all channels you control
            channels = [chan for chan in ChannelDB.objects.get_all_channels()
                        if chan.access(caller, 'control')]
            for channel in channels:
                caller.execute_cmd("@cdestroy %s" % channel.key)
        elif args == "who":
            # run a who, listing the subscribers on visible channels.
            string = "\n{CChannel subscriptions{n"
            channels = [chan for chan in ChannelDB.objects.get_all_channels()
                        if chan.access(caller, 'listen')]
            if not channels:
                string += "No channels."
            for channel in channels:
                string += "\n{w%s:{n\n" % channel.key
                conns = PlayerChannelConnection.objects.get_all_connections(channel)
                if conns:
                    string += "  " + ", ".join([conn.player.key for conn in conns])
                else:
                    string += "  <None>"
            self.msg(string.strip())
        else:
            # wrong input
            self.msg("Usage: allcom on | off | who | clear")


class CmdChannels(MuxPlayerCommand):
    """
    @clist

    Usage:
      @channels
      @clist
      comlist

    Lists all channels available to you, whether you listen to them or not.
    Use 'comlist' to only view your current channel subscriptions.
    Use addcom/delcom to join and leave channels
    """
    key = "@channels"
    aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
    help_category = "Comms"
    locks = "cmd: not pperm(channel_banned)"

    def func(self):
        "Implement function"

        caller = self.caller

        # all channels we have available to listen to
        channels = [chan for chan in ChannelDB.objects.get_all_channels()
                    if chan.access(caller, 'listen')]
        #print channels
        if not channels:
            self.msg("No channels available.")
            return
        # all channel we are already subscribed to
        subs = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)]
        #print subs

        if self.cmdstring == "comlist":
            # just display the subscribed channels with no extra info
            comtable = prettytable.PrettyTable(["{wchannel",
                                                "{wmy aliases",
                                                "{wdescription"])
            for chan in subs:
                clower = chan.key.lower()
                nicks = caller.nicks.get(category="channel")
                comtable.add_row(["%s%s" % (chan.key, chan.aliases.all() and
                                  "(%s)" % ",".join(chan.aliases.all()) or ""),
                                  "%s".join(nick for nick in make_iter(nicks)
                                  if nick and nick.lower() == clower),
                                  chan.db.desc])
            caller.msg("\n{wChannel subscriptions{n (use {w@channels{n to list all, {waddcom{n/{wdelcom{n to sub/unsub):{n\n%s" % comtable)
        else:
            # full listing (of channels caller is able to listen to)
            comtable = prettytable.PrettyTable(["{wsub",
                                                "{wchannel",
                                                "{wmy aliases",
                                                "{wlocks",
                                                "{wdescription"])
            for chan in channels:
                clower = chan.key.lower()
                nicks = caller.nicks.get(category="channel")
                nicks = nicks or []
                comtable.add_row([chan in subs and "{gYes{n" or "{rNo{n",
                                  "%s%s" % (chan.key, chan.aliases.all() and
                                  "(%s)" % ",".join(chan.aliases.all()) or ""),
                                  "%s".join(nick for nick in make_iter(nicks)
                                  if nick.lower() == clower),
                                  str(chan.locks),
                                  chan.db.desc])
            caller.msg("\n{wAvailable channels{n (use {wcomlist{n,{waddcom{n and {wdelcom{n to manage subscriptions):\n%s" % comtable)


class CmdCdestroy(MuxPlayerCommand):
    """
    @cdestroy

    Usage:
      @cdestroy <channel>

    Destroys a channel that you control.
    """

    key = "@cdestroy"
    help_category = "Comms"
    locks = "cmd: not pperm(channel_banned)"

    def func(self):
        "Destroy objects cleanly."
        caller = self.caller

        if not self.args:
            self.msg("Usage: @cdestroy <channelname>")
            return
        channel = find_channel(caller, self.args)
        if not channel:
            self.msg("Could not find channel %s." % self.args)
            return
        if not channel.access(caller, 'control'):
            self.msg("You are not allowed to do that.")
            return

        message = "%s is being destroyed. Make sure to change your aliases." % channel
        msgobj = create.create_message(caller, message, channel)
        channel.msg(msgobj)
        channel.delete()
        CHANNELHANDLER.update()
        self.msg("Channel '%s' was destroyed." % channel)


class CmdCBoot(MuxPlayerCommand):
    """
    @cboot

    Usage:
       @cboot[/quiet] <channel> = <player> [:reason]

    Switches:
       quiet - don't notify the channel

    Kicks a player or object from a channel you control.

    """

    key = "@cboot"
    locks = "cmd: not pperm(channel_banned)"
    help_category = "Comms"

    def func(self):
        "implement the function"

        if not self.args or not self.rhs:
            string = "Usage: @cboot[/quiet] <channel> = <player> [:reason]"
            self.msg(string)
            return

        channel = find_channel(self.caller, self.lhs)
        if not channel:
            return
        reason = ""
        if ":" in self.rhs:
            playername, reason = self.rhs.rsplit(":", 1)
            searchstring = playername.lstrip('*')
        else:
            searchstring = self.rhs.lstrip('*')
        player = self.caller.search(searchstring, player=True)
        if not player:
            return
        if reason:
            reason = " (reason: %s)" % reason
        if not channel.access(self.caller, "control"):
            string = "You don't control this channel."
            self.msg(string)
            return
        if not PlayerChannelConnection.objects.has_player_connection(player, channel):
            string = "Player %s is not connected to channel %s." % (player.key, channel.key)
            self.msg(string)
            return
        if not "quiet" in self.switches:
            string = "%s boots %s from channel.%s" % (self.caller, player.key, reason)
            channel.msg(string)
        # find all player's nicks linked to this channel and delete them
        for nick in [nick for nick in
                     player.character.nicks.get(category="channel") or []
                     if nick.db_real.lower() == channel.key]:
            nick.delete()
        # disconnect player
        channel.disconnect_from(player)
        CHANNELHANDLER.update()


class CmdCemit(MuxPlayerCommand):
    """
    @cemit - send a message to channel

    Usage:
      @cemit[/switches] <channel> = <message>

    Switches:
      noheader - don't show the [channel] header before the message
      sendername - attach the sender's name before the message
      quiet - don't echo the message back to sender

    Allows the user to broadcast a message over a channel as long as
    they control it. It does not show the user's name unless they
    provide the /sendername switch.

    """

    key = "@cemit"
    aliases = ["@cmsg"]
    locks = "cmd: not pperm(channel_banned)"
    help_category = "Comms"

    def func(self):
        "Implement function"