#! /usr/bin/python
# encoding: utf-8
# syntax:python

import sys,os,os.path,time
try:
	import _io
except ImportError:
	import io

# Add search path for yade Python-modules
# It allows to use both Yade-versions (packaged and self-compiled one).
# See LP:1254708 for more details
# https://bugs.launchpad.net/yade/+bug/1254708

sys.path.insert(1,'/usr/lib/x86_64-linux-gnu/yade/py')

# get yade path (allow YADE_PREFIX to override)
prefix,suffix='/usr' if not os.environ.has_key('YADE_PREFIX') else os.environ['YADE_PREFIX'],''
# duplicate some items from yade.config here, so that we can increase verbosity when the c++ part is booting
features,version,debugbuild=' vtk openmp gts qt4 opengl'.split(' '),'1.07.0',' '

libPATH='lib/x86_64-linux-gnu'
if (libPATH[1:] == '{LIBRARY_OUTPUT_PATH}'): libPATH='lib'

## find available builds
libDir=prefix+'/'+libPATH+'/yade'+suffix
if not (os.path.exists(libDir+'/py/yade/__init__.py')):
	raise RuntimeError('Libraries are not found! ('+libDir+'/py/yade/__init__.py, /py/yade/__init__.py)')

# handle command-line options first
try:
	import argparse
except ImportError: # argparse not present, print error message
	raise RuntimeError("\n\nPlease install 'python-argparse' package.\n")
prog = os.path.basename(sys.argv[0])
par=argparse.ArgumentParser(usage='%s [options] [ simulation.xml[.bz2] | script.py [script options]]'%prog,
  prog=prog,description="Yade: open-source platform for dynamic compuations. It\
  is an extensible open-source framework for discrete numerical models, focused\
  on Discrete Element Method. The computation parts are written in c++ using\
  flexible object model, allowing independent implementation of new algorithms\
  and interfaces. Python is used for rapid and concise scene construction, \
  simulation control, postprocessing and debugging.\
  Available features: %s.\
  Homepage http://www.yade-dem.org, code hosted at http://www.launchpad.net/yade."%features
  )
par.add_argument('-v','--version',help='Print version and exit.',dest='version',action='store_true')
par.add_argument('-j','--threads',help='Number of OpenMP threads to run; defaults to 1. Equivalent to setting OMP_NUM_THREADS environment variable.',dest='threads',type=int)
par.add_argument('--cores',help='Set number of OpenMP threads (as \-\-threads) and in addition set affinity of threads to the cores given.',dest='cores',type=str)
par.add_argument('--update',help='Update deprecated class names in given script(s) using text search & replace. Changed files will be backed up with ~ suffix. Exit when done without running any simulation.',dest='updateScripts',nargs='+')
par.add_argument('--nice',help='Increase nice level (i.e. decrease priority) by given number.',dest='nice',type=int)
par.add_argument('-x',help='Exit when the script finishes',dest='exitAfter',action='store_true')
par.add_argument('-n',help="Run without graphical interface (equivalent to unsetting the DISPLAY environment variable)",dest='nogui',action='store_true')
par.add_argument('--test',help="Run regression test suite and exit; the exists status is 0 if all tests pass, 1 if a test fails and 2 for an unspecified exception.",dest="test",action='store_true')
par.add_argument('--checks',help='Run a series of user-defined check tests as described in /yade/scripts/checks-and-tests/checks/README',dest='checks',action='store_true')
par.add_argument('--performance',help='Starts a test to measure the productivity',dest='performance',action='store_true')
par.add_argument('script',nargs='?',default='',type=str,help=argparse.SUPPRESS)
par.add_argument('args',nargs=argparse.REMAINDER,help=argparse.SUPPRESS) # see argparse doc, par.disable_interspersed_args() from optargs module
par.add_argument('-l',help='import libraries at startup before importing yade libs. May be used when the ordering of imports matter (see e.g. https://bugs.launchpad.net/yade/+bug/1183402/comments/3). The option can be use multiple times, as in "yade -llib1 -llib2"',default=None,action='append',dest='impLibraries',type=str)
opts=par.parse_args()
args = opts.args

if opts.impLibraries:
	sys.path.append('.')
	for lib in opts.impLibraries:
		__import__(lib)

if opts.version:
	print 'Yade version: %s%s'%(version,debugbuild)
	sys.exit(0)

if opts.script:
	args.insert(0,opts.script) # for compatibility with userSession(), could be modified in the future


