class BlackJackGame(object):
    """Representation of a game of Black Jack - The equivalent of a Black Jack casino table."""
    MAX_PLAYERS = 5

    def __init__(self, gametype=None, game_id=None, lang_id="en"):
        self.logger = logging.getLogger(__name__)
        self.__on_start_handlers = []
        self.__on_stop_handlers = []
        self.list_won = []
        self.list_tie = []
        self.list_lost = []
        self.bets_active = True
        self._current_player = 0
        self.players = []
        self.running = False
        self.deck = Deck(lang_id)
        self.dealer = Dealer("Dealer")

        self.type = gametype or BlackJackGame.Type.SINGLEPLAYER
        self.id = game_id
        self.lang_id = lang_id

    class Type(Enum):
        """Enum describing the type of a game"""
        SINGLEPLAYER = 1
        MULTIPLAYER_GROUP = 2
        MULTIPLAYER_DIRECT = 3

    def register_on_start_handler(self, func):
        """
        Registers a callback function as on_start_handler.
        :param func: Function reference that will be called when the game is starting. It receives a reference to the game as parameter.
        :return:
        """
        self.__on_start_handlers.append(func)

    def register_on_stop_handler(self, func):
        """
        Registers a callback function as on_stop_handler.
        :param func: Function reference that will be called when the game is stopping. It receives a reference to the game as parameter.
        :return:
        """
        self.__on_stop_handlers.append(func)

    # noinspection PyBroadException
    def _run_handlers(self, handlers):
        """
        Call all handlers of the passed 'handlers' list
        :param handlers: List of handlers (e.g. __on_start_handlers, __on_stop_handlers)
        :return:
        """
        for handler in handlers:
            try:
                handler(self)
            except Exception as e:
                self.logger.error("Couldn't run handler '{0}' - The following exception occurred: '{1}'".format(handler, e))

    def start(self, user_id):
        """
        Sets up the players' and the dealer's hands
        :return:
        """
        if self.running:
            raise errors.GameAlreadyRunningException

        if (self.type == BlackJackGame.Type.SINGLEPLAYER and len(self.players) < 1) or \
                (self.type in [BlackJackGame.Type.MULTIPLAYER_DIRECT, BlackJackGame.Type.MULTIPLAYER_GROUP] and len(self.players) < 2):
            raise errors.NotEnoughPlayersException

        if user_id != self.players[0].user_id:
            raise errors.InsufficientPermissionsException

        self.running = True

        # Give every player and the dealer 2 cards
        for player in (self.players + [self.dealer]) * 2:
            card = self.deck.pick_one_card()
            player.give_card(card)

        self._run_handlers(self.__on_start_handlers)

    def stop(self, user_id):
        """
        Stops the game, if the user has sufficient permissions
        :param user_id: The user_id of the user requesting to stop the game
        :return:
        """
        if user_id != -1 and user_id != self.players[0].user_id:
            raise errors.InsufficientPermissionsException
        self.running = False
        self._run_handlers(self.__on_stop_handlers)

    def get_current_player(self):
        return self.players[self._current_player]

    def add_player(self, user_id, first_name):
        """
        Add a new player to the game as long as it didn't start yet
        :param user_id: The user_id of the player
        :param first_name: The player's first_name
        :return:
        """
        if self.running:
            raise errors.GameAlreadyRunningException("Not adding player, the game is already on!")

        if user_id in [player.user_id for player in self.players]:
            raise errors.PlayerAlreadyExistingException

        if len(self.players) >= self.MAX_PLAYERS:
            raise errors.MaxPlayersReachedException

        player = Player(user_id, first_name)
        self.logger.debug("Adding new player: {}!".format(player))
        self.players.append(player)

        if self.type == BlackJackGame.Type.SINGLEPLAYER:
            self.logger.debug("Starting game now, because it's a singleplayer game")
            self.start(user_id)

    def draw_card(self):
        """
        Draw one card and add it to the player's hand
        :return:
        """
        if not self.running:
            raise errors.GameNotRunningException("The game must be started before you can draw cards")

        player = self.get_current_player()
        card = self.deck.pick_one_card()

        player.give_card(card)

        if player.cardvalue > 21:
            self.logger.debug("While giving user {} the card {}, they busted.".format(player.first_name, card))
            raise errors.PlayerBustedException
        if player.cardvalue == 21:
            raise errors.PlayerGot21Exception

    def next_player(self):
        """
        Marks the next player as active player. If all players are finished, go to dealer's turn
        :return:
        """
        if not self.running:
            raise errors.GameNotRunningException("The game must be started before it's the next player's turn")

        if self._current_player >= len(self.players) - 1:
            self.logger.debug("Next player is dealer!")
            self._current_player = -1
            self.dealers_turn()
            raise errors.NoPlayersLeftException

        self.get_current_player().turn_over = True
        self._current_player += 1

    def dealers_turn(self):
        if not self.running:
            raise errors.GameNotRunningException("The game must be started before it's the dealer's turn")

        while self.dealer.cardvalue <= 16:
            card = self.deck.pick_one_card()
            self.dealer.give_card(card)

        self.dealer.turn_over = True
        self.running = False

    def evaluation(self):
        """
        Check which player won and which lost. Also calculate profits if applicable
        :return:
        """
        list_busted = [player for player in self.players if player.busted]
        list_not_busted = [player for player in self.players if not player.busted]

        list_won = []
        list_tie = []
        list_lost = []

        if self.dealer.busted:
            for player in list_not_busted:
                if player.has_blackjack():
                    # BlackJack pays 3:2 -> return bet + 1.5 x bet
                    player.pay(factor=2.5)
                else:
                    player.pay(factor=2)
                list_won.append(player)

        elif self.dealer.has_blackjack():
            for player in list_not_busted:
                if player.has_blackjack():
                    player.pay(1)
                    list_tie.append(player)
                else:
                    list_lost.append(player)
        elif self.dealer.cardvalue <= 21:
            for player in list_not_busted:
                if player.cardvalue > self.dealer.cardvalue:
                    player.pay(2)
                    list_won.append(player)
                elif player.cardvalue == self.dealer.cardvalue:
                    player.pay(1)
                    list_tie.append(player)
                elif player.cardvalue < self.dealer.cardvalue:
                    list_lost.append(player)

        list_lost.extend(list_busted)

        self.list_won = sorted(list_won, key=lambda player: player.cardvalue, reverse=True)
        self.list_tie = sorted(list_tie, key=lambda player: player.cardvalue, reverse=True)
        self.list_lost = sorted(list_lost, key=lambda player: player.cardvalue, reverse=True)

        return self.list_won, self.list_tie, self.list_lost

    def get_player_list(self):
        return "\n".join(["👤{}".format(p.first_name) for p in self.players]) def remove_inline_keyboard(update, context):
    """
    Removes the inline keyboard for a given message
    :param update: PTB update object
    :param context: PTB context object
    :return:
    """
    if update.effective_message.from_user.id == context.bot.id:
        try:
            update.effective_message.edit_reply_markup(reply_markup=None)
        except Exception:
            # When the message already has no reply markup, simply ignore the exception
            # We can't check for message.reply_markup, because it might have been removed earlier
            pass def get_start_keyboard(lang_id):
    translator = Translator(lang_id)
    start_button = InlineKeyboardButton(text=translator("inline_keyboard_start"), callback_data="start")
    return InlineKeyboardMarkup(inline_keyboard=[[start_button]]) def html_mention(user_id, first_name):
    """Generate HTML code that will mention a user in a group"""
    first_name = html.escape(first_name)
    return '<a href="tg://user?id={}">{}</a>'.format(user_id, first_name) def get_game_keyboard(game_id, lang_id):
    """Generates a game keyboard translated into the given language
    :param game_id: A unique identifier for each game
    :param lang_id: The language identifier for a specific chat
    :return:
    """
    translator = Translator(lang_id)
    one_more_button = InlineKeyboardButton(text=translator("inline_keyboard_hit"), callback_data="hit_{}".format(game_id))
    no_more_button = InlineKeyboardButton(text=translator("inline_keyboard_stand"), callback_data="stand_{}".format(game_id))
    stop_button = InlineKeyboardButton(text="Stop", callback_data="stop_{}".format(game_id))
    return InlineKeyboardMarkup(inline_keyboard=[[one_more_button, no_more_button]]) def get_join_keyboard(game_id, lang_id):
    """
    Generates a join keyboard translated into the given language
    :param game_id: A unique identifier for each game
    :param lang_id: The language identifier for a specific chat
    :return:
    """
    translator = Translator(lang_id)
    join_button = InlineKeyboardButton(text=translator("inline_keyboard_join"), callback_data="join_{}".format(game_id))
    start_button = InlineKeyboardButton(text=translator("inline_keyboard_start"), callback_data="start_{}".format(game_id))
    return InlineKeyboardMarkup(inline_keyboard=[[join_button, start_button]]) def needs_active_game(func):
    """Decorator for making sure a game exists for a certain chat"""

    @functools.wraps(func)
    def wrapper(update, context, *args, **kwargs):
        chat = update.effective_chat
        lang_id = Database().get_lang_id(chat.id)
        translator = Translator(lang_id=lang_id)

        try:
            game = GameStore().get_game(chat.id)
        except NoActiveGameException:
            remove_inline_keyboard(update, context)
            update.effective_message.reply_text(translator("mp_no_created_game_callback"))
            return

        return func(update, context)

    return wrapper class NoActiveGameException(Exception):
    pass class GameStore(object):
    _instance = None
    _initialized = False

    def __new__(cls):
        if GameStore._instance is None:
            GameStore._instance = super(GameStore, cls).__new__(cls)
        return GameStore._instance

    def __init__(self):
        if not self._initialized:
            self._chat_dict = {}
            self._game_dict = {}
            self.logger = logging.getLogger(__name__)
            self._initialized = True

    @staticmethod
    def _generate_id():
        return randint(1000000, 9999999)

    def add_game(self, chat_id, game):
        if self.has_game(chat_id):
            raise Exception

        game.id = self._generate_id()
        while self._game_dict.get(game.id, None):
            game.id = self._generate_id()

        self.logger.info("Adding game with id {}".format(game.id))
        game.register_on_stop_handler(self._game_stopped_callback)
        self._chat_dict[chat_id] = game
        self._game_dict[game.id] = chat_id

    def get_game(self, chat_id):
        """

        :param chat_id:
        :return:
        """
        game = self._chat_dict.get(chat_id)
        if game is None:
            raise NoActiveGameException
        return game

    def has_game(self, chat_id):
        return chat_id in self._chat_dict

    def remove_game(self, chat_id):
        """
        Removes the game of a specific chat from the store
        :param chat_id:
        :return:
        """
        if chat_id == -1:
            return

        try:
            game = self._chat_dict.pop(chat_id)
            self._game_dict.pop(game.id)
            self.logger.debug("Removing game for {} ({})".format(chat_id, game.id))
        except KeyError:
            self.logger.error("Can't remove game for {}, because there is no such game!".format(chat_id))

    def _game_stopped_callback(self, game):
        """
        Callback to remove game from the GameStore
        :param game:
        :return:
        """
        for player in game.players:
            database.statistics.add_game_played(player.user_id)
            if player in game.list_won:
                database.statistics.set_game_won(player.user_id)
        self.remove_game(self._game_dict[game.id])

        self.logger.debug("Current games: {}".format(len(self._chat_dict))) class Translator(object):

    def __init__(self, lang_id):
        self.lang_id = lang_id or "en"

    def translate(self, string):
        return translate(string, self.lang_id)

    def __call__(self, string):
        """
        Wrapper for the translate method. Calling the object directly
        instead of the translate method will leave you with more tidy code
        """
        return self.translate(string) def get_cards_string(player, lang_id):
    """Returns the translated string representation of a player's hand"""
    if player.is_dealer and not player.turn_over:
        # We need to check, if we are allowed to display all of the dealer's cards already
        # Only the dealer's first card is shown until the dealer finished their turn
        return "{}  •  [❔]".format(get_card_string(player.cards[0], lang_id))
    else:
        return '  •  '.join(get_card_string(card, lang_id) for card in player.cards) class Database(object):
    dir_path = os.path.dirname(os.path.abspath(__file__))

    _instance = None
    _initialized = False
    _banned_users = set()

    def __new__(cls):
        if not Database._instance:
            Database._instance = super(Database, cls).__new__(cls)
        return Database._instance

    def __init__(self):
        if self._initialized:
            return

        database_path = os.path.join(self.dir_path, "users.db")
        self.logger = logging.getLogger(__name__)

        if not os.path.exists(database_path):
            self.logger.debug("File '{}' does not exist! Trying to create one.".format(database_path))
        try:
            self.create_database(database_path)
        except Exception:
            self.logger.error("An error has occurred while creating the database!")

        self.connection = sqlite3.connect(database_path)
        self.connection.row_factory = sqlite3.Row
        self.connection.text_factory = lambda x: str(x, 'utf-8', "ignore")
        self.cursor = self.connection.cursor()

        self.load_banned_users()

        self._initialized = True

    @staticmethod
    def create_database(database_path):
        """
        Create database file and add admin and users table to the database
        :param database_path:
        :return:
        """
        open(database_path, 'a').close()

        connection = sqlite3.connect(database_path)
        connection.text_factory = lambda x: str(x, 'utf-8', "ignore")
        cursor = connection.cursor()

        cursor.execute("CREATE TABLE IF NOT EXISTS 'admins' "
                       "('user_id' INTEGER NOT NULL,"
                       "'first_name' TEXT,"
                       "'username' TEXT,"
                       "PRIMARY KEY('user_id'));")

        cursor.execute("CREATE TABLE IF NOT EXISTS 'users'"
                       "('user_id' INTEGER NOT NULL,"
                       "'first_name' TEXT,"
                       "'last_name' TEXT,"
                       "'username' TEXT,"
                       "'games_played' INTEGER DEFAULT 0,"
                       "'games_won' INTEGER DEFAULT 0,"
                       "'games_tie' INTEGER DEFAULT 0,"
                       "'last_played' INTEGER DEFAULT 0,"
                       "'banned' INTEGER DEFAULT 0,"
                       "PRIMARY KEY('user_id'));")

        cursor.execute("CREATE TABLE IF NOT EXISTS 'chats'"
                       "('chat_id' INTEGER NOT NULL,"
                       "'lang_id' TEXT NOT NULL DEFAULT 'en',"
                       "PRIMARY KEY('chat_id'));")
        connection.commit()
        connection.close()

    def load_banned_users(self):
        """Loads all banned users from the database into a list"""
        self.cursor.execute("SELECT user_id FROM users WHERE banned=1;")
        result = self.cursor.fetchall()

        if not result:
            return

        for row in result:
            print(int(row["user_id"]))
            self._banned_users.add(int(row["user_id"]))

    def get_banned_users(self):
        """Returns a list of all banned user_ids"""
        return self._banned_users

    def get_user(self, user_id):
        self.cursor.execute("SELECT user_id, first_name, last_name, username, games_played, games_won, games_tie, last_played, banned"
                            " FROM users WHERE user_id=?;", [str(user_id)])

        result = self.cursor.fetchone()
        if not result or len(result) == 0:
            return None
        return result

    def is_user_banned(self, user_id):
        """Checks if a user was banned by the admin of the bot from using it"""
        # user = self.get_user(user_id)
        # return user is not None and user[8] == 1
        return int(user_id) in self._banned_users

    def ban_user(self, user_id):
        """Bans a user from using a the bot"""
        self.cursor.execute("UPDATE users SET banned=1 WHERE user_id=?;", [str(user_id)])
        self.connection.commit()
        self._banned_users.add(int(user_id))

    def unban_user(self, user_id):
        """Unbans a user from using a the bot"""
        self.cursor.execute("UPDATE users SET banned=0 WHERE user_id=?;", [str(user_id)])
        self.connection.commit()
        self._banned_users.remove(int(user_id))

    def get_recent_players(self):
        one_day_in_secs = 60 * 60 * 24
        current_time = int(time())
        self.cursor.execute("SELECT user_id FROM users WHERE last_played>=?;", [current_time - one_day_in_secs])

        return self.cursor.fetchall()

    def get_played_games(self, user_id):
        self.cursor.execute("SELECT games_played FROM users WHERE user_id=?;", [str(user_id)])

        result = self.cursor.fetchone()

        if not result:
            return 0

        if len(result) > 0:
            return int(result[0])
        else:
            return 0

    def get_all_users(self):
        self.cursor.execute("SELECT rowid, * FROM users;")
        return self.cursor.fetchall()

    @Cache(timeout=60)
    def get_admins(self):
        self.cursor.execute("SELECT user_id from admins;")
        admins = self.cursor.fetchall()
        admin_list = []
        for admin in admins:
            admin_list.append(admin[0])
        return admin_list

    @Cache(timeout=120)
    def get_lang_id(self, chat_id):
        self.cursor.execute("SELECT lang_id FROM chats WHERE chat_id=?;", [str(chat_id)])
        result = self.cursor.fetchone()
        if result:
            if result[0]:
                # Make sure that the database stored an actual value and not "None"
                return result[0]
        return "en"

    def set_lang_id(self, chat_id, lang_id):
        if lang_id is None:
            lang_id = "en"
        Cache().invalidate_lang_cache(chat_id)
        try:
            self.cursor.execute("INSERT INTO chats (chat_id, lang_id) VALUES(?, ?);", [chat_id, lang_id])
        except sqlite3.IntegrityError:
            self.cursor.execute("UPDATE chats SET lang_id = ? WHERE chat_id = ?;", [lang_id, chat_id])
        self.connection.commit()

    def add_user(self, user_id, lang_id, first_name, last_name, username):
        if self.is_user_saved(user_id):
            return
        self._add_user(user_id, lang_id, first_name, last_name, username)

    def _add_user(self, user_id, lang_id, first_name, last_name, username):
        try:
            self.cursor.execute("INSERT INTO users VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0);", [str(user_id), first_name, last_name, username])
            self.cursor.execute("INSERT INTO chats VALUES (?, ?);", [str(user_id), lang_id])
            self.connection.commit()
        except sqlite3.IntegrityError:
            return

    def set_games_won(self, games_won, user_id):
        self.cursor.execute("UPDATE users SET games_won = ? WHERE user_id = ?;", [games_won, str(user_id)])
        self.connection.commit()

    def set_games_played(self, games_played, user_id):
        self.cursor.execute("UPDATE users SET games_played = ? WHERE user_id = ?;", [games_played, str(user_id)])
        self.connection.commit()

    def set_last_played(self, last_played, user_id):
        self.cursor.execute("UPDATE users SET last_played = ? WHERE user_id = ?;", [last_played, str(user_id)])
        self.connection.commit()

    def is_user_saved(self, user_id):
        self.cursor.execute("SELECT rowid, * FROM users WHERE user_id=?;", [str(user_id)])

        result = self.cursor.fetchall()
        if len(result) > 0:
            return True
        else:
            return False

    def user_data_changed(self, user_id, first_name, last_name, username):
        self.cursor.execute("SELECT * FROM users WHERE user_id=?;", [str(user_id)])

        result = self.cursor.fetchone()

        # check if user is saved
        if result:
            if result[2] == first_name and result[3] == last_name and result[4] == username:
                return False
            return True
        else:
            return True

    def update_user_data(self, user_id, first_name, last_name, username):
        self.cursor.execute("UPDATE users SET first_name=?, last_name=?, username=? WHERE user_id=?;", [first_name, last_name, username, str(user_id)])
        self.connection.commit()

    def reset_stats(self, user_id):
        self.cursor.execute("UPDATE users SET games_played='0', games_won='0', games_tie='0', last_played='0' WHERE user_id=?;", [str(user_id)])
        self.connection.commit()

    def close_conn(self):
        self.connection.close() def create_game(update, context):
    """Create a new game instance for the chat of the user"""
    user = update.effective_user
    chat = update.effective_chat
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    # Create either a singleplayer or multiplayer game
    if chat.type == "private":
        game_type = BlackJackGame.Type.SINGLEPLAYER
    elif chat.type == "group" or chat.type == "supergroup":
        game_type = BlackJackGame.Type.MULTIPLAYER_GROUP
    else:
        logger.error("Chat type '{}' not supported!".format(chat.type))
        return

    game = BlackJackGame(gametype=game_type)
    game.add_player(user_id=user.id, first_name=user.first_name)
    GameStore().add_game(chat.id, game)

    # TODO currently the game starts instantly - this should change with multiplayer rooms
    if game.type == BlackJackGame.Type.SINGLEPLAYER:
        update.effective_message.reply_text(translator("game_starts_now").format("", get_cards_string(game.dealer, lang_id)))
        players_turn(update, context)
    else:
        text = translator("mp_request_join").format(game.get_player_list())
        update.effective_message.reply_text(text=text, reply_markup=get_join_keyboard(game.id, lang_id)) def players_turn(update, context):
    """Execute a player's turn"""
    chat = update.effective_chat
    game = GameStore().get_game(chat.id)
    player = game.get_current_player()
    user_mention = html_mention(user_id=player.user_id, first_name=player.first_name)

    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    logger.info("Player's turn: {}".format(player))
    player_cards = get_cards_string(player, lang_id)

    # Check if player already has 21 or a BlackJack before their move. If so, automatically jump to the next player.
    # We need reply_text here, because we must send a new message (this is the first message for the player)!
    if player.has_blackjack():
        text = (translator("your_cards_are") + "\n\n" + translator("got_blackjack")).format(user_mention, player.cardvalue, player_cards)
        update.effective_message.reply_text(text=text, parse_mode=ParseMode.HTML, reply_markup=None)
        next_player(update, context)
    elif player.cardvalue == 21:
        text = (translator("your_cards_are") + "\n\n" + translator("got_21")).format(user_mention, player.cardvalue, player_cards)
        update.effective_message.reply_text(text=text, parse_mode=ParseMode.HTML, reply_markup=None)
        next_player(update, context)
    else:
        text = translator("your_cards_are").format(user_mention, player.cardvalue, player_cards)
        update.effective_message.reply_text(text=text, parse_mode=ParseMode.HTML, reply_markup=get_game_keyboard(game.id, lang_id)) @needs_active_game
