# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
                        unicode_literals, with_statement)

import logging
import os
import posixpath
from contextlib import contextmanager

from twitter.common.collections import OrderedSet

from pants.base.build_environment import get_buildroot
from pants.base.exceptions import TaskError
from pants.net.http.fetcher import Fetcher
from pants.subsystem.subsystem import Subsystem
from pants.util.contextutil import temporary_file
from pants.util.dirutil import chmod_plus_x, safe_delete, safe_open
from pants.util.osutil import get_os_id


_DEFAULT_PATH_BY_ID = {
  ('linux', 'x86_64'): ('linux', 'x86_64'),
  ('linux', 'amd64'): ('linux', 'x86_64'),
  ('linux', 'i386'): ('linux', 'i386'),
  ('linux', 'i686'): ('linux', 'i386'),
  ('darwin', '9'): ('mac', '10.5'),
  ('darwin', '10'): ('mac', '10.6'),
  ('darwin', '11'): ('mac', '10.7'),
  ('darwin', '12'): ('mac', '10.8'),
  ('darwin', '13'): ('mac', '10.9'),
  ('darwin', '14'): ('mac', '10.10'),
  ('darwin', '15'): ('mac', '10.11'),
}


logger = logging.getLogger(__name__)


class BinaryUtil(object):
  """Wraps utility methods for finding binary executables.

  :API: public
  """

  class Factory(Subsystem):
    """
    :API: public
    """
    options_scope = 'binaries'

    @classmethod
    def register_options(cls, register):
      register('--baseurls', type=list, advanced=True,
               default=['https://dl.bintray.com/pantsbuild/bin/build-support'],
               help='List of urls from which binary tools are downloaded.  Urls are searched in '
                    'order until the requested path is found.')
      register('--fetch-timeout-secs', type=int, default=30, advanced=True,
               help='Timeout in seconds for url reads when fetching binary tools from the '
                    'repos specified by --baseurls')
      register('--path-by-id', type=dict, advanced=True,
               help='Maps output of uname for a machine to a binary search path.  e.g. '
               '{ ("darwin", "15"): ["mac", "10.11"]), ("linux", "arm32"): ["linux", "arm32"] }')

    @classmethod
    def create(cls):
      """
      :API: public
      """
      # NB: create is a class method to ~force binary fetch location to be global.
      options = cls.global_instance().get_options()
      return BinaryUtil(options.baseurls, options.fetch_timeout_secs, options.pants_bootstrapdir,
                        options.path_by_id)

  class MissingMachineInfo(TaskError):
    """Indicates that pants was unable to map this machine's OS to a binary path prefix."""
    pass

  class BinaryNotFound(TaskError):

    def __init__(self, binary, accumulated_errors):
      super(BinaryUtil.BinaryNotFound, self).__init__(
          'Failed to fetch binary {binary} from any source: ({sources})'
          .format(binary=binary, sources=', '.join(accumulated_errors)))

  class NoBaseUrlsError(TaskError):
    """Indicates that no urls were specified in pants.ini."""
    pass

  def _select_binary_base_path(self, supportdir, version, name, uname_func=None):
    """Calculate the base path.

    Exposed for associated unit tests.
    :param supportdir: the path used to make a path under --pants_bootstrapdir.
    :param version: the version number of the tool used to make a path under --pants-bootstrapdir.
    :param name: name of the binary to search for. (e.g 'protoc')
    :param uname_func: method to use to emulate os.uname() in testing
    :returns: Base path used to select the binary file.
    """
    uname_func = uname_func or os.uname
    os_id = get_os_id(uname_func=uname_func)
    if not os_id: