"""
Storycraft - collaborative nonsense story writing
Copyright (C) 2011  Brian S. Stephan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import random
import re
import sqlite3
import time

from Module import Module

__author__ = "Brian S. Stephan"
__copyright__ = "Copyright 2011, Brian S. Stephan"
__credits__ = ["Brian S. Stephan", "Mike Bloy", "#lh"]
__license__ = "GPL"
__version__ = "0.1"
__maintainer__ = "Brian S. Stephan"
__email__ = "bss@incorporeal.org"
__status__ = "Development"

class Storycraft(Module):

    """Play Storycraft, the game of varying authors adding to a story mostly devoid of context.

    This is a game for people who like making up facts, insulting each other, telling
    insane stories, making in-jokes, and generally having a good time writing what can
    fairly only be described as pure nonsense. It does not assume that all players are
    available at any given time, so games may take a while, but the module allows for
    concurrency in the number of games running.
    """

    class StorycraftGame():

        """Track a storycraft game details."""

        pass

    class StorycraftPlayer():

        """Track a storycraft player details."""

        pass

    class StorycraftLine():

        """Track a storycraft line details."""

        pass

    class StorycraftSettings():

        """Track the settings for the server."""

        pass

    def __init__(self, irc, config, server):
        """Set up trigger regexes."""

        Module.__init__(self, irc, config, server)

        rootcommand = '^!storycraft'
        statuspattern = rootcommand + '\s+status(\s+(\d+)$|$)'
        newgamepattern = rootcommand + '\s+new\s+game(\s+with\s+config\s+(.*)$|$)'
        joingamepattern = rootcommand + '\s+join\s+game\s+(\d+)$'
        listgamespattern = rootcommand + '\s+list\s+games\s+(open|in progress|completed|my games|waiting for me)$'
        startgamepattern = rootcommand + '\s+start\s+game\s+(\d+)$'
        showlinepattern = rootcommand + '\s+game\s+(\d+)\s+show\s+line$'
        addlinepattern = rootcommand + '\s+game\s+(\d+)\s+add\s+line\s+(.*)$'
        exportpattern = rootcommand + '\s+export\s+(\d+)$'

        self.statusre = re.compile(statuspattern)
        self.newgamere = re.compile(newgamepattern)
        self.joingamere = re.compile(joingamepattern)
        self.listgamesre = re.compile(listgamespattern)
        self.startgamere = re.compile(startgamepattern)
        self.showlinere = re.compile(showlinepattern)
        self.addlinere = re.compile(addlinepattern)
        self.exportre = re.compile(exportpattern)

    def db_init(self):
        """Set up the database tables, if they don't exist."""

        version = self.db_module_registered(self.__class__.__name__)
        if (version == None):
            # have to create the database tables
            db = self.get_db()
            try:
                db.execute('''
                    CREATE TABLE storycraft_config (
                        master_channel TEXT NOT NULL,
                        concurrent_games INTEGER NOT NULL,
                        default_round_mode INTEGER NOT NULL,
                        default_game_length INTEGER NOT NULL,
                        default_line_length INTEGER NOT NULL,
                        default_random_method INTEGER NOT NULL,
                        default_lines_per_turn INTEGER NOT NULL
                    )''')
                db.execute('''
                    INSERT INTO storycraft_config
                        (master_channel, concurrent_games, default_round_mode,
                         default_game_length, default_line_length,
                         default_random_method, default_lines_per_turn)
                        VALUES ('#drbotzo', 10, 1, 20, 140, 1, 2)
                    ''')
                db.execute('''
                    CREATE TABLE storycraft_game (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        round_mode INTEGER NOT NULL,
                        game_length INTEGER NOT NULL,
                        line_length INTEGER NOT NULL,
                        random_method INTEGER NOT NULL,
                        lines_per_turn INTEGER NOT NULL,
                        status TEXT NOT NULL,
                        owner_nick TEXT NOT NULL,
                        owner_userhost TEXT NOT NULL,
                        start_time TEXT DEFAULT CURRENT_TIMESTAMP,
                        end_time TEXT DEFAULT NULL
                    )''')
                db.execute('''
                    CREATE TABLE storycraft_player (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        game_id INTEGER NOT NULL,
                        nick TEXT NOT NULL,
                        userhost TEXT NOT NULL,
                        FOREIGN KEY(game_id) REFERENCES storycraft_game(id)
                    )''')
                db.execute('''
                    CREATE TABLE storycraft_line (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        game_id INTEGER NOT NULL,
                        player_id INTEGER NOT NULL,
                        line TEXT NOT NULL,
                        time TEXT DEFAULT CURRENT_TIMESTAMP,
                        FOREIGN KEY(game_id) REFERENCES storycraft_game(id)
                        FOREIGN KEY(player_id) REFERENCES storycraft_player(id)
                    )''')

                sql = 'INSERT INTO drbotzo_modules VALUES (?,?)'
                db.execute(sql, (self.__class__.__name__, 1))
                db.commit()
                version = 1
            except sqlite3.Error as e:
                db.rollback()
                print("sqlite error: " + str(e))
                raise

    def do(self, connection, event, nick, userhost, what, admin_unlocked):
        """Pass storycraft control commands to the appropriate method based on input."""

        if self.statusre.search(what):
            return self.storycraft_status(connection, event, nick, userhost, what, admin_unlocked)
        elif self.newgamere.search(what):
            return self.storycraft_newgame(connection, event, nick, userhost, what, admin_unlocked)
        elif self.joingamere.search(what):
            return self.storycraft_joingame(connection, event, nick, userhost, what, admin_unlocked)
        elif self.listgamesre.search(what):
            return self.storycraft_listgames(connection, event, nick, userhost, what, admin_unlocked)
        elif self.startgamere.search(what):
            return self.storycraft_startgame(connection, event, nick, userhost, what, admin_unlocked)
        elif self.showlinere.search(what):
            return self.storycraft_showline(connection, event, nick, userhost, what, admin_unlocked)
        elif self.addlinere.search(what):
            return self.storycraft_addline(connection, event, nick, userhost, what, admin_unlocked)
        elif self.exportre.search(what):
            return self.storycraft_export(connection, event, nick, userhost, what, admin_unlocked)

    def storycraft_status(self, connection, event, nick, userhost, what, admin_unlocked):
        """Print information about the storycraft games, or one specific game."""

        match = self.statusre.search(what)
        if match:
            if match.group(2):
                game_id = int(match.group(2))

                # do the status of one game
                game = self._get_game_details(game_id)
                if game:
                    # get game string
                    status_str = self._get_game_summary(game)

                    return status_str
                else:
                    return 'Game #{0:d} does not exist.'.format(game_id)
            else:
                # do the general status of all games
                count_completed = self._get_completed_game_count()
                count_in_progress = self._get_in_progress_game_count()
                count_open = self._get_open_game_count()
                count_free = self._get_free_game_count()

                return 'Storycraft {0:s} - {1:d} games completed, {2:d} in progress, {3:d} open. {4:d} slots free.'.format(__version__, count_completed, count_in_progress, count_open, count_free)

    def storycraft_newgame(self, connection, event, nick, userhost, what, admin_unlocked):
        """Add a game to the system, which users can join."""

        match = self.newgamere.search(what)
        if match:
            # ensure the system isn't at capacity
            count_free = self._get_free_game_count()
            if count_free > 0:
                # get the default settings
                settings = self._get_storycraft_settings()

                master_channel = settings.master_channel
                round_mode = settings.default_round_mode
                game_length = settings.default_game_length
                line_length = settings.default_line_length
                random_method = settings.default_random_method
                lines_per_turn = settings.default_lines_per_turn

                # check for custom options
                if match.group(2):
                    options = match.group(2)

                    # override settings with provided options
                    tuples = options.split(';')
                    for option in tuples:
                        optpair = option.split(',')

                        if len(optpair) == 2:
                            if optpair[0] == 'round_mode':
                                round_mode = int(optpair[1])
                            elif optpair[0] == 'game_length':
                                game_length = int(optpair[1])
                            elif optpair[0] == 'line_length':
                                line_length = int(optpair[1])
                            elif optpair[0] == 'random_method':
                                random_method = int(optpair[1])
                            elif optpair[0] == 'lines_per_turn':
                                lines_per_turn = int(optpair[1])

                # add a new game
                game_id = self._add_new_game(round_mode, game_length, line_length, random_method, lines_per_turn, nick, userhost)
                if game_id:
                    # add the player to the game, too
                    self._add_player_to_game(game_id, nick, userhost)

                    # tell the control channel
                    self.irc.reply(connection, master_channel, '{0:s} created a game of storycraft - do \'!storycraft join game {1:d}\' to take part!'.format(nick, game_id))

                    return 'Game #{0:d} has been created. When everyone has joined, do \'!storycraft start game {0:d}\''.format(game_id)
                else:
                    return 'Error creating game.'
            else:
                return 'All slots are full.'

    def storycraft_joingame(self, connection, event, nick, userhost, what, admin_unlocked):
        """Add a player to an open game."""

        match = self.joingamere.search(what)
        if match:
            game_id = int(match.group(1))

            # ensure the game exists
            game = self._get_game_details(game_id)
            if game:
                # check that the game hasn't started yet
                if game.status == 'OPEN':
                    # see if userhost is already in the game
                    if not self._get_player_exists_in_game(game_id, userhost):
                        # add the player
                        if self._add_player_to_game(game_id, nick, userhost):
                            # output results

                            # get the default settings
                            settings = self._get_storycraft_settings()
                            master_channel = settings.master_channel

                            self.irc.reply(connection, master_channel, '{0:s} joined storycraft #{1:d}!'.format(nick, game_id))
                            return '{0:s}, welcome to the game.'.format(nick)
                        else:
                            return 'Error joining game.'
                    else:
                        return 'You are already in game #{0:d}.'.format(game_id)
                else:
                    return 'Game #{0:d} is not open for new players.'.format(game_id)
            else:
                return 'Game #{0:d} does not exist.'.format(game_id)

    def storycraft_listgames(self, connection, event, nick, userhost, what, admin_unlocked):
        """Get the listing of either open or in progress games."""

        match = self.listgamesre.search(what)
        if match:
            category = match.group(1)

            if category == 'open':
                games = self._get_open_games()
            elif category == 'in progress':
                games = self._get_in_progress_games()
            elif category == 'completed':
                games = self._get_completed_games()
            elif category == 'my games':
                games = self._get_active_games_with_player(nick)
            elif category == 'waiting for me':
                games = self._get_games_waiting_on_player(nick)

            if len(games) > 5:
                # just list the game ids
                gameids = []
                for game in games:
                    gameids.append(str(game.id))

                return 'Too many to list! ids: ' + ','.join(gameids)
            else:
                # show game details, since there's not many
                gamestrs = []
                for game in games:
                    gamestrs.append(self._get_game_summary(game))

                return '\n'.join(gamestrs)

    def storycraft_startgame(self, connection, event, nick, userhost, what, admin_unlocked):
        """Start a game, closing the period to join and starting line trading."""

        match = self.startgamere.search(what)
        if match:
            game_id = int(match.group(1))

            # retrieve the game
            game = self._get_game_details(game_id)
            if game:
                # check that the user is the owner of the game
                if userhost == game.owner_userhost:
                    # check that the game is open
                    if 'OPEN' == game.status:
                        # start the game
                        if self._start_game(game_id):
                            # determine the first person to send a line
                            if self._pick_next_player(game_id):
                                # indicate the status
                                game = self._get_game_details(game_id)
                                line = self._get_lines_for_game(game_id)[0]
                                player = self._get_player_by_id(line.player_id)

                                # get the default settings
                                settings = self._get_storycraft_settings()
                                master_channel = settings.master_channel

                                # tell the control channel
                                self.irc.reply(connection, master_channel, '{0:s} started storycraft #{1:d}! - first player is {2:s}, do \'!storycraft game {1:d} show line\' when the game is assigned to you.'.format(nick, game_id, player.nick))

                                return 'Game #{0:d} started.'.format(game_id)
                            else:
                                return 'Failed to assign game to first player.'
                        else:
                            return 'Could not start game.'
                    else:
                        return 'Game #{0:d} is not open.'.format(game_id)
                else:
                    return 'You are not the owner of #{0:d}.'.format(game_id)
            else:
                return 'Game #{0:d} does not exist.'.format(game_id)

    def storycraft_showline(self, connection, event, nick, userhost, what, admin_unlocked):
        """List the line to continue with, if queryer is the assignee."""

        match = self.showlinere.search(what)
        if match:
            game_id = int(match.group(1))

            # retrieve the game
            game = self._get_game_details(game_id)
            if game:
                # check that the game is in progress
                if 'IN PROGRESS' == game.status:
                    # get the most recent line
                    lines = self._get_lines_for_game(game_id)
                    if lines:
                        line = lines[0]

                        # check that the line is a prompt for input
                        if line.line == '':
                            player = self._get_player_by_id(line.player_id)

                            # check that the caller is the person the line is assigned to
                            if player.userhost == userhost:
                                # check previous line
                                if len(lines) > 1:
                                    # get previous line and display it
                                    repline = lines[1]
                                    return 'The current line is \'{0:s}\'. Continue the story with \'!storycraft game {1:d} add line YOUR TEXT\'.'.format(repline.line, game_id)
                                else:
                                    # player is starting the story
                                    return 'You are starting the story! Add the first line with \'!storycraft game {0:d} add line YOUR TEXT\'.'.format(game_id)
                            else:
                                return 'You are not the assignee of the current game.'
                        else:
                            return 'Game is in progress but the most recent line is not available - internal consistency error.'
                    else:
                        return 'Game #{0:d} has no lines - internal consistency error.'.format(game_id)
                else:
                    return 'Game #{0:d} has not been started.'.format(game_id)
            else:
                return 'Game #{0:d} does not exist.'.format(game_id)

    def storycraft_addline(self, connection, event, nick, userhost, what, admin_unlocked):
        """Add a line to an in progress game."""

        match = self.addlinere.search(what)
        if match:
            game_id = int(match.group(1))
            input_line = match.group(2)

            # retrieve the game
            game = self._get_game_details(game_id)
            if game:
                # check that the game is in progress
                if 'IN PROGRESS' == game.status:
                    # get the most recent line
                    lines = self._get_lines_for_game(game_id)
                    if lines:
                        line = lines[0]

                        # check that the line is a prompt for input
                        if line.line == '':
                            player = self._get_player_by_id(line.player_id)
                            if player.userhost == userhost:
                                # check the input line length
                                if len(input_line) <= game.line_length:
                                    # add line
                                    if self._update_line(line.id, input_line):
                                        # determine the first person to send a line
                                        if self._pick_next_player(game_id):
                                            # indicate the status
                                            game = self._get_game_details(game_id)
                                            line = self._get_lines_for_game(game_id)[0]
                                            last_line = self._get_lines_for_game(game_id)[1]
                                            player = self._get_player_by_id(line.player_id)

                                            return_msg = 'Line logged.'

                                            # get progress, so user knows how much time is left
                                            progress_str = self._get_progress_string_for_game(game)

                                            # message the new owner
                                            if player.nick == nick:
                                                # simpily notify them they're up again
                                                return_msg = 'Line logged. Please add another. ' + progress_str
                                            else:
                                                # notify the new owner, too
                                                self.irc.reply(connection, player.nick, 'You have a new line in storycraft #{0:d}: \'{1:s}\' {2:s}'.format(game_id, last_line.line, progress_str))

                                            # get the default settings
                                            settings = self._get_storycraft_settings()
                                            master_channel = settings.master_channel

                                            # log output
                                            if game.status == 'IN PROGRESS':
                                                self.irc.reply(connection, master_channel, '{0:s} added a line to storycraft #{1:d}! - next player is {2:s}'.format(nick, game_id, player.nick))
                                                return return_msg
                                            else:
                                                self.irc.reply(connection, master_channel, '{0:s} finished storycraft #{1:d}!'.format(nick, game_id))
                                                return 'Line logged (and game completed).'
                                        else:
                                            return 'Failed to assign game to next player.'
                                    else:
                                        return 'Failed to save your line.'
                                else:
                                    return 'The maximum line length for this game is {0:d} characters. (You were over by {1:d}.)'.format(game.line_length, len(input_line)-game.line_length)
                            else:
                                return 'You are not the assignee of the current game.'
                        else:
                            return 'Game is in progress but the most recent line is not available - internal consistency error.'
                    else:
                        return 'Game #{0:d} has no lines - internal consistency error.'.format(game_id)
                else:
                    return 'Game #{0:d} has not been started.'.format(game_id)
            else:
                return 'Game #{0:d} does not exist.'.format(game_id)

    def storycraft_export(self, connection, event, nick, userhost, what, admin_unlocked):
        """Provide the story for access outside of the bot."""

        match = self.exportre.search(what)
        if match:
            game_id = int(match.group(1))

            # retrieve the game
            game = self._get_game_details(game_id)
            if game:
                # check that the game is completed
                if 'COMPLETED' == game.status:
                    # get the most recent line
                    lines = self._get_lines_for_game(game_id)
                    if lines:
                        # write the story to disk
                        fn = self._export_game_to_disk(game, lines)
                        if fn:
                            return 'Story written as {0:s}.'.format(fn)
                        else:
                            return 'Error writing story to disk.'
                    else:
                        return 'Game #{0:d} has no lines - internal consistency error.'.format(game_id)
                else:
                    return 'Game #{0:d} has not been completed.'.format(game_id)
            else:
                return 'Game #{0:d} does not exist.'.format(game_id)

    def _get_game_details(self, game_id):
        """Get the details of one game."""

        try:
            # get the specified game and populate a StorycraftGame
            db = self.get_db()
            query = '''
                SELECT id, round_mode, game_length, line_length, random_method,
                    lines_per_turn, status, owner_nick, owner_userhost, start_time,
                    end_time
                FROM storycraft_game WHERE id = ?
                '''
            cursor = db.execute(query, (game_id,))
            result = cursor.fetchone()
            if result:
                game = self.StorycraftGame()

                game.id = int(result['id'])
                game.round_mode = int(result['round_mode'])
                game.game_length = int(result['game_length'])
                game.line_length = int(result['line_length'])
                game.random_method = int(result['random_method'])
                game.lines_per_turn = int(result['lines_per_turn'])
                game.status = result['status']
                game.owner_nick = result['owner_nick']
                game.owner_userhost = result['owner_userhost']
                game.start_time = result['start_time']
                game.end_time = result['end_time']

                return game
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_game_summary(self, game):
        """Display game info for a general summary."""

        status_str = '#{0:d} - created on {1:s} by {2:s}, {3:s}'.format(game.id, game.start_time, game.owner_nick, game.status)
        if game.status == 'COMPLETED' and game.end_time:
            status_str = status_str + ' ({0:s})'.format(game.end_time)
        elif game.status == 'IN PROGRESS':
            lines = self._get_lines_for_game(game.id)
            player = self._get_player_by_id(lines[0].player_id)
            status_str = status_str + ' ({0:d} lines, next is {1:s})'.format(len(lines)-1, player.nick)

        # get players in game
        player_names = []
        players = self._get_player_list_for_game(game.id)
        if players:
            for player in players:
                player_names.append(player.nick)
            status_str = status_str + ', players: {0:s}'.format(', '.join(player_names))

        status_str = status_str + '. (o:{0:d}[{1:d}],{2:d},{3:d},{4:d})'.format(game.round_mode, game.game_length, game.line_length, game.random_method, game.lines_per_turn)

        return status_str

    def _get_progress_string_for_game(self, game):
        """Get a terse summary of the game's progress."""

        if game.round_mode == 1:
            lines = self._get_lines_for_game(game.id)
            num_lines = len(lines)-1

            progress = '(lines: {0:d}/{1:d}'.format(num_lines, game.game_length)

            if num_lines == game.game_length - 1:
                progress = progress + ' LAST LINE'

            progress = progress + ')'
            return progress
        else:
            return ''

    def _add_new_game(self, round_mode, game_length, line_length, random_method, lines_per_turn, nick, userhost):
        """Add a new game to the system."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                INSERT INTO storycraft_game (
                    round_mode, game_length, line_length, random_method,
                    lines_per_turn, status, owner_nick, owner_userhost
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                '''
            cur.execute(statement, (round_mode, game_length, line_length, random_method, lines_per_turn, 'OPEN', nick, userhost))
            db.commit()
            return cur.lastrowid
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_player_list_for_game(self, game_id):
        """Get the list of players in one game."""

        players = []
        try:
            # get the players for specified game and populate a list
            db = self.get_db()
            query = 'SELECT id, game_id, nick, userhost FROM storycraft_player WHERE game_id = ?'
            cursor = db.execute(query, (game_id,))
            results = cursor.fetchall()
            for result in results:
                player = self.StorycraftPlayer()

                player.id = result['id']
                player.game_id = result['game_id']
                player.nick = result['nick']
                player.userhost = result['userhost']

                players.append(player)

            return players
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_game_exists(self, game_id):
        """Return the existence of a game."""

        return self._get_game_details(game_id) is not None

    def _start_game(self, game_id):
        """Start a game, if it's currently open."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                UPDATE storycraft_game SET status = 'IN PROGRESS' WHERE status = 'OPEN' AND id = ?
                '''
            cur.execute(statement, (game_id,))
            db.commit()
            return game_id
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _pick_next_player(self, game_id):
        """Based on the game's settings and state, set who will be replying next."""

        game = self._get_game_details(game_id)
        if game:
            # do round_mode stuff
            if game.round_mode == 1:
                count_by_latest_player = 0
                latest_player_id = 0

                # get the lines in this game, latest first
                lines = self._get_lines_for_game(game_id)

                for line in lines:
                    if latest_player_id == 0:
                        latest_player_id = line.player_id

                    if latest_player_id == line.player_id:
                        count_by_latest_player = count_by_latest_player + 1
                    else:
                        break

                # now we know how many times the most recent person has responded,
                # figure out what to do
                if count_by_latest_player == 0:
                    # must be a new game, get a random player
                    players = self._get_player_list_for_game(game.id)
                    random_player = players[random.randint(1,len(players))-1]
                    return self._assign_game_to_player(game_id, random_player.id)
                elif count_by_latest_player >= game.lines_per_turn and len(lines) < game.game_length:
                    # player has reached the max, get a random different player to assign
                    players = self._get_player_list_for_game(game.id)
                    random_player_id = latest_player_id
                    if len(players) > 1:
                        while random_player_id == latest_player_id:
                            random_player = players[random.randint(1,len(players))-1]
                            random_player_id = random_player.id
                    return self._assign_game_to_player(game_id, random_player_id)
                elif count_by_latest_player >= game.lines_per_turn and len(lines) >= game.game_length:
                    # end of game
                    return self._end_game(game_id)
                elif count_by_latest_player < game.lines_per_turn:
                    # player should continue getting lines
                    return self._assign_game_to_player(game_id, latest_player_id)

    def _assign_game_to_player(self, game_id, player_id):
        """Assign the game to a player, prompting them for responses."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                INSERT INTO storycraft_line (game_id, player_id, line)
                VALUES (?, ?, ?)
                '''
            cur.execute(statement, (game_id, player_id, ''))
            db.commit()
            return cur.lastrowid
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _end_game(self, game_id):
        """End the given game, disallowing adding lines."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                UPDATE storycraft_game SET status = 'COMPLETED', end_time = CURRENT_TIMESTAMP
                WHERE status = 'IN PROGRESS' AND id = ?
                '''
            cur.execute(statement, (game_id,))
            db.commit()
            return game_id
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_player_by_id(self, player_id):
        """Get the player details based on id."""

        try:
            # get the specified player and populate a StorycraftPlayer
            db = self.get_db()
            query = 'SELECT id, game_id, nick, userhost FROM storycraft_player WHERE id = ?'
            cursor = db.execute(query, (player_id,))
            result = cursor.fetchone()
            if result:
                player = self.StorycraftPlayer()

                player.id = result['id']
                player.game_id = result['game_id']
                player.nick = result['nick']
                player.userhost = result['userhost']

                return player
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_player_by_userhost_in_game(self, game_id, userhost):
        """Get the player details if they exist in the given game."""

        try:
            # get the specified player if they exist in the game and populate a StorycraftPlayer
            db = self.get_db()
            query = 'SELECT id, game_id, nick, userhost FROM storycraft_player WHERE game_id = ? AND userhost = ?'
            cursor = db.execute(query, (game_id,userhost))
            result = cursor.fetchone()
            if result:
                player = self.StorycraftPlayer()

                player.id = result['id']
                player.game_id = result['game_id']
                player.nick = result['nick']
                player.userhost = result['userhost']

                return player
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_completed_games(self):
        """Get the games with COMPLETED status."""

        return self._get_games_of_type('COMPLETED')

    def _get_in_progress_games(self):
        """Get the games with IN PROGRESS status."""

        return self._get_games_of_type('IN PROGRESS')

    def _get_open_games(self):
        """Get the games with OPEN status."""

        return self._get_games_of_type('OPEN')

    def _get_active_games_with_player(self, player_nick):
        """Return the in progress/open games that include the player nick."""

        games = []
        try:
            # get the games of specified type and populate a list
            db = self.get_db()
            query = '''
                SELECT game.id, game.round_mode, game.game_length, game.line_length,
                    game.random_method, game.lines_per_turn, game.status, game.owner_nick,
                    game.owner_userhost, game.start_time, game.end_time
                FROM storycraft_game game
                INNER JOIN storycraft_player player ON player.game_id = game.id
                WHERE player.nick = ? AND (game.status = 'OPEN' OR game.status = 'IN PROGRESS')
                '''
            cursor = db.execute(query, (player_nick,))
            results = cursor.fetchall()
            for result in results:
                game = self.StorycraftGame()

                game.id = int(result['id'])
                game.round_mode = int(result['round_mode'])
                game.game_length = int(result['game_length'])
                game.line_length = int(result['line_length'])
                game.random_method = int(result['random_method'])
                game.lines_per_turn = int(result['lines_per_turn'])
                game.status = result['status']
                game.owner_nick = result['owner_nick']
                game.owner_userhost = result['owner_userhost']
                game.start_time = result['start_time']
                game.end_time = result['end_time']

                games.append(game)

            return games
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_games_waiting_on_player(self, player_nick):
        """Return the games where the player nick is the owner of a pending line.

        Include the games that the player started and are open, since they're waiting
        on the person too.
        """

        games = []
        try:
            # get the games of specified type and populate a list
            db = self.get_db()
            query = '''
                SELECT game.id, game.round_mode, game.game_length, game.line_length,
                    game.random_method, game.lines_per_turn, game.status, game.owner_nick,
                    game.owner_userhost, game.start_time, game.end_time
                FROM storycraft_game game
                INNER JOIN storycraft_player player ON player.game_id = game.id
                INNER JOIN storycraft_line line ON line.player_id = player.id
                WHERE player.nick = ? AND game.status = 'IN PROGRESS' AND line.line = ''
                UNION
                SELECT game.id, game.round_mode, game.game_length, game.line_length,
                    game.random_method, game.lines_per_turn, game.status, game.owner_nick,
                    game.owner_userhost, game.start_time, game.end_time
                FROM storycraft_game game
                WHERE game.owner_nick = ? AND game.status = 'OPEN'
                '''
            cursor = db.execute(query, (player_nick, player_nick))
            results = cursor.fetchall()
            for result in results:
                game = self.StorycraftGame()

                game.id = int(result['game.id'])
                game.round_mode = int(result['game.round_mode'])
                game.game_length = int(result['game.game_length'])
                game.line_length = int(result['game.line_length'])
                game.random_method = int(result['game.random_method'])
                game.lines_per_turn = int(result['game.lines_per_turn'])
                game.status = result['game.status']
                game.owner_nick = result['game.owner_nick']
                game.owner_userhost = result['game.owner_userhost']
                game.start_time = result['game.start_time']
                game.end_time = result['game.end_time']

                games.append(game)

            return games
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_games_of_type(self, game_type):
        """Return the games of the specified type."""

        games = []
        try:
            # get the games of specified type and populate a list
            db = self.get_db()
            query = '''
                SELECT id, round_mode, game_length, line_length, random_method,
                    lines_per_turn, status, owner_nick, owner_userhost, start_time,
                    end_time
                FROM storycraft_game WHERE status = ?
                '''
            cursor = db.execute(query, (game_type,))
            results = cursor.fetchall()
            for result in results:
                game = self.StorycraftGame()

                game.id = int(result['id'])
                game.round_mode = int(result['round_mode'])
                game.game_length = int(result['game_length'])
                game.line_length = int(result['line_length'])
                game.random_method = int(result['random_method'])
                game.lines_per_turn = int(result['lines_per_turn'])
                game.status = result['status']
                game.owner_nick = result['owner_nick']
                game.owner_userhost = result['owner_userhost']
                game.start_time = result['start_time']
                game.end_time = result['end_time']

                games.append(game)

            return games
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _add_player_to_game(self, game_id, nick, userhost):
        """Add a player to a game, so that they may eventually play."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                INSERT INTO storycraft_player (game_id, nick, userhost)
                VALUES (?, ?, ?)
                '''
            cur.execute(statement, (game_id, nick, userhost))
            db.commit()
            return cur.lastrowid
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _get_player_exists_in_game(self, game_id, userhost):
        """Return the existence of a player in a game."""

        return self._get_player_by_userhost_in_game(game_id, userhost) is not None

    def _get_completed_game_count(self):
        """Get the number of games with COMPLETED status."""

        return self._get_game_type_count('COMPLETED')

    def _get_in_progress_game_count(self):
        """Get the number of games with IN PROGRESS status."""

        return self._get_game_type_count('IN PROGRESS')

    def _get_open_game_count(self):
        """Get the number of games with OPEN status."""

        return self._get_game_type_count('OPEN')

    def _get_free_game_count(self):
        """Get the number of slots available, given current in progess/open games."""

        all_slots = self._get_concurrent_game_count()
        count_in_progress = self._get_in_progress_game_count()
        count_open = self._get_open_game_count()
        return all_slots - count_in_progress - count_open

    def _get_game_type_count(self, game_type):
        """Return the number of games of the specified type."""

        count = 0
        try:
            # get count of game_type games
            db = self.get_db()
            query = 'SELECT COUNT(*) FROM storycraft_game WHERE status = ?'
            cursor = db.execute(query, (game_type,))
            result = cursor.fetchone()
            if result:
                count = result['COUNT(*)']
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

        return count

    def _get_concurrent_game_count(self):
        """Return the current game server concurrency."""

        concurrency = 0
        try:
            # get the concurrency value from config table
            db = self.get_db()
            query = 'SELECT concurrent_games FROM storycraft_config'
            cursor = db.execute(query)
            result = cursor.fetchone()
            if result:
                concurrency = result['concurrent_games']
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

        return concurrency

    def _get_lines_for_game(self, game_id):
        """Get the lines for the specified game_id."""

        lines = []
        try:
            # get the games of specified type and populate a list
            db = self.get_db()
            query = '''
                SELECT id, game_id, player_id, line, time
                FROM storycraft_line WHERE game_id = ?
                ORDER BY time DESC, id DESC
                '''
            cursor = db.execute(query, (game_id,))
            results = cursor.fetchall()
            for result in results:
                line = self.StorycraftLine()

                line.id = result['id']
                line.game_id = result['game_id']
                line.player_id = result['player_id']
                line.line = result['line']
                line.time = result['time']

                lines.append(line)

            return lines
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _update_line(self, line_id, input_line):
        """Update the specified line with the given text."""

        try:
            db = self.get_db()
            cur = db.cursor()
            statement = '''
                UPDATE storycraft_line SET line = ?, time = CURRENT_TIMESTAMP WHERE id = ?
                '''
            cur.execute(statement, (input_line, line_id))
            db.commit()
            return line_id
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

    def _export_game_to_disk(self, game, lines):
        """Write a game to disk."""

        filename = 'storycraft-{0:0000d}-{1:s}-{2:s}.txt'.format(game.id, game.owner_nick, time.strftime('%Y%m%d%H%M%S'))
        f = open(filename, 'w')

        lines_by_player = {}
        line_count = 0

        # the array is in LIFO, so reverse it
        lines.reverse()

        for line in lines:
            # get player for this line
            player = self._get_player_by_id(line.player_id)

            line_count = line_count + 1
            if not player.nick in lines_by_player:
                lines_by_player[player.nick] = []
            lines_by_player[player.nick].append(str(line_count))

            f.write(line.line + '\n')

        legend = ''
        f.write('\n')
        for player in lines_by_player.keys():
            f.write(player + ':' + ','.join(lines_by_player[player]) + '\n')

        f.close()
        return filename

    def _get_storycraft_settings(self):
        """Get the server settings."""

        try:
            # get the settings and return in StorycraftSettings
            db = self.get_db()
            query = '''
                SELECT master_channel, concurrent_games, default_round_mode,
                    default_game_length, default_line_length, default_random_method,
                    default_lines_per_turn
                FROM storycraft_config
                '''
            cursor = db.execute(query)
            result = cursor.fetchone()
            if result:
                settings = self.StorycraftSettings()

                settings.master_channel = result['master_channel']
                settings.concurrent_games = int(result['concurrent_games'])
                settings.default_round_mode = int(result['default_round_mode'])
                settings.default_game_length = int(result['default_game_length'])
                settings.default_line_length = int(result['default_line_length'])
                settings.default_random_method = int(result['default_random_method'])
                settings.default_lines_per_turn = int(result['default_lines_per_turn'])

                return settings
        except sqlite3.Error as e:
            print('sqlite error: ' + str(e))
            raise

# vi:tabstop=4:expandtab:autoindent