#!/usr/bin/python
"""
Play a crossword puzzle in a curses grid. Takes a command line argument of the
crossword puzzle file.
"""
# curses_crossword.py - play crosswords in a terminal
# Copyright (C) 2009 Brett Parker <iDunno@sommitrealweird.co.uk>
#
# 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 2 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import curses
import curses.ascii
import locale
import codecs
import getopt
import os
import sys

locale.setlocale(locale.LC_ALL, '')
code = locale.getpreferredencoding()

superscript_numbers = {}

try:
    superscript_numbers = {
        "0": unichr(8304).encode(code),
        "1": unichr(185).encode(code),
        "2": unichr(178).encode(code),
        "3": unichr(179).encode(code),
        "4": unichr(8308).encode(code),
        "5": unichr(8309).encode(code),
        "6": unichr(8310).encode(code),
        "7": unichr(8311).encode(code),
        "8": unichr(8312).encode(code),
        "9": unichr(8313).encode(code),
    }
except UnicodeError, e:
    for number in range(0, 10):
        superscript_numbers[str(number)] = str(number)

filename = None

try:
    (options, args) = getopt.getopt(sys.argv[1:], "f:", "file=")
except getopt.GetoptError, err:
    print str(err)
    sys.exit(2)

for option in options:
    if option[0] == "-f" or option[0] == "--file":
        filename = option[1]

if not filename and len(args) > 0:
    filename = args[0]

if not filename:
    sys.stderr.write("No crossword file specified, exiting.\n")
    sys.exit(0)

if os.path.exists(filename) and os.path.isfile(filename):
    crossworddata = codecs.open(filename, "r", "utf-8").read()
else:
    sys.stderr.write("Couldn't open file %s\n" %(filename))
    sys.exit(0)

def parsecrossword(crossworddata):
    """
    Read the content of a crossword file in to memory and rejig it in to the 3
    seperate sections that we expect - namely the grid, the across clues and
    the down clues.
    """
    ingrid = False
    inacross = False
    indown = False
    crossword = {"grid": [],
        "across": {},
        "down": {},
        "grid_questions_start": [],
        "grid_questions_end": []}

    for line in crossworddata.split("\n"):
        line = line.strip("\n")
        if line == "GRID":
            ingrid = True
            inacross = False
            indown = False
        elif line == "ACROSS":
            ingrid = False
            inacross = True
            indown = False
        elif line == "DOWN":
            ingrid = False
            inacross = False
            indown = True
        else:
            if ingrid:
                if line != "":
                    crossword["grid"].append(line)
            if inacross:
                if line != "":
                    parts = line.split()
                    question_number = int(parts[0].split(",")[0])
                    clue = " ".join(parts[1:])
                    crossword["across"][int(question_number)] = \
                        clue.encode(code)
            if indown:
                if line != "":
                    parts = line.split()
                    question_number = int(parts[0].split(",")[0])
                    clue = " ".join(parts[1:])
                    crossword["down"][int(question_number)] = clue.encode(code)
    num_cols = len(crossword["grid"][0])
    num_rows = len(crossword["grid"])

    current_clue_number = 1

    for row in range(0, num_rows):
        crossword["grid_questions_start"].append([])

    for row in range(0, num_rows):
        for col in range(0, num_cols):
            have_clue = False
            if col > 0 \
                and crossword["grid"][row][col - 1] == "x" \
                and crossword["grid"][row][col] != "x" \
                and col < (num_cols - 1) \
                and crossword["grid"][row][col + 1] != "x":
                have_clue = True
            if col == 0 and crossword["grid"][row][col] != "x" \
                and crossword["grid"][row][col + 1] != "x":
                have_clue = True
            if row > 0 and crossword["grid"][row-1][col] == "x" \
                and row < (num_rows - 1) \
                and crossword["grid"][row][col] != "x" \
                and crossword["grid"][row + 1][col] != "x":
                have_clue = True
            if row == 0 and crossword["grid"][row][col] != "x" \
                and crossword["grid"][row + 1][col] != "x":
                have_clue = True
            if have_clue:
                crossword["grid_questions_start"][row] \
                    .append(current_clue_number)
                current_clue_number += 1
            else:
                crossword["grid_questions_start"][row].append(0)

    return crossword

