#!/bin/bash

"export" "LD_LIBRARY_PATH_ORIG=${LD_LIBRARY_PATH}"
"export" "LD_LIBRARY_PATH="
"export" "PYTHONPATH_ORIG=${PYTHONPATH}"
"export" "PYTHONPATH=${PANDA_PYTHONPATH}"
"export" "PYTHONHOME_ORIG=${PYTHONHOME}"
"unset" "PYTHONHOME"
"exec" "/usr/bin/python" "-u" "-Wignore" "$0" "$@"

import re
import os
import sys
import code
import atexit
import signal
import tempfile
from pandatools.MiscUtils import commands_get_output
try:
    long()
except Exception:
    long = int

import optparse
import readline

from pandatools import PLogger
from pandatools import Client
from pandatools import PandaToolsPkgInfo

# readline support
readline.parse_and_bind('tab: complete')
readline.parse_and_bind('set show-all-if-ambiguous On')

# history support
pconfDir = os.path.expanduser(os.environ['PANDA_CONFIG_ROOT'])
if not os.path.exists(pconfDir):
    os.makedirs(pconfDir)
historyFile = '%s/.history' % pconfDir
# history file
if os.path.exists(historyFile):
    readline.read_history_file(historyFile)
readline.set_history_length(1024)

# set dummy CMTSITE
if 'CMTSITE' not in os.environ:
    os.environ['CMTSITE'] = ''

# make tmp dir
tmpDir = tempfile.mkdtemp()

# set tmp dir in Client
Client.setGlobalTmpDir(tmpDir)

# fork PID 
fork_child_pid = None

# exit action
def _onExit(dirName,hFile):
    # save history only for master process
    if fork_child_pid == 0:
        readline.write_history_file(hFile)
    # remove tmp dir
    commands_get_output('rm -rf %s' % dirName)
atexit.register(_onExit,tmpDir,historyFile)



# look for PandaTools package
for path in sys.path:
    if path == '':
        path = '.'
    if os.path.exists(path) and os.path.isdir(path) and 'pandatools' in os.listdir(path) \
           and os.path.exists('%s/pandatools/__init__.py' % path):
        # make symlink for module name
        os.symlink('%s/pandatools' % path,'%s/taskbuffer' % tmpDir)
        break
sys.path = [tmpDir]+sys.path

from pandatools import PBookCore


