###
# Copyright (c) 2014, KgBot
# 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 supybot.schedule as schedule
import json
import random
import time
import os

import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
try:
    from supybot.i18n import PluginInternationalization
    _ = PluginInternationalization('BlackJack')
except:
    # Placeholder that allows to run the plugin on a bot
    # without the i18n module
    _ = lambda x:x

class BlackJack(callbacks.Plugin):
    """Add the help for "@plugin help BlackJack" here
    This should describe *how* to use this plugin."""
    threaded = True
    
    def __init__(self, irc):
        self.__parent = super(BlackJack, self)
        self.__parent.__init__(irc)
        # Dictionary of players, holds important things about each player.
        self.players = {}
        # List of scheduled games
        self.events = []
        self.minStake = 10
        self.maxStake = 50
        
    def _isScheduled(self, name):
        """ Checks to see if player is already playing game."""
        if name in self.events:
            return True
        else:
            return False
        
    def _waitingPlayerAction(self, player):
        """ Checks to see if player has got his first 2 cards and now we're waiting for him to hit/stand or double."""
        if player in self.players.keys() and self.players[player]["waitingAction"] == True:
            return True
        else:
            return False
        
    def _deal(self, who, player):
        _allowed_whos = ["player", "bank"]
        if who in _allowed_whos:
            cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"]
            card = random.choice(cards)
            # Because we J/Q/K are not numbers we must convert them to 10's, but this is not nice, and we must implement
            # 2 types of cards here, one that will be answered to player and one that will be added to score
            if card == "J" or card == "Q" or card == "K":
                card = 10
            if who == "player":
                if card == 1:
                # If 11 + score is bigger then 21 1 will be 1
                    if self.players[player]["score"] + 11 > 21:
                        card = 1
                    # If 1 + score is less then 21 1 will be 11
                    else:
                        card = 11
                self.players[player]["score"] = self.players[player]["score"] + card
                # Returns card because we might want to tell the player which card he's got.
                return card
            else:
                if card == 1:
                    if self.players[player]["bankScore"] + 11 > 21:
                        card = 1
                    else:
                        card = 11
                self.players[player]["bankScore"] = self.players[player]["bankScore"] + card
                return card
            
    def _finishGame(self, irc, player):
        self.players[player]["waitingAction"] = False
        playerScore = self.players[player]["score"]
        bankScore = self.players[player]["bankScore"]
        if playerScore == 21 and bankScore != 21:
            self._playerWins(irc, player)
        elif playerScore == 21 and bankScore == 21:
            irc.reply("This is push, nobody wins.")
            self._push(player)
        elif bankScore == 21 and playerScore != 21:
            self._bankWins(irc, player)
        elif playerScore < 21 and playerScore > bankScore:
            self._playerWins(irc, player)
        elif bankScore < 21 and bankScore > playerScore:
            self._bankWins(irc, player)
        elif playerScore == bankScore:
            irc.reply("This is push, nobody wins.")
            self._push(player)
        elif playerScore > 21 and bankScore < 21:
            self._bankWins(irc, player)
        else:
            self._playerWins(irc, player)
            
    def _removeScheduledGame(self, name):
        try:
            schedule.removeEvent(name)
            self.events.remove(name)
        except:
            try:
                self.events.remove(name)
            except:
                pass
            
    def _push(self, player):
        stake = self.players[player]["stake"]
        chips = Chips()
        chips._addChips(player, stake)
        game_name = "game_%s" % player
        self._removeScheduledGame(game_name)
            
    def _playerWins(self, irc, player, blackjack=False):
        game_name = "game_%s" % player
        if blackjack:
            amount = self._blackjackPrize(self.players[player]["stake"])
        else:
            amount = self.players[player]["stake"] * 2
        chips = Chips()
        chips._addChips(player, amount)
        irc.reply("Congrats, you have won and you got %s chips." % amount)
        self._removeScheduledGame(game_name)
        
    def _bankWins(self, irc, player):
        game_name = "game_%s" % player
        irc.reply("Sorry but you've lost this time.")
        self._removeScheduledGame(game_name)
            
    def _playerChips(self, player):
        """ Return number of chips that player has."""
        chipsClass = Chips()
        chips = chipsClass._getChips(player)
        return chips
    
    def _finishBank(self, irc, player):
        if player in self.players.keys():
            while(self.players[player]["bankScore"] < 17):
                card = self._deal("bank", player)
                irc.reply("Dealer has got \x02%s\x02, and his score is \x02%s\x02." % (card, self.players[player]["bankScore"]))
            self._finishGame(irc, player)
    
    def _addNewPlayer(self, player, stake):
        """ Adds new player and needed things to self.players dictionary."""
        self.players[player] = {}
        # This is set because this is the best way to check if player is playing when he wants to hit/double/stand
        self.players[player]["waitingAction"] = False
        # We must keep track of players score after each card is dealt
        self.players[player]["score"] = 0
        # Also we must keep track of bank score
        self.players[player]["bankScore"] = 0
        # And we need to know how much the player wants to stake
        self.players[player]["stake"] = stake
        # We must also remove initial stake from player, just like he put it on table
        chips = Chips()
        chips._removeChips(player, stake)
        
    def _scheduleNewGame(self, command, when, name, player, irc):
        # Here we're adding game name to the list of games, from where we check who's playing and who's not
        self.events.append(name)
        # Here we schedule the command to run after 10s and to finish game
        schedule.addEvent(command, time.time() + when, name, args=(irc, player))
        
    def _checkBlackjack(self, irc, player):
        """ Checking to see if enyone has blackjack after first two cards are dealt."""
        if player in self.players.keys():
            if self.players[player]["score"] == 21 and self.players[player]["bankScore"] != 21:
                self._playerWins(irc, player, True)
                self.players[player]["waitingAction"] = False
            elif self.players[player]["bankScore"] == 21 and self.players[player]["score"] != 21:
                self._bankWins(irc, player)
            else:
                return
            
    def _blackjackPrize(self, stake):
        # BlackJack prize is not same as regular prize. It is 3:1 so we must calculate it
        halfStake = stake / 2
        newStake = (stake * 2) + halfStake
        return newStake
    
    
    def blackjack(self, irc, msg, args, stake):
        """<stake> - amount of stake, between 10 and 50
        
        Starts a new game."""
        # Because nicks are case-insensitive we must turn nick to lower version.
        player = str(msg.nick).lower()
        # Nicks are uniqe and we will use nick for scheduling game.
        game_name = "game_%s" % player
        # Checks if player has any chips.
        chips = self._playerChips(player)
        chipsClass = Chips()
        # If this is True player is already playing blackjack and only one game is allowed per player.
        if self._isScheduled(game_name):
            irc.reply("You can play only one instance of the game in same time.")
        # If player does not have chips he can't play, logical.
        if chips == "NoChipsFile" or chips == False or chips == None:
            chipsClass._addChips(player, stake)
            chips = stake
        elif stake > chips:
            chipsClass._addChips(player, stake - chips)
            chips = stake
        if stake >= self.minStake and stake <= self.maxStake and stake <= chips:
            # Now is good time to add new player and actually start a game.
            self._addNewPlayer(player, stake)
            self._startNewGame(irc, player)
        else:
            irc.reply("Something is wrong with your stake, maybe it's too high or too low, or maybe you don't have enough chips.")
    blackjack = wrap(blackjack, ["int"])
    
    def _startNewGame(self, irc, player):
        # We deal first card to the player
        playerFirstCard = self._deal('player', player)
        # And we must tell the player which card he got
        irc.reply("Your first card is %s" % playerFirstCard)
        # We must also deal first card to the bank
        bankFirstCard = self._deal('bank', player)
        # And player shouldn't know which is the dealers first card
        irc.reply("Dealer has got his first card, face down.")
        # Now we deal second card to the player
        playerSecondCard = self._deal('player', player)
        # And we have to tell the player what's his second card, and also what's his score
        irc.reply("Your second card is %s, and you're score is %s" % (playerSecondCard, self.players[player]["score"]))
        # Bank just got it's second card
        bankSecondCard = self._deal('bank', player)
        # Player needs to know which is dealer's second card
        irc.reply("Dealer has got his second card %s, his score is now \x02%s\02.What are you gonna do, stand, hit or double. You have 20 seconds to decide." % (bankSecondCard, self.players[player]["bankScore"]))
        # Now we wait for user to hit/double/stand and our bot must know that he's waiting for player action
        self.players[player]["waitingAction"] = True
        # Each game will wait 10s for user input and then it'll end the game if nothing happens, and we must schedule it
        self._scheduleNewGame(self._finishBank, 20, "game_%s" % player, player, irc)
        # We must check if somebody has got blackjack after first 2 cards
        self._checkBlackjack(irc, player)
    
    def hit(self, irc, msg, args):
        """Takes no arguments
        
        Hit!!!"""
        player = str(msg.nick).lower()
        if self._waitingPlayerAction(player) == True:
            game_name = "game_%s" % player
            self._removeScheduledGame(game_name)
            card = self._deal('player', player)
            irc.reply("You've got %s, and your score is %s." % (card, self.players[player]["score"]))
            if self.players[player]["score"] > 21:
                irc.reply("You're busted.")
                self._bankWins(irc, player)
            else:
                self._scheduleNewGame(self._finishBank, 20, game_name, player, irc)
        else:
            irc.reply("You're not playing blackjack at this moment. Start a new game with +blackjack 50")
    hit = wrap(hit)
    
    def double(self, irc, msg, args):
        """Takes no arguments
        
        Double!!!!"""
        player = str(msg.nick).lower()
        if self._waitingPlayerAction(player) == True:
            stake = self.players[player]["stake"]
            chips = self._playerChips(player)
            if chips != False and chips != None and chips != "NoChipsFile" and chips >= stake:
                game_name = "game_%s" % player
                self._removeScheduledGame(game_name)
                chipsClass = Chips()
                chipsClass._removeChips(player, stake)
                self.players[player]["stake"] = stake * 2
                irc.reply("Your stake was %s, and now it is %s." % (stake, self.players[player]["stake"]))
                card = self._deal('player', player)
                irc.reply("You've got %s, and your score is %s" % (card, self.players[player]["score"]))
                if self.players[player]["score"] > 21:
                    irc.reply("You're busted.")
                    self._bankWins(irc, player)
                else:
                    self._finishBank(irc, player)
            else:
                irc.reply("You don't have enough chips for double stake.")
        else:
            irc.reply("You're not playing blackjack at this moment. Start a new game with +blackjack 50")
    double = wrap(double)
    
    def stand(self, irc, msg, args):
        """Takes no arguments.
        
        Stand!!!"""
        player = str(msg.nick).lower()
        if self._waitingPlayerAction(player) == True:
            self._finishBank(irc, player)
        else:
            irc.reply("You're not playing blackjack at this moment. Start a new game with +blackjack 50")
    stand = wrap(stand)

class Chips():
    def __init__(self):
        try:
            with open("{0}/local/chips.json".format(os.path.dirname(os.path.abspath(__file__))), "r") as chipsFile:
                self.players = json.load(chipsFile)
        except:
            self.players = False
            
    def _getChips(self, player):
        if self.players:
            if player in self.players.keys():
                try:
                    return self.players[player]["chips"]
                except:
                    return False
            else:
                return None
        else:
            return "NoChipsFile"
        
    def _addChips(self, player, amount):
        if self.players:
            if player in self.players.keys():
                self.players[player]["chips"] = self.players[player]["chips"] + amount
                self._saveChips()
            else:
                self.players[player] = {}
                self.players[player]["chips"] = amount
                self._saveChips()
        else:
            return "NoChipsFile"
        
    def _removeChips(self, player, amount):
        if self.players:
            if player in self.players.keys():
                self.players[player]["chips"] = self.players[player]["chips"] - amount
                self._saveChips()
                
    def _saveChips(self):
        with open("{0}/local/chips.json".format(os.path.dirname(os.path.abspath(__file__))), "w") as chips:
            json.dump(self.players, chips, indent=4)
    
    
Class = BlackJack


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

