#! /usr/bin/env python
import os
import glob
import socket
import numpy
#import h5py
import netCDF4
import sys

def runme(id=None,exclude=None,benchmark='nightly',procedure='check',output='none',rank=1,numprocs=1):
	"""
	RUNME - test deck for ISSM nightly runs
 
	    In a test deck directory (tests/Vertification/NightlyRun for example)
	    The following command will launch all the existing tests:
	    >>> runme()
	    To run the tests 101 and 102:
	    >>> runme(id=[101,102])
	    etc...
 
	    Available options:
	       'id'            followed by the list of ids requested
	       'exclude'       ids to be excluded from the test
	       'benchmark'     'nightly' (nightly run/ daily run)
	                       'ismip'  : validation of ismip-hom tests
	                       'eismint': validation of eismint tests
	                       'thermal': validation of thermal tests
	                       'mesh'   : validation of mesh tests
	                       ...
	       'procedure'     'check' : run the test (default)
	                       'update': update the archive
 
	    Usage:
	       md=runme(varargin);
 
	    Examples:
	       runme()
	       runme(exclude=101)
	       md=runme(id=102,procedure='update')
	"""

	from parallelrange import parallelrange
	from IdToName import IdToName

	#Get ISSM_DIR variable
	ISSM_DIR=os.environ['ISSM_DIR']
	print 'ISSM_DIR =',ISSM_DIR

	#Process options
	#GET benchmark {{{1
	if not benchmark.lower() in ['all','nightly','ismip','eismint','thermal','mesh','validation','tranforcing']:
		print "runme warning: benchmark '%s' not supported, defaulting to test 'nightly'." % benchmark
		benchmark='nightly'
	# }}}
	#GET procedure {{{1
	if not procedure.lower() in ['check','update']:
		print "runme warning: procedure '%s' not supported, defaulting to test 'check'." % procedure
		procedure='check'
	# }}}
	#GET output {{{1
	if not output.lower() in ['nightly','daily','none']:
		print "runme warning: output '%s' not supported, defaulting to test 'none'." % output
		output='none'
	# }}}
	#GET RANK and NUMPROCS for multithreaded runs {{{1
	if (numprocs<rank):
		numprocs=1
	# }}}

	print 'id =',id
	print 'exclude =',exclude
	print 'benchmark =',benchmark
	print 'procedure =',procedure
	print 'output =',output
	print 'rank =',rank
	print 'numprocs =',numprocs

	#GET ids  {{{1
	flist=glob.glob('test*.py')    #File name must start with 'test' and must end by '.py' and must be different than 'test.py'
	list_ids=[int(file[4:-3]) for file in flist if not file == 'test.py']    #Keep test id only (skip 'test' and '.py')
	print 'list_ids =',list_ids

	i1,i2=parallelrange(rank,numprocs,len(list_ids))    #Get tests for this cpu only
	list_ids=list_ids[i1:i2+1]
	print 'list_ids after parallelrange =',list_ids

	if id:
		if isinstance(id,list):
			test_ids=id
		else:
			test_ids=[id]
		test_ids=set(test_ids).intersection(set(list_ids))
	else:
		test_ids=set(list_ids)
#	print 'test_ids after list =',test_ids
	# }}}

	#GET exclude {{{1
	if exclude:
		if isinstance(exclude,list):
			exclude_ids=exclude
		else:
			exclude_ids=[exclude]
		test_ids=test_ids.difference(set(exclude_ids))
#	print 'test_ids after exclude =',test_ids
	# }}}

	#Process Ids according to benchmarks {{{1
	if   benchmark.lower() == 'nightly':
		test_ids=test_ids.intersection(set(range(1,1000)))
	elif benchmark.lower() == 'ismip':
		test_ids=test_ids.intersection(set(range(1101,1200)))
	elif benchmark.lower() == 'eismint':
		test_ids=test_ids.intersection(set(range(1201,1300)))
	elif benchmark.lower() == 'thermal':
		test_ids=test_ids.intersection(set(range(1301,1400)))
	elif benchmark.lower() == 'mesh':
		test_ids=test_ids.intersection(set(range(1401,1500)))
	elif benchmark.lower() == 'validation':
		test_ids=test_ids.intersection(set(range(1001,2000)))
	elif benchmark.lower() == 'tranforcing':
		test_ids=test_ids.intersection(set(range(1501,1503)))
#	print 'test_ids after benchmark =',test_ids
	test_ids=list(test_ids)
	test_ids.sort()
	print 'test_ids after sort =',test_ids
	# }}}

	#Loop over tests and launch sequence
	root=os.getcwd()
	for id in test_ids:
		try:

			#Execute test
			os.chdir(root)
			id_string=IdToName(id)
			execfile('test'+str(id)+'.py',globals())

			#UPDATE ARCHIVE?
			archive_name='Archive'+str(id)
			if procedure.lower() == 'update':

				if not socket.gethostname().lower().split('.')[0] == 'larsen':
#					raise RuntimeError("Nightly run archives must be saved on 'larsen' (hostname is '"+socket.gethostname()+"').")
					print "Nightly run archives must be saved on 'larsen' (hostname is '"+socket.gethostname()+"')."
#				f = h5py.File(os.path.join('..','Archives',archive_name+'.hdf5'),'w')
				f = netCDF4.Dataset(os.path.join('..','Archives',archive_name+'.nc'),'w')
				for k,fieldname in enumerate(field_names):
					field=numpy.array(field_values[k],dtype=float)
