###
# Copyright (c) SpiderDave
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

import re
import random

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import time
import os, errno
import pickle

try:
    from supybot.i18n import PluginInternationalization

    _ = PluginInternationalization("UNO")
except ImportError:
    # Placeholder that allows to run the plugin on a bot
    # without the i18n module
    _ = lambda x: x


class UNO(callbacks.Plugin):
    """
    UNO!
    """

    threaded = True

    game = [{}, {}, {}, {}, {}]

    channeloptions = {}
    channeloptions["allow_game"] = False
    channeloptions["debug"] = False
    channeloptions["use_queue"] = True
    channeloptions["nplayers"] = 10
    channeloptions["maxbots"] = 9
    channeloptions["use_colors"] = True
    channeloptions["use_notice"] = True

    lastgame = time.time()

    def make_sure_path_exists(path):
        try:
            os.makedirs(path)
        except OSError as exception:
            if exception.errno != errno.EEXIST:
                raise

    make_sure_path_exists(r"%s%suno" % (conf.supybot.directories.data(), os.sep))
    dataPath = r"%s%suno%s" % (conf.supybot.directories.data(), os.sep, os.sep)
    prefixChar = conf.supybot.reply.whenAddressedBy.chars()[0]

    def start(self, irc, msg, args, text):
        """
        Start a new game of UNO. For the rules of the game, use the "uno rules" command.
        """
        try:
            self._read_options(irc)
        except:
            pass
        if self.channeloptions["allow_game"] == False:
            irc.reply("Error: allow_game=False")
            return

        def get(group):
            v = group.getSpecific(channel=msg.args[0])
            return v()

        try:
            prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0]
        except:
            prefixChar = ""

        # may want to add a game variant later
        if text:
            gametype = text.lower().strip()
            if gametype.replace(" ", "") == "globalthermalnuclearwar":
                irc.reply("Curious game. The only winning move is not to play.")
                return
            if gametype not in ["uno"]:
                irc.reply("Error: Invald game type %s." % gametype)
                return
        else:
            gametype = "uno"
        nick = msg.nick
        table = self._gettablefromnick(nick)
        if table != None:
            gametype = self.game[table].get("type").capitalize()
            irc.reply("Error: You are already in a game of %s." % gametype)
            return

        table = self._getopentable()
        if table == None:
            irc.reply("Sorry, all the game tables are in use at the moment.")
            return

        self._cleanup(table)
        self.game[table]["channel"] = msg.args[0]
        self.game[table]["type"] = gametype

        if gametype == "uno":
            self.game[table]["players"][nick] = {}
            # self.game[table]['nplayers']=int(self.channeloptions[gametype+'_nplayers'])
            self.game[table]["nplayers"] = int(self.channeloptions["nplayers"])
            irc.reply(
                "%s has started a new game of %s at table %s. For the rules of the"
                ' game, type "%suno rules". To accept this challenge, join with "%suno'
                ' join". To add a cpu player, type "%suno join cpu".'
                % (
                    nick,
                    gametype.capitalize(),
                    table + 1,
                    prefixChar,
                    prefixChar,
                    prefixChar,
                ),
                prefixNick=False,
            )
        self.game[table]["phase"] = "join"

    start = wrap(start, ["public", optional("something")])

    def begin(self, irc, msg, args):
        """
        Begin the UNO game.
        """
        self._uno_begin(irc, msg.nick)

    begin = wrap(begin)

    def _uno_begin(self, irc, nick):
        table = self._gettablefromnick(nick)
        if table == None:
            irc.reply("Error: You are not playing a game at any of the tables.")
            return
        if self.game[table]["type"] != "uno":
            irc.reply("Error: Not an UNO game.")
            return
        if self.game[table]["phase"] != "join":
            irc.reply("Error: You can't use this command right now.")
            return
        nplayers = list(self.game[table]["players"].keys())
        if len(nplayers) < 2:
            irc.reply("Error: You need at least two players for UNO.")
            return
        # start things for real
        for n in list(self.game[table]["players"].keys()):
            self.game[table]["players"][n]["hand"] = []
            # each player draws 7 initial cards
            for i in range(0, 7):
                card = self.game[table]["deck"].pop(
                    random.randint(0, len(self.game[table]["deck"]) - 1)
                )
                self.game[table]["players"][n]["hand"].append(card)
        self.game[table]["phase"] = "running"
        irc.reply(
            "Game started!  Check your notices and follow the instructions.",
            prefixNick=False,
            to=self.game[table]["channel"],
        )
        for n in list(self.game[table]["players"].keys()):
            self._uno_tell_status(irc, n)
        self._uno_do_cpu(irc, table)

    def _uno_do_cpu(self, irc, table):
        while 1:
            if self.game[table].get("phase") != "running":
                return
            if self.game[table].get("type") != "uno":
                return
            turnplayer = list(self.game[table]["players"].keys())[
                self.game[table]["turn"]
            ]
            if self.game[table]["players"][turnplayer].get("cpu"):
                self._uno_cpu_play(irc, table)
            else:
                return

    def _uno_tell_status(self, irc, nick):
        table = self._gettablefromnick(nick)
        if table == None:
            irc.reply("Error: You are not playing UNO at any of the tables.")
            return
        channel = self.game[table]["channel"]

        def get(group):
            v = group.getSpecific(channel=channel)
            return v()

        try:
            prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0]
        except:
            prefixChar = ""

        opponents = [p for p in list(self.game[table]["players"].keys()) if p != nick]
        opponent = opponents[0]
        opponent_cards = [
            "%s has %s cards" % (n, len(self.game[table]["players"][n]["hand"]))
            for n in list(self.game[table]["players"].keys())
            if n != nick
        ]
        opponent_cards = ", ".join(opponent_cards)
        topcard = self.game[table]["discard"][-1]
        if "Wild" in topcard:
            topcard = "%s (%s)" % (topcard, self.game[table].get("wildcolor"))
        yourhand = self.game[table]["players"][nick]["hand"]
        ncards = len(self.game[table]["players"][nick]["hand"])
        opponent_ncards = len(self.game[table]["players"][opponent]["hand"])
        turnplayer = list(self.game[table]["players"].keys())[self.game[table]["turn"]]
        if turnplayer == nick:
            if self.game[table]["players"][turnplayer].get("cpu"):
                # We'll skip notice in the channel since the cpu player will
                # take their turn quickly, and no need to announce it.
                pass
            else:
                if self.channeloptions["use_colors"] == True:
                    if "Red" in topcard:
                        topcardcolor = ircutils.mircColor(topcard, "red", "black")
                    elif "Blue" in topcard:
                        topcardcolor = ircutils.mircColor(
                            topcard, "light blue", "black"
                        )
                    elif "Green" in topcard:
                        topcardcolor = ircutils.mircColor(
                            topcard, "light green", "black"
                        )
                    else:
                        topcardcolor = ircutils.mircColor(topcard, "yellow", "black")
                    txt = "It is %s's turn. The top card is %s." % (nick, topcardcolor)
                    txt = txt.replace("s's", "s'")
                    irc.reply(txt, to=channel)
                    turnplayer = "your"
                    yourhandcolor = []
                    for i in range(len(yourhand)):
                        if "Red" in yourhand[i]:
                            yourhandcolor.append(
                                ircutils.mircColor(yourhand[i], "red", "black")
                            )
                        elif "Green" in yourhand[i]:
                            yourhandcolor.append(
                                ircutils.mircColor(yourhand[i], "light green", "black")
                            )
                        elif "Blue" in yourhand[i]:
                            yourhandcolor.append(
                                ircutils.mircColor(yourhand[i], "light blue", "black")
                            )
                        elif "Yellow" in yourhand[i]:
                            yourhandcolor.append(
                                ircutils.mircColor(yourhand[i], "yellow", "black")
                            )
                        else:
                            yourhandcolor.append(
                                ircutils.mircColor(yourhand[i], None, "black")
                            )
                    yourhandcolor = utils.str.commaAndify(yourhandcolor)
                    txt = (
                        "It is your turn. The top card is %s. You have %s cards (%s)."
                        ' %s. To play a card, use the "%suno play" command.'
                        % (
                            topcardcolor,
                            ncards,
                            yourhandcolor,
                            opponent_cards,
                            prefixChar,
                        )
                    )
                    if self.channeloptions["use_notice"] == True:
                        self.reply(irc, txt, to=nick, notice=True, private=True)
                    else:
                        self.reply(irc, txt, to=nick, notice=False, private=True)
                else:
                    yourhand = utils.str.commaAndify(yourhand)
                    txt = "It is %s's turn. The top card is %s." % (nick, topcard)
                    txt = txt.replace("s's", "s'")
                    irc.reply(txt, to=channel)
                    turnplayer = "your"
                    txt = (
                        "It is your turn. The top card is %s. You have %s cards (%s)."
                        ' %s. To play a card, use the "%suno play" command.'
                        % (topcard, ncards, yourhand, opponent_cards, prefixChar)
                    )
                    if self.channeloptions["use_notice"] == True:
                        self.reply(irc, txt, to=nick, notice=True, private=True)
                    else:
                        self.reply(irc, txt, to=nick, notice=False, private=True)
        else:
            pass

    def _uno_is_valid_play(self, table, card, discard, wildcolor):
        if card == "Wild":
            # Wild is always a valid play for now
            return True
        if card == "Wild Draw 4":
            turnplayer = list(self.game[table]["players"].keys())[
                self.game[table]["turn"]
            ]
            if "Wild" not in discard:
                discardcolor = discard.split(" ", 1)[0]
            else:
                discardcolor = wildcolor
            for c in self.game[table]["players"][turnplayer]["hand"]:
                if discardcolor in c:
                    return False
            return True

        unocolors = ["Blue", "Green", "Red", "Yellow"]
        for color in unocolors:
            if color in card:
                if "Wild" in discard:
                    # Wild or Wild Draw 4
                    if color == self.game[table].get("wildcolor"):
                        return True
                    else:
                        return False
                if color in discard:
                    # Color matches
                    return True
                if card.split(" ", 1)[1] == discard.split(" ", 1)[1]:
                    # Number or word matches
                    return True
        return False

    def _uno_draw_card(self, table, player):
        if len(self.game[table]["deck"]) > 1:
            card = self.game[table]["deck"].pop(
                random.randint(0, len(self.game[table]["deck"]) - 1)
            )
            self.game[table]["players"][player]["hand"].append(card)
        else:
            card = self.game[table]["deck"].pop(0)
            self.game[table]["players"][player]["hand"].append(card)
        if len(self.game[table]["deck"]) == 0:
            self.game[table]["deck"] = self.game[table]["discard"]
            self.game[table]["discard"] = []
            random.shuffle(self.game[table]["deck"])
            topcard = self.game[table]["deck"].pop(
                random.randint(0, len(self.game[table]["deck"]) - 1)
            )
            self.game[table]["discard"].append(topcard)
        return card

    def tellstatus(self, irc, msg, args):
        """
        Tell current UNO status
        """
        self._uno_tell_status(irc, msg.nick)

    tellstatus = wrap(tellstatus)

    def rules(self, irc, msg, args, text):
        """
        Display rules for uno. Start a game of uno with the "uno start" command.
        """
        if text:
            gametype = text.lower().strip()
        else:
            gametype = "uno"
        if gametype == "uno":
            irc.reply("Rules for UNO: http://www.wonkavator.com/uno/unorules.html")
        else:
            irc.reply("Unknown game type.")

    rules = wrap(rules, [additional("text")])

    def join(self, irc, msg, args, table, fakenick):
        """[<table>]
        Join a game of UNO previously started with the "uno start" command.
        Specify <table> if there is more than one game to join in that channel.
        """
        try:
            self._read_options(irc)
        except:
            pass
        if self.channeloptions["allow_game"] == False:
            irc.reply("Error: allow_game=False")
            return

        def get(group):
            v = group.getSpecific(channel=msg.args[0])
            return v()

        try:
            prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0]
        except:
            prefixChar = ""

        nick = msg.nick
        if table != None:
            table -= 1  # make tables zero based
        tables = self._getcurrenttables()
        if not tables:
            # no games running
            irc.reply("Error: There are no games to join.")
            return
        if table != None and table not in tables:
            # given table doesn't have a game going
            if table not in list(range(len(self.game))):
                irc.reply("Error: That table doesn't exist")
                return
            irc.reply("Error: There is no game at that table")
            return
        tables = [t for t in tables if self.game[t]["channel"] == msg.args[0]]
        if table != None:
            if table not in tables:
                irc.reply("Error: That table is in another channel.")
                return
            tables = [table]  # success!
        if len(tables) == 0:
            irc.reply("Error: There are no games to join in this channel.")
            return
        elif len(tables) == 1:
            table = tables[0]
        else:
            messagetxt = (
                "Please specify which table you'd like to play at (uno join <table>)."
                " Current tables are: "
            )
            for t in tables:
                messagetxt += "Table %s (%s), " % (
                    t + 1,
                    " ".join(list(self.game[t]["players"].keys())),
                )
            messagetxt = messagetxt.rsplit(", ", 1)[0] + "."
            irc.reply(messagetxt)
            return
        isfake = False
        iscpu = False
        if ((self.channeloptions["debug"]) and fakenick) or (
            fakenick and fakenick.lower() == "cpu"
        ):
            nick = fakenick
            isfake = True
            if fakenick.lower() == "cpu":
                iscpu = True
        if self.game[table]["phase"] == "join":
            if nick in list(self.game[table]["players"].keys()):
                irc.reply("Error: you have already joined.")
                return
            if self.game[table]["type"] == "uno":
                self._init_uno(table)
                if iscpu == True:
                    nick = self._uno_make_cpu(table)
                else:
                    self.game[table]["players"][nick] = {}
                if isfake == True:
                    self.game[table]["players"][nick]["fake"] = True

                if (
                    len(list(self.game[table]["players"].keys()))
                    < self.game[table]["nplayers"]
                ):
                    irc.reply(
                        "%s has joined the %s game at table %s. Use %suno begin to"
                        " begin the game."
                        % (nick, self.game[table]["type"], table + 1, prefixChar),
                        prefixNick=False,
                        to=self.game[table]["channel"],
                    )
                    return

                for n in list(self.game[table]["players"].keys()):
                    self.game[table]["players"][n]["hand"] = []
                    # each player draws 7 initial cards
                    for i in range(0, 7):
                        card = self.game[table]["deck"].pop(
                            random.randint(0, len(self.game[table]["deck"]) - 1)
                        )
                        self.game[table]["players"][n]["hand"].append(card)
                irc.reply(
                    "Game started!  Check your notices and follow the instructions.",
                    prefixNick=False,
                    to=self.game[table]["channel"],
                )
                for n in list(self.game[table]["players"].keys()):
                    self._uno_tell_status(irc, n)
            self.game[table]["phase"] = "running"
        else:
            if self.game[table]["phase"] == "running":
                irc.reply("Error: Game already running.")
                return
            elif self.game[table]["phase"] == "":
                irc.reply(
                    'Error: You need to create a game with the "uno start" command'
                    " first."
                )
                return
            else:
                # don't know when this would happen, but whatever
                irc.reply("Error: not join phase.")
                return

    join = wrap(join, ["public", optional("int"), optional("something")])

    def leave(self, irc, msg, args, fakenick):
        """
        Leave a game of UNO.
        """
        try:
            self._read_options(irc)
        except:
            pass
        if self.channeloptions["allow_game"] == False:
            irc.reply("Error: allow_game=False")
            return

        nick = msg.nick
        if self.channeloptions["debug"] and fakenick:
            nick = fakenick
        table = self._gettablefromnick(nick)
        if table == None:
            irc.reply("Error: You are not playing a game at any of the tables.")
            return

        if self.game[table].get("type") == "uno":
            self._leavegame(irc, msg, nick)
            self._uno_do_cpu(irc, table)  # only works if game type is uno
            return

    leave = wrap(leave, ["public", optional("something")])

    def _leavegame(self, irc, msg, nick):
        """
        Leave a game of UNO.
        """
        try:
            self._read_options(irc)
        except:
            pass
        if self.channeloptions["allow_game"] == False:
            irc.reply("Error: allow_game=False")
            return

        table = self._gettablefromnick(nick)
        if table == None:
            return

        channel = self.game[table]["channel"]

        # leaving a game when you're the only player
        if len(self.game[table]["players"]) == 1:
            irc.reply("There are no more players; The game is over.", to=channel)
            self.game[table]["phase"] = "gameover"
            self._cleanup(table)
            return

        # check if it's only bot players left
        Human = False
        for n in list(self.game[table]["players"].keys()):
            if not self.game[table]["players"][n].get("cpu"):
                if n != nick:
                    Human = True

        if not Human:
            irc.reply("There are no more human players; the game is over.", to=channel)
            self.game[table]["phase"] = "gameover"
            self._cleanup(table)
            return

        # ---- replace with cpu ----
        oldnick = nick
        nick = self._uno_make_cpu(table)
        del self.game[table]["players"][
            nick
        ]  # remove new cpu player (we just want the nick)

        self.game[table]["players"][nick] = self.game[table]["players"][oldnick]
        del self.game[table]["players"][oldnick]
        self.game[table]["players"][nick]["fake"] = True
        self.game[table]["players"][nick]["cpu"] = True

        irc.reply(
            "%s has been replaced by %s." % (oldnick, nick),
            prefixNick=False,
            to=self.game[table]["channel"],
        )
        return

    def _init_uno(self, table):
        self.game[table]["deck"] = []
        self.game[table]["discard"] = []
        self.game[table]["wildcolor"] = "Red"
        self.game[table]["turn"] = 0
        self.game[table]["direction"] = 1
        unocolors = ["Blue", "Green", "Red", "Yellow"]
        for unocolor in unocolors:
            for i in range(10):
                self.game[table]["deck"].append("%s %s" % (unocolor, i))
            for i in range(1, 10):
                self.game[table]["deck"].append("%s %s" % (unocolor, i))
            for i in range(0, 2):
                self.game[table]["deck"].append("%s Draw Two" % unocolor)
                self.game[table]["deck"].append("%s Reverse" % unocolor)
                self.game[table]["deck"].append("%s Skip" % unocolor)
        for i in range(0, 4):
            self.game[table]["deck"].append("Wild")
            self.game[table]["deck"].append("Wild Draw 4")
        self.unodeck = [card for card in self.game[table]["deck"]]
        random.shuffle(self.game[table]["deck"])
        card = self.game[table]["deck"].pop(
            random.randint(0, len(self.game[table]["deck"]) - 1)
        )
        self.game[table]["discard"].append(card)

    def _uno_make_cpu(self, table):
        nicklist = [
            "Snarl",
            "Slag",
            "Sludge",
            "Swoop",
            "Grimlock",
            "Data",
            "Lore",
            "B-4",
            "Marvin",
            "Deep Thought",
            "Gort",
            "C-3PO",
            "R2-D2",
            "Daryl",
            "Bishop",
            "Johnny 5",
            "Bender",
            "Flexo",
            "Dorothy",
            "Robot B-9",
            "K-9",
            "Vicki",
            "Buffybot",
            "Lyla",
            "Robo",
            "Cyrax",
            "Sektor",
        ]
        nicklist = [
            n for n in nicklist if n not in list(self.game[table]["players"].keys())
        ]
        nick = random.choice(nicklist)
        # assumes nick isn't taken atm
        self.game[table]["players"][nick] = {}
        self.game[table]["players"][nick]["fake"] = True
        self.game[table]["players"][nick]["cpu"] = True
        return nick

    def _uno_cpu_play(self, irc, table):
        channel = self.game[table]["channel"]

        Human = False
        for n in list(self.game[table]["players"].keys()):
            if not self.game[table]["players"][n].get("cpu"):
                Human = True

        if not Human:
            irc.reply("There are no more human players; the game is over.", to=channel)
            self.game[table]["phase"] = "gameover"
            self._cleanup(table)
            return

        nick = list(self.game[table]["players"].keys())[self.game[table]["turn"]]
        discard = self.game[table]["discard"][-1]
        wildcolor = self.game[table].get("wildcolor")
        novalid = True
        for c in self.game[table]["players"][nick]["hand"]:
            if self._uno_is_valid_play(table, c, discard, wildcolor) == True:
                novalid = False
        if novalid == True:
            # draw a card
            card = self._uno_draw_card(table, nick)
            self.game[table]["players"][nick]["hasdrawn"] = True

            if self._uno_is_valid_play(table, card, discard, wildcolor) == True:
                # always play the card if possible
                ncards = len(self.game[table]["players"][nick]["hand"])
                irc.reply(
                    "%s draws a card, and plays it; It's a %s (%s cards left in hand)."
                    % (nick, card, ncards),
                    to=channel,
                )
                self.game[table]["players"][nick]["hand"].remove(card)
                self.game[table]["discard"].append(card)
            else:
                # Can't play a card, end turn
                ncards = len(self.game[table]["players"][nick]["hand"])
                irc.reply(
                    "%s draws a card (%s cards in hand)." % (nick, ncards),
                    to=self.game[table]["channel"],
                )
                card = ""
        else:
            # pick a random card
            playablecards = [
                c
                for c in self.game[table]["players"][nick]["hand"]
                if self._uno_is_valid_play(table, c, discard, wildcolor) == True
                or "Wild" in c
            ]
            card = random.choice(playablecards)
            if card == "Wild" or card == "Wild Draw 4":
                # Just do dumb blind color choice
                unocolors = ["Blue", "Green", "Red", "Yellow"]
                self.game[table]["wildcolor"] = random.choice(unocolors)
            self.game[table]["players"][nick]["hand"].remove(card)
            self.game[table]["discard"].append(card)
            ncards = len(self.game[table]["players"][nick]["hand"])
            if "Wild" in card:
                card = "%s (%s)" % (card, self.game[table]["wildcolor"])
            if self.channeloptions["use_colors"] == True:
                if "Red" in card:
                    cardcolor = ircutils.mircColor(card, "red", "black")
                elif "Blue" in card:
                    cardcolor = ircutils.mircColor(card, "light blue", "black")
                elif "Green" in card:
                    cardcolor = ircutils.mircColor(card, "light green", "black")
                else:
                    cardcolor = ircutils.mircColor(card, "yellow", "black")
                irc.reply(
                    "%s played %s (%s cards left in hand)." % (nick, cardcolor, ncards),
                    to=channel,
                )
            else:
                irc.reply(
                    "%s played %s (%s cards left in hand)." % (nick, card, ncards),
                    to=channel,
                )

        ncards = len(self.game[table]["players"][nick]["hand"])
        if ncards == 0:
            irc.reply(
                "The game is over. " + ircutils.bold("%s wins!" % nick), to=channel
            )
            self.game[table]["phase"] = "gameover"
            self._cleanup(table)
            return

        if "Reverse" in card:
            self.game[table]["direction"] *= -1

        nplayers = len(list(self.game[table]["players"].keys()))
        turn = self.game[table]["turn"] + 1 * self.game[table]["direction"]
        if turn > nplayers - 1:
            turn = 0
        if turn < 0:
            turn = nplayers - 1
        self.game[table]["turn"] = turn

        if "Draw Two" in card or "Draw 4" in card:
            ndrawcards = 2
            if "Draw 4" in card:
                ndrawcards = 4
            drawplayer = list(self.game[table]["players"].keys())[
                self.game[table]["turn"]
            ]
            for n in range(ndrawcards):
                c = self.game[table]["deck"].pop(
                    random.randint(0, len(self.game[table]["deck"]) - 1)
                )
                self.game[table]["players"][drawplayer]["hand"].append(c)
            ncards = len(self.game[table]["players"][drawplayer]["hand"])
            irc.reply(
                "%s draws %s cards (%s cards in hand)."
                % (drawplayer, ndrawcards, ncards),
                to=channel,
            )

        # Skip turn
        if (
            "Skip" in card
            or "Draw Two" in card
            or "Draw 4" in card
            or ("Reverse" in card and nplayers == 2)
        ):
            turn = self.game[table]["turn"] + 1 * self.game[table]["direction"]
            if turn > nplayers - 1:
                turn = 0
            if turn < 0:
                turn = nplayers - 1
            self.game[table]["turn"] = turn

        for n in list(self.game[table]["players"].keys()):
            self._uno_tell_status(irc, n)
        return

    def play(self, irc, msg, args, text):
        """<card>
        Play a <card> for the UNO game. Examples: "uno play red 0", "uno play wild blue", "uno play draw", "uno play done"
        """

        nick = msg.nick

        def get(group):
            v = group.getSpecific(channel=msg.args[0])
            return v()

        try:
            prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0]
        except:
            prefixChar = ""

        table = self._gettablefromnick(nick)
        if table == None:
            irc.reply("Error: You are not playing a game at any of the tables.")
            return
        channel = self.game[table]["channel"]
        if (
            self.channeloptions["debug"]
            and text.rsplit(" ", 1)[-1] in self.game[table]["players"]
        ):
            fakenick = [
                p
                for p in self.game[table]["players"]
                if p.lower() == text.rsplit(" ", 1)[-1].lower()
            ]
            if len(fakenick) == 0:
                fakenick = None
            else:
                fakenick = fakenick[0]
                nick = fakenick
                text = text.rsplit(" ", 1)[:-1][0]

        if self.game[table]["phase"] == "running":
            nplayers = len(list(self.game[table]["players"].keys()))
            if nick not in self.game[table]["players"]:
                irc.reply("Error: You're not playing this game.")
                return
            opponent = [p for p in self.game[table]["players"] if p != nick][0]

            turnplayer = list(self.game[table]["players"].keys())[
                self.game[table]["turn"]
            ]
            if nick != turnplayer:
                # Note: it will prefix nick in notice -- need to fix that
                irc.reply("%s: It is %s's turn." % (nick, turnplayer), prefixNick=False)
                return

            text = text.strip()

            discard = self.game[table]["discard"][-1]
            wildcolor = self.game[table].get("wildcolor")
            novalid = True

            for c in self.game[table]["players"][nick]["hand"]:
                if self._uno_is_valid_play(table, c, discard, wildcolor) == True:
                    novalid = False

            if text.lower() == "draw":
                if self.game[table]["players"][nick].get("hasdrawn") == True:
                    irc.reply("You have already drawn a card.")
                    return
                if novalid == False:
                    irc.reply("You can't draw because you have a card you can play")
                    return
                else:
                    # Draw a card
                    c = self.game[table]["deck"].pop(
                        random.randint(0, len(self.game[table]["deck"]) - 1)
                    )
                    self.game[table]["players"][nick]["hand"].append(c)
                    if self._uno_is_valid_play(table, c, discard, wildcolor) == True:
                        self.reply(
                            irc,
                            "You draw a %s from the draw pile. You can choose not to"
                            ' play this card using "%suno play done"'
                            % (c, prefixChar),
                            to=nick,
                            notice=True,
                            private=True,
                        )
                        self.game[table]["players"][nick]["hasdrawn"] = True
                        return
                        # card=c
                    else:
                        # Can't play a card, end turn
                        ncards = len(self.game[table]["players"][nick]["hand"])
                        self.reply(
                            irc,
                            "You draw a %s from the draw pile. You have no cards to"
                            " play, and your turn is over."
                            % c,
                            to=nick,
                            notice=True,
                            private=True,
                        )
                        irc.reply(
                            "%s draws a card (%s cards in hand)." % (nick, ncards),
                            to=self.game[table]["channel"],
                        )

                        turn = (
                            self.game[table]["turn"] + 1 * self.game[table]["direction"]
                        )
                        if turn > nplayers - 1:
                            turn = 0
                        if turn < 0:
                            turn = nplayers - 1
                        self.game[table]["turn"] = turn

                        for n in list(self.game[table]["players"].keys()):
                            self._uno_tell_status(irc, n)
                        self._uno_do_cpu(irc, table)
                        return

            if text.lower() == "done":
                if self.game[table]["players"][nick].get("hasdrawn") != True:
                    self.reply(
                        irc,
                        "You can't finish your turn without playing or drawing a card.",
                        to=nick,
                        notice=True,
                        private=True,
                    )
                    return
                ncards = len(self.game[table]["players"][nick]["hand"])
                irc.reply(
                    "%s draws a card (%s cards in hand)." % (nick, ncards),
                    to=self.game[table]["channel"],
                )
                self.game[table]["players"][nick]["hasdrawn"] = False

                turn = self.game[table]["turn"] + 1 * self.game[table]["direction"]
                if turn > nplayers - 1:
                    turn = 0
                if turn < 0:
                    turn = nplayers - 1
                self.game[table]["turn"] = turn

                for nn in list(self.game[table]["players"].keys()):
                    self._uno_tell_status(irc, nn)
                self._uno_do_cpu(irc, table)
                return

            card = text

            card = [card for card in self.unodeck if card.lower() == text.lower()]
            validwild = [
                "Wild Blue",
                "Wild Green",
                "Wild Red",
                "Wild Yellow",
                "Wild Draw 4 Blue",
                "Wild Draw 4 Green",
                "Wild Draw 4 Red",
                "Wild Draw 4 Yellow",
            ]
            if len(card) == 0:
                card = [c for c in validwild if c.lower() == text.lower()]
                if len(card) != 0:
                    card = card[0].rsplit(" ", 1)
                    newcolor = card[1]
                    card = [card[0]]
            else:
                if card[0] == "Wild" or card[0] == "Wild Draw 4":
                    irc.reply("You must specify a color when playing a Wild card.")
                    return

            if len(card) == 0:
                irc.reply("That is not a valid card.")
                return
            else:
                card = card[0]

            hand = self.game[table]["players"][nick]["hand"]
            if card not in hand:
                irc.reply("That card is not in your hand.")
                return

            # check for illegal move
            if self._uno_is_valid_play(table, card, discard, wildcolor) == False:
                irc.reply("You can't play that card.")
                return

            # play the card
            self.game[table]["players"][nick]["hand"].remove(card)
            self.game[table]["discard"].append(card)
            ncards = len(self.game[table]["players"][nick]["hand"])
            if "Wild" in card:
                self.game[table]["wildcolor"] = newcolor
                card = "%s (%s)" % (card, self.game[table]["wildcolor"])
            if self.channeloptions["use_colors"] == True:
                if "Red" in card:
                    cardcolor = ircutils.mircColor(card, "red", "black")
                elif "Blue" in card:
                    cardcolor = ircutils.mircColor(card, "light blue", "black")
                elif "Green" in card:
                    cardcolor = ircutils.mircColor(card, "light green", "black")
                else:
                    cardcolor = ircutils.mircColor(card, "yellow", "black")
                if self.game[table]["players"][nick].get("hasdrawn") == True:
                    irc.reply(
                        "%s draws a card, and plays it; It's a %s (%s cards left in"
                        " hand)."
                        % (nick, cardcolor, ncards),
                        to=channel,
                    )
                    self.game[table]["players"][nick]["hasdrawn"] = False
                else:
                    irc.reply(
                        "%s played %s (%s cards left in hand)."
                        % (nick, cardcolor, ncards),
                        to=channel,
                    )
            else:
                if self.game[table]["players"][nick].get("hasdrawn") == True:
                    irc.reply(
                        "%s draws a card, and plays it; It's a %s (%s cards left in"
                        " hand)."
                        % (nick, card, ncards),
                        to=channel,
                    )
                    self.game[table]["players"][nick]["hasdrawn"] = False
                else:
                    irc.reply(
                        "%s played %s (%s cards left in hand)." % (nick, card, ncards),
                        to=channel,
                    )

            ncards = len(self.game[table]["players"][nick]["hand"])
            if ncards == 0:
                irc.reply(
                    "The game is over. " + ircutils.bold("%s wins!" % nick), to=channel
                )
                self.game[table]["phase"] = "gameover"
                self._cleanup(table)
                return

            if "Reverse" in card:
                self.game[table]["direction"] *= -1

            turn = self.game[table]["turn"] + 1 * self.game[table]["direction"]
            if turn > nplayers - 1:
                turn = 0
            if turn < 0:
                turn = nplayers - 1
            self.game[table]["turn"] = turn

            if "Draw Two" in card or "Draw 4" in card:
                ndrawcards = 2
                if "Draw 4" in card:
                    ndrawcards = 4
                drawplayer = list(self.game[table]["players"].keys())[
                    self.game[table]["turn"]
                ]
                for n in range(ndrawcards):
                    c = self.game[table]["deck"].pop(
                        random.randint(0, len(self.game[table]["deck"]) - 1)
                    )
                    self.game[table]["players"][drawplayer]["hand"].append(c)
                ncards = len(self.game[table]["players"][drawplayer]["hand"])
                irc.reply(
                    "%s draws %s cards (%s cards in hand)."
                    % (drawplayer, ndrawcards, ncards),
                    to=channel,
                )

            # Skip turn
            if (
                "Skip" in card
                or "Draw Two" in card
                or "Draw 4" in card
                or ("Reverse" in card and nplayers == 2)
            ):
                turn = self.game[table]["turn"] + 1 * self.game[table]["direction"]
                if turn > nplayers - 1:
                    turn = 0
                if turn < 0:
                    turn = nplayers - 1
                self.game[table]["turn"] = turn

            for n in list(self.game[table]["players"].keys()):
                self._uno_tell_status(irc, n)
            self._uno_do_cpu(irc, table)
        else:
            irc.reply("Error: game not running")

    play = wrap(play, ["text"])

    def setoption(self, irc, msg, args, channel, text, value):
        """<option> <value>
        Changes an option for UNO game. You can view the
        options for the current channel with the "uno showoptions" command.
        """
        try:
            self._read_options(irc)
        except:
            pass
        if value.lower() == "true":
            value = True
        elif value.lower() == "false":
            value = False
        elif value.lower() == "unset":
            if text in self.channeloptions:
                irc.reply("Set %s %s-->(unset)" % (text, self.channeloptions[text]))
                del self.channeloptions[text]
                try:
                    self._write_options(irc)
                except:
                    irc.reply("Failed to write options to file. :(")
            else:
                irc.reply("%s was already unset." % text)
            return
        if text in self.channeloptions:
            irc.reply("Set %s %s-->%s" % (text, self.channeloptions[text], value))
            self.channeloptions[text] = value
        else:
            irc.reply("Set %s (unset)-->%s" % (text, value))
            self.channeloptions[text] = value
        try:
            self._write_options(irc)
        except:
            irc.reply("Failed to write options to file. :(")

    setoption = wrap(
        setoption, [("checkChannelCapability", "op"), "something", "something"]
    )

    def showoptions(self, irc, msg, args):
        """
        Shows options for UNO game for the current channel.
        """
        try:
            self._read_options(irc)
        except:
            pass
        txt = ", ".join(
            [
                "=".join([str(i) for i in item])
                for item in list(self.channeloptions.items())
            ]
        )
        irc.reply(txt)

    showoptions = wrap(showoptions)

    def _cleanup(self, table):
        self.game[table] = {}
        self.game[table]["players"] = {}
        self.game[table]["phase"] = ""

    def _getopentable(self):
        openslot = [i for i in range(len(self.game)) if not self.game[i].get("phase")]
        if len(openslot) == 0:
            return None
        else:
            return openslot[0]

    def _getcurrenttables(self):
        slot = [i for i in range(len(self.game)) if self.game[i].get("phase")]
        return slot

    def _gettablefromnick(self, n):
        tables = self._getcurrenttables()
        if not tables:
            return None
        for table in tables:
            if n.lower() in [
                x.lower() for x in list(self.game[table]["players"].keys())
            ]:
                return table
        return None

    def _read_options(self, irc):
        network = irc.network.replace(" ", "_")
        channel = irc.msg.args[0]
        # irc.reply('test: %s.%s.options' % (irc.network, irc.msg.args[0] ))

        f = "%s%s.%s.options" % (self.dataPath, network, channel)
        if os.path.isfile(f):
            inputfile = open(f, "rb")
            self.channeloptions = pickle.load(inputfile)
            inputfile.close()
        else:
            # Use defaults
            channeloptions = {}
            channeloptions["allow_game"] = False
            channeloptions["debug"] = False
            channeloptions["use_queue"] = True
            channeloptions["nplayers"] = 10
            channeloptions["maxbots"] = 9
            channeloptions["use_colors"] = True
            channeloptions["use_notice"] = True
        return

    def _write_options(self, irc):
        network = irc.network.replace(" ", "_")
        channel = irc.msg.args[0]
        outputfile = open("%s%s.%s.options" % (self.dataPath, network, channel), "wb")
        pickle.dump(self.channeloptions, outputfile)
        outputfile.close()

    def doNick(self, irc, msg):
        oldNick = msg.nick
        newNick = msg.args[0]
        table = self._gettablefromnick(oldNick)
        if table == None:
            return
        self.game[table]["players"][newNick] = self.game[table]["players"][oldNick]
        del self.game[table]["players"][oldNick]

    def doQuit(self, irc, msg):
        nick = msg.nick
        table = self._gettablefromnick(nick)
        if table == None:
            return
        self._leavegame(irc, msg, nick)
        self._uno_do_cpu(irc, table)  # only works if game type is uno

    def doPart(self, irc, msg):
        # self.log.info('doPart debug: msg.args[0]=%s, msg.args[1]=%s, msg.command=%s, msg.nick=%s' % (msg.args[0], msg.args[1], msg.command, msg.nick))
        nick = msg.nick
        table = self._gettablefromnick(nick)
        if table == None:
            return
        if msg.args[0] == self.game[table]["channel"]:
            self._leavegame(irc, msg, nick)
            self._uno_do_cpu(irc, table)  # only works if game type is uno

    def doKick(self, irc, msg):
        (channel, nicks) = msg.args[:2]
        nicks = nicks.split(",")
        for nick in nicks:
            table = self._gettablefromnick(nick)
            if table != None:
                self._leavegame(irc, msg, nick)
                self._uno_do_cpu(irc, table)  # only works if game type is uno

    def _sendMsg(self, irc, msg):
        if self.channeloptions["use_queue"]:
            irc.queueMsg(msg)
        else:
            irc.sendMsg(msg)
        irc.noReply()

    def reply(
        self,
        irc,
        text,
        action=False,
        notice=False,
        private=False,
        prefixNick=False,
        to="",
        fast=False,
    ):
        table = self._gettablefromnick(to)
        if table == None:
            # hopefully it's a valid channel
            pass
        else:
            if self.game[table]["players"][to].get("fake"):
                if self.channeloptions["debug"]:
                    text = "(to %s): %s" % (to, text)
                    text = ircutils.mircColor(text, fg=14)
                    to = self.game[table]["channel"]
                else:
                    # No need to show cpu actions anywhere if debug is false.
                    return

        if action == True or fast == False:
            irc.reply(
                text,
                action=action,
                notice=notice,
                private=private,
                prefixNick=prefixNick,
                to=to,
            )
        else:
            if (prefixNick) and ("#" not in to):
                text = "%s: %s" % (to, text)
            m = ircmsgs.privmsg(to, text)
            self._sendMsg(irc, m)


Class = UNO

# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
