""" stretch.py

    This simple wxPython application displays a window which guides the user
    through a series of simple stretches.  The application quits once the
    stretches are over.

    The stretches are stored in plain-text files in the "routines"
    sub-directory.  If multiple routines are present, a routine is selected at
    random.

    Written by Erik Westra (ewestra at gmail dot com).  Stretch is completely
    free to use or modify in any way you like, but of course it comes with no
    warranty either.  Use at your own risk...

    Updated 6/April/2009 to ensure the application bundle works with Leopard.
"""
import random
import os
import os.path
import sys
import time
import traceback

import wx

#############################################################################

def getRoutinesDir():
    """ Return the directory path to our "routines" sub-directory.
    """
    # Find the directory in which our script is running.

    scriptDir = os.path.split(sys.argv[0])[0]

    # See if there's a "routines" directory within the script's directory.  If
    # so, return it directly.

    if os.path.exists(os.path.join(scriptDir, "routines")):
        return os.path.join(scriptDir, "routines")

    # See if we're running in an application bundle.

    dir = os.path.split(scriptDir)[0] # Skip "Resources" directory.
    dir = os.path.split(dir)[0]       # Skip "Contents" directory.
    dir = os.path.split(dir)[0]       # Skip app package directory.
    dir = os.path.join(dir, "routines")

    if os.path.exists(dir):
        return dir

    # If we get here, we haven't got a clue where the routines directory is ->
    # give up.

    return None


class StretchFrame(wx.Frame):
    """ Our test frame.

        This is simply a wrapper around the GUIPanel, which does all the work.
    """
    def __init__(self):
        """ Standard initialiser.
        """
        wx.Frame.__init__(self, None, -1, "Stretching")

        self._label = wx.StaticText(self, -1, "",
                                    style=wx.ALIGN_CENTRE|wx.ST_NO_AUTORESIZE)
        self._label.SetFont(wx.Font(18, self.GetFont().GetFamily(),
                                    wx.NORMAL, wx.BOLD))

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add((0, 0), 1, wx.EXPAND)
        sizer.Add(self._label, 0, wx.EXPAND)
        sizer.Add((0, 0), 1, wx.EXPAND)

        self.SetAutoLayout(True)
        self.SetSizer(sizer)
        self.SetSize(wx.Size(800, 600))
        self.Centre()

        wx.CallAfter(self.run)


    def run(self):
        """ Select and run a stretching script.
        """
        # Build a list of all the available scripts.

        routinesDir = getRoutinesDir()
        if routinesDir == None:
            self._message("Unable to find 'Routines' directory")
            return

        scripts = []
        for fName in os.listdir(routinesDir):
            if fName.startswith("."): continue
            if not fName.endswith(".txt"): continue
            scripts.append(fName)

        if len(scripts) == 0:
            self._message("No script found")
            return

        # Choose a script at random.

        self._curScript = random.choice(scripts)

        # Process the script, one line at a time.

        f = open(os.path.join(routinesDir, self._curScript), "r")
        lines = f.readlines()
        f.close()

        self._lines   = lines
        self._lineNum = 0

        wx.CallAfter(self.nextCommand)


    def nextCommand(self):
        """ Perform the next command in our script.
        """
        if self._lineNum >= len(self._lines):
            self.finished()
            return

        line = self._lines[self._lineNum].strip()
        self._lineNum = self._lineNum + 1

        if len(line) == 0 or line.startswith("#"): # Blank or comment.
            wx.CallAfter(self.nextCommand)
            return

        namespace = {'message' : self.doMessageCmd,
                     'pause'   : self.doPauseCmd,
                     'say'     : self.doSayCmd}

        try:
            exec line in namespace
        except Exception,e:
            type,value,tb = sys.exc_info()
            errMsg = traceback.format_exception_only(type, value)
            errMsg = "".join(errMsg).rstrip()
            self._message("Error processing file "+ repr(self._curScript)+":" +
                          "\n\n" +
                          errMsg +
                          "\n\n" +
                          "Line " + str(self._lineNum) + ": " + line)


    def finished(self):
        """ Respond to the current script being finished.
        """
        self._message("All done!")
        wx.FutureCall(2000, self.Destroy)

    # ======================
    # == COMMAND HANDLERS ==
    # ======================

    def doMessageCmd(self, msg):
        """ Respond to a "message" command.
        """
        self._message(msg)
        wx.CallAfter(self.nextCommand)


    def doPauseCmd(self, delay):
        """ Respond to a "pause" command.
        """
        wx.FutureCall(delay * 1000, self.nextCommand)


    def doSayCmd(self, text):
        """ Respond to a "say" command.
        """
        os.system("say '" + text + "' &")
        wx.CallAfter(self.nextCommand)

    # =====================
    # == PRIVATE METHODS ==
    # =====================

    def _message(self, msg):
        """ Display the given message.
        """
        self._label.SetLabel(msg)
        self.Layout()

#############################################################################

class StretchApp(wx.App):
    """ Our application object.
    """
    def OnInit(self):
        """ Start up the stretching app.
        """
        frame = StretchFrame()
        frame.Show()
        self.SetTopWindow(frame)
        return True

#############################################################################

def main():
    """ Our main program.
    """
    app = StretchApp(False)
    app.MainLoop()

#############################################################################

if __name__ == "__main__":
    main()