## remove later
## python2.5 relative module imports workaround
v=sys.version_info
if v[0]==2 and v[1]<=5:
	for submodule in ('yade','gts','yade/tests'):
		sys.path.append(os.path.join(libDir,'py',submodule))

sys.path.append(os.path.join(libDir,'py'))

# run regression test suite and exit
if opts.test:
	import yade.tests
	try:
		result=yade.tests.testAll()
	except:
		print 20*'*'+' UNEXPECTED EXCEPTION WHILE RUNNING TESTS '+20*'*'
		print 20*'*'+' '+str(sys.exc_info()[0])
		print 20*'*'+" Please report bug at http://bugs.launchpad.net/yade providing the following traceback:"
		import traceback; traceback.print_exc()
		print 20*'*'+' Thank you '+20*'*'
		sys.exit(2)
	if result.wasSuccessful():
		print "*** ALL TESTS PASSED ***"
		sys.exit(0)
	else:
		print 20*'*'+' SOME TESTS FAILED '+20*'*'
		sys.exit(1)

if not 'openmp' in features and (opts.cores or (opts.threads and opts.threads>1)):
	print 'WARNING: compiled without OpenMP, -j/--threads/--cores have no effect.'

# OpenMP env variables must be se before loading yade libs ("import yade" below)
# changes have no effeect after libgomp initializes
if opts.cores:
	if opts.threads: print 'WARNING: --threads ignored, since --cores specified.'
	try:
		cores=[int(i) for i in opts.cores.split(',')]
	except ValueError:
		raise ValueError('Invalid --cores specification %s, should be a comma-separated list of non-negative integers'%opts.cores)
	opts.nthreads=len(cores)
	os.environ['GOMP_CPU_AFFINITY']=' '.join([str(cores[0])]+[str(c) for c in cores])
	os.environ['OMP_NUM_THREADS']=str(len(cores))
elif opts.threads: os.environ['OMP_NUM_THREADS']=str(opts.threads)
else: os.environ['OMP_NUM_THREADS']='1'

if __name__ == "__main__": # do not print this while importing yade in other python application
	sys.stderr.write('Welcome to Yade '+version+debugbuild+'\n')

# initialization and c++ plugins import
import yade
# other parts we will need soon
import yade.config
import yade.wrapper
import yade.system
import yade.runtime

# continue option processing

if opts.updateScripts:
	yade.system.updateScripts(args)
	sys.exit(0)

# modify sys.argv in-place so that it can be handled by userSession
sys.argv=yade.runtime.argv=args
yade.runtime.opts=opts

from yade import utils, pack
from yade.utils import *
from yade.pack import *
from math import *

# Run the check tests listed in scripts/checks-and-tests/checks/checkList.py
if opts.checks:
	checksPath=libDir+'/py/yade/tests/checks'
	execfile(checksPath+'/checkList.py')

# Run performance check test
if opts.performance:
	checksPath=libDir+'/py/yade/tests/checks/performance'
	execfile(checksPath+'/checkPerf.py')

