# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2016 Continuum Analytics, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""Main Anaconda Navigator API grouping all other APIs."""

# Standard library imports
from collections import OrderedDict
import os
import shutil
import tempfile
import yaml


# Third party imports
from qtpy.QtGui import QIcon, QImage, QPixmap

# Local imports
from anaconda_navigator import images
from anaconda_navigator.api.project_api import Project
from anaconda_navigator.api.manager_api import _ManagerAPI
from anaconda_navigator.config import (CHANNELS_PATH, DEFAULT_ENV,
                                       DEFAULT_PROJECT_PATH, DEVTOOLS_PATH,
                                       IMAGE_ICON_SIZE, PROJECT_ENVS_FOLDER,
                                       PROJECT_YAML_FILE, VALID_DEV_TOOLS)
from anaconda_navigator.utils.qthelpers import qapplication
from anaconda_navigator.utils.logs import logger


IS_WINDOWS = os.name == 'nt'


class _AnacondaAPI(_ManagerAPI):
    """Anaconda API."""

    def __init__(self):
        """Anaconda API."""
        super(_AnacondaAPI, self).__init__()

    # --- Helper methods
    # -------------------------------------------------------------------------
    def _create_project_folders(self, apps, force=False):
        """Create local dev tools folders."""
        paths = []

        # Temporal hardcoded images
        image_paths = {
            'glueviz': images.GLUEVIZ_ICON_1024_PATH,
            'spyder-app': images.SPYDER_ICON_1024_PATH,
            'spyder': images.SPYDER_ICON_1024_PATH,
            'ipython-qtconsole': images.IPYTHON_QTCONSOLE_ICON_1024_PATH,
            'qtconsole': images.IPYTHON_QTCONSOLE_ICON_1024_PATH,
            'ipython-notebook': images.IPYTHON_NOTEBOOK_ICON_1024_PATH,
            'notebook': images.NOTEBOOK_ICON_1024_PATH,
            'orange-app': images.ORANGE_ICON_1024_PATH,
            'rodeo': images.RODEO_ICON_1024_PATH,
            'veusz': images.VEUSZ_ICON_1024_PATH, }

        invalid_apps = ['spyder-app', 'ipython-qtconsole', 'ipython-notebook']

        for app in apps:
            if app in invalid_apps:
                continue
            data = apps[app]
            project_path = os.sep.join([DEVTOOLS_PATH, app])

            if os.path.isdir(project_path) and force:
                if IS_WINDOWS:
                    temp_path = tempfile.mkdtemp()
                    shutil.move(project_path, temp_path)
                else:
                    try:
                        shutil.rmtree(project_path)
                    except Exception as error:
                        logger.error(error)