def crossword(stdscr, crossworddata):
    crossword = parsecrossword(crossworddata)
    cury = 0
    grid_length = len(crossword["grid"][0])
    curx = 0
    stdscr.addch(cury, curx, curses.ACS_ULCORNER)
    curx += 1
    while ((curx - 1) / 4) < grid_length:
        stdscr.addch(cury, curx, curses.ACS_HLINE)
        stdscr.addch(cury, curx+1, curses.ACS_HLINE)
        stdscr.addch(cury, curx+2, curses.ACS_HLINE)
        stdscr.addch(cury, curx+3, curses.ACS_TTEE)
        curx += 4
    curx -= 1
    stdscr.addch(cury, curx, curses.ACS_URCORNER)
    cury += 1
    curgridy = 0
    for line in crossword["grid"]:
        curx = 0
        curgridx = 0
        for curch in line:
            stdscr.addch(cury, curx, curses.ACS_VLINE)
            if curx > 0:
                stdscr.addch(cury+1, curx, curses.ACS_PLUS)
            else:
                stdscr.addch(cury+1, curx, curses.ACS_LTEE)
            stdscr.addch(cury+1, curx + 1, curses.ACS_HLINE)
            stdscr.addch(cury+1, curx + 2, curses.ACS_HLINE)
            stdscr.addch(cury+1, curx + 3, curses.ACS_HLINE)
            curx += 1
            if curch == "x":
                stdscr.addch(cury, curx, curses.ACS_BLOCK)
                stdscr.addch(cury, curx+1, curses.ACS_BLOCK)
                stdscr.addch(cury, curx+2, curses.ACS_BLOCK)
            elif crossword["grid_questions_start"][curgridy][curgridx] > 0:
                stdscr.addstr(cury, curx, \
                    ''.join( \
                        [superscript_numbers[x].decode(code) \
                        for x in str( \
                            crossword["grid_questions_start"] \
                            [curgridy][curgridx])]\
                        ).encode(code))
            curx += 3
            curgridx += 1
        else:
            stdscr.addch(cury, curx, curses.ACS_VLINE)
            stdscr.addch(cury + 1, curx, curses.ACS_RTEE)
        cury += 2
        curgridy += 1
    cury -= 1
    curx = 0
    stdscr.addch(cury, curx, curses.ACS_LLCORNER)
    curx += 1
    while ((curx - 1) / 4) < grid_length:
        stdscr.addch(cury, curx, curses.ACS_HLINE)
        stdscr.addch(cury, curx+1, curses.ACS_HLINE)
        stdscr.addch(cury, curx+2, curses.ACS_HLINE)
        stdscr.addch(cury, curx+3, curses.ACS_BTEE)
        curx += 4
    curx -= 1
    stdscr.addch(cury, curx, curses.ACS_LRCORNER)
    # draw the clues in (in their own pad)
    cluespad = curses.newpad( \
        len(crossword["across"].keys()) \
        + len(crossword["down"].keys()) \
        + 3, stdscr.getmaxyx()[1])
    cury = (len(crossword["grid"]) * 2) + 1
    curx = 0
    padtlx = curx
    padtly = cury

    (padbry, padbrx) = stdscr.getmaxyx()
    if padbry > cluespad.getmaxyx()[0] + padtly:
        padbry = cluespad.getmaxyx()[0] + padtly
    cury = 0
    cluespad.addstr(cury, curx, "Across")
    cury += 1
    for cluenumber in crossword["across"].keys():
        cluespad.addstr(cury, curx, "%3s: %s" \
            %(str(cluenumber), crossword["across"][cluenumber]))
        cury += 1

    cury += 1
    cluespad.addstr(cury, curx, "Down")
    cury += 1
    for cluenumber in crossword["down"].keys():
        cluespad.addstr(cury, curx, "%3s: %s" \
            %(str(cluenumber), crossword["down"][cluenumber]))
        cury += 1

    curx = 3
    cury = 1
    gridx = 0
    gridy = 0

    while crossword["grid"][gridy][gridx] == "x":
        curx += 4
        gridx += 1

    stdscr.move(cury, curx)
    cluescury = 0

    cluespad.refresh(cluescury, 0, padtly, padtlx, padbry - 1, padbrx - 1)
    cluespad.overlay(stdscr, cluescury, 0, padtly, padtlx, padbry - 1, padbrx - 1)


    while 1:
        inch = stdscr.getch()
        if inch == curses.ascii.ESC:
            break
        if inch == curses.KEY_NPAGE:
            if cluescury < cluespad.getmaxyx()[0] - (padbry - padtly):
                cluescury += 1
            cluespad.refresh(cluescury, 0, padtly, padtlx, padbry - 1, padbrx - 1)
            stdscr.refresh()
        if inch == curses.KEY_PPAGE:
            if cluescury > 0:
                cluescury -= 1
            cluespad.refresh(cluescury, 0, padtly, padtlx, padbry - 1, padbrx - 1)
            stdscr.refresh()
        if inch == curses.KEY_RIGHT:
            if gridx < (len(crossword["grid"][0]) - 1):
                gridx += 1
                curx += 4
                while gridx < (len(crossword["grid"][0]) -1) \
                    and crossword["grid"][gridy][gridx] == "x":
                    gridx += 1
                    curx += 4
                while crossword["grid"][gridy][gridx] == "x":
                    gridx -= 1
                    curx -= 4
                stdscr.move(cury, curx)
        if inch == curses.KEY_LEFT:
            if gridx > 0:
                curx -= 4
                gridx -= 1
                while gridx > 0 \
                    and crossword["grid"][gridy][gridx] == "x":
                    gridx -= 1
                    curx -= 4
                while crossword["grid"][gridy][gridx] == "x":
                    gridx += 1
                    curx += 4
                stdscr.move(cury, curx)
        if inch == curses.KEY_UP:
            if gridy > 0:
                gridy -= 1
                cury -= 2
                while gridy > 0 \
                    and crossword["grid"][gridy][gridx] == "x":
                    gridy -= 1
                    cury -= 2
                while crossword["grid"][gridy][gridx] == "x":
                    gridy += 1
                    cury += 2
                stdscr.move(cury, curx)
        if inch == curses.KEY_DOWN:
            if gridy < (len(crossword["grid"]) - 1):
                gridy += 1
                cury += 2
                while gridy < (len(crossword["grid"]) - 1) \
                    and crossword["grid"][gridy][gridx] == "x":
                    gridy += 1
                    cury += 2
                while crossword["grid"][gridy][gridx] == "x":
                    gridy -= 1
                    cury -= 2
                stdscr.move(cury, curx)
        if curses.ascii.isalpha(inch) or inch == ord(" "):
            stdscr.addch(cury, curx, inch)
            stdscr.move(cury, curx)
        if inch == curses.KEY_BACKSPACE or inch == curses.KEY_DC:
            stdscr.addch(cury, curx, ord(" "))
            stdscr.move(cury, curx)


curses.wrapper(crossword, crossworddata)
