# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
    __import__('pkg_resources').declare_namespace(__name__)
except ImportError:
    from pkgutil import extend_path
    __path__ = extend_path(__path__, __name__)

import errno, json, logging, os, shutil, stat, subprocess
from hashlib import md5
from zc.buildout import UserError
from zc.buildout.rmtree import rmtree as buildout_rmtree

def generatePassword(length=8):
  from random import SystemRandom
  from string import ascii_lowercase
  choice = SystemRandom().choice
  return ''.join(choice(ascii_lowercase) for _ in range(length))

def is_true(value, default=False):
  return default if value is None else ('false', 'true').index(value)

def make_read_only(path):
  if not os.path.islink(path):
    os.chmod(path, os.stat(path).st_mode & 0o555)

def make_read_only_recursively(path):
  make_read_only(path)
  for root, dir_list, file_list in os.walk(path):
    for dir_ in dir_list:
      make_read_only(os.path.join(root, dir_))
    for file_ in file_list:
      make_read_only(os.path.join(root, file_))

def multiarch():
  try:
    m = multiarch.cache
  except AttributeError:
    try:
      m = subprocess.check_output(
        ('gcc', '-dumpmachine'),  universal_newlines=True).rstrip()
    except Exception as e:
      m = e
    multiarch.cache = m
  if type(m) is str:
    if m:
      return m
    raise Exception("empty multiarch value")
  raise m

def rmtree(path):
  try:
    buildout_rmtree(path)
  except OSError as e:
    if e.errno == errno.ENOENT:
      return
    if e.errno != errno.ENOTDIR:
      raise
    os.remove(path)


class EnvironMixin(object):

  def __init__(self, allow_none=True):
    environment = self.options.get('environment')
    if environment:
      if '=' in environment:
        self._environ = env = {}
        for line in environment.splitlines():
          line = line.strip()
          if line:
            try:
              k, v = line.split('=', 1)
            except ValueError:
              raise UserError('Line %r in environment is incorrect' % line)
            k = k.rstrip()
            if k in env:
              raise UserError('Key %r is repeated' % k)
            env[k] = v.lstrip()
      else:
        self._environ = self.buildout[environment]
    else:
      self._environ = None if allow_none else {}

  def __getattr__(self, attr):
    if attr == 'logger':
      value = logging.getLogger(self.name)
    elif attr == 'environ':
      env = self._environ
      del self._environ
      if env is None:
        value = None
      else:
        from os import environ
        value = environ.copy()
        for k in sorted(env):
          value[k] = v = env[k] % environ
          self.logger.info('[ENV] %s = %s', k, v)
    else:
      return self.__getattribute__(attr)
    setattr(self, attr, value)
    return value


class Shared(object):

  keep_on_error = False
  mkdir_location = True
  signature = None

  def __init__(self, buildout, name, options):
    self.maybe_shared = shared = is_true(options.get('shared'))
    if shared:
      # Trigger computation of part signature for shared signature.
      # From now on, we should not pull new dependencies.
      # Ignore if buildout is too old.
      options.get('__buildout_signature__')
      shared = buildout['buildout'].get('shared-part-list')
      if shared:
        profile_base_location = options.get('_profile_base_location_')
        signature = json.dumps({
          k: (v.replace(profile_base_location, '${:_profile_base_location_}')
              if profile_base_location else v)
          for k, v in options.items()
          if k != '_profile_base_location_'
        }, ensure_ascii=False, indent=2, sort_keys=True,
          # BBB: Python 2 ( https://bugs.python.org/issue16333 )
          separators=(',', ': '))
        if not isinstance(signature, bytes): # BBB: Python 3
          signature = signature.encode()
        digest = md5(signature).hexdigest()
        location = None
        for shared in shared.splitlines():
          shared = shared.strip().rstrip('/')
          if shared:
            location = os.path.join(os.path.join(shared, name), digest)
            if os.path.exists(location):
              break
        if location:
          self.logger = logging.getLogger(name)
          self.logger.info('shared at %s', location)
          self.location = location
          self.signature = signature
          return
    self.location = os.path.join(buildout['buildout']['parts-directory'], name)

  def assertNotShared(self, reason):
    if self.maybe_shared:
      raise UserError("When shared=true, " + reason)

  def install(self, install):
    signature = self.signature
    location = self.location
    if signature is not None:
      path = os.path.join(location, '.buildout-shared.json')
      if os.path.exists(path):
        self.logger.info('shared part is already installed')
        return ()
    rmtree(location)
    try:
      if self.mkdir_location:
        os.makedirs(location)
      else:
        parent = os.path.dirname(location)
        if not os.path.isdir(parent):
          os.makedirs(parent)
      install()
      try:
        s = os.stat(location)
      except OSError as e:
        if e.errno != errno.ENOENT:
          raise
        raise UserError('%r was not created' % location)
      if self.maybe_shared and not stat.S_ISDIR(s.st_mode):
        raise UserError('%r is not a directory' % location)
      if signature is None:
        return [location]
      tmp = path + '.tmp'
      with open(tmp, 'wb') as f:
        f.write(signature)
      # XXX: The following symlink is for backward compatibility with old
      #      'slapos node prune' (slapos.core).
      os.symlink('.buildout-shared.json', os.path.join(location,
        '.buildout-shared.signature'))
      os.rename(tmp, path)
    except:
      if not self.keep_on_error:
        rmtree(location)
      raise
    make_read_only_recursively(location)
    return ()