
# WORK IN PROGRESS

if True: # text editor fold
	#look into use of EVT_THREAD

	import time
	time.clock() # initialize asp

	#import logging

	"""
	Entry point for client app. Should be like Minecraft's Launcher or 
	MultiMC or Magic Launcher or linux sysadm gui tool or X3's mod loader 
	utility - From the START of this project.

	This wx gui client creates a thread purposed to read stdin. The thread
	reads from stdin which is "BLOCKING".

	Do not use pythonw.exe or pyw.exe as the originating command.exe/cmd.exe
	is used to enter text commnand into or pipe a file into.
	"""

	author = 'DevPlayer@gmail.com'
	version = '0.5.2'
	copyright = '2013-Sep-10'
	license = 'LGPL, give credit please'
	credits = '''Robin Dunn and co-authors of wxPydemo for which parts '''\
		'''of this work is derived and directly copied from.'''


	#----------------------------------------------------------------------

	import sys, os
	import wx
	#import threading
	import thread	# wxPython examples use this


	#----------------------------------------------------------------------

	# main frame
	launcher_frame_title = 'Game Launcher'
	statusbar_update_millis = 900 # intentially under 1 sec

	# used by SplashScreen
	splashDuration = 4000
	splashscreen_bitmap_filename = "SplashScreen.png"

	# used by StdInConsumerThread
	prompt = 'CMD:>'
	terminator = 'stop'


	#----------------------------------------------------------------------

	import  wx.lib.newevent

	# This creates a new Event class and a EVT binder function
	(DataAvailableEvent, EVT_DATA_AVAILABLE) = wx.lib.newevent.NewEvent()
	(StatusUpdateEvent, EVT_STATUS_UPDATE) = wx.lib.newevent.NewEvent()
	(DestroyThreadEvent, EVT_THREAD_DESTROY) = wx.lib.newevent.NewEvent()


	EOL = '\n'

#----------------------------------------------------------------------

class StdInConsumerThread:
	def __init__(self, win, prompt='', terminator='stop'):
		self.win = win
		self.terminator = terminator
		self.keepGoing = self.running = False

	def Start(self):
		self.keepGoing = self.running = True
		thread.start_new_thread(self.Run, ())

	def Stop(self):
		self.keepGoing = False

	def IsRunning(self):
		return self.running

	def Run(self):
		while self.keepGoing:
			try:
				# raw_input can be replaced with sys.stdin.read(1) for
				# keystroke processing;

				# using raw_input() is mostly expecting ascii string
				# EOL terminated like objects as opposed to unicode or
				# binary data.

				# THIS BLOCKS; hence put into a thread
				data_in = raw_input(prompt)

			except KeyboardInterrupt as err:
				self.keepGoing = False
				#break

			except EOFError as err:
				#if lines == 0:
				#	sys.stderr.write('Do: "C:\\type textfile.txt | python.exe consume.py"\nDo not do: "C:\\type textfile.txt | consume.py"')
				self.keepGoing = False
				#break

			except IOError as err:
				#sys.stderr.write('Do: "C:\\type textfile.txt | python.exe consume.py"\nDo not do: "C:\\type textfile.txt | consume.py"')
				self.keepGoing = False

			if self.terminator in data_in:
				# Don't presume all data from stdin is EOL terminated.
				#data_in = data_in.replace( self.terminator, '' )
				self.keepGoing = False

			else:
				print('not terminator') # debug message
				pass

			wx.PostEvent(self.win, DataAvailableEvent(data=data_in))

		self.running = False
		msg = 'thread terminating'  # DEBUG MESSAGE
		wx.PostEvent(self.win, DataAvailableEvent(data=msg))
		wx.PostEvent(self.win, DestroyThreadEvent(data=msg))
		print(msg) # debug message


class LauncherPanel(wx.Panel):
	def __init__(self, *args, **kwargs):
		wx.Panel.__init__(self, *args, **kwargs)
		#self.GetParent().LauncherPanel = self

		wx.GetApp().SetStatus('LauncherPanel initializing')

		self.CreateWidgets()
		self.PositionWidgets()

		wx.GetApp().SetStatus('LauncherPanel Idle')


	def CreateWidgets(self):
		self.tc_out = wx.TextCtrl(self, 
			style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.TE_DONTWRAP)
		self.tc_inn = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
		self.tc_inn.SetFocus()
		self.tc_inn.SetInsertionPoint(0)
		self.tc_inn.Bind(wx.EVT_TEXT_ENTER, self.OnEnterKey, self.tc_inn)
		self.tc_inn.Bind(wx.EVT_TEXT, self.OnTextEvt, self.tc_inn)

		self.InjectSelfReferenceIntoParent()

	def PositionWidgets(self):
		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.tc_out, 1, wx.EXPAND|wx.ALL, 0)
		sizer.Add(self.tc_inn, 0, wx.EXPAND|wx.ALL, 0)
		self.SetSizer(sizer)


	def InjectSelfReferenceIntoParent(self):
		self.GetParent().logger = self.tc_out
		self.GetParent().logger.incoming = self.tc_out.AppendText


	def OnEnterKey(self, event):
		wx.GetApp().SetStatus('LauncherPanel processing entered command')

		text = self.tc_inn.GetValue()
		self.tc_out.AppendText( text + '\n' )
		self.tc_inn.Clear()


	def OnTextEvt(self, event):
		try:
			self.GetParent().SetStatusText('Status: typing...', 0)
			wx.GetApp().SetStatus('LauncherPanel typing...')
		except: pass


