#!/usr/bin/env python
# Hopefully this is free software, you should be able to use, modify,
# and redistribute non-commercially freely, as long as you grant
# these same rights to anyone who wishes to have them... or so... :-)

# Actual code starts down at 'globals/config'

# An interactive lerning tool of the flip-card kind.
# see http://www.lojban.org/tiki/tiki-index.php?page=Parallel+2 for
# further information

# Jul 20 2008, purpleposeidon <$MY_NAME@gmail.com>
# Some stuff: pygame for sound, play() function, full list below.
# Could be easily ported to windows. I haven't tested it much, but it should be fine.

# 2008-05-30, ydo <tobbe at tobbe dot nu>
# modified to use oggs or mp3s if they exist, the wave format in data.tar.gz
# was obscure and not readable by mplayer (libmad)

# 2007-11-14, twey (twey at twey dot co dot uk)
# added a line to quit PyGame's mixer, freeing up the audio device
# for mplayer/realplayer/player of choice.

# 30/01/07, thkruege at googlemail dot com
# made radical changes and therefore introduced next version (0.3)
# Is it ok to regard this as GPLed software?
# (for changes, see below)

# 20/01/06 curv (Kio) Modified to fit on smaller screens,
# replaced string.rjust() with string.zfill(), changed default
# colours to be easier on the eyes. (Matter of opinion of course)

# December 2005, Yann Le Du http://yledu.free.fr
# Second improved version

# Directions for use :
# Prerequisites : python, pygame with sdl and ttf, your favourite
# wav-player (default: mplayer), data.tar.gz (Parallel 2 data files from
# http://www.lojban.org/tiki/tiki-download_wiki_attachment.php?attId=225&download=y )
# run this program from the directory where the parallel 2 data files
# reside. Or specify a path to the data directory below
# Run it with python by typing "python parallel03.py"

# Changes by thkruege at googlemail dot com:

# - introduced proper versioning
# - keyboard commands now pygame based, therefore:
# - does not need a terminal for inputs any more
# - does not depend on sdl-mixer any more (for now)
# - plays audio files using custom command (or not at all)
#     (this got rid of the rather nasty realplayer dependency)
# - data can now reside in arbitrary locations
# - slightly changed "gameplay"
#     (now you only listen to sounds by pressing an extra key)
# - threw away some redundant code
# - threw away unused variables
# - reorganized code so that global variables are easyier to access
#     for non developers
# - fixed SDL-ttf-render fail with some voodoo
# - fixed a typo ;-)

# Changes by purpleposeidon:

# - added two TODO's, removed one
# - made it use pygame for sound instead of a command (this is faster)
# - added a message for pygame dependancy
# - alphabatized keyboard events
# - moved screen = init() into __main__ so that you can import it
# - os.path.join instead of adding strings together, but more is needed
# - whitespace cleanup
# - introduced VERBOSE variable
# - fixed a weirdness so that you don't have to press h twice

# TODO:
# - allow keyboard interrupts in scrolling/random mode
# - get rid of all those ugly hacks
# - get rid of all this hardcoded stuff
# - clean up code, do object orientation
# - implement more colours
# - document properly
# - ask author if its ok to license under GNU GPL
# - (maybe repackage data.tar.gz with compressed audio files)
# - add program arguments
# - fix CPU usage


# CREDITS:
# Original idea and Windows version "Parallel 2" by Yanis Batura
# Some pygame technique taken from "Learnfast" program by Michal Wallace

### globals/config #################################################

# if the data does not reside in the path parallel.py is called from,
# adjust the datapath below
DATAPATH = "./"


# Set your prefered window geometry here.
# below (790, 400) things get ugly, though :-)
WINSIZE = (790, 400)

YELLOW = 255, 255, 0
BLACK = 0, 0, 0
CYAN = 0, 255, 255

FONT = None #Uses default font
FONT_SIZE = 23

TOPLINE, TOPLOWLINE, BOTTOMHIGHLINE,BOTTOMLINE = -2,-1,1,2
CENTER = (WINSIZE[0]/2,WINSIZE[1]/2)
FOOTER = WINSIZE[1]

VERBOSE = True

### imports ########################################################

#Standard modules
import sys, os, subprocess
import time, random, math
import string, re


try:
    import pygame #, pygame.font, pygame.key
    from pygame.locals import *
except ImportError:
    print "It seems that Pygame is not installed. Pygame is a dependancy of parallel, please install it. It is available at <http://www.pygame.org/download.shtml> or from your local package manager."
    raise SystemExit



