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

from __future__ import absolute_import

import os
import posixpath
from collections import Iterable

from .compatibility import string as compatible_string
from .compatibility import PY3, WINDOWS, pathname2url, url2pathname
from .util import Memoizer

if PY3:
  import urllib.parse as urlparse
else:
  import urlparse


class Link(object):
  """Wrapper around a URL."""

  @classmethod
  def wrap(cls, url):
    """Given a url that is either a string or :class:`Link`, return a :class:`Link`.

    :param url: A string-like or :class:`Link` object to wrap.
    :returns: A :class:`Link` object wrapping the url.
    """
    if isinstance(url, cls):
      return url
    elif isinstance(url, compatible_string):
      return cls(url)
    else:
      raise ValueError('url must be either a string or Link.')

  @classmethod
  def wrap_iterable(cls, url_or_urls):
    """Given a string or :class:`Link` or iterable, return an iterable of :class:`Link` objects.

    :param url_or_urls: A string or :class:`Link` object, or iterable of string or :class:`Link`
      objects.
    :returns: A list of :class:`Link` objects.
    """
    try:
      return [cls.wrap(url_or_urls)]
    except ValueError:
      pass
    if isinstance(url_or_urls, Iterable):
      return [cls.wrap(url) for url in url_or_urls]
    raise ValueError('url_or_urls must be string/Link or iterable of strings/Links')

  @classmethod
  def _normalize(cls, filename):
    return urlparse.urljoin('file:', pathname2url(
        os.path.realpath(os.path.expanduser(filename))))

  # A cache for the result of from_filename
  _FROM_FILENAME_CACHE = Memoizer()

  @classmethod
  def from_filename(cls, filename):
    """Return a :class:`Link` wrapping the local filename."""
    result = cls._FROM_FILENAME_CACHE.get(filename)
    if result is None:
      result = cls(cls._normalize(filename))
      cls._FROM_FILENAME_CACHE.store(filename, result)
    return result

  def __init__(self, url):
    """Construct a :class:`Link` from a url.

    :param url: A string-like object representing a url.
    """
    purl = urlparse.urlparse(url)
    if purl.scheme == '' or (
        WINDOWS and len(purl.scheme) == 1):  # This is likely a drive letter.
      purl = urlparse.urlparse(self._normalize(url))

    self._url = purl

  def __ne__(self, other):
    return not self.__eq__(other)

  def __eq__(self, link):
    return self.__class__ == link.__class__ and self._url == link._url

  def __hash__(self):
    return hash(self._url)

  def join(self, href):
    """Given a href relative to this link, return the :class:`Link` of the absolute url.

    :param href: A string-like path relative to this link.
    """
    return self.wrap(urlparse.urljoin(self.url, href))

  @property
  def filename(self):
    """The basename of this url."""
    return urlparse.unquote(posixpath.basename(self._url.path))

  @property
  def path(self):
    """The full path of this url with any hostname and scheme components removed."""
    return urlparse.unquote(self._url.path)

  @property
  def local_path(self):
    """Returns the local filesystem path (only works for file:// urls)."""
    assert self.local, 'local_path called on a non-file:// url %s' % (self.url,)
    return url2pathname(self.path)

  @property
  def url(self):
    """The url string to which this link points."""
    return urlparse.urlunparse(self._url)

  @property
  def fragment(self):
    """The url fragment following '#' if any."""
    return urlparse.unquote(self._url.fragment)

  @property
  def scheme(self):
    """The URI scheme used by this Link."""
    return self._url.scheme

  @property
  def local(self):
    """Is the url a local file?"""
    return self._url.scheme in ('', 'file')

  @property
  def remote(self):
    """Is the url a remote file?"""
    return self._url.scheme in ('http', 'https')

  def __repr__(self):
    return '%s(%r)' % (self.__class__.__name__, self.url)
