class Command(object):

    class CommandResult(object):
        def __init__(self, returncode=0, process=None, duration=0.0, terminated=False, global_terminated=False):
            self.returncode = returncode
            self.process = process
            self.duration = duration
            self.terminated = terminated
            self.global_terminated = global_terminated

    def __init__(self, args, inn, out, err):
        self.inn_file = inn
        self.out_file = out
        self.err_file = err
        self.inn = None
        self.out = None
        self.err = None

        self.process = None
        self.args = args
        self.command = '; '.join(args.command)
        self.timer = Timer()
        self.terminated = False
        self.scale = 1.0

    def open_streams(self):
        ensure_path(self.inn_file)
        ensure_path(self.out_file)
        ensure_path(self.err_file)

        self.inn = PIPE if self.inn_file is None else open(self.inn_file, "rb")
        self.out = open(self.out_file, "wb")
        self.err = open(self.err_file, "wb")

    def close_streams(self):
        if self.inn_file is not None:
            self.inn.close()
        self.out.close()
        self.err.close()

    def _run(self, timeout):
        timeout = min([max_wait_time, timeout]) * self.scale

        def target():
            Logger.instance().info('Running command with time limit {:1.2f} s: {} in {}'.format(timeout, self.args.command, self.args.cwd))
            self.process = Popen(self.args.command, stdout=self.out, stderr=self.err, stdin=self.inn, cwd=self.args.cwd)
            Logger.instance().info('started PID {}'.format(self.process.pid))
            self.process.wait()  # process itself is not limited but there is global limit
            Logger.instance().info('Command finished with %d' % self.process.returncode)

        thread = threading.Thread(target=target)
        thread.start()
        thread.join(GlobalTimeout.time_left())

        if thread.is_alive():
            Logger.instance().info('Terminating process')
            self.terminated = True
            self.global_terminate = GlobalTimeout.time_left() < 0

            try:
                self.process.terminate()
            except Exception as e:
                print(e)

            try:
                self.process.kill()
            except Exception as e:
                print(e)
            thread.join()

    def run(self, timeout=max_wait_time):
        """
        :rtype: jobs.job_processing.Command.CommandResult
        """
        # empty command such as interpret language compilation
        if not self.args.command:
            return Command.CommandResult()

        self.open_streams()
        self.timer.tick()
        self._run(timeout)
        self.timer.tock()
        self.close_streams()

        # save global termination
        GlobalTimeout.decrease(self.timer.duration)

        # return run result
        return Command.CommandResult(
            returncode=self.process.returncode,
            duration=self.timer.duration*1000,
            terminated=self.terminated,
            global_terminated=self.terminated and GlobalTimeout.invalid()
        )