### defs ###########################################################



def getkeys():
    for event in pygame.event.get():
        # change on key release feels nicer
        # of course, we don't care if they press two buttons at once
        if event.type == KEYUP: 
            if event.key == K_ESCAPE:
                sys.exit()
                # return "esc" (possible alternative)
            elif event.key == K_b: return "b"
            elif event.key == K_f: return "f"
            elif event.key == K_g: return "g"
            elif event.key == K_h: return "h"
            elif event.key == K_m: return "m"
            elif event.key == K_n: return "n"
            elif event.key == K_p: return "p"
            elif event.key == K_q: return "q"
            elif event.key == K_s: return "s"
            elif event.key == K_0: return "0"
            elif event.key == K_1: return "1"
            elif event.key == K_2: return "2"
            elif event.key == K_3: return "3"
            elif event.key == K_4: return "4"
            else:
                if VERBOSE:
                    print "undefined key %r" % event.key
                return "undefined"
        else:
            return None



def drawtext(screen, font, text, color, line):
    w,h = font.size(text)
    if line==TOPLINE:
        y = CENTER[1]-font.get_height()*2
    elif line==TOPLOWLINE:
        y = CENTER[1]-font.get_height()/2*1.7
    elif line==BOTTOMHIGHLINE:
        y = CENTER[1]+font.get_height()/2*1.7
    elif line==BOTTOMLINE:
        y = CENTER[1]+font.get_height()*2
    else:
        raise Exception("No such position variable: ", line)
    
    # hack: changed x2 to 1 from 0, fixed (my) SDL-ttf render fail
    # if you get an error at this line, try changing back to:
    # screen.blit(font.render(text, 0, color), (CENTER[0]-(w/2), y))
    screen.blit(font.render(text, 1, color), (CENTER[0]-(w/2), y))


def init():
    pygame.init()
    screen = pygame.display.set_mode(WINSIZE)
    pygame.display.set_caption("Parallel")
    screen.fill(BLACK)
    return screen


def printatheight(text,height):
    font = pygame.font.Font(FONT, FONT_SIZE)
    w, h = font.size(text)
    screen.blit(font.render(text, 1, YELLOW), (CENTER[0]-(w/2),height-h))
    pygame.display.flip()

def store():
    """
    Stores the English/Lojban examples for easy retrieval
    """
    level = None

    printatheight("Which level do you want? 0 (pronounciation), 1 (easy), 2, 3, 4 (hard)?",CENTER[1])
    printatheight("press either 0 1 2 3 4 or esc (to quit)", FOOTER)
    
    while level not in ["0","1","2","3","4"]:
        level = getkeys()

    if level == "0":
        datafile = "Lojban Pronunciation.txt"
        datadir = "Pronunciation"
    else:
        datafile = "Lojban" + level.zfill(3) + "_Eng.txt"
        datadir = "Lojban_" + level.zfill(3)
    
        
    f = open(os.path.join(DATAPATH,datafile),"r")
    total = f.readlines()
    f.close()

    counter = 0
    symplus = False
    toplay=""
    working = total[1:]+["  "]
    timefortwo = .4
    if level == "0":
        timeforspace = 1.1
    else:
        timeforspace = .3

    storage = [] # Contains the English sentence and Lojban translation, it contains a series of "toadd", i.e. a series of examples

    # One "toadd" for each example Structure is :
    # [[1st English, 2nd English, etc.],[1st Lojban, 2nd Lojban, etc.],[wavfilename,time of play]]. An example may have multiple sentences, i.e. question+answer
    toadd = [[],[],[]]
    
    for el in working:
        if el[1] == "+":
            word = string.join(el[2:].split("\x00"),"").strip()
            if symplus: # In this case a Lojban answer to a question
                symplus = False
                toadd[1].append(word)
                much = sum([len(m)/2.*timefortwo for m in word.split(" ")])+word.count(" ")*timeforspace # Time for pronunciation
                toadd[2][1] += much # Add time for answer
            else: # In that case an English answer to a question
                symplus = True
                toadd[0].append(word)

        elif el[1] == "`": # In this case, we have the Lojban sentence
            counter = string.join(el.split("`")[1].split("\x00"),"").strip() # Voodoo for extracting Wav file number from unicode string
            toplay = datadir+os.sep+counter+".wav" # Wav file name

            word = string.join(el.split("`")[2].split("\x00"),"").strip() # Voodoo for extracting sentence from unicode string
            much = sum([len(m)/2.*timefortwo for m in word.split(" ")])+word.count(" ")*timeforspace # Time for pronunciation

            toadd[1].append(word)
            toadd[2].append(toplay)
            toadd[2].append(much)
            
        else: # In that case, we have the English sentence
            storage.append(toadd) # We have a new English example, so we store the previous one...
            toadd = [[],[],[]] # and a new example begins
            word = string.join(el.split("\x00"),"").strip()
            toadd[0].append(word)

    return storage[1:], level


