text: # -*- coding: utf-8 -*-
  #
  # Copyright (c) 2016-2017 Kevin Chung
  # Copyright (c) 2018 German Mendez Bravo (Kronuz)
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to
  # deal in the Software without restriction, including without limitation the
  # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  # sell copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  # IN THE SOFTWARE.
  #
  
  from __future__ import division, absolute_import
  
  import os
  import re
  import sys
  import json
  import tarfile
  import fnmatch
  import logging
  import tempfile
  import textwrap
  import subprocess
  import collections
  from shutil import copyfile
  
  import requests
  from filelock import Timeout, FileLock
  from clint.textui import colored, puts_err
  from clint.textui import progress
  
  from .compat import raw_input, b2s
  
  logger = logging.getLogger(__name__)
  
  
  HOME = os.path.expanduser('~/.mech')
  DATA_DIR = os.path.join(HOME, 'data')
  
  
  def makedirs(name, mode=0o777):
      try:
          os.makedirs(name, mode)
      except OSError:
          pass
  
  
  def uncomment(text):
      def e(m):
          return '\x00%02x' % ord(m.group(1))
      e.re = re.compile(r'\\(.)', re.DOTALL | re.MULTILINE)
  
      def r(m):
          s = m.group(0)
          if s.startswith('/'):
              return ''
          if s.startswith(','):
              return s[1:]
          return s
      r.re = re.compile(r'//.*?$|/\*.*?\*/|\'.*?\'|".*?"|,\s*?(?:}|])', re.DOTALL | re.MULTILINE)
  
      def u(m):
          return '\\%s' % chr(int(m.group(1), 16))
      u.re = re.compile(r'\x00(..)', re.DOTALL | re.MULTILINE)
  
      return u.re.sub(u, r.re.sub(r, e.re.sub(e, text)))
  
  
  def confirm(prompt, default='y'):
      default = default.lower()
      if default not in ['y', 'n']:
          default = 'y'
      choicebox = '[Y/n]' if default == 'y' else '[y/N]'
      prompt = prompt + ' ' + choicebox + ' '
  
      while True:
          input = raw_input(prompt).strip()
          if input == '':
              if default == 'y':
                  return True
              else:
                  return False
  
          if re.match('y(?:es)?', input, re.IGNORECASE):
              return True
  
          elif re.match('n(?:o)?', input, re.IGNORECASE):
              return False
  
  
  def save_mechfile(mechfile, path):
      with open(os.path.join(path, 'Mechfile'), 'w+') as fp:
          json.dump(mechfile, fp, sort_keys=True, indent=2, separators=(',', ': '))
      return True
  
  
  def locate(path, glob):
      for root, dirnames, filenames in os.walk(path):
          for filename in filenames:
              if fnmatch.fnmatch(filename, glob):
                  return os.path.abspath(os.path.join(root, filename))
  
  
  def parse_vmx(path):
      vmx = collections.OrderedDict()
      with open(path) as fp:
          for line in fp:
              line = line.strip().split('=', 1)
              if len(line) > 1:
                  vmx[line[0].rstrip()] = line[1].lstrip()
      return vmx
  
  
  def update_vmx(path):
      updated = False
  
      vmx = parse_vmx(path)
  
      # Check if there is an existing interface
      has_network = False
      for vmx_key in vmx:
          if vmx_key.startswith('ethernet'):
              has_network = True
  
      # Write one if there is not
      if not has_network:
          vmx["ethernet0.addresstype"] = "generated"
          vmx["ethernet0.bsdname"] = "en0"
          vmx["ethernet0.connectiontype"] = "nat"
          vmx["ethernet0.displayname"] = "Ethernet"
          vmx["ethernet0.linkstatepropagation.enable"] = "FALSE"
          vmx["ethernet0.pcislotnumber"] = "32"
          vmx["ethernet0.present"] = "TRUE"
          vmx["ethernet0.virtualdev"] = "e1000"
          vmx["ethernet0.wakeonpcktrcv"] = "FALSE"
          puts_err(colored.yellow("Added network interface to vmx file"))
          updated = True
  
      if updated:
          with open(path, 'w') as new_vmx:
              for key in vmx:
                  value = vmx[key]
                  row = "{} = {}".format(key, value)
                  new_vmx.write(row + os.linesep)
  
      # puts_err(colored.yellow("Upgrading VM..."))
      # vmrun = VMrun(path)
      # vmrun.upgradevm()
  
  
  def instances():
      makedirs(DATA_DIR)
      index_path = os.path.join(DATA_DIR, 'index')
      index_lock = os.path.join(DATA_DIR, 'index.lock')
      try:
          with FileLock(index_lock, timeout=3):
              updated = False
              if os.path.exists(index_path):
                  with open(index_path) as fp:
                      instances = json.loads(uncomment(fp.read()))
                  # prune unexistent Mechfiles
                  for k in list(instances):
                      instance_data = instances[k]
                      path = instance_data and instance_data.get('path')
                      if not path or not os.path.exists(os.path.join(path, 'Mechfile')):
                          del instances[k]
                          updated = True
              else:
                  instances = {}
              if updated:
                  with open(index_path, 'w') as fp:
                      json.dump(instances, fp, sort_keys=True, indent=2, separators=(',', ': '))
              return instances
      except Timeout:
          puts_err(colored.red(textwrap.fill("Couldn't access index, it seems locked.")))
          sys.exit(1)
      except json.decoder.JSONDecodeError:
          puts_err(colored.red(textwrap.fill("Index file seems broken. Try to remove {}.".format(index_path))))
          sys.exit(1)
  
  
  def settle_instance(instance_name, obj=None, force=False):
      makedirs(DATA_DIR)
      index_path = os.path.join(DATA_DIR, 'index')
      index_lock = os.path.join(DATA_DIR, 'index.lock')
      try:
          with FileLock(index_lock, timeout=3):
              updated = False
              if os.path.exists(index_path):
                  with open(index_path) as fp:
                      instances = json.loads(uncomment(fp.read()))
                  # prune unexistent Mechfiles
                  for k in list(instances):
                      instance_data = instances[k]
                      path = instance_data and instance_data.get('path')
                      if not path or not os.path.exists(os.path.join(path, 'Mechfile')):
                          del instances[k]
                          updated = True
              else:
                  instances = {}
              instance_data = instances.get(instance_name)
              if not instance_data or force:
                  if obj:
                      instance_data = instances[instance_name] = obj
                      updated = True
                  else:
                      instance_data = {}
              if updated:
                  with open(index_path, 'w') as fp:
                      json.dump(instances, fp, sort_keys=True, indent=2, separators=(',', ': '))
              return instance_data
      except Timeout:
          puts_err(colored.red(textwrap.fill("Couldn't access index, it seems locked.")))
          sys.exit(1)
      except json.decoder.JSONDecodeError:
          puts_err(colored.red(textwrap.fill("Index file seems broken. Try to remove {}.".format(index_path))))
          sys.exit(1)
  
  
  def load_mechfile(pwd):
      while pwd:
          mechfile = os.path.join(pwd, 'Mechfile')
          if os.path.isfile(mechfile):
              with open(mechfile) as fp:
                  try:
                      return json.loads(uncomment(fp.read()))
                  except ValueError:
                      puts_err(colored.red("Invalid Mechfile." + os.linesep))
                      break
          new_pwd = os.path.basename(pwd)
          pwd = None if new_pwd == pwd else new_pwd
      puts_err(colored.red(textwrap.fill(
          "Couldn't find a Mechfile in the current directory any deeper directories. "
          "A Mech environment is required to run this command. Run `mech init` "
          "to create a new Mech environment. Or specify the name of the VM you'd "
          "like to start with `mech up <name>`. A final option is to change to a "
          "directory with a Mechfile and to try again."
      )))
      sys.exit(1)
  
  
  def build_mechfile(descriptor, name=None, version=None, requests_kwargs={}):
      mechfile = {}
      if descriptor is None:
          return mechfile
      if any(descriptor.startswith(s) for s in ('https://', 'http://', 'ftp://')):
          mechfile['url'] = descriptor
          if not name:
              name = os.path.splitext(os.path.basename(descriptor))[0]
          mechfile['box'] = name
          if version:
              mechfile['box_version'] = version
          return mechfile
      elif descriptor.startswith('file:') or os.path.isfile(re.sub(r'^file:(?://)?', '', descriptor)):
          descriptor = re.sub(r'^file:(?://)?', '', descriptor)
          try:
              with open(descriptor) as fp:
                  catalog = json.loads(uncomment(fp.read()))
          except Exception:
              mechfile['file'] = descriptor
              if not name:
                  name = os.path.splitext(os.path.basename(descriptor))[0]
              mechfile['box'] = name
              if version:
                  mechfile['box_version'] = version
              return mechfile
      else:
          try:
              account, box, v = (descriptor.split(os.path.sep, 2) + ['', ''])[:3]
              if not account or not box:
                  puts_err(colored.red("Provided box name is not valid"))
              if v:
                  version = v
              puts_err(colored.blue("Loading metadata for box '{}'{}".format(descriptor, " ({})".format(version) if version else "")))
              url = 'https://app.vagrantup.com/{}/boxes/{}'.format(account, box)
              r = requests.get(url, **requests_kwargs)
              r.raise_for_status()
              catalog = r.json()
          except (requests.HTTPError, ValueError) as exc:
              puts_err(colored.red("Bad response from HashiCorp's Vagrant Cloud API: %s" % exc))
              sys.exit(1)
          except requests.ConnectionError:
              puts_err(colored.red("Couldn't connect to HashiCorp's Vagrant Cloud API"))
              sys.exit(1)
      return catalog_to_mechfile(catalog, name, version)
  
  
  def catalog_to_mechfile(catalog, name=None, version=None):
      mechfile = {}
      versions = catalog.get('versions', [])
      for v in versions:
          current_version = v['version']
          if not version or current_version == version:
              for provider in v['providers']:
                  if 'vmware' in provider['name']:
                      mechfile['box'] = catalog['name']
                      mechfile['box_version'] = current_version
                      mechfile['url'] = provider['url']
                      return mechfile
      puts_err(colored.red("Couldn't find a VMWare compatible VM for '{}'{}".format(name, " ({})".format(version) if version else "")))
      sys.exit(1)
  
  
  def tar_cmd(*args, **kwargs):
      try:
          startupinfo = None
          if os.name == "nt":
              startupinfo = subprocess.STARTUPINFO()
              startupinfo.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW
          proc = subprocess.Popen(['tar', '--help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
      except OSError:
          return None
      if proc.returncode:
          return None
      stdoutdata, stderrdata = map(b2s, proc.communicate())
      tar = ['tar']
      if kwargs.get('wildcards') and re.search(r'--wildcards\b', stdoutdata):
          tar.append('--wildcards')
      if kwargs.get('force_local') and re.search(r'--force-local\b', stdoutdata):
          tar.append('--force-local')
      if kwargs.get('fast_read') and sys.platform.startswith('darwin'):
          tar.append('--fast-read')
      tar.extend(args)
      return tar
  
  
  def init_box(name, version, force=False, save=True, requests_kwargs={}):
      if not locate('.mech', '*.vmx'):
          name_version_box = add_box(name, name=name, version=version, force=force, save=save, requests_kwargs=requests_kwargs)
          if not name_version_box:
              puts_err(colored.red("Cannot find a valid box with a VMX file in it"))
              sys.exit(1)
          name, version, box = name_version_box
          # box = locate(os.path.join(*filter(None, (HOME, 'boxes', name, version))), '*.box')
  
          puts_err(colored.blue("Extracting box '{}'...".format(name)))
          makedirs('.mech')
          if sys.platform == 'win32':
              cmd = tar_cmd('-xf', box, force_local=True)
          else:
              cmd = tar_cmd('-xf', box)
          if cmd:
              startupinfo = None
              if os.name == "nt":
                  startupinfo = subprocess.STARTUPINFO()
                  startupinfo.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW
              proc = subprocess.Popen(cmd, cwd='.mech', startupinfo=startupinfo)
              if proc.wait():
                  puts_err(colored.red("Cannot extract box"))
                  sys.exit(1)
          else:
              tar = tarfile.open(box, 'r')
              tar.extractall('.mech')
  
          if not save and box.startswith(tempfile.gettempdir()):
              os.unlink(box)
  
      vmx = get_vmx()
  
      update_vmx(vmx)
  
      return vmx
  
  
  def add_box(descriptor, name=None, version=None, force=False, save=True, requests_kwargs={}):
      mechfile = build_mechfile(descriptor, name=name, version=version, requests_kwargs=requests_kwargs)
      return add_mechfile(mechfile, name=name, version=version, force=force, save=save, requests_kwargs=requests_kwargs)
  
  
  def add_mechfile(mechfile, name=None, version=None, force=False, save=True, requests_kwargs={}):
      url = mechfile.get('url')
      file = mechfile.get('file')
      name = mechfile.get('box')
      version = mechfile.get('box_version')
      if file:
          return add_box_file(name, version, file, force=force, save=save)
      if url:
          return add_box_url(name, version, url, force=force, save=save, requests_kwargs=requests_kwargs)
      puts_err(colored.red("Couldn't find a VMWare compatible VM for '{}'{}".format(name, " ({})".format(version) if version else "")))
  
  
  def add_box_url(name, version, url, force=False, save=True, requests_kwargs={}):
      boxname = os.path.basename(url)
      box = os.path.join(*filter(None, (HOME, 'boxes', name, version, boxname)))
      exists = os.path.exists(box)
      if not exists or force:
          if exists:
              puts_err(colored.blue("Attempting to download box '{}'...".format(name)))
          else:
              puts_err(colored.blue("Box '{}' could not be found. Attempting to download...".format(name)))
          try:
              puts_err(colored.blue("URL: {}".format(url)))
              r = requests.get(url, stream=True, **requests_kwargs)
              r.raise_for_status()
              try:
                  length = int(r.headers['content-length'])
                  progress_args = dict(expected_size=length // 1024 + 1)
                  progress_type = progress.bar
              except KeyError:
                  progress_args = dict(every=1024 * 100)
                  progress_type = progress.dots
              fp = tempfile.NamedTemporaryFile(delete=False)
              try:
                  for chunk in progress_type(r.iter_content(chunk_size=1024), label="{} ".format(boxname), **progress_args):
                      if chunk:
                          fp.write(chunk)
                  fp.close()
                  if r.headers.get('content-type') == 'application/json':
                      # Downloaded URL might be a Vagrant catalog if it's json:
                      catalog = json.load(fp.name)
                      mechfile = catalog_to_mechfile(catalog, name, version)
                      return add_mechfile(mechfile, name=name, version=version, force=force, save=save, requests_kwargs=requests_kwargs)
                  else:
                      # Otherwise it must be a valid box:
                      return add_box_file(name, version, fp.name, url=url, force=force, save=save)
              finally:
                  os.unlink(fp.name)
          except requests.HTTPError as exc:
              puts_err(colored.red("Bad response: %s" % exc))
              sys.exit(1)
          except requests.ConnectionError:
              puts_err(colored.red("Couldn't connect to '%s'" % url))
              sys.exit(1)
      return name, version, box
  
  
  def add_box_file(name, version, filename, url=None, force=False, save=True):
      puts_err(colored.blue("Checking box '{}' integrity...".format(name)))
  
      if sys.platform == 'win32':
          cmd = tar_cmd('-tf', filename, '*.vmx', wildcards=True, fast_read=True, force_local=True)
      else:
          cmd = tar_cmd('-tf', filename, '*.vmx', wildcards=True, fast_read=True)
      if cmd:
          startupinfo = None
          if os.name == "nt":
              startupinfo = subprocess.STARTUPINFO()
              startupinfo.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW
          proc = subprocess.Popen(cmd, startupinfo=startupinfo)
          valid_tar = not proc.wait()
      else:
          tar = tarfile.open(filename, 'r')
          files = tar.getnames()
          valid_tar = False
          for i in files:
              if i.endswith('vmx'):
                  valid_tar = True
                  break
              if i.startswith('/') or i.startswith('..'):
                  puts_err(colored.red(textwrap.fill(
                      "This box is comprised of filenames starting with '/' or '..' "
                      "Exiting for the safety of your files."
                  )))
                  sys.exit(1)
  
      if valid_tar:
          if save:
              boxname = os.path.basename(url if url else filename)
              box = os.path.join(*filter(None, (HOME, 'boxes', name, version, boxname)))
              path = os.path.dirname(box)
              makedirs(path)
              if not os.path.exists(box) or force:
                  copyfile(filename, box)
          else:
              box = filename
          return name, version, box
  
  
  def index_active_instance(instance_name):
      path = os.getcwd()
      instance = settle_instance(instance_name, {
          'path': path,
      })
      if instance.get('path') != path:
          puts_err(colored.red(textwrap.fill((
              "There is already a Mech box with the name '{}' at {}"
          ).format(instance_name, instance.get('path')))))
          sys.exit(1)
      return path
  
  
  def init_mechfile(instance_name, descriptor, name=None, version=None, requests_kwargs={}):
      if not instance_name:
          instance_name = os.path.basename(os.getcwd())
      path = index_active_instance(instance_name)
      mechfile = build_mechfile(descriptor, name=name, version=version, requests_kwargs=requests_kwargs)
      mechfile['name'] = instance_name
      return save_mechfile(mechfile, path)
  
  
  def get_requests_kwargs(arguments):
      requests_kwargs = {}
      if arguments['--insecure']:
          requests_kwargs['verify'] = False
      elif arguments['--capath']:
          requests_kwargs['verify'] = arguments['--capath']
      elif arguments['--cacert']:
          requests_kwargs['verify'] = arguments['--cacert']
      elif arguments['--cert']:
          requests_kwargs['cert'] = arguments['--cert']
      return requests_kwargs
  
  
  def get_vmx(silent=False):
      vmx = locate('.mech', '*.vmx')
      if not vmx and not silent:
          puts_err(colored.red("Cannot locate a VMX file"))
          sys.exit(1)
      return vmx
  
  
  def provision_file(vm, source, destination):
      return vm.copyFileFromHostToGuest(source, destination)
  
  
  def provision_shell(vm, inline, path, args=[]):
      tmp_path = vm.createTempfileInGuest()
      if tmp_path is None:
          return
  
      try:
          if path and os.path.isfile(path):
              puts_err(colored.blue("Configuring script {}...".format(path)))
              if vm.copyFileFromHostToGuest(path, tmp_path) is None:
                  return
          else:
              if path:
                  if any(path.startswith(s) for s in ('https://', 'http://', 'ftp://')):
                      puts_err(colored.blue("Downloading {}...".format(path)))
                      try:
                          r = requests.get(path)
                          r.raise_for_status()
                          inline = r.read()
                      except requests.HTTPError:
                          return
                      except requests.ConnectionError:
                          return
                  else:
                      puts_err(colored.red("Cannot open {}".format(path)))
                      return
  
              if not inline:
                  puts_err(colored.red("No script to execute"))
                  return
  
              puts_err(colored.blue("Configuring script..."))
              fp = tempfile.NamedTemporaryFile(delete=False)
              try:
                  fp.write(inline)
                  fp.close()
                  if vm.copyFileFromHostToGuest(fp.name, tmp_path) is None:
                      return
              finally:
                  os.unlink(fp.name)
  
          puts_err(colored.blue("Configuring environment..."))
          if vm.runScriptInGuest('/bin/sh', "chmod +x '{}'".format(tmp_path)) is None:
              return
  
          puts_err(colored.blue("Executing program..."))
          return vm.runProgramInGuest(tmp_path, args)
  
      finally:
          vm.deleteFileInGuest(tmp_path, quiet=True)
  
  
  def config_ssh_string(config_ssh):
      ssh_config = "Host {}".format(config_ssh['Host']) + os.linesep
      for k, v in config_ssh.items():
          if k != 'Host':
              ssh_config += "  {} {}".format(k, v) + os.linesep
      return ssh_config
  