def next_player(update, context):
    chat = update.effective_chat
    user = update.effective_user
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    game = GameStore().get_game(chat.id)

    try:
        if user.id != game.get_current_player().user_id:
            update.callback_query.answer(translator("mp_not_your_turn_callback").format(user.first_name))
            return

        remove_inline_keyboard(update, context)
        game.next_player()
    except NoPlayersLeftException:
        # TODO merge messages
        update.effective_message.reply_text(translator("dealers_cards_are").format(game.dealer.cardvalue,
                                                                                   get_cards_string(game.dealer, lang_id)),
                                            parse_mode=ParseMode.HTML)
        evaluation_string = generate_evaluation_string(game, lang_id)

        newgame_button = InlineKeyboardButton(text=translator("inline_keyboard_newgame"), callback_data="newgame")
        keyboard = InlineKeyboardMarkup(inline_keyboard=[[newgame_button]])
        update.effective_message.reply_text(evaluation_string, reply_markup=keyboard)
        game.stop(-1)
        return

    players_turn(update, context) def is_button_affiliated(update, context, game, lang_id):
    try:
        game_id = int(update.callback_query.data.split("_")[1])
        if game.id != game_id:
            update.callback_query.answer("Sorry, the button you pressed is not for your current game!")
            remove_inline_keyboard(update, context)
            return False
        return True
    except Exception as e:
        logger.error("Something unexpected happened while checking button affiliation: {} - {}".format(update.callback_query.data, e))
        return False