def play(file):
    if VERBOSE:
        print "Playing %s..." % file
    sound_object = pygame.mixer.Sound(file)
    sound_object.play()

def sound_stop():
    pygame.mixer.stop()


def main():
    font = pygame.font.Font(None, 30) #Be nice to specify your own font


    storage, level = store()

    automatise = ""
    screen.fill(BLACK)
    printatheight("MANUAL (recommended) (press key for next/previous example and translation)",CENTER[1]-30)
    printatheight("GUESS (examples given RANDOMLY one after the other with short",CENTER[1]-10)
    printatheight("delay between Enlgish and Lojban displays to find translation)",CENTER[1]+10)
    printatheight("SCROLLING (examples given RANDOMLY one after the other with English/Lojban displayed simultaneously)",CENTER[1]+30)
    printatheight("NOTE: GUESS and SCROLLING can not yet be conveniently interrupted, use with caution!",CENTER[1]-70)
    printatheight("press either m(anual) g(uess) s(croll) or esc (to quit)",FOOTER)
    
    while automatise not in ["m","g","s"]:
        automatise = getkeys()
    screen.fill(BLACK)
    



    tick, tock = YELLOW, BLACK
    this, next = tick, tock

    numexamples = len(storage)
    poslist = range(numexamples) # List of examples' positions
    

    pos = 0
    choice = None
    numex = 0
    shown = False

    while True:

        if automatise != "m":

            if poslist == []:
                break
            numex += 1
            pos = random.randrange(len(poslist)) # Random example for Learning and Scrolling mode
            el = storage[poslist[pos]]
            poslist.pop(pos) # Remove the index of the example drawn

        else:
            numex = pos+1 # If automatise != "M" pos and poslist[pos] are equal
            el = storage[pos]
            
        


        pygame.display.set_caption("Parallel - Level " + level + " - Example number " + str(numex) + "/" + str(numexamples))
        drawtext(screen, font, el[0][0], YELLOW, TOPLINE)
        pygame.display.flip()
        if len(el[0]) == 2:
            drawtext(screen, font, el[0][1], YELLOW, BOTTOMHIGHLINE)
            pygame.display.flip()
            
        if automatise == "m":
            
            
            p = " (p)revious" * (pos > 0)
            n = "(n)ext" * (pos < numexamples-1)
            choice = None
            printatheight("press either"+p+" "+n+" h(ear) or esc (to quit)",FOOTER)
            while choice not in ["n"*(pos < numexamples-1), "p"*(pos > 0), "h"]:
                choice = getkeys();
            screen.fill(BLACK)
            
            
            if choice == "h":
                p = re.compile('(/.*?)\.wav') #The only time re is used in the entire program
                ogg = p.sub(r'/cleaned/\1.ogg',el[2][0])
                mp3 = p.sub(r'/cleaned/\1.mp3',el[2][0])
                if os.path.exists(ogg):
                    sound = ogg
                elif os.path.exists(mp3):
                    sound = mp3
                elif os.path.exists(el[2][0]):
                    sound = el[2][0]
                else:
                    print "Neither ogg, mp3 '%s' nor '%s' exists!" % (mp3, el[2][0])
                    sound = None
                if sound is not None:
                    play(sound)
                shown = True
            elif choice == "n": #and n != "":
                if shown == True:
                    pos += 1
                    shown = False
                    continue
                else:
                    shown = True
                    pass
            elif choice == "p": #and p != "":
                pos -= 1
                continue
            else:
                pass
            
        elif automatise == "g":
            if level != "0":
                time.sleep(el[2][1])
            else:
                pass
        
        drawtext(screen, font, el[1][0], CYAN, TOPLOWLINE)
        pygame.display.flip()

        if automatise != "m":
            time.sleep(el[2][1])
            screen.fill(BLACK)

        
        if len(el[1]) == 2:
            drawtext(screen, font, el[1][1], CYAN, BOTTOMLINE)
            pygame.display.flip()



if __name__=="__main__":
    screen = init() # ugly hack, remove
    main()
