#! /usr/bin/env python
# -*- coding: utf-8 -*-

import config
from subprocess import *
import re
import os
import resource


class CommandOutputParser:
    """Usefull in conjunction with a Command. A CommandOutputParser is
    initialized with a grouped regular expression. It parses line for
    this expression and sets res to the first group if regexp matches.
    """
    def __init__(self):
        self.patterns = {
            "sys_time"  : (r"^sys(?:tem)? +([\d\.]+)(?: s)?", float),
            "usr_time"  : (r"^user +([\d\.]+)(?: s)?", float),
            "states"    : (r"^Total explored states: +(\d+)", int),
            "trace"     : (r"^Trace length: +(\d+)", int),
            "initial"   : (r"^(\d+)", int),
            "mem"       : (r"^vsize +(\d)+ KB", int),
            "safe_vars" : (r"safe variables: (.*)", str),
            "relevant_clocks" : (r"relevant clocks: (.*)", str),
            "irrelevant_clocks" : (r"irrelevant clocks: (.*)", str),
            "exit_code" : (r"exit +(d+)", int),
            "property"  : (r"Property: +(.*)", str),
            }
        self.res = None

        for k, v in self.patterns.iteritems():
            setattr(self, k, v[1](0))


    def __call__(self, line):
        for k, v in self.patterns.iteritems():
            m = re.match(v[0], line)
            if m:
                setattr(self, k, v[1](m.group(1)))


    def __str__(self):
        res = ["%s %s" % (k, str(getattr(self, k))) for k in self.patterns]
        return " ".join(res)


class Command(object):
    def __init__(self, cmd=None, runtime=None):
        self.parser = CommandOutputParser()
        if cmd:
            self.__call__(cmd, runtime)


    def _replace_cmds(self, cmd):
        time = "time -p %s"
        for c in "MCTA ABSTRACTOR GRAPH SAFEABS".split():
            if c == "ABSTRACTOR":
                cmd = cmd.replace(c, time % getattr(config, c))
            else:
                cmd = cmd.replace(c, getattr(config, c))
        return cmd


    def __str__(self):
        return str(self.parser)
    

    def __call__(self, cmd, runtime):
        """cmd is a shell command. There is a special treatment for
        some charaters and names: if '>' appears in cmd, the text
        right of this sign is the filename in which the programs
        output is written. Names in uppercase like MCTA, GRAPH, ...
        are replaced by config.MCTA, config.GRAPH,...
        """
        def setlimits():
            if runtime:
                resource.setrlimit(resource.RLIMIT_CPU, (runtime, runtime+1))
        
        self.result = None
        cmd = self._replace_cmds(cmd)
        cmd = map(str.strip, cmd.split(">"))
        output = None
        if len(cmd) == 2:
            output = cmd[1]
        cmd = cmd[0]
        try:
            if output:
                output = file(output, 'w')            
            proc = Popen(cmd.split(), stderr=PIPE, stdout=PIPE, preexec_fn=setlimits)   
            for line in proc.stdout:
                self.parser(line)
                if output:
                    output.write(line)
            for line in proc.stderr:
                self.parser(line)
            if output:
                output.close()
            proc.wait()
            if (runtime and -proc.returncode == 24):
                pass
            elif proc.returncode < 0:
                error_msg = "%s terminated with exit code %d" % (cmd, -proc.returncode)
                raise Exception(error_msg)
            return self
        except KeyboardInterrupt:
            os.kill(proc.pid, 9)
            exit(1)
