# Copyright (c) 2010 ActiveState Software Inc. All rights reserved.
"""
    PyPM client base
    ~~~~~~~~~~~~~~~~
    
"""

from os import path as P
import sys
from contextlib import contextmanager

from applib import sh
from applib.base import Application
from applib.misc import xjoin

import pypm
from pypm.common import python
from pypm.common.supported import PLATNAME
from pypm.common.util import pypm_file
from pypm.common.util import dlocked
from pypm.common.repository import RemoteRepositoryManager
from pypm.common.repository import RemoteRepositorySet
from pypm.common.repository import MultiRepositoryConfig

from pypm.client import store

__all__ = ['make_pypm_environment']


application = Application(
    'PyPM', 'ActiveState', '.'.join(pypm.__version_info__[:2]))

CONF_FILE_GLOBAL = pypm_file('pypm', 'client', 'client.conf')
CONF_FILE_LOCAL  = P.join(application.locations.user_data_dir, 'client.conf')

IDX_PATH = P.join(application.locations.user_cache_dir, 'idx')

DEFAULT_OPTIONS = dict(
    interactive = False,      # Are we attached to an interactive terminal?
    force = False,            # Force overwrite of files during installation?
)


class PyPMEnvironment(object):
    """A PyPM environment that is tied to

      - one `PythonEnvironment`
      - one or more `RemoteRepository`

    Packages can, thus, be searched and installed from any number of remote
    repositories (although usually it is the main respository) but can only be
    installed to the specified Python environment
    """

    def __init__(self, pyenv, repository_list, **options):
        """
        pyenv             -- An instance of pypm.common.python.*Environment
        repository_list   -- List of repositories
        options           -- Override for DEFAULT_OPTIONS
        """
        self.pyenv = pyenv
        self.repository_list = repository_list
        self.options = DEFAULT_OPTIONS.copy()
        self.options.update(options)
        
        self.pypm_dir = P.join(self.pyenv.site_packages_dir, '_pypm')
        self.repo_store = store.RepoPackageStore(
            RemoteRepositoryManager(IDX_PATH), repository_list)

        if not P.exists(self.pypm_dir):
            self.pyenv.ensure_write_access()
            sh.mkdirs(self.pypm_dir)
        self.installed_store = store.InstalledPackageStore(
            P.join(self.pypm_dir, 'installed.db')
        )
        
    def clone(self, altattr={}):
        """Clone ``self`` with alternate attributes
        
        I am used by the test cases (test_client.py)
        """
        return PyPMEnvironment(
            pyenv = altattr.get('pyenv', self.pyenv),
            repository_list = altattr.get('repository_list', self.repository_list),
        )

    @contextmanager 
    def locked(self):
        """Lock the PyPM environment"""
        with dlocked(self.pypm_dir):
            yield

def resolve_repo_urls(aliases='default'):
    """Resolve the repository URLs
    """
    mrc = MultiRepositoryConfig(CONF_FILE_GLOBAL)
    return mrc.get_urls(aliases)
    

def make_pypm_environment(pyenv, *repo_urls):
    return make_pypm_environment2(repo_urls, pyenv=pyenv)

def make_pypm_environment2(repo_urls, pyenv, **kwargs):
    """Make a PyPMEnvironment

    If repo_urls is None or empty, the one from the default config file is used.
    """
    repository_list = [
        RemoteRepositorySet(url.strip()).get_repository(
            pyenv, PLATNAME)
        for url in (repo_urls or resolve_repo_urls('default'))
    ]
    return PyPMEnvironment(repository_list=repository_list,
                           pyenv=pyenv, **kwargs)
    

class ImagePythonEnvironment(python.PythonEnvironment):
    """A Python environment pointing to a *temporary* Python image

    Consequently, the path to a *permanet* Python executable must also be
    specified; this path will be used by pypm.client.fixer (the shebang fixer).
    
    This is used in APy source: support/apybuild/pypm_targets.py
    """

    def __init__(self, image_root, target_python_exe):
        """
        - image_root: path to Python image
        - target_python_exe: actual python executable path to use (in fixer)
        """
        self.target_python_exe = target_python_exe
        super(ImagePythonEnvironment, self).__init__(image_root)
     
    def _transform_path(self, p):
        """Transform original path using our image_root"""
        # Find the "base directory" of target Python executable
        # And then replace that with our image root in `p`
        if sys.platform.startswith('win'):
            original_root = xjoin(P.dirname(self.target_python_exe))
        else:
            original_root = xjoin(P.dirname(self.target_python_exe), '..')

        # Use `lower()` for case-insensitive filesystems like NTFS/FAT
        if p.lower().startswith(self.root_dir.lower()):
            return p
        else:
            assert p.lower().startswith(original_root.lower()), \
                '"%s" is different from "%s"' % (p, original_root)
            return xjoin(self.root_dir + p[len(original_root):])

    def get_install_scheme_path(self, path):
        return self._transform_path(
            super(ImagePythonEnvironment, self
                  ).get_install_scheme_path(path))