def userSession(qt4=False,qapp=None):
	# prepare nice namespace for users
	import yade.runtime
	import sys
	if __name__ != "__main__": # for importing as python module
		return
	# start non-blocking qt4 app here; need to ask on the mailing list on how to make it functional
	## with ipython 0.11, start the even loop early (impossible with 0.10, which is thread-based)
	#if qt4 and yade.runtime.ipython_version==11:
	#	import IPython
	#	IPython.appstart_qt4(qapp)
	if len(sys.argv)>0:
		arg0=sys.argv[0]
		if qt4: yade.qt.Controller();
		if sum(bool(arg0.endswith(ext)) for ext in ('.xml','.xml.bz2','.xml.gz','.yade','.yade.gz','.yade.bz2','.bin','.bin.gz','.bin.bz2'))>0:
			if len(sys.argv)>1: raise RuntimeError('Extra arguments to saved simulation to run: '+' '.join(sys.argv[1:]))
			sys.stderr.write("Running simulation "+arg0+'\n')
		if arg0.endswith('.py'):
			def runScript(script):
				sys.stderr.write("Running script "+arg0+'\n')
				try:
					execfile(script,globals())
				except SystemExit: raise
				except: # all other exceptions
					import traceback
					traceback.print_exc()
					if yade.runtime.opts.exitAfter: sys.exit(1)
				if yade.runtime.opts.exitAfter: sys.exit(0)
			runScript(arg0)
	if yade.runtime.opts.exitAfter: sys.exit(0)
	# common ipython configuration
	banner='[[ ^L clears screen, ^U kills line. '+', '.join((['F12 controller','F11 3d view (use h-key for showing help)','F10 both','F9 generator'] if (qt4) else [])+['F8 plot'])+'. ]]'
	ipconfig=dict( # ipython options, see e.g. http://www.cv.nrao.edu/~rreid/casa/tips/ipy_user_conf.py
		prompt_in1='Yade [\#]: ',
		prompt_in2='     .\D.: ',
		prompt_out=" ->  [\#]: ",
		separate_in='',separate_out='',separate_out2='',
		#execfile=[prefix+'/lib/yade'+suffix+'/py/yade/ipython.py'],
		readline_parse_and_bind=[
			'tab: complete',
			# only with the gui; the escape codes might not work on non-linux terminals.
			]
			+(['"\e[24~": "\C-Uyade.qt.Controller();\C-M"','"\e[23~": "\C-Uyade.qt.View();\C-M"','"\e[21~": "\C-Uyade.qt.Controller(), yade.qt.View();\C-M"','"\e[20~": "\C-Uyade.qt.Generator();\C-M"'] if (qt4) else []) #F12,F11,F10,F9
			+['"\e[19~": "\C-Uimport yade.plot; yade.plot.plot();\C-M"', #F8
				'"\e[A": history-search-backward', '"\e[B": history-search-forward', # incremental history forward/backward
		]
	)
	
	# show python console
	# handle both ipython 0.10 and 0.11 (incompatible API)
	if yade.runtime.ipython_version==10:
		from IPython.Shell import IPShellEmbed
		ipshell=IPShellEmbed(banner=banner,rc_override=ipconfig)
		ipshell()
		# save history -- a workaround for atexit handlers not being run (why?)
		# http://lists.ipython.scipy.org/pipermail/ipython-user/2008-September/005839.html
		import IPython.ipapi
		IPython.ipapi.get().IP.atexit_operations()
	elif yade.runtime.ipython_version==11:
		from IPython.frontend.terminal.embed import InteractiveShellEmbed
		# use the dict to set attributes
		for k in ipconfig: setattr(InteractiveShellEmbed,k,ipconfig[k])
		InteractiveShellEmbed.banner1=banner+'\n'  # called banner1 here, not banner anymore
		ipshell=InteractiveShellEmbed()
		ipshell()
	elif yade.runtime.ipython_version>=12:
		from IPython.frontend.terminal.embed import InteractiveShellEmbed
		from IPython.config.loader import Config
		cfg = Config()
		prompt_config = cfg.PromptManager
		prompt_config.in_template = ipconfig['prompt_in1']
		prompt_config.in2_template = ipconfig['prompt_in2']
		prompt_config.out_template = ipconfig['prompt_out']
		import readline
		for k in ipconfig['readline_parse_and_bind']: readline.parse_and_bind(k)
		InteractiveShellEmbed.config=cfg
		InteractiveShellEmbed.banner1=banner+'\n'
		ipshell=InteractiveShellEmbed()
		ipshell()

## run userSession in a way corresponding to the features we use:
gui=None
yade.runtime.hasDisplay=False # this is the default initialized in the module, anyway
if 'qt4' in features: gui='qt4'
if opts.nogui: gui=None
if gui:
	import Xlib.display
	# PyQt4's QApplication does exit(1) if it is unable to connect to the display
	# we however want to handle this gracefully, therefore
	# we test the connection with bare xlib first, which merely raises DisplayError
	try:
		# contrary to display.Display, _BaseDisplay does not check for extensions and that avoids spurious message "Xlib.protocol.request.QueryExtension" (bug?)
		Xlib.display._BaseDisplay();
		yade.runtime.hasDisplay=True
	except: 
		# usually Xlib.error.DisplayError, but there can be Xlib.error.XauthError etc as well
		# let's just pretend any exception means the display would not work
		gui=None
		print 'Warning: no X rendering available (see https://bbs.archlinux.org/viewtopic.php?id=13189)'

# run remote access things, before actually starting the user session (not while imported by other python application)
if __name__ == "__main__":
	from yade import remote
	yade.remote.useQThread=(gui=='qt4')
	yade.remote.runServers()

if gui==None:
	userSession()
elif gui=='qt4':
	## we already tested that DISPLAY is available and can be opened
	## otherwise Qt4 might crash at this point
	import PyQt4
	from PyQt4 import QtGui
	from PyQt4.QtCore import *
	import yade.qt # this yade.qt is different from the one that comes with qt3
	qapp=QtGui.QApplication(sys.argv)
	userSession(qt4=True,qapp=qapp)

if __name__ == "__main__":
	O.exitNoBacktrace()
