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

import sys,os,os.path,time
# 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='gts,opengl,openmp,qt4,vtk'.split(','),'0.70.0'

## find available builds
nonDebugLibDir=prefix+'/lib/yade'+suffix
debugLibDir=nonDebugLibDir+'/dbg'
hasDebug,hasNonDebug=os.path.exists(debugLibDir+'/py/yade/__init__.py'),os.path.exists(nonDebugLibDir+'/py/yade/__init__.py')
if hasDebug and hasNonDebug: buildsAvailable='both non-debug and debug build'
elif hasDebug and not hasNonDebug: buildsAvailable='debug build only'
elif not hasDebug and hasNonDebug: buildsAvailable='non-debug build only'
else:
	raise RuntimeError('Neither non-debug nor debug build found! ('+nonDebugLibDir+'/py/yade/__init__.py, '+debugLibDir+'/py/yade/__init__.py)')


# handle command-line options first
import optparse
par=optparse.OptionParser(usage='%prog [options] [ simulation.xml[.bz2] | script.py [script options]]',prog=os.path.basename(sys.argv[0]),version='%s (%s; %s)'%(version,','.join(features),buildsAvailable),description="Yade: open-source platform for dynamic compuations. Homepage http://www.yade-dem.org, code hosted at http://www.launchpad.net/yade. This is version %s (with features %s, %s)."%(version,','.join(features),buildsAvailable))
par.add_option('-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_option('--cores',help='Set number of OpenMP threads (as \-\-threads) and in addition set affinity of threads to the cores given.',dest='cores',type='string')
par.add_option('--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',action='store_true')
par.add_option('--nice',help='Increase nice level (i.e. decrease priority) by given number.',dest='nice',type='int')
par.add_option('-x',help='Exit when the script finishes',dest='exitAfter',action='store_true')
par.add_option('-v',help='Increase logging verbosity; first occurence sets default logging level to info, second to debug, third to trace.'+
	('' if 'log4cxx' in features else " (Since this build doesn't use log4cxx, this option will only have effect if repeated twice (\-vv), equivalent to setting YADE_DEBUG environment variable)"),action='count',dest='verbosity')
par.add_option('-n',help="Run without graphical interface (equivalent to unsetting the DISPLAY environment variable)",dest='nogui',action='store_true')
par.add_option('--generate-manpage',help="Generate man page documenting this program and exit",dest='manpage',metavar='FILE')
par.add_option('--rebuild',help="Re-run build in the source directory, then run the updated yade with the same command line except \-\-rebuild. The build profile for this build (deb) and its stored parameters will be used.",dest='rebuild',action='store_true')
par.add_option('--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_option('--debug',help='Run the debug build, if available.',dest='debug',action='store_true')
par.add_option('--checks',help='Run a series of user-defined check tests as described in /build/buildd/yade-0.70.0/scripts/test/checks/README',dest='checks',action='store_true')
par.add_option('--performance',help='Starts a test to measure the productivity',dest='performance',action='store_true')
par.add_option('--no-gdb',help='Do not show backtrace when yade crashes (only effective with \-\-debug).',dest='noGdb',action='store_true',)
par.disable_interspersed_args()

opts,args=par.parse_args()

# re-build yade so that the binary is up-to-date
if opts.rebuild:
	import subprocess
	# rebuild
	sourceRoot,profile='/build/buildd/yade-0.70.0','deb' # replaced at install-time
	cmd=['scons','-Q','-C',sourceRoot,'profile=%s!'%profile,'debug=%d'%(1 if opts.debug else 0),'execCheck=%s'%(prefix+'/bin/yade'+suffix)]
	print 'Rebuilding yade using',' '.join(cmd)
	if subprocess.call(cmd): raise RuntimeError('Error rebuilding Yade (--rebuild).')
	# run ourselves
	argv=[v for v in sys.argv if v!='--rebuild']
	print 'Running yade using',' '.join(argv)
	sys.exit(subprocess.call(argv))

if opts.debug:
	if not hasDebug:
		raise RuntimeError('Debug build not available (run without --debug, or try --debug --rebuild)')
	libDir=debugLibDir
else:
	if not hasNonDebug:
		print 'WARNING: non-debug build not available, running with --debug instead (try --rebuild to get the non-debug build).'
	libDir=nonDebugLibDir

## 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)

# c++ boot code checks for YADE_DEBUG at some places; debug verbosity is equivalent
# do this early, to have debug messages in the boot code (plugin registration etc)
if opts.verbosity>1: os.environ['YADE_DEBUG']='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'

sys.stderr.write('Welcome to Yade '+version+'%s\n'%(' (debug build)' if opts.debug else ''))

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

# continue option processing

if opts.updateScripts:
	yade.system.updateScripts(args)
	sys.exit(0)
if opts.manpage:
	import yade.manpage
	yade.manpage.generate_manpage(par,yade.config.metadata,opts.manpage,section=1,seealso='yade%s-batch (1)'%suffix)
	print 'Manual page %s generated.'%opts.manpage
	sys.exit(0)
if opts.nice:
	os.nice(opts.nice)
if yade.config.debug and opts.noGdb:
	yade.wrapper.Omega().disableGdb()
if 'log4cxx' in yade.config.features and opts.verbosity:
	yade.log.setLevel('',[yade.log.INFO,yade.log.DEBUG,yade.log.TRACE][min(opts.verbosity,2)])

# 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 *
from math import *
from utils import *
from yade import pack
from pack import *

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

# Run performance check test
if opts.performance:
	checksPath='/build/buildd/yade-0.70.0'+'/scripts/test/performance'
	execfile(checksPath+'/checkPerf.py')

def userSession(qt4=False,qapp=None):
	# prepare nice namespace for users
	import yade, yade.runtime
	import sys
	# 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')
			O=yade.wrapper.Omega(); O.load(arg0); O.run()
		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','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()

## 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

# run remote access things, before actually starting the user session
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)