from telegram.parsemode import ParseMode
from blackjack.game import BlackJackGame
from blackjackbot.commands.util import html_mention, get_game_keyboard, get_join_keyboard, get_start_keyboard, remove_inline_keyboard
from blackjackbot.commands.util.decorators import needs_active_game
from blackjackbot.errors import NoActiveGameException
from blackjackbot.gamestore import GameStore
from blackjackbot.lang import Translator
from blackjackbot.util import get_cards_string
from database import Database
from .functions import create_game, players_turn, next_player, is_button_affiliated
import blackjack.errors as errors
# -*- coding: utf-8 -*-




def start_cmd(update, context):
    """Handles messages contianing the /start command. Starts a game for a specific user"""
    user = update.effective_user
    chat = update.effective_chat
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    Database().add_user(user.id, user.language_code, user.first_name, user.last_name, user.username)

    try:
        GameStore().get_game(update.effective_chat.id)
        # TODO notify user that there is a running game already?
    except NoActiveGameException:
        # If there is no game, we create one
        create_game(update, context)


def start_callback(update, context):
    """Starts a game that has been created already"""
    user = update.effective_user
    chat = update.effective_chat
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    try:
        game = GameStore().get_game(update.effective_chat.id)

        if not is_button_affiliated(update, context, game, lang_id):
            return
    except NoActiveGameException:
        update.callback_query.answer(translator("mp_no_created_game_callback"))
        remove_inline_keyboard(update, context)
        return

    try:
        game.start(user.id)
        update.callback_query.answer(translator("mp_starting_game_callback"))
    except errors.GameAlreadyRunningException:
        update.callback_query.answer(translator("mp_game_already_begun_callback"))
        return
    except errors.NotEnoughPlayersException:
        update.callback_query.answer(translator("mp_not_enough_players_callback"))
        return
    except errors.InsufficientPermissionsException:
        update.callback_query.answer(translator("mp_only_creator_start_callback").format(user.first_name))
        return

    if game.type != BlackJackGame.Type.SINGLEPLAYER:
        players_are = translator("mp_players_are")
        for player in game.players:
            players_are += "👤{}\n".format(player.first_name)
        players_are += "\n"
    else:
        players_are = ""

    update.effective_message.edit_text(translator("game_starts_now").format(players_are, get_cards_string(game.dealer, lang_id)))
    players_turn(update, context)


@needs_active_game
def stop_cmd(update, context):
    """Stops a game for a specific user"""
    user = update.effective_user
    chat = update.effective_chat
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    game = GameStore().get_game(chat.id)

    user_id = user.id
    try:
        if chat.type == "group" or chat.type == "supergroup":
            # If yes, get the chat admins
            admins = context.bot.get_chat_administrators(chat_id=chat.id)
            # if user.id in chat admin IDs, let them end the game with admin powers
            if user.id in [x.user.id for x in admins]:
                user_id = -1

        game.stop(user_id)
        update.effective_message.reply_text(translator("game_ended"))
    except errors.InsufficientPermissionsException:
        update.effective_message.reply_text(translator("mp_only_creator_can_end"))


@needs_active_game
def join_callback(update, context):
    """
    CallbackQueryHandler callback for the 'join' inline button. Adds the executing player to the game of the specific chat
    """
    user = update.effective_user
    chat = update.effective_chat
    lang_id = Database().get_lang_id(chat.id)
    translator = Translator(lang_id=lang_id)

    game = GameStore().get_game(chat.id)


    if not is_button_affiliated(update, context, game, lang_id):