#            else:
#                paths.append(project_path)
#                continue

            project = Project(name=app)
            project.default_channels = self.conda_get_condarc_channels()
            project.is_app = True
            project.is_conda_app = True
            project.dev_tool = app in VALID_DEV_TOOLS

            image_path = image_paths.get(app)
            project.save(project_path)

            # Open, file, adjust size if it is too big and save it as icon.png
            if image_path:
                project.icon = 'icon.png'
                self.save_icon(image_path, project_path=project_path,
                               project=project)
            project.version = '0.1.0'

            versions = data.get('versions')
            version = versions[-1]  # Versions are sorted from small to big
            app_entry = data.get('app_entry').get(version, '')

            # Handle deprecated entrypoints for notebook and qtconsole
            if 'ipython notebook' in app_entry.lower():
                app_entry = app_entry.replace('ipython notebook',
                                              'jupyter-notebook')
            elif 'ipython qtconsole' in app_entry.lower():
                app_entry = app_entry.replace('ipython qtconsole',
                                              'jupyter-qtconsole')

            project.commands = [app_entry]
            project.save(project_path)

            spec_file = os.sep.join([project_path, 'env.yaml'])
            with open(spec_file, 'w') as f:
                f.write("{0}={1}".format(app, version))

            paths.append(project_path)

        return paths

    def get_dev_tool_version(self, path, project=None):
        """Get the version of a devtool loacated at `path`."""
        if project:
            prefix = project.env_prefix(path)
        else:
            project = self.load_project(path)
            prefix = project.env_prefix(path)

        return self.conda_package_version(prefix=prefix, pkg=project.name)

    def load_spec_file(self, path, project=None):
        """
        Load a spec file for project located at `path`.

        Create a default one in case of non existence.
        """
        if project is None:
            project = self.load_project(path)

        env = project.default_environment
        spec_file = os.path.join(path,
                                 project.environments.get(env, 'x'))

        # Try the defined project spec file
        if not os.path.exists(spec_file):
            spec_file = os.path.join(path, 'default.yaml')
            project.env = 'default'

            # Try default, and create default spec if not found
            if not os.path.exists(spec_file):
                if project.dev_tool:
                    pkgs = [project.name]
                else:
                    pkgs = ['jupyter']

                with open(spec_file, 'w') as f:
                    f.write('\n'.join(pkgs))

        # Load whatever spec_file was left
        with open(spec_file, 'r') as f:
            data = f.read()

        pkgs = data.split('\n')
        while '' in pkgs:
            pkgs.remove('')

        return pkgs

    def is_project_installed(self, path, project=None):
        """Check if a project located at `path` is installed."""
        if project:
            prefix = project.env_prefix(path)
        else:
            project = self.load_project(path)
            prefix = project.env_prefix(path)

        return self.conda_environment_exists(prefix=prefix)

    # --- Project API
    # -------------------------------------------------------------------------
    def create_default_project(self):
        """Create the default project folder on startup."""
        logger.debug("")
        channels = self._conda_api.get_condarc_channels()
        project = Project(default_channels=channels)
        project.is_default = True
        project.icon = 'icon.png'
        project.save(DEFAULT_PROJECT_PATH)

        self.save_icon(images.ANACONDA_ICON_256_PATH,
                       project_path=DEFAULT_PROJECT_PATH,
                       project=project)

    def create_application_projects(self, apps, add_project=False):
        """Create initial dev tools projects folder and set up environment."""
        logger.debug(str((apps.keys(), add_project)))
        # For now no installation of apps on startup
        paths = self._create_project_folders(apps, force=add_project)
        if add_project:
            for path in paths:
                process_worker = self.add_project(path, dev_tool_only=True)
                if process_worker:
                    process_worker.communicate()

    def create_new_project(self, path=None, name=None, channels=None):
        """
        Create project located at `path` with a given set of `channels`.

        If no channels are provided, default channels are used.
        """
        logger.debug(str((path, name, channels)))
        if channels is None:
            channels = self.conda_get_condarc_channels()

        # TODO: Check if project exists?
        project = Project(name=name, default_channels=channels)
        project.save(path)

        worker = self.add_project(path=path)
        return worker

    def add_project(self, path=None, dev_tool_only=False):
        """
        Add (import) a new project located on a path.

        Path must contain a valid `project.yaml`.
        """
        logger.debug(str((path, dev_tool_only)))
        # TEMPORAL FIX
