from __future__ import division, print_function
import wx
from threading import Thread
import sys
from math import sin, cos
import time

# The user program imports this module, which does this:
#   (1) Use wxpython to set up the GUI, but not start MainLoop.
#   (2) Read the source of the user (importing) program.
#   (3) In the user source, process the import of this file, then
#       comment out the import statement.
#   (4) exec the source, with suitable globals set up for the exec.
# The user program must periodically call rate(), which drives the
# single-shot function oneMainLoopIteration() to process events.

# All of this complexity is caused by the fact that the Cocoa framework
# on the Mac will not run if MainLoop is not in the primary thread.
# Basically, we've turned things inside out: Instead of directly executing
# the user's program, the user's program imports this module which in turn
# executes a modified version of the user's program.

# Additional complexity is caused by the fact that Python does not permit
# imports from a secondary thread (or inside a function in an import module).

width = 400 # default window width and height
height = 400

renderable_objects = [] # active objects to be rendered periodically

class line(): # a simple 2D object (a line)
    def __init__(self):
        self.cx = width/2
        self.cy = (height-50)/2
        self.L = 140
        self.angle = 0
        renderable_objects.append(self)

    def update(self):
        dx = (self.L/2)*cos(self.angle)
        dy = (self.L/2)*sin(self.angle)
        return (self.cx-dx, self.cy+dy, self.cx+dx, self.cy-dy) # invert y coordinates

lastrate = 0 # time of last call to rate()
def rate(f): # limit a loop rate to f iterations per second
    global lastrate
    dt = 1.0/f
    lastrate += dt
    t = time.time()
    if lastrate > t:
        oneMainLoopIteration()
        time.sleep(lastrate-t)
    else:
        if lastrate < t-0.1: lastrate = t

class Frame(wx.Frame): # set up the GUI environment
    def __init__(self, parent, w, h, title):
        super(Frame, self).__init__(parent, title=title, 
            size=(w, h), pos=(0,30))
        global width, height
        width = w
        height = h

        MenuBar = wx.MenuBar()
        FileMenu = wx.Menu()
        item = FileMenu.Append(wx.ID_EXIT, "&Exit")
        self.Bind(wx.EVT_MENU, self.OnQuit, item)
        MenuBar.Append(FileMenu, "&File")
        self.SetMenuBar(MenuBar)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
        self.timer.Start(30) # render every 30 milliseconds

        self.Show()

    def render(self): # periodically render existing objects, based on the current attributes
        if len(renderable_objects) > 0:
            dc = wx.ClientDC(self)
            dc.Clear()
            x1, y1, x2, y2 = renderable_objects[0].update() # only one object for now, a line
            dc.DrawLine(x1, y1, x2, y2)

    def OnQuit(self, evt):
        self.Destroy()

    def OnTimer(self, evt):
        self.render()

    def OnPaint(self, evt):
        self.render()

app = wx.App()  # create a wxpython application
Frame(None, 300, 300, 'Rotating Line') # set up wxpython GUI environment
         
evtloop = wx.GUIEventLoop()
wx.EventLoop.SetActive(evtloop)

def oneMainLoopIteration():
    while not evtloop.Pending() and evtloop.ProcessIdle(): pass
    if wx.GetApp(): wx.GetApp().ProcessPendingEvents()
    if not evtloop.Dispatch(): return
    # Currently on wxOSX Pending always returns true, so the
    # ProcessIdle above is not ever called. Call it here instead.
    if 'wxOSX' in wx.PlatformInfo: evtloop.ProcessIdle()
    while True:
        checkAgain = False
        if wx.GetApp() and wx.GetApp().HasPendingEvents():
            wx.GetApp().ProcessPendingEvents()
            checkAgain = True
        if 'wxOSX' not in wx.PlatformInfo and evtloop.Pending():
            evtloop.Dispatch()
            checkAgain = True
        if not checkAgain:
            break

# Imports must be handled here, at base level in the module (not in a def).
# For the import statement in the user file that imports this module, we perform
# the import and extract the imported information, adding it to the globals
# used to exec the file.

# Initialize exec's globals so that user program uses future division and print_function:
ginfo = {'division':globals()['division'], 'print_function':globals()['print_function']}
filename = sys.argv[0]
prog = open(filename).read() # read starting .py file

def underscore(prefix): # convert wxpoll.rate to wxpoll_rate
    global prog
    prefix += '.'
    start = 0
    while True:
        f = prog.find(prefix)
        if f < 0: break
        f += len(prefix)-1
        prog = prog[:f]+'_'+prog[f+1:]

importables = ['rate', 'line']
start = 0
while True:
    # Find a statement "from x import y" or a statement "import x" or "import x as y"
    # Assumes for the moment that the import statement is not indented, nor preceded by "; "
    # See test program parse_test.py
    imp = prog.find('import', start)
    if imp == -1: break
    begin = prog.rfind('\n', 0, imp)+1
    if begin == -1: begin = 0
    end = prog.find('\n', begin)
    if end == -1: end = len(prog)-1
    start = end+1
    if ( imp == begin and not prog[imp+6].isalnum() ) or \
           ( prog[begin:begin+5] == 'from ' and \
             not prog[imp-1].isalnum() and not prog[imp+6].isalnum() ):
        # Found a legitimate import statement
        stmt = prog[begin:end] # the import statement
        prog = prog[:begin]+'#'+prog[begin:] # comment out the import statement in source
        t = stmt.split()
        if t[0] == 'import':
            if len(t) < 2:
                raise ImportError(s)
            X = t[1]
            if len(t) == 2:
                import_type = 0
                import_info = ['import', X]
            elif t[2] == 'as':
                if len(t) != 4:
                    raise ImportError(s)
                import_type = 1
                import_info = ['import', X, 'as', t[3]]
            else:
                raise ImportError(s)
        elif t[0] == 'from':
            if len(t) < 4 or t[2] != 'import':
                raise ImportError(s)
            X = t[1]
            if t[-1] == '*' and len(t) == 4:
                import_type = 3
                import_info = ['from', X, 'import', '*']
            else:
                names = []
                for n in t[3:]:
                    for sp in n.split(','):
                        if sp == '': continue
                        names.append(sp)
                import_info = ['from', X, 'import']
                for n in names:
                    import_info.append(n)
                import_type = 2
        else:
            raise ImportError(stmt)
        if import_info[1] == __name__: # handle imports from this module
            if import_type == 0: # import this_module
                for name in importables:
                    ginfo[import_info[1]+'_'+name] = globals()[name]
                underscore(import_info[1])
            elif import_type == 1: # import this_module as something
                for name in importables:
                    ginfo[import_info[3]+'_'+name] = globals()[name]
                underscore(import_info[3])   
            elif import_type == 2: # from this_module import x, y, z
                for name in import_info[3:]:
                    if name in importables:
                        ginfo[name] = globals()[name]
                    else:
                        raise ImportError(stmt)
            elif import_type == 3: # from this_module import *
                for im in importables:
                    ginfo[im] = globals()[im]
            break
    start = end

exec(prog, ginfo)
while True: # at end of user program, wait for user to close the program
    rate(30)
