import os
import socket
import signal
import shutil
import slapos.slap
import subprocess
import time
import atexit
from erp5testreporthandler import ERP5TestReportHandler

process_group_pid_list = []
def clean():
  for pgpid in process_group_pid_list:
    try:
      os.killpg(pgpid, signal.SIGTERM)
    except:
      pass

def sigterm_handler(signal, frame):
  clean()

def sigint_handler(signal, frame):
  clean()
  raise KeyboardInterrupt

signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigterm_handler)
atexit.register(clean)

def getCurrentBranchName(config, p):
  r = subprocess.Popen([config['git_binary'], 'branch'], stdout=subprocess.PIPE, cwd=p).communicate()[0]
  for f in r.splitlines():
    if f.startswith('*'):
      return f.split()[1]
  return ''

def getRevision(config, p):
  return subprocess.Popen([config['git_binary'], 'rev-parse', 'HEAD'], stdout=subprocess.PIPE, cwd=p).communicate()[0].strip()

def getCurrentFetchRemote(config, p):
  r = subprocess.Popen([config['git_binary'], 'remote', '-v'], stdout=subprocess.PIPE, cwd=p).communicate()[0]
  remote = ''
  for f in r.splitlines():
    if f.startswith('origin') and f.endswith('(fetch)'):
      if remote != '':
        raise ValueError('Too many remotes: %s' % r)
      remote = r.split()[1]
  return remote

def getMachineIdString():
  """Returns machine identification string"""
  kw = dict(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  idstr = subprocess.Popen(["uname", "-m"], **kw).communicate()[0].strip()

  # try to detect gcc version
  try:
    gcc_list = subprocess.Popen(["gcc", "-v"], **kw).communicate()[0].split(
        '\n')
    for gcc in gcc_list:
      if gcc.startswith('gcc version'):
        idstr += ' gcc:' + gcc.split()[2]
        break
  except IndexError:
    pass

  # try to detect libc version
  try:
    libdir = os.path.sep + 'lib'
    for libso in os.listdir(libdir):
      if libso.startswith('libc.') and os.path.islink(os.path.join(libdir,
        libso)):
        libc = os.readlink(os.path.join(libdir, libso))
        if libc.endswith('.so'):
          idstr += ' libc:' + libc.split('-')[1][:-3]
        else:
          idstr += ' ' + libc
        break
  except IndexError:
    pass

  return idstr

def run(args):
  config = args[0]
  for k,v in config['environment'].iteritems():
    os.environ[k] = v
  proxy = None
  slapgrid = None
  last_revision_file = os.path.join(config['working_directory'],
        'revision.txt')
  if os.path.exists(last_revision_file):
    os.unlink(last_revision_file)
  # fetch repository from git
  repository_clone = os.path.join(config['working_directory'], 'repository')
  profile_path = os.path.join(repository_clone, config['profile_path'])
  if os.path.exists(config['proxy_database']):
    os.unlink(config['proxy_database'])
  proxy = subprocess.Popen([config['slapproxy_binary'],
    config['slapos_config']], close_fds=True, preexec_fn=os.setsid)
  process_group_pid_list.append(proxy.pid)
  slap = slapos.slap.slap()
  slap.initializeConnection(config['master_url'])
  while True:
    try:
      slap.registerSupply().supply(profile_path,
        computer_guid=config['computer_id'])
    except socket.error:
      time.sleep(1)
      pass
    else:
      break
  while True:
    info_list = []
    a = info_list.append
    while True:
      try:
        if os.path.exists(repository_clone):
          if getCurrentFetchRemote(config, repository_clone) != config['repository']:
            shutil.rmtree(repository_clone)
        if not os.path.exists(repository_clone):
          subprocess.check_call([config['git_binary'], 'clone',
            config['repository'], repository_clone])
        # switch to branch
        branch = getCurrentBranchName(config, repository_clone)
        if branch != config['branch']:
          subprocess.check_call([config['git_binary'], 'checkout', '--force',
            '--track', '-b', config['branch'], 'origin/'+config['branch']],
            cwd=repository_clone)
        subprocess.check_call([config['git_binary'], 'pull', '--rebase'],
            cwd=repository_clone)
      except Exception:
        print 'Retrying git in 60s'
        time.sleep(60)
      else:
        break
    a('Tested repository: %s' % config['repository'])
    a('Machine identification: %s' % getMachineIdString())
    erp5_report = ERP5TestReportHandler(config['test_suite_master_url'],
        '@'.join([config['suite_name'], branch]))
    last_revision = ''
    if os.path.exists(last_revision_file):
      last_revision = open(last_revision_file).read().strip()
    revision = getRevision(config, repository_clone)
    open(last_revision_file, 'w').write(revision)
    if revision != last_revision:
      print 'Running for revision %r' % revision
      while True:
        try:
          erp5_report.reportStart()
        except Exception:
          print 'Retrying in 5s'
          time.sleep(5)
        else:
          break
      if os.path.exists(config['software_root']):
        shutil.rmtree(config['software_root'])
      os.mkdir(config['software_root'])
      out_file = os.path.join(config['working_directory'], 'slapgrid.out')
      if os.path.exists(out_file):
        os.unlink(out_file)
      out = open(out_file, 'w')
      begin = time.time()
      slapgrid_environment = os.environ.copy()
      for k, v in config['slapgrid_environment'].iteritems():
        slapgrid_environment[k] = v
      a('Slapgrid environment: %r'% config['slapgrid_environment'])
      slapgrid = subprocess.Popen([config['slapgrid_software_binary'], '-vc',
        config['slapos_config']], close_fds=True, preexec_fn=os.setsid,
        stdout=out, stderr=subprocess.STDOUT, env=slapgrid_environment)
      process_group_pid_list.append(slapgrid.pid)
      slapgrid.communicate()
      out.close()
      while True:
        try:
          erp5_report.reportFinished(out_file,revision,
              slapgrid.returncode == 0, time.time() - begin,
              '\n'.join(info_list))
        except Exception:
          print 'Retrying in 5s'
          time.sleep(5)
        else:
          break
    print 'Sleeping for 600s'
    time.sleep(600)