#        project = self.load_project(path)
#
#        if project:
#            if project.name not in VALID_DEV_TOOLS and dev_tool_only:
#                return
#
#            prefix = project.env_prefix(path)
#
#            if os.path.isdir(prefix):
#                if IS_WINDOWS:
#                    temp_path = tempfile.mkdtemp()
#                    shutil.move(prefix, temp_path)
#                else:
#                    shutil.rmtree(prefix)
#
#            pkgs = self.load_spec_file(path, project=project)
#            process_worker = self._conda_api.create(
#                prefix=prefix,
#                pkgs=pkgs,
#                channels=project.default_channels)
#            return process_worker

    def load_project(self, path):
        """Load a project located in a given from  path."""
        logger.debug(path)
        file_path = os.path.join(path, PROJECT_YAML_FILE)

        if os.path.isfile(file_path):
            with open(file_path, 'r') as f:
                project_details = yaml.load(f.read())

            project = Project(**project_details)
        else:
            project = None

        return project

    def load_applications(self, paths):
        """Load all available project applications located in paths."""
        logger.debug(str(paths))
        projects_dict = {}
        ordered_paths = []
        for path in paths:
            if path != DEFAULT_PROJECT_PATH:
                project = self.load_project(path)

                if project:
                    projects_dict[path] = project
                    ordered_paths.append((project.name, path))

        ordered_paths = sorted(ordered_paths)
        ordered_projects_dict = OrderedDict()

        for name, path in ordered_paths:
            ordered_projects_dict[path] = projects_dict[path]

        return ordered_projects_dict

    def get_application_environments(self):
        """Return the dev tools application environments."""
        application_environments = OrderedDict()
        for app in sorted(os.listdir(DEVTOOLS_PATH)):
            env_path = os.sep.join([DEVTOOLS_PATH, app, PROJECT_ENVS_FOLDER,
                                    DEFAULT_ENV])
            if (os.path.isdir(env_path) and
                    self.conda_environment_exists(prefix=env_path)):
                application_environments[app] = env_path
        return application_environments

    def load_projects(self, paths):
        """
        Load all available project located in paths.

        Project are created if an environment exists.
        """
        logger.debug(str(paths))
        ordered_projects_dict = OrderedDict()
        ordered_projects = []
        projects_dict = {}

        for path in paths:
            project = self.load_project(path)
            env_prefix = os.sep.join([path, PROJECT_ENVS_FOLDER, DEFAULT_ENV])

            if (os.path.isdir(env_prefix) and
               self._conda_api.environment_exists(prefix=env_prefix) and
               project):
                projects_dict[path] = project
                ordered_projects.append((project.name, project, path))

            if path == DEFAULT_PROJECT_PATH and project:
                projects_dict[path] = project

        ordered_projects = sorted(ordered_projects, key=lambda x: x[0])
        default_project_path = projects_dict[DEFAULT_PROJECT_PATH]
        ordered_projects_dict[DEFAULT_PROJECT_PATH] = default_project_path

        for name, project, path in ordered_projects:
            if not project.is_conda_app:
                ordered_projects_dict[path] = projects_dict[path]

        for name, project, path in ordered_projects:
            if project.is_conda_app:
                ordered_projects_dict[path] = projects_dict[path]

        return ordered_projects_dict

    def save_icon(self, image_path, project_path, project=None):
        """Save a project icon based on a given image path."""
        logger.debug(str((image_path, project_path, project)))
        from PIL.ImageQt import ImageQt
        from PIL import Image

        if project is None:
            project = self.load_project(project_path)

        if os.path.isfile(image_path):
            new_icon_path = os.sep.join([project_path, project.icon])
            extension = image_path.split('.')[-1]

            with open(image_path, 'rb') as f:
                img = Image.open(f)
                img.thumbnail(IMAGE_ICON_SIZE)
                image = ImageQt(img)
                qt_image = QImage(image)

            if os.path.isfile(new_icon_path):
                os.remove(new_icon_path)
            try:
                qt_image.save(new_icon_path, format=extension)
            except Exception as err:
                print(err)

    def load_icon(self, project_path, project=None, as_pixmap=False):
        """Load project icon for project located at `project_path`."""
        logger.debug(str((project_path, project['name'], as_pixmap)))
        from PIL.ImageQt import ImageQt
        from PIL import Image

        if project is None:
            project = self.load_project(project_path)

        icon_path = os.sep.join([project_path, project.icon])

        if os.path.isfile(icon_path):
            try:
                icon = QIcon()
                image = Image.open(icon_path)
                image = ImageQt(image)
                qt_image = QImage(image)
                pixmap = QPixmap.fromImage(qt_image)
                icon = QIcon(pixmap)
            except Exception:
                icon = QIcon()
        else:
            icon = QIcon()

        if as_pixmap:
            try:
                icon = icon.pixmap(icon.availableSizes()[0])
            except Exception:
                icon = QPixmap(*IMAGE_ICON_SIZE)
        return icon


ANACONDA_API = None


def AnacondaAPI():
    """Anaconda API Wrapper."""
    global ANACONDA_API

    if ANACONDA_API is None:
        ANACONDA_API = _AnacondaAPI()

    return ANACONDA_API


# --- Local testing
# -----------------------------------------------------------------------------
def test_create_project_folders():  # pragma: no cover
    """Repodata must be downloaded first and located at CHANNELS_PATH."""
    app = qapplication()
    api = AnacondaAPI()
    api.set_data_directory(CHANNELS_PATH)
    repodata_files = api.repodata_files(CHANNELS_PATH)
    print(repodata_files)
    packages, apps = api._client_api._load_repodata(repodata_files)
    print(apps)
    print(api._create_project_folders(apps, force=True))
    print(api.create_application_projects(apps, add_project=True))
    app.exec_()


if __name__ == '__main__':  # pragma: no cover
    test_create_project_folders()
