from builtins import str
from builtins import object
import logging
import os
import subprocess
import tempfile

class Process(object):
    '''
    Define a process to execute
    '''

    def __init__(self, name, exe, args, desc=None, proc_type=None, expand=True, bank_env=None, log_dir=None):
        '''
        Define one process

        :param name: name of the process (descriptive)
        :type name: str
        :param exe: path to the executable (relative to process.dir or full path)
        :type exe: str
        :param args: arguments
        :type args: str
        :param desc: process description
        :type desc: str
        :param proc_type: types of data generated by process
        :type proc_type: str
        :param expand: allow shell expansion on command line
        :type expand: bool
        :param bank_env: environnement variables to set
        :type bank_env: list
        :param log_dir: directroy to place process stdout and stderr
        :type log_dir: str
        '''
        # Replace env vars in args
        if args:
            for key, value in bank_env.items():
                if value is not None:
                    args = args.replace('${'+key+'}', value)

        self.name = name
        self.exe = exe
        self.desc = desc
        if args is not None:
            self.args = args.split()
        else:
            self.args = []
        self.bank_env = bank_env
        self.type = proc_type
        self.expand = expand
        if log_dir is not None:
            self.output_file = os.path.join(log_dir, name+'.out')
            self.error_file = os.path.join(log_dir, name+'.err')
        else:
            self.output_file = name+'.out'
            self.error_file = name+'.err'

        self.types = ''
        self.format = ''
        self.tags = ''
        self.files = ''

    def run(self, simulate=False):
        '''
        Execute process

        :param simulate: does not execute process
        :type simulate: bool
        :return: exit code of process
        '''
        args = [self.exe] + self.args
        logging.debug('PROCESS:EXEC:'+str(self.args))
        err = False
        if not simulate:
            logging.info('PROCESS:RUN:'+self.name)
            with open(self.output_file, 'w') as fout:
                with open(self.error_file, 'w') as ferr:
                    if self.expand:
                        args = " ".join(args)
                        proc = subprocess.Popen(args, stdout=fout, stderr=ferr, env=self.bank_env, shell=True)
                    else:
                        proc = subprocess.Popen(args, stdout=fout, stderr=ferr, env=self.bank_env, shell=False)
                    proc.wait()
                    if proc.returncode == 0:
                        err = True
                    else:
                        logging.error('PROCESS:ERROR:'+self.name)
                    fout.flush()
                    ferr.flush()
        else:
            err = True
        logging.info('PROCESS:EXEC:' + self.name + ':' + str(err))

        return err

class DockerProcess(Process):
    def __init__(self, name, exe, args, desc=None, proc_type=None, docker=None, expand=True, bank_env=None, log_dir=None, use_sudo=True):
        Process.__init__(self, name, exe, args, desc, proc_type, expand, bank_env, log_dir)
        self.docker = docker
        self.use_sudo = use_sudo

    def run(self, simulate=False):
        '''
        Execute process in docker container

        :param simulate: does not execute process
        :type simulate: bool
        :return: exit code of process
        '''
        use_sudo = ''
        if self.use_sudo:
            use_sudo = 'sudo'
        release_dir = self.bank_env['datadir']+'/'+self.bank_env['dirversion']+'/'+self.bank_env['localrelease']
        env = ''
        if self.bank_env:
            for key, value in self.bank_env.items():
                env += ' -e "{0}={1}"'.format(key, value)
        #         docker run with data.dir env as shared volume
        #         forwarded env variables
        cmd = '''uid={uid}
    gid={gid}
    {sudo} docker pull {container_id}
    {sudo} docker run --rm -w {bank_dir}  -v {data_dir}:{data_dir} {env} {container_id} \
    bash -c "groupadd --gid {gid} {group_biomaj} && useradd --uid {uid} --gid {gid} {user_biomaj}; \
    {exe} {args}; \
    chown -R {uid}:{gid} {bank_dir}"'''.format(uid=os.getuid(),
                                              gid=os.getgid(),
                                              data_dir=self.bank_env['datadir'],
                                              env=env,
                                              container_id=self.docker,
                                              group_biomaj='biomaj',
                                              user_biomaj='biomaj',
                                              exe=self.exe,
                                              args=' '.join(self.args),
                                              bank_dir=release_dir,
                                              sudo=use_sudo
                                              )

        (handler, tmpfile) = tempfile.mkstemp('biomaj')
        os.write(handler, cmd)
        os.close(handler)
        os.chmod(tmpfile, 0o755)
        args = [tmpfile]
        logging.debug('PROCESS:EXEC:Docker:'+str(self.args))
        logging.debug('PROCESS:EXEC:Docker:Tmpfile:'+tmpfile)
        err = False
        if not simulate:
            logging.info('PROCESS:RUN:Docker:'+self.docker+':'+self.name)
            with open(self.output_file, 'w') as fout:
                with open(self.error_file, 'w') as ferr:
                    if self.expand:
                        args = " ".join(args)
                        proc = subprocess.Popen(args, stdout=fout, stderr=ferr, env=self.bank_env, shell=True)
                    else:
                        proc = subprocess.Popen(args, stdout=fout, stderr=ferr, env=self.bank_env, shell=False)
                    proc.wait()
                    if proc.returncode == 0:
                        err = True
                    else:
                        logging.error('PROCESS:ERROR:'+self.name)
                    fout.flush()
                    ferr.flush()
        else:
            err = True
        logging.info('PROCESS:EXEC:' + self.name + ':' + str(err))
        os.remove(tmpfile)
        return err


class DrmaaProcess(Process):
    def __init__(self, name, exe, args, desc=None, proc_type=None, native=None, expand=True, bank_env=None, log_dir=None):
        Process.__init__(self, name, exe, args, desc, proc_type, expand, bank_env, log_dir)
        self.native = native


    def run(self, simulate=False):
        '''
        Execute process

        :param simulate: does not execute process
        :type simulate: bool
        :return: exit code of process
        '''
        args = [self.exe] + self.args
        logging.debug('PROCESS:EXEC:'+str(self.args))
        err = False
        if not simulate:
            logging.info('Run process '+self.name)
            # Execute on DRMAA
            try:
                import drmaa
                with drmaa.Session() as s:
                    jt = s.createJobTemplate()
                    jt.remoteCommand = self.exe
                    jt.args = self.args
                    jt.joinFiles = False
                    jt.workingDirectory = os.path.dirname(os.path.realpath(self.output_file))
                    jt.jobEnvironment = self.bank_env
                    if self.native:
                        jt.nativeSpecification = " "+self.native+" "
                    jt.outputPath = self.output_file
                    jt.errorPath = self.error_file
                    jobid = s.runJob(jt)
                    retval = s.wait(jobid, drmaa.Session.TIMEOUT_WAIT_FOREVER)
                    if  retval.hasExited > 0:
                        err = True
                    else:
                        logging.error('PROCESS:ERROR:'+self.name)
                    s.deleteJobTemplate(jt)

            except Exception as e:
                logging.error('Drmaa process error: '+str(e))
                return False
        else:
            err = True
        logging.info('PROCESS:EXEC:' + self.name + ':' + str(err))

        return err