#					f.create_dataset(archive_name+'_field'+str(k),data=field)
					f.createDimension(archive_name+'_field'+str(k)+'_0',numpy.size(field,0))
					f.createDimension(archive_name+'_field'+str(k)+'_1',numpy.size(field,1))
					v = f.createVariable(archive_name+'_field'+str(k),'f8',(archive_name+'_field'+str(k)+'_0',archive_name+'_field'+str(k)+'_1'))
					v[:] = field
				f.close()
				print "File '%s' saved.\n" % os.path.join('..','Archives',archive_name+'.nc')

			#ELSE: CHECK TEST
			else:

				#load archive
				if os.path.exists(os.path.join('..','Archives',archive_name+'.nc')):
#					f = h5py.File(os.path.join('..','Archives',archive_name+'.hdf5'),'r')
					f = netCDF4.Dataset(os.path.join('..','Archives',archive_name+'.nc'),'r')
				else:
					raise IOError("Archive file '"+os.path.join('..','Archives',archive_name+'.nc')+"' does not exist.")

				for k,fieldname in enumerate(field_names):

					try:
						#Get field and tolerance
						field=numpy.array(field_values[k],dtype=float)
#						print 'field =',field
						tolerance=field_tolerances[k]
#						print 'tolerance =',tolerance

						#compare to archive
#						if archive_name+'_field'+str(k) in f:
#							archive=f[archive_name+'_field'+str(k)][...]
						if archive_name+'_field'+str(k) in f.variables:
							archive=f.variables[archive_name+'_field'+str(k)][:]
						else:
							raise NameError("Field name '"+archive_name+'_field'+str(k)+"' does not exist in archive file.")
#						print 'archive =',archive
						error_diff=numpy.amax(numpy.abs(archive-field),axis=1)/ \
								   (numpy.amax(numpy.abs(archive),axis=1)+sys.float_info.epsilon)
#						print 'error_diff =',error_diff

						#disp test result
						if (error_diff>tolerance):
							print 'ERROR   difference: %-7.2g > %7.2g test id: %i test name: %s field: %s' % \
								(error_diff,tolerance,id,id_string,fieldname)
						else:
							print 'SUCCESS difference: %-7.2g < %7.2g test id: %i test name: %s field: %s' % \
								(error_diff,tolerance,id,id_string,fieldname)

					except Exception as me2:

						#something went wrong, print failure message:
						directory=os.getcwd().split('/')    #  not used?
						message=me2
						if   output.lower() == 'nightly':
							fid=open(os.path.join(ISSM_DIR,'nightlylog','matlaberror.log'), 'a')
							fid.write('%s' % message)
							fid.write('\n------------------------------------------------------------------\n')
							fid.close()
							print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,fieldname)
						elif output.lower() == 'daily':
							fid=open(os.path.join(ISSM_DIR,'dailylog','matlaberror.log'), 'a')
							fid.write('%s' % message)
							fid.write('\n------------------------------------------------------------------\n')
							fid.close()
							print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,fieldname)
						else:
							print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,fieldname)
							raise RuntimeError(me2)

				f.close()

		except Exception as me:

			#something went wrong, print failure message:
			directory=os.getcwd().split('/')    #  not used?
			message=me
			if   output.lower() == 'nightly':
				fid=open(os.path.join(ISSM_DIR+'nightlylog','matlaberror.log'), 'a')
				fid.write('%s' % message)
				fid.write('\n------------------------------------------------------------------\n')
				fid.close()
				print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,'N/A')
			elif output.lower() == 'daily':
				fid=open(os.path.join(ISSM_DIR+'dailylog','matlaberror.log'), 'a')
				fid.write('%s' % message)
				fid.write('\n------------------------------------------------------------------\n')
				fid.close()
				print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,'N/A')
			else:
				print 'FAILURE difference: N/A test id: %i test name: %s field: %s' % (id,id_string,'N/A')
				raise RuntimeError(me)

#	#output md if requested
#	if nargout==1
#		varargout{1}=md;
#	end

	return

if __name__ == '__main__':
	if 'PYTHONSTARTUP' in os.environ:
		PYTHONSTARTUP=os.environ['PYTHONSTARTUP']
		print 'PYTHONSTARTUP =',PYTHONSTARTUP
		if os.path.exists(PYTHONSTARTUP):
			try:
				execfile(PYTHONSTARTUP)
			except Exception as e:
				print "PYTHONSTARTUP error: ",e
		else:
			print "PYTHONSTARTUP file '%s' does not exist." % PYTHONSTARTUP

	import argparse
	parser = argparse.ArgumentParser(description='RUNME - test deck for ISSM nightly runs')
	parser.add_argument('-i','--id', nargs='*', type=int, help='followed by the list of ids requested', default=[])
	parser.add_argument('-e','--exclude', nargs='*', type=int, help='ids to be excluded from the test', default=[])
	parser.add_argument('-b','--benchmark', help='nightly/ismip/eismint/thermal/mesh/...', default='nightly')
	parser.add_argument('-p','--procedure', help='check/update', default='check')
	parser.add_argument('-o','--output', help='nightly/daily/none', default='none')
	parser.add_argument('-r','--rank', type=int, help='rank', default=1)
	parser.add_argument('-n','--numprocs', type=int, help='numprocs', default=1)
	args = parser.parse_args()

	md = runme(args.id, args.exclude, args.benchmark, args.procedure, args.output, args.rank, args.numprocs)

	exit(md)

