#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
(Bullshit) Bingo Card Generator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

*Bullshit Bingo* is a slight variation of `Buzzword Bingo`_.  The first player
completing a full row his/her card shouts "Bullshit!" to kindly inform the
presenter of a possible overuse of empty phrases.

Actually, this program is not restricted to this special type of Bingo.
Different kinds of words (and numbers, of course) can be used.

A wordlist (meaning a plain text file with one word on a line of its own) is
required as source for the words that should be randomly used to create the
bingo cards.

The randomly generated cards are put out as XHTML and can be viewed and
printed with a common Web browser (e.g. Firefox_).

Software requirements are Python_ version 2.5 or greater, and Genshi_.

When generating multiple cards, two of them (with the default size) should fit
on one page of DIN A4 paper when printed.  Check your browser's printing
preview and, if necessary, adjust the CSS section of the XHTML template.

.. _Buzzword Bingo: http://en.wikipedia.org/wiki/Buzzword_bingo
.. _Firefox:        http://www.mozilla.com/firefox/
.. _Python:         http://www.python.org/
.. _Genshi:         http://genshi.edgewall.org/

:Copyright: 2007 Jochen Kupperschmidt
:Date: 09-Nov-2007
:License: MIT
"""

from __future__ import with_statement
from optparse import OptionParser
from random import sample, shuffle

from genshi.template import MarkupTemplate


XHTML_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:py="http://genshi.edgewall.org/">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <style type="text/css">
    @media print {
      html,
      body {
        margin: 0;
      }
    }

    h1 {
      font-size: 6mm;
    }
    table {
      border-color: #666666;
      border-spacing: 0;
      border-style: solid;
      border-width: 0 0.25mm 0.25mm 0;
      margin-bottom: 1cm;
    }
    td {
      border-color: #666666;
      border-style: solid;
      border-width: 0.25mm 0 0 0.25mm;
      font-size: 4mm;
      height: 2cm;
      text-align: center;
      width: 3cm;
    }
    td strong {
      font-size: 120%;
      text-transform: uppercase;
    }
  </style>
  <title>Bullshit Bingo</title>
</head>
<body>

<py:for each="table in tables">
  <h1>Bullshit Bingo</h1>
  <table cellspacing="0">
    <tr py:for="row in table">
      <td py:for="word in row">
        <strong py:if="word is None">Bingo</strong>
        <py:if test="word is not None">${word}</py:if>
      </td>
    </tr>
  </table>
</py:for>

</body>
</html>"""

def load_wordlist(filename):
    """Generatively load words from a file.

    Every line is to be considered as one word.
    """
    with open(filename, 'rb') as f:
        for line in f:
            # Only yield non-empty, non-whitespace lines.
            line = line.strip()
            if line:
                yield line

def render_xhtml(tables):
    # Assemble an XHTML page with cards from the word tables.
    tmpl = MarkupTemplate(XHTML_TEMPLATE)
    return tmpl.generate(tables=tables).render('xhtml')

if __name__ == '__main__':
    # Prepare the option and argument parser.
    parser = OptionParser(usage='%prog [options] <wordlist file>')
    parser.add_option('-b', '--bonus-field', dest='bonus_field',
        action='store_true', default=False,
        help='add one bonus field per card')
    parser.add_option('-c', '--num-cards', dest='num_cards',
        type='int', default=1,
        help='number of cards to create (default: 1)')
    parser.add_option('-s', '--card-size', dest='card_size',
        type='int', default=5,
        help='number of rows and columns per card (default: 5)')

    # Parse arguments.
    opts, args = parser.parse_args()
    if len(args) != 1:
        parser.print_help()
        parser.exit()

    # Load words from given file.
    words = list(load_wordlist(args[0]))

    # Create tables of words.
    tables = [[sample(words, opts.card_size)
        for j in xrange(opts.card_size)]
            for i in xrange(opts.num_cards)]
    if opts.bonus_field:
        # Insert bonus field (replaces an existing one) at a random position.
        for table in tables:
            table[0][0] = None
            shuffle(table[0])
            shuffle(table)

    # Write XHTML output to stdout.  To create a file, redirect the data by
    # appending something like ``> cards.html`` at the command line.
    print render_xhtml(tables)