# main for interactive session
def intmain(pbookCore,comString):

    # help
    def help(*arg):
        # print available methods
        tmp_str = """The following commands are available:

   sync
   show
   kill
   retry
   clean
   finish
   killAndRetry
   getUserJobMetadata
         
For more info, do help(show) for example."""
        print(tmp_str)

    # show status
    def show(JobID=None,upperJobID=None,forceUpdate=False,showPandaIDinState='',longFormat=False):
        """Print job records. All jobs will be shown if JobID is omitted. Jobs between JobID and upperJobID are shown when upperJobID is specified. Only running jobs and last N jobs will be shown if show('running') and show(-N), respectively. If forceUpdate=True, it will try to retrieve job infomation from the panda server. This may be useful to fix the local DB when synchronization between the local DB and the panda DB is lost somehow. When longFormat=True, jobs in the job set will be individually shown. showPandaIDinState takes a list of states so that PandaIDs in one of those states will be shown.
        
         example:
           >>> show()
           >>> show(12)
           >>> show(12,15)           
           >>> show(-5)
           >>> show('running')
           >>> show(15,showPandaIDinState='activated,running')
           >>> show(15,longFormat=True)
         """
        if JobID is None or JobID < 0 or upperJobID is not None:
            # check range
            if upperJobID != None:
                if JobID > upperJobID:
                    tmpLog = PLogger.getPandaLogger()
                    tmpLog.error("upper JobID must be larger than %s" % JobID)
                    return
                if JobID is None or JobID < 0:
                    tmpLog = PLogger.getPandaLogger()
                    tmpLog.error("JobID must be positive when upper JobID is given")
                    return
            # show all local info
            jobList = pbookCore.getLocalJobList()
            if JobID is not None and JobID < 0:
                jobList = jobList[JobID:]
            # print
            for job in jobList:
                if upperJobID != None:
                    if hasattr(job,'JobID'):
                        if job.JobID < JobID or job.JobID > upperJobID:
                            continue
                    else:
                        if (JobID is not None and long(job.JobsetID) < JobID) or long(job.JobsetID) > upperJobID:
                            continue
                print("======================================")
                if showPandaIDinState != '':
                    job.flag_showSubstatus = showPandaIDinState
                if longFormat:
                    job.flag_longFormat = True
                print(job)
        elif JobID == 'running':
            # show running jobs
            jobList = pbookCore.getLocalJobList()
            for job in jobList:
                if job.dbStatus != 'frozen':
                    if hasattr(job,'JobID'):
                        # job
                        show(job.JobID,forceUpdate=forceUpdate,
                             showPandaIDinState=showPandaIDinState,longFormat=longFormat)
                    else:
                        # jobset
                        show(long(job.JobsetID),forceUpdate=forceUpdate,
                             showPandaIDinState=showPandaIDinState,longFormat=longFormat)
        else:
            job = pbookCore.statusJobJobset(JobID,forceUpdate)
            # print
            print("======================================")
            if showPandaIDinState:
                job.flag_showSubstatus = showPandaIDinState
            if longFormat:
                job.flag_longFormat = True
            print(job)

    # kill
    def kill(JobID,upperJobID=None,useJobsetID=True):
        """Kill all subJobs in JobID or JobsetID. Jobsets between JobID and UpperJobID will be killed if upperJobID is given. Set useJobsetID=False if you want to kill JobIDs between JobID and UpperJobID. If 'running' is used as JobID, all running jobs will be killed

         example:
           >>> kill(15)
           >>> kill(15,20)
           >>> kill('running')
        """
        if JobID == 'running':
            # show running jobs
            jobList = pbookCore.getLocalJobList()
            for job in jobList:
                if job.dbStatus != 'frozen':
                    if hasattr(job,'JobID'):
                        # job
                        retK = pbookCore.kill(job.JobID,False)
                    else:
                        # jobset
                        retK = pbookCore.kill(long(job.JobsetID),True)
        elif upperJobID != None:
            # check range
            if JobID > upperJobID:
                tmpLog = PLogger.getPandaLogger()
                tmpLog.error("upper JobID must be larger than %s" % JobID)
            else:
                # kill jobs between the range
                for tmpJobID in range(JobID,upperJobID+1):
                    retK = pbookCore.kill(tmpJobID,useJobsetID)
        else:            
            retK = pbookCore.kill(JobID)

    # finish
    def finish(JobID,upperJobID=None,soft=False):
        """finish all subJobs in JobID or JobsetID. Jobsets between JobID and UpperJobID will be killed if upperJobID is given. If soft is False (default), all running jobs are killed and the task finishes immediately. If soft is True, new jobs are not generated and the task finishes once all running jobs finish. 

         example:
           >>> kill(15)
           >>> kill(15,20)
           >>> kill('running')
        """
        if JobID == 'running':
            # show runnig jobs
            jobList = pbookCore.getLocalJobList()
            for job in jobList:
                if job.dbStatus != 'frozen':
                    if hasattr(job,'JobID'):
                        # job
                        retK = pbookCore.finish(job.JobID,soft)
                    else:
                        # jobset
                        retK = pbookCore.finish(long(job.JobsetID),soft)
        elif upperJobID != None:
            # check range
            if JobID > upperJobID:
                tmpLog = PLogger.getPandaLogger()
                tmpLog.error("upper JobID must be larger than %s" % JobID)
            else:
                # kill jobs between the range
                for tmpJobID in range(JobID,upperJobID+1):
                    retK = pbookCore.finish(tmpJobID,useJobsetID,soft)
        else:            
            retK = pbookCore.finish(JobID,soft)

    # retry
    def retry(JobID,upperJobID=None,newOpts=None,noSubmit=False,ignoreDuplication=False,useJobsetID=True,retryBuild=False):
        """Retry failed/cancelled subJobs in JobID or JobsetID. Jobsets between JobID and UpperJobID will be retried if upperJobID is given. Set useJobsetID=False if you want to retry JobIDs between JobID and UpperJobID. This means that you need to have the same runtime env (such as Athena version, run dir, source files) as the previous submission. One can use newOpts which is a map of options and new arguments like {'nFilesPerJob':10,'excludedSite':'ABC,XYZ'} to overwrite task parameters. The list of changeable parameters is site,excludedSite,includedSite,nFilesPerJob,nGBPerJob,nFiles,nEvents. If input files were used or are being used by other jobs for the same output dataset container, those file are skipped to avoid job duplication when retrying failed subjobs. If you need to disable duplication check (e.g., you are using the same EVNT file for multiple simulation subjobs, set ignoreDuplication=True. Set retryBuild=True if you want to retry jobs which have a failed buildJob

         example:
           >>> retry(15)
           >>> retry(15,20)
           >>> retry(15,newOpts={'excludedSite':'siteA,siteB'})
           >>> retry(15,ignoreDuplication=True)
        """
        if newOpts is None:
            newOpts = {}
        # use range or not    
        if upperJobID != None:
            # check range
            if JobID > upperJobID:
                tmpLog = PLogger.getPandaLogger()
                tmpLog.error("upper JobID must be larger than %s" % JobID)
            else:
                # retry jobs between the range
                for tmpJobID in range(JobID,upperJobID+1):
                    pbookCore.retry(tmpJobID,newOpts=newOpts,noSubmit=noSubmit,
                                    ignoreDuplication=ignoreDuplication,
                                    useJobsetID=useJobsetID,retryBuild=retryBuild)
        else:            
            pbookCore.retry(JobID,newOpts=newOpts,noSubmit=noSubmit,
                            ignoreDuplication=ignoreDuplication,retryBuild=retryBuild)

    # debug mode
    def debug(PandaID,modeOn):
        """Turn the debug mode on/off for a subjob with PandaID. modeOn is True/False to enable/disable the debug mode. Note that the maxinum number of debug subjobs is limited. If you already hit the limit you need to disable the debug mode for a subjob before debugging another subjob
        
         example:
           >>> debug(1234,True)
        """
        pbookCore.debug(PandaID,modeOn)

    # delete jobs older than nDays
    def clean(nDays):
        """Delete jobs older than nDays from local database. Old jobs are automatically deleted 90 days after they were created, to keep the database size reasonable

         example:
           >>> clean(60)
        """
        pbookCore.clean(nDays)

    # kill and retry
    def killAndRetry(JobID,newSite=False,newOpts=None,ignoreDuplication=False,retryBuild=False):
        """Kill JobID and then retry failed/cancelled sub-jobs. Concerning newSite and newOpts, see help(retry)

         example:
           >>> killAndRetry(15)
        """
        if newOpts is None:
            newOpts = {}
        ret = pbookCore.killAndRetry(JobID,newSite=newSite,newOpts=newOpts,ignoreDuplication=ignoreDuplication,
                                     retryBuild=retryBuild)

    # synchronize local repository
    def sync():
        """Synchronize local repository

         example:
           >>> sync()
        """
        pbookCore.sync()

    # get user job metadata
    def getUserJobMetadata(taskID, outputFileName):
        """Get user metadata of successful jobs in a task and write them in a json file

         example:
           >>> getUserJobMetadata(123, 'meta.json')
        """
        pbookCore.getUserJobMetadata(taskID, outputFileName)

    # execute command in the batch mode
    if comString != '':
        # decompose to function name and argument
        match = re.search('^([^\(]+)\(([^\)]*)\)$',comString)
        comName = match.group(1)
        comArgS = match.group(2)
        comArg = []
        comMap = {}
        if comArgS != '':
            for item in comArgS.split(','):
                item = item.strip()
                if '=' in item:
                    tmpKey = item.split('=')[0]
                    tmpVal = item.split('=')[-1]
                    comMap[tmpKey.strip()] = eval(tmpVal)
                else:    
                    comArg.append(eval(item))
        comArg = tuple(comArg)            
        # update map
        pbookCore.updateTaskJobsetMap()
        # exec : exec cannot be used due to unqualified exec with nested functions
        locals()[comName](*comArg, **comMap)
        # exit
        sys.exit(0)
    # run sync
    pbookCore.sync()
    # delete old jobs
    pbookCore.clean()
    # go to interactive prompt
    code.interact(banner="\nStart pBook %s" % PandaToolsPkgInfo.release_version,
                  local=locals())


