########################################################################
# Copyright (C) 2010, 2020 VMWare, Inc.
# All Rights Reserved
########################################################################

import os
import posixpath
import re
import sys

if sys.version_info[0] >= 3:
   from urllib.request import pathname2url
   from urllib.parse import urlparse, urlunparse, urljoin
else:
   from urllib import pathname2url
   from urlparse import urlparse, urlunparse, urljoin


ZIP_SEP = '?'
DATASTORE_ROOT = '/vmfs/volumes'
DATASTORE_RE = re.compile(r'^\[(?P<store>.+)\](?P<path>.+)')

def UrlJoin(baseurl, url, **kargs):
   '''Construct a full ('absolute') URL by combining a 'base URL' (baseurl)
      with another URL (url). If url is already an absolute URL, url is
      returned. If baseurl is an absolute file path, covert it to file://. If
      url is None or empty, baseurl is returned.

      Parameters:
         * url       - Either an absolute, or a relative path of the form
                       "dir1/dir2", which will be joined with baseurl
         * baseurl   - The absolute URL, which is  used to compute
                       the absolute URL if 'url' is relative.
   '''
   # Convert baseurl to a file:// url if it's a local path, since
   # urljoin does not handle Windows local paths well
   if baseurl:
      scheme, netloc = urlparse(baseurl)[:2]
      if not (scheme and netloc) and \
            IsAbsolutePath(baseurl):
         baseurl =  FilepathToUrl(baseurl)

   if url:
      scheme, netloc = urlparse(url)[:2]
      if (scheme and netloc) or scheme in ('file', 'zip'):
         return url

      # We want to use urljoin here so that any filenames in absolute
      # URLs will get replaced with the relative paths.  Eg., a basesurl of
      # 'http://vmware.com/patches/index.xml' wtih a relativepath of
      # 'vmware/vmw-index.xml' should produce a final URL of
      # 'http://vmware.com/patches/vmware/vmw-index.xml'.
      #
      # Same thing for paths.
      #
      if baseurl is not None:
         scheme = urlparse(baseurl)[0]
         if scheme == 'zip':
            if ZIP_SEP in baseurl:
               ind = baseurl.rfind(ZIP_SEP)
               dn = posixpath.dirname(baseurl[ind + 1:])
               path = posixpath.join(dn, url)
               return ZIP_SEP.join([baseurl[:ind], path])
            else:
               return ZIP_SEP.join([baseurl, url])
         else:
            return urljoin(baseurl, url, **kargs)
      # If there's no baseurl and url is an absolute path, translate it
      elif IsAbsolutePath(url):
         return FilepathToUrl(url)

   return baseurl

def IsAbsolutePath(url):
   """ Returns True if the url string is actually an absolute file path.
       Works for both Windows and Linux.
       For Linux, this is any string starting with '/'.
       For Windows, this is a string starting with '\' or with ':\' in the
       second and third chars.
   """
   str = url.strip()
   return (str.startswith('/') or str.startswith('\\') or str[1:3] == ':\\')

def UrlDirname(url):
   """ Returns the part of url without the basename of the hierarchical path
       when there is no params, query or fragment in the result of urlparse.
       If the input url doesn't end with hierarchical path, the original url
       will be returned.
   """
   scheme, netloc, path = urlparse(url)[:3]
   if scheme == 'zip':
      if ZIP_SEP in path:
         ind = path.rfind(ZIP_SEP)
         bn = posixpath.basename(path[ind + 1:])
      else:
         bn = ''
   else:
      bn = posixpath.basename(path)

   bnl = len(bn)
   dirname = url
   if bnl > 0 and dirname.endswith(bn):
      dirname = dirname[:-bnl]
   return dirname

def CustomNormPath(path):
   '''like os.path.normpath, but also strips leading slashes.
      This is intended to work for Unix style path only.'''
   if path == '':
      raise AssertionError
   slash = posixpath.sep
   path = posixpath.normpath(path)
   if path.startswith(slash):
         # posixpath.normpath might leave multiple leading slashes
         path = path.lstrip(slash) #remove leading slash(es)
         path = slash + path
   return path

def FilepathToUrl(path):
   '''converts a file path to file URL.'''
   return  urlunparse(['file', '', pathname2url(path), '', '', ''])

def FileURLToPath(url):
   ''' Gets file path from a file URL.'''
   return urlparse(url).path

def CreateZipUrl(zippath, filepath):
   '''creates 'zip:' URL.
      Parameters:
         * zippath  - The file path to the zip file.
         * filepath - The file path within the zip file.
      Returns:
         A zip URL, which is in the format of 'zip:PATH_TO_ZIP?PATH_IN_ZIP'
   '''
   if IsDatastorePath(zippath):
      zippath = DatastoreToFilepath(zippath)
   return ZIP_SEP.join(['zip:%s' % (os.path.abspath(zippath)), filepath])

def ZipURLToPath(zipUrl):
   ''' Get the zip file path from the zip URL. '''
   zipPos = zipUrl.find(':') + 1
   endPos = zipUrl.rfind(ZIP_SEP)
   return zipUrl[zipPos : endPos]

def IsDatastorePath(pathspec):
   '''Return True if pathspec is a datastore path. Datastore path is of the
      following format: [Datastore_Name]relative_path_from_datastore. Relative
      path is in posixpath format.
   '''
   if DATASTORE_RE.match(pathspec):
      return True
   else:
      return False

def DatastoreToFilepath(pathspec):
   '''Convert datastore file path 'pathspec' to an absolute file path. For
      example, [Storage1]testdata/data.txt' will be converted to
      /vmfs/volumes/Storage1/testdata/data.txt. If pathspec is not a datastore
      path, the original pathspec is returned.
   '''
   m = DATASTORE_RE.match(pathspec)
   if m:
      relpath = m.group('path').lstrip('/')
      return posixpath.join(DATASTORE_ROOT, m.group('store'), relpath)
   return pathspec
