# Python script
#
#
#     Copyright (C) 2014 --- 2019 Fei Long,  G. Murshudov
#
#     This code is distributed under the terms and conditions of the
#     CCP4 Program Suite Licence Agreement as a CCP4 Library.
#
#====================================================================
## The date of last modification: 21/07/2016
#

from __future__ import print_function
from builtins import object
import os,os.path,sys
import platform
import glob,shutil
import re,string
from optparse import OptionParser 
import time
import math
import select
import random

if os.name != 'nt':
    import fcntl
import signal

#################################################   

class CExeCode(object) :
    """ A generic abstract base class that is to be inheritted by other classes that warp 
        a executable code. Basically this class defines two main methods that characterize the
        procedures a job by an executable code, i.e.  forming a command line object and then
        run the code according to the command line object"""

    def __init__( self ):

        # set pathes
 
        self.exitCode = 0

        # test 
        self._setCmdLineAndFile()

    def _setCmdLineAndFile(self ):         # will be overrided by individual derived classes
        self._cmdline = 'ls '
        self.batch_name = None
        self._log_name = None
            
    def subExecute(self, mode = 0, err_str ="" ):

        if self._log_name :
            if os.name == 'nt':
                self.logModeWin(mode, err_str)
            else: 
                self.logModeLin(mode, err_str)
        else :
            self.interactiveMode()

    def logModeLin(self, mode = 0, err_str=""):
        """ execute self._cmdline and output to a log file (self._log_name)  
        in a nonblocking way"""
        
        aRet = True

        logfile = open(self._log_name, 'w+')
        logfile.write("\n============ PROCESS INFORMATION =============\n")

        # spawn a sub-process for a job and connect to its input/output(and error)
        # streams using pipes
        try : 
            import subprocess
            subProcess = subprocess.Popen(self._cmdline, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, universal_newlines=True)
            subProcess.stdin.close()
            outfile_sub  = subProcess.stdout
            outfd_sub    = outfile_sub.fileno()
        except ImportError:
            print("module 'subprocess' has not be found, you use a version of PYTHON below 2.4")
            print("Import module 'popen2' instead, the calculations will not be affected")
            import popen2
            subProcess = popen2.Popen4(self._cmdline)
            subProcess.tochild.close()
            outfile_sub  = subProcess.fromchild
            outfd_sub    = outfile_sub.fileno()
    
        self._pid  = subProcess.pid
        pid_c      = self._pid  + 1
        p_name = os.path.basename(self._cmdline.strip().split()[0])
        t__log_name = os.path.basename(self._log_name)
        
       
        log_size = 0 
        log_max  = 500000000
        outfile_eof = 0
        if not mode:
            self._nonBlockingFile(outfd_sub)
            while not outfile_eof :
                ready_to_read, ready_to_write, in_error = \
                     select.select([outfd_sub],[],[],60.0)
                if outfd_sub in ready_to_read:
                    out_quantum = outfile_sub.read()
                    out_quantum_bt = len(out_quantum)
                    log_size = log_size + out_quantum_bt
                    if log_size > log_max:
                        logfile.write("\nTHE SIZE OF LOG FILE, %s, EXCEEDED THE LIMIT AND PROCESS STOPPED\n"%t__log_name)
                        logfile.write("The process running %s is killed\n"%p_name)
                        logfile.close()
                        os.kill(self._pid, signal.SIGKILL)
                        os.kill(pid_c, signal.SIGKILL)
                        print("The process running %s is killed " %p_name)
                        time.sleep(1.0)
                        # be safe stop all process
                        sys.exit(1)
                    else :
                        if out_quantum == '':
                            outfile_eof = 1
                        s_out_quantum = str(out_quantum)
                        logfile.write(s_out_quantum)
                        logfile.flush()
        else :
            self._nonBlockingFile(outfd_sub)
            while not outfile_eof :
                ready_to_read, ready_to_write, in_error = \
                     select.select([outfd_sub],[],[],10)
                if outfd_sub in ready_to_read:
                    out_quantum = outfile_sub.read()
                    if out_quantum == '':
                        outfile_eof = 1
                    s_out_quantum = str(out_quantum)
                    logfile.write(s_out_quantum)
                    #logfile.write(out_quantum)
                    logfile.flush()
                    # allProcInfo.append(out_quantum)

        self.exitCode = subProcess.wait()
      
        # if mode:
        #    for item in allProcInfo:
        #        logfile.write(item)

        outfile_sub.close()
        
        logfile.write("=========END OF PROCESS INFORMATION ==========\n")
        logfile.close()

        

        if self.exitCode:
            print("#-----------------------------------------------------------------#")
            print("The process stoped.\nCheck the associated log file '%s'\nfor the error information!"%self._log_name)
            print("#-----------------------------------------------------------------#")
            aRet = False
            #if not err_str:
            #    # if normal runtime errors, stop the program(using log. no err_str)  
            #    sys.exit(1)
                
        return aRet

    def logModeWin(self, mode = 0, err_str=""):
        """ execute self._cmdline and output to a log file (self._log_name)  
        under Windows system"""

        logfile = open(self._log_name, 'w')
        logfile.write("\n============ PROCESS INFORMATION =============\n")

        # spawn a sub-process for a job and connect to its input/output(and error)
        # streams using pipes

        try :
            import subprocess
            subProcess = subprocess.Popen(self._cmdline, shell=True, stdout=logfile)
            #subProcess.stdin.close()
            #outfile_sub  = subProcess.stdout
            #outfd_sub    = outfile_sub.fileno()
        except ImportError:
            print("module 'subprocess' has not be found, you use a version of PYTHON below 2.4")
            print("Import module 'popen2' instead, the calculations will not be affected")
            print("This should not be happening. Exiting Now.")
            sys.exit()
            #import popen2
            #subProcess = popen2.Popen4(self._cmdline)
            #subProcess.tochild.close()
            #outfile_sub  = subProcess.fromchild
            #outfd_sub    = outfile_sub.fileno()

        self._pid  = subProcess.pid
        pid_c      = self._pid  + 1
        p_name = os.path.basename(self._cmdline.strip().split()[0])
        t__log_name = os.path.basename(self._log_name)

        self.exitCode = subProcess.wait()

        logfile.write("=========END OF PROCESS INFORMATION ==========\n")
        logfile.close()

        #outfile_sub.close()

        if self.exitCode:
            print("#-----------------------------------------------------------------#")
            print("The process stoped.\nCheck the associated log file '%s'\nfor the error information!"%self._log_name)
            print("#-----------------------------------------------------------------#")
            if not err_str:
                # if normal runtime errors, stop the program(using log. no err_str)
                sys.exit(1)

        return True
    
    def _nonBlockingFile(self,fd):
        f_flag = fcntl.fcntl(fd, fcntl.F_GETFL,0)
        fcntl.fcntl(fd, fcntl.F_SETFL, f_flag | os.O_NONBLOCK)

    def interactiveMode(self):
        """ execute self._cmdline  just like what is done in a shell """
        
        os.system(self._cmdline)


# end