# main for GUI session
def guimain(pbookCore):
    import gtk
    from pandatools import BookGUI
    pbookGuiMain = BookGUI.PBookGuiMain(pbookCore)
    # get logger
    tmpLog = PLogger.getPandaLogger()
    tmpLog.info("Start pBook %s" % PandaToolsPkgInfo.release_version)
    # GTK main
    gtk.main()
    

# kill whole process
def catch_sig(sig, frame):
    # cleanup
    _onExit(tmpDir,historyFile)
    # kill
    commands_get_output('kill -9 -- -%s' % os.getpgrp())


# overall main
if __name__ == "__main__":
    # parse option
    parser = optparse.OptionParser()
    parser.add_option("-v",action="store_true",dest="verbose",default=False,
                      help="verbose")
    parser.add_option("--gui",action="store_true",dest="gui",default=False,
                      help="use GUI")
    parser.add_option('-c',action='store',dest='comString',default='',type='string',
                      help='execute a command in the batch mode')
    parser.add_option("--restoreDB",action="store_true",dest="restoreDB",default=False,
                      help="restore local database")
    parser.add_option("--noPass",action="store_true",dest="noPass",default=True,
                      help=optparse.SUPPRESS_HELP)
    parser.add_option("--inputPass",action="store_true",dest="inputPass",default=False,
                      help="enter pass phrase so that pbook periodically renews the grid proxy in GUI mode")
    parser.add_option('--version',action='store_const',const=True,dest='version',default=False,
                      help='Displays version')
    parser.add_option('--devSrv',action='store_const',const=True,dest='devSrv',default=False,
                      help="Please don't use this option. Only for developers to use the dev panda server")
    parser.add_option('--intrSrv',action='store_const',const=True, dest='intrSrv',default=False,
                      help="Please don't use this option. Only for developers to use the intr panda server")
    
    options,args = parser.parse_args()

    # display version
    if options.version:
        print("Version: %s" % PandaToolsPkgInfo.release_version)
        sys.exit(0)

    # use dev server
    if options.devSrv:
        Client.useDevServer()

    # use INTR server
    if options.intrSrv:
        Client.useIntrServer()

    # fork for Ctl-c
    fork_child_pid = os.fork()
    if fork_child_pid == -1:
        print("ERROR : Failed to fork")
        sys.exit(1)
    if fork_child_pid == 0:
        # main
        if options.gui:
            # instantiate core with pass phrase
            if options.noPass or not options.inputPass:
                pbookCore = PBookCore.PBookCore(False,options.verbose)
            else:
                pbookCore = PBookCore.PBookCore(True,options.verbose)                
            # GUI
            guimain(pbookCore)
        else:
            # instantiate core
            if options.verbose:
                print(options)
            pbookCore = PBookCore.PBookCore(False, options.verbose, options.restoreDB)
            
            # CUI
            intmain(pbookCore,options.comString)
    else:
        # set handler
        signal.signal(signal.SIGINT, catch_sig)
        signal.signal(signal.SIGHUP, catch_sig)
        signal.signal(signal.SIGTERM,catch_sig)
        os.wait()
