#
# YodaProcess()
# author  Maurizio Nagni
# version 1.0 - 21 September 2005
#
# This is the main thread for the managements of the Yoda processing.
#
# Parameters:
# - scriptHost     The directory where the ROOT scritps are stored  (required)
# - pitHost        The monitored directory for "new" files          (required)
# - sourceHost     The directory where to the "new" files are moved (optional)
#

import os, sys, time, threading, traceback, logging, fcntl, shutil, string
from ROOT import gROOT
from ConfigParser import *
from ngn.utils.benchmark import Benchmark

#------------------ yodaThread Class -----------------------------------------------------
class YodaProcess(threading.Thread):
    def __init__(self, pitHost="", configFile="", fileName="", logger=""):
        threading.Thread.__init__(self)
        self.__pitHost      = pitHost
        self.__fileName     = fileName
	self.__logger       = logger
        self.__benchmarkOn  = False
	self.__overwriteFiles  = False
        gROOT.SetBatch(True)
	
	
	self.__config		  = ConfigParser()
	self.__config.optionxform = unicode
	self.__config.read(configFile)
	try:
		self.__yodaUnpackDir	= self.__config.get('YodaProcess', 'yodaUnpackDir')
		if not os.path.exists(self.__yodaUnpackDir):
			os.makedirs(self.__yodaUnpackDir)
                

                self.__tmpFolder        = str(int(time.time())) + '/'
                self.__yodaTmpUnpack	= self.__config.get('YodaProcess', 'yodaTmpUnpack')
                self.__tmpFinal         = self.__yodaTmpUnpack + '/' + self.__tmpFolder
                self.__finalPath        = ''
                self.__finalName        = ''
		if not os.path.exists(self.__tmpFinal):
			os.makedirs(self.__tmpFinal)

		self.__executables	= self.__config.get('YodaProcess', 'executables')
		self.__yodaParameter	= self.__config.get('YodaProcess', 'yodaParameter')
		self.__yodaStorage	= ''
		self.__QLOOKlist	= []

                if (self.__config.has_option('YodaProcess', 'benchmarkFile')):
                    self.__bench = Benchmark(self.__config.get('YodaProcess', 'benchmarkFile'))
                    self.__benchmarkOn      = True
                
		if (self.__config.has_option('YodaProcess', 'overwriteFiles')):
                    self.__overwriteFiles = self.__config.get('YodaProcess', 'overwriteFiles')
		    
		#After YODA unpacking should process the root file throught QLOOK programs?
		if self.__config.has_section('QLOOKs') and (len(self.__config.items('QLOOKs')) > 0):
			self.__QLOOKlist	= self.__config.items('QLOOKs')
	except NoSectionError, detail:
		raise Exception('Section missing in configuration file: ', detail)
	except NoOptionError, detail:
		raise Exception('Wrong option in configuration file: ', detail)

    def __del__(self):
        if (self.__benchmarkOn):
            self.__bench.close()

    def run(self):
        # Infinite loop waiting for file upload comes to an end
        try:
            self.__logger.info('ready to unpack')
            if(self.__unpack()):
		#If the QLOOKs configure file section is not empty
		if (len(self.__QLOOKlist) > 0):
			self.__processScripts()
        except Exception:
	    self.__logger.exception('Error')
        sys.exit(0);



    '''
    # The scope of this function is to prepare the logfile and start a yoda session
    # The function can:
    # 1) generate dinamically the log4cxx.conf in order to allow the user to customize the log4cxx.conf file.
    # 2) create a default log4cxx.conf file
    '''
    def __unpack(self):
	#check if the file has been already unpacked and in case if it have to be overwritten
	self.__finalPath        = self.__yodaUnpackDir + '/' + self.__fileName.split('.')[0] + '/'
	if (os.path.exists(self.__finalPath) and not self.__overwriteFiles):
		self.__logger.info('The unpacked folder %s already exist!', self.__finalPath)
		return False
	
	# Should create the default log4cxx.conf file?
        if not os.path.exists(self.__tmpFinal + "log4cxx.conf"): 
		self.__defaultLog4cxx(self.__tmpFinal + "log4cxx.conf")

	os.rename(self.__tmpFinal + "log4cxx.conf", self.__tmpFinal + "log4cxx.old")
        oldConf = open(self.__tmpFinal + "log4cxx.old", "r")
        newConf = open(self.__tmpFinal + "log4cxx.conf" , "w")
        newLog  = ""
	#Look for a specific line and change it with a unpack-specific one
        for line in oldConf:
            if (line.find("log4j.appender.toFile.File=") > -1):
                parts = line.split("=")
                newLog = self.__fileName.split('.')[0] + ".log"
                newConf.write(parts[0] + "=" + self.__tmpFinal + newLog + "\n")
            else :
                newConf.write(line)
        newConf.close()
        oldConf.close()
        os.remove(self.__tmpFinal + "log4cxx.old")

	#<---- START YODA ---->#
	self.__logger.info("Starting YODA: " + self.__executables + 'yoda ' + self.__pitHost + self.__fileName + ' ' + self.__yodaParameter + ' -o  ' + self.__tmpFinal)	
        if (self.__benchmarkOn):
                       self.__bench.start(self.__pitHost, self.__fileName, 'YODA')
        terminal = os.popen(self.__executables + 'yoda ' + self.__pitHost + self.__fileName + ' ' + self.__yodaParameter + ' -o  ' + self.__tmpFinal)
        #I have commented out the yoda stdout because of too much output
        #output = terminal.readlines()
        #self.__logger.info('YODA output: ')
        #for out in output:
        #    self.__logger.info(' %s', out)
	self.__logger.info(string.join(terminal.readlines()))
	status = None
	try:
		# The close seems to generate an error. I catch in a except but nominally
		# it should "close" without problem. That's why in the except there is some fake code.
		# TO BE FIXED.
		status = terminal.close()
	except IOError:
		a = 0
		#self.__logger.error(' - ')
        if (status == None):
            if (self.__benchmarkOn):
                self.__bench.end()
            self.__logger.info('YODA stopped correctly')
	    # Move the produced YODA's ROOT output file to a specific folder
            # Moves the created output files in the temp directory
            self.__finalName        = self.__fileName.split('.')[0] + '.root'
	    if (os.path.exists(self.__finalPath) and self.__overwriteFiles):
	    	shutil.rmtree(self.__finalPath)
            os.rename(self.__tmpFinal, self.__finalPath)
            return True
	else:
	    self.__logger.error('YODA program exit with code %i', status)
            return False

    def __processScripts(self):
	processes = []
	for qlook, params in self.__QLOOKlist:
        	#Then I start to analize
                if ((self.__finalPath != '') and (self.__finalName != '') and (os.path.exists(self.__finalPath))):
                        try:
			    # Change the directory because it is supposed that the default output 
			    # for the "qlook" is in the current directory
			    os.chdir(self.__finalPath)
			    command = qlook + ' ' + self.__finalPath + self.__finalName + ' ' +params
			    self.__logger.info('Executing script %s.', command)
                            benchmarker = None
                            if (self.__benchmarkOn):
                                out = qlook.split('/')
                                #benchmarker = [self.__config.get('YodaProcess', 'benchmarkFile'), self.__finalPath, self.__finalName, out[len(out) - 1]]
			    qlook = processQLOOK(command, self.__logger, qlook, benchmarker)
			    processes.append(qlook)
			    qlook.start()
                        except:
                            self.__logger.exception('Error while executing script %s.', qlook)
                else:
                    self.__logger.error('Script %s not available.', qlook)
                    raise Exception
	#Wait the end of all the qlook
	for process in processes:
		process.join()


    def __defaultLog4cxx(self, filePath):
	newConf = open(filePath, "w")
	newConf.write("##Eventually you can add more loggers using something like this below" + "\n")
	newConf.write("#log4j.rootLogger=debug, A1, yourLogger1, yourLogger2" + "\n \n")
	newConf.write("#The details level are:" + "\n")
	newConf.write("# tons of logs --> DEBUG, INFO, WARN, ERROR, FATAL <-- just a few" + "\n \n")
	newConf.write("log4j.rootLogger=INFO, toStdout" + "\n \n")
	newConf.write("#----------------------------------------------------------------" + "\n")
	newConf.write("#Uncomment ALL the 'log4j' lines below to activate the file log" + "\n")
	newConf.write("#Threshold is a bug fixed in the to-be-released 0.9.8 version" + "\n")
	newConf.write("#log4j.appender.toFile.Threshold=INFO" + "\n")
	newConf.write("#Set here the detail level for the output to file" + "\n")		
	newConf.write("log4j.rootLogger=INFO, toFile" + "\n \n")
	newConf.write("log4j.appender.toFile=org.apache.log4j.FileAppender" + "\n")
	newConf.write("log4j.appender.toFile.File=" + filePath + "\n")
	newConf.write("log4j.appender.toFile.layout=org.apache.log4j.PatternLayout" + "\n")
	newConf.write("log4j.appender.toFile.layout.ConversionPattern=%p %t %c - %m%n" + "\n")
	newConf.close()
	
	
class processQLOOK(threading.Thread):
    def __init__(self, command, logger, qlook, benchmark = None):
        threading.Thread.__init__(self)
        self.__command      = command
	self.__logger       = logger
	self.__qlook        = qlook
        self.__benchmarkOn  = False
       
        if (benchmark != None):
            self.__benchFile   = benchmark[0]
            self.__path        = benchmark[1]
            self.__file        = benchmark[2]
            self.__program     = benchmark[3]
            self.__benchmarkOn = True

    def run(self):
        if (self.__benchmarkOn):
            self.__bench = Benchmark(self.__benchFile)
            self.__bench.start(self.__path, self.__file, self.__program)
	terminal = os.popen(self.__command)
	self.__logger.info(string.join(terminal.readlines()))
	status = None
	try:
		# The close seems to generate an error. I catch in a except but nominally
		# it should "close" without problem. That's why in the except there is some fake code.
		# TO BE FIXED.
		status = terminal.close()
	except IOError:
		a = 0 
		#self.__logger.error(' - ')
        if (status == None):
		if (self.__benchmarkOn):
            		self.__bench.end()