class StatusBar(wx.StatusBar):
	def __init__(self, parent, id=-1, *args, **kwargs):
		wx.StatusBar.__init__(self, parent, id, *args, **kwargs)
		self.SetFieldsCount(3)
		self.timer = wx.Timer(self)
		self.timer.Start(statusbar_update_millis)

		self.Bind(wx.EVT_TIMER, self.OnTimerTick, self.timer)
		self.Bind(EVT_STATUS_UPDATE, self.OnStatusUpdate)  # REMOVE THIS?

	def OnStatusUpdate(self, event):  # REMOVE THIS?
		self.SetStatusText( 'Status: %s' % event.status, 0)  # REMOVE THIS?

	def OnTimerTick(self, event):
		self.SetStatusText( 'Status: %s' % self.GetParent().GetStatus(), 0)
		self.SetStatusText( 'Seconds: %s' % str(int(time.clock())), 1)
		self.SetStatusText( time.asctime(), 2)

	def OnClose(self, event):
		self.timer.Stop()


class LauncherFrame(wx.Frame):

	threads = []

	def __init__(self, *args, **kwargs):
		wx.Frame.__init__(self, *args, **kwargs)

		# inject reference into App
		# allows for multiple toplevel frames of different types
		wx.GetApp().LauncherFrame = self

		self.CreateMenuBar()
		self.CreateToolBar()
		self.CreateStatusBar()
		self.CreateFramePanel()
		self.CreateThreads()
		self.Show()
		self.StartThreads()


	def CreateThreads(self):
		thread = self.CreateStdInConsumerThread()
		self.threads.append(thread)


	def CreateStdInConsumerThread(self):
		thread = StdInConsumerThread(self, prompt, terminator)
		self.Bind(EVT_DATA_AVAILABLE, self.OnData)
		return thread


	def StartThreads(self, index=None):
		if index:
			self.threads[index].Start()
		else:
			for thread in self.threads:
				thread.Start()


	def StopThreads(self):
		for t in self.threads:
			t.Stop()

		running = 1

		while running:
			running = 0

			for t in self.threads:
				running = running + t.IsRunning()

			time.sleep(0.1)


	def CreateMenuBar(self):
		menubar = wx.MenuBar()

		menu = wx.Menu()
		menuitem = menu.Append(wx.ID_EXIT)
		self.Bind(wx.EVT_MENU, self.OnClose, menuitem)
		menubar.Append(menu, "&File")

		self.SetMenuBar(menubar)
		return menubar


	def CreateToolBar(self):
		return


	def CreateStatusBar(self):
		#self.SetStatusBar( super(
		#	LauncherFrame, self).CreateStatusBar(3) )
		self.SetStatusBar( StatusBar(self) )
		return self.GetStatusBar()


	def CreateFramePanel(self):
		self.LauncherPanel = LauncherPanel(self)
		return self.LauncherPanel


	def OnData(self, event):
		'''Data processing collection and distribution point for various
		incoming/outgoing data sources.'''
		# so far just the one.
		self.ProcessIncomingData(event.data)


	def ProcessIncomingData(self, data):
		# do something to data
		print('ProcessIncomingData(%s)' % repr(data) )

		if not hasattr(self, 'logger'):
			print('Launcherframe does NOT have logger')
		else:
			#print('LauncherFrame has logger')
			if not hasattr(self.logger, 'incoming'):
				print('LauncherFrame.logger does NOT have incoming')
			else:
				#print('LauncherFrame.logger has incoming')

				if not callable(self.logger.incoming):
					print('LauncherFrame.logger.incoming is NOT callable')
				else:
					#print('LauncherFrame.logger.incoming is callable')
					self.logger.incoming(data+EOL)


	def OnClose(self, event):
		busy = wx.BusyInfo("One moment please, waiting for threads to die...")
		wx.Yield()
		self.StopThreads()
		self.Destroy()


	def GetStatus(self):
		return 'Idle'


class SplashScreen(wx.SplashScreen):
	def __init__(self):

		aBitmap = wx.Image(name=splashscreen_bitmap_filename).ConvertToBitmap()
		splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT
		parent = None

		wx.SplashScreen.__init__(self, aBitmap, splashStyle, splashDuration, parent)
		self.Bind(wx.EVT_CLOSE, self.OnExit)
		self.Show()
		wx.Yield()

	def OnExit(self, event):
		self.Hide()
		frame = LauncherFrame(None, title=launcher_frame_title)
		frame.LauncherPanel.tc_out.AppendText(instructions)

		wx.GetApp().SetTopWindow( frame )
		frame.Show()
		event.Skip()


class App(wx.App):

	status = {}

	def OnInit(self):
		self.SetStatus('App initializing')
		SplashScreen()
		self.SetStatus('App initialized')
		return True

	def SetStatus(self, statusMessage):
		widget, status = statusMessage.split(' ', 1)
		cpu_ticks = str(time.clock())
		self.status[widget] = (status, time.clock())
		print('App.status[%s] = (%s, %s)' % (widget, status, cpu_ticks))



if __name__ == "__main__":
	instructions = \
	'''. Type stop<enter> in the DOS console to terminate the thread from there.\n'''\
	'''. Clicking the X frame button does not even yet try to properly terminate \n'''\
	'''  the thread. Needs to be added.\n'''\
	'''. Menu File->Close does not properly work eventhough it similar to the \n'''\
	'''  wxPydemo.\n'''

	print(instructions)
	print

	app = App()
	app.MainLoop()
