#!/usr/bin/python
#
# Copyright (c) 2007,2008,2009 MBARI
# MBARI Proprietary Information.  All Rights Reserved
#

import sys, os, subprocess, threading
import math, re

import utils
import config

from Slate import Slate
from Location import Location
    
class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None
        self.output = None

    def run(self, timeout):
        def target():
            #print 'Thread started'
            self.process = subprocess.Popen(self.cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            self.output = self.process.communicate()
            #print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process due to timeout'
            self.process.terminate()
            thread.join()
        return self.output[0],self.process.returncode;
    
class MissionTester:
  
  def __init__(self, lrauvBin, unserializeBin, slatePath, skipRun):
    self.lrauvBin = lrauvBin
    self.unserializeBin = unserializeBin
    self.slatePath = slatePath
    self.codeReg = re.compile("TEST_CODE_START(.*)TEST_CODE_END", \
        re.DOTALL | re.I)
    self.criticalReg = re.compile(r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z,\d+\.\d+ \[[\w\d_]+\]\(CRITICAL\):[^\r\n]+)", re.MULTILINE | re.I)
    self.valgrindReg = re.compile(r"^\s*TEST_WITH_VALGRIND", re.MULTILINE )
    self.valWarn = re.compile(r"(==\d+==\s+Warning: [^\r\n]+(?:[\r\n]==\d+==\s+at [^\r\n]+(?:[\r\n]==\d+==\s+by [^\r\n]+)*)?)", re.MULTILINE)    
    self.valErr = re.compile(r"(==\d+==\s+(?:Error:|Invalid|Conditional|Syscall|Invalid|Mismatched|Source) [^\r\n]+(?:[\r\n]==\d+==\s+at [^\r\n]+(?:[\r\n]==\d+==\s+by [^\r\n]+)*(?:[\r\n]==\d+==\s+Address [^\r\n]+(?:[\r\n]==\d+==\s+at [^\r\n]+(?:[\r\n]==\d+==\s+by [^\r\n]+)*)?)?)?)", re.MULTILINE)
    self.valLeak = re.compile(r"(==\d+==\s+\d+ [b\(][^\r\n]+(?:[\r\n]==\d+==\s+at [^\r\n]+(?:[\r\n]==\d+==\s+by [^\r\n]+)*)?)", re.MULTILINE)    
    self.xmlre = re.compile(".*\.(xml|tx)$", re.I)
    self.skipRun = skipRun
  
  def testMission(self, missionFilename,skipValgrind):
    if not os.path.isfile(missionFilename):
      return False
    # make sure the xml file has the testing script in it
    missionData = utils.readFile(missionFilename)
    if missionData == None:
      return False
    m = self.codeReg.search(missionData)
    if m == None:
      return False
    filename = os.path.basename(missionFilename)
    valgrind = (not skipValgrind) and self.valgrindReg.search(missionData) is not None
    if valgrind:
       sys.stdout.write("Valgrind ")
    sys.stdout.write("Testing " + filename + "...\n")
    sys.stdout.flush()
    passed = False
    code = m.group(1)
    if self.__runMission(missionFilename, valgrind) == False:
      print "** FAILED **"
      return False
    missionName = os.path.splitext( os.path.basename(missionFilename) )[0]
    slate = Slate(self.unserializeBin, self.slatePath + "/" + missionName + "/slate")
    exec code
    #passed = self.__devFunction(slate) # used for dev
    if passed:
      print "passed"
    else:
      print "** FAILED **"
    return passed

  def testMissionsInDir(self, missionDirectory,skipValgrind):
    if not os.path.isdir(missionDirectory):
      return False
    if skipValgrind:
      print "skipping Valgrind testing"
    dir = os.path.normcase(missionDirectory)
    results = []
    fileList = utils.getFilesInDir(dir)
    fileList.sort()
    fileList = [f for f in fileList if self.xmlre.search(f) != None]
    for f in fileList:
      missionFilename = os.path.join(dir, f)
      # make sure the xml file has the testing script in it
      missionData = utils.readFile(missionFilename)
      if missionData == None:
        continue
      m = self.codeReg.search(missionData)
      if m == None:
        continue
      # test the mission
      passed = self.testMission(missionFilename,skipValgrind)
      results.append([f, passed])
    return results

  def __runMission(self, missionFilename, valgrind):
    if self.skipRun:
      return True
    passed = False
    if not os.path.isfile(self.lrauvBin) or \
       not os.path.isfile(missionFilename):
      return passed
    try:
      cwd = os.getcwd()
      missionName = os.path.splitext( os.path.basename(missionFilename) )[0]
      timeout = 1800 if valgrind else 300
      cmd = "" #["bash","%s/Tools/regression/timeout.sh"%cwd,"-t","%d"%timeout]
      if valgrind:
        cmd = cmd + "valgrind --leak-check=full --show-reachable=yes --track-origins=yes --leak-resolution=high --num-callers=40 --suppressions=valgrind.supp -v "
      cmd = cmd + self.lrauvBin + " -l " + missionName + " -c regressionTests -x \"run %s quitAtEnd\"" % (missionFilename)
      path = os.path.split(os.path.dirname(self.lrauvBin))[0]
      os.chdir(path)
      #print cmd
      passed = True
      command = Command(cmd);
      output,returnCode = command.run(timeout)

      criticalCount = 0
      for item in self.criticalReg.findall(output):
        criticalCount = criticalCount + 1
        if criticalCount > 20:
            print "Additional critical errors not shown"
            break
        print "Got critical error:"
        print item
        passed = False

      if returnCode != 0:
        if returnCode == -15 or returnCode == -9:
          print "Script timed out!"
        else:
          print "Simulation failed, returned code %d" % returnCode
        passed = False
        command2 = Command("stty sane");
        command2.run(5)
      elif valgrind:
        for item in self.valWarn.findall(output):
          print "Warning:"
          print item.decode('string-escape')
        for item in self.valErr.findall(output):
          passed = False
          print "Error:"
          print item.decode('string-escape')
        for item in self.valLeak.findall(output):
          if "startCommandLine" in item:
            continue
          else:
            passed = False
            print "Memory Leak:"
            print item.decode('string-escape')
      os.chdir(cwd)
    except Exception as e:
      print "Exception:", e
      pass
    return passed

  def __devFunction(self, slate):
    passed = False
    return passed

