#!/usr/bin/env python3
import os
import argparse
import yaml
import subprocess
import re

parser = argparse.ArgumentParser(description='Bring up the docker image that can be fetched from CI config files. Additionally, this will merge the project directory and everything required to bring up wayland or xorg clients from your host.')
parser.add_argument('image', type=str, help='Do not search for a docker file in CI files, use the supplied ID instead', nargs='?')
parser.add_argument('--update', help='Update the docker image before running the command', dest='update', action='store_const', const=True, default=False)
parser.add_argument('--sys-cap', help='Run docker with system capabilities, usefull for running gdb in it.', dest='sys_cap', action='store_const', const=True, default=False)
args = parser.parse_args()
#
# Dev container provider
# will simply look for .dev-container with the container name in there
#
class DevContainerImageProvider:
  def __init__(self):
    self.image = None

  def evalulate (self, project_dir):
    workflow_dir = os.path.join(project_dir, ".dev-container")
    if os.path.isfile(workflow_dir) == False:
      return
    file = open(workflow_dir)
    self.image = file.read().strip('\n')

  def get_image_name(self, project_dir):
    self.evalulate(project_dir)
    return self.image

  def is_present(self, project_dir):
    self.evalulate(project_dir)
    return self.image != None

  def name(self):
    return "dev-container"
#
# Github action provider
#
class GithubActionsImageProvider:
  def __init__(self):
    self.evalulated = False
    self.image = None

  def evalulate_workflow_job(self, job):
    linux_runs_on = ['ubuntu-latest', 'ubuntu-18.04', 'ubuntu-16.04']
    if job['runs-on'][0] in linux_runs_on:
      return job['container']['image']
    return None

  def evalulate_workflows(self, project_dir):
    if self.evalulated:
      return
    self.evalulated = True
    workflow_dir = os.path.join(project_dir, '.github', 'workflows')
    if os.path.isdir(workflow_dir) == False:
      return
    for filename in os.listdir(workflow_dir):
      workflow_file = open(os.path.join(workflow_dir, filename), mode='r')
      workflow_yaml = yaml.load(workflow_file.read(), Loader=yaml.FullLoader)
      jobs = workflow_yaml["jobs"]
      for k in jobs.keys():
        self.image = self.evalulate_workflow_job(jobs[k])
        if self.image != None:
          return

  def get_image_name(self, project_dir):
    self.evalulate_workflows(project_dir)
    return self.image

  def is_present(self, project_dir):
    self.evalulate_workflows(project_dir)
    return self.image != None

  def name(self):
    return "Github-Actions"

#
# Travis Image Provider
# Will fetch the first docker pull command from the .travis.yml, incase it depends on any env var, the env var needs to be set here
#
class TravisCIImageProvider:
  def __init__(self):
    self.match = None
    self.regex = re.compile('docker pull ([\\w\\-$]+)(\\/[\\w\\-$]+)?(:[\\w\\-$]+)?')

  def get_match(self, project_dir):
    if self.match != None:
      return self.match
    if os.path.isfile(os.path.join(project_dir, ".travis.yml")) == False:
      return None
    yaml_file = open(os.path.join(project_dir, ".travis.yml") ,mode='r')
    travis_yml = yaml_file.read()
    self.match = self.regex.search(travis_yml)
    return self.match

  def get_image_name(self, project_dir):
    match = self.get_match(project_dir)
    groups = match.groups()
    result = ""
    for x in range(0,3):
      sep = ''
      val = groups[x]
      if x > 0:
        sep = val[0]
        val = val[1:]
      if '$' in val:
        key = val
        val = os.getenv(key[1:])
        if val == None:
          print("Error, environment variable "+key+" cannot be fetch from travis.yml, must be provided by environment")
          exit(-1)
      result += sep + val

    return result

  def is_present(self, project_dir):
    match = self.get_match(project_dir)
    return match != None

  def name(self):
    return "Travis-CI"

# Will fetch the docker image of the first job.
class CircleCIImageProvider:
  def get_image_name(self, project_dir):
    yaml_file = open(self.circleci_config, mode='r')
    yaml_data = yaml.load(yaml_file.read(), Loader=yaml.FullLoader)
    jobs = yaml_data['jobs']
    for k in jobs.keys():
      return jobs[k]['docker'][0]['image']

  def is_present(self, project_dir):
    self.circleci_config = os.path.join(project_dir, ".circleci", "config.yml")
    return os.path.isfile(self.circleci_config)

  def name(self):
    return "CircleCi"

image_providers = [DevContainerImageProvider(), CircleCIImageProvider(), TravisCIImageProvider(), GithubActionsImageProvider()]

update_command = []
update_command += ["docker", "pull"]

command = []
command += ["docker","run","-it"]

if args.sys_cap:
  command += ['--cap-add', 'SYS_ADMIN', '--security-opt', 'seccomp=unconfined']

command += ["-e", "XDG_RUNTIME_DIR=/tmp"]

#support for X11
#This might need the host system to allow docker via xauth
if os.getenv('DISPLAY'):
  display = os.getenv('DISPLAY')
  command += ['-e', 'DISPLAY='+display]
  command += ["-v", "/tmp/.X11-unix:/tmp/.X11-unix"]

#support for wayland
if os.getenv('WAYLAND_DISPLAY'):
  wayland_display = os.getenv('WAYLAND_DISPLAY')
  xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR')
  command += ["-e", "WAYLAND_DISPLAY="+wayland_display]
  command += ["-v", os.path.join(xdg_runtime_dir, wayland_display)+":"+os.path.join("tmp", wayland_display)]


#first fetch the project name
project_dir = os.getcwd()
project_name = "/"+os.path.basename(project_dir)
command += ["-v", project_dir+":"+project_name]
print("Using project dir "+project_dir)

command += ["-w", "/"+project_name]

#decide if we need have a passed image or not.
if args.image != None:
  command += image
else:
  found = False;
  for provider in image_providers:
    if provider.is_present(project_dir) == False:
      continue
    image = provider.get_image_name(project_dir)
    command += [image]
    update_command += [image]
    print("Using Image from "+provider.name()+" ("+image+")")
    found = True
    break;
  if found == False:
    print("Error - did not find any usable image, either provider a image, or check your project.")
    exit(-1)

print("   _____ _______       _____ _______ ______ _____     _____            _        _                 ")
print("  / ____|__   __|/\   |  __ \__   __|  ____|  __ \   / ____|          | |      (_)                ")
print(" | (___    | |  /  \  | |__) | | |  | |__  | |  | | | |     ___  _ __ | |_ __ _ _ _ __   ___ _ __ ")
print("  \___ \   | | / /\ \ |  _  /  | |  |  __| | |  | | | |    / _ \| '_ \| __/ _` | | '_ \ / _ \ '__|")
print("  ____) |  | |/ ____ \| | \ \  | |  | |____| |__| | | |___| (_) | | | | || (_| | | | | |  __/ |   ")
print(" |_____/   |_/_/    \_\_|  \_\ |_|  |______|_____/   \_____\___/|_| |_|\__\__,_|_|_| |_|\___|_|   ")

if args.update:
  subprocess.call(update_command)

if os.getenv('DISPLAY'):
  print(" Started with X11 support, you might need to call \"xhost +local:docker\"(Please ensure you know what that does!)")

subprocess.call(command)
