# -*- coding: utf-8 -*-
#
# Copyright 2016 Continuum Analytics, Inc.
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
#

"""
Widgets to list applications available to launch from the Home tab.
"""

from __future__ import absolute_import, division, print_function

# Standard library imports
import os
import sys

# Third party imports
from qtpy.QtCore import QSize, QPoint, Qt, QTimer, Signal
from qtpy.QtGui import QIcon, QMovie, QPixmap
from qtpy.QtWidgets import (QAbstractItemView, QFrame, QLabel, QListWidget,
                            QListWidgetItem, QMenu, QVBoxLayout, QWidget)

# Local imports
from anaconda_navigator.api import AnacondaAPI
from anaconda_navigator.images import (ANACONDA_ICON_64_PATH,
                                       CONDA_MANAGER_UPGRADE_ARROW,
                                       SPINNER_WHITE_16_PATH)
from anaconda_navigator.widgets import ButtonBase
from anaconda_navigator.utils.launch import launch
from anaconda_navigator.utils.logs import logger
from anaconda_navigator.utils.py3compat import to_text_string
from anaconda_navigator.utils.qthelpers import (add_actions, create_action,
                                                update_pointer)
from anaconda_navigator.utils.styles import load_style_sheet


# --- Widgets used in CSS styling
# -----------------------------------------------------------------------------
class ButtonApplicationInstall(ButtonBase):
    pass


class ButtonApplicationLaunch(ButtonBase):
    pass


class ButtonApplicationOptions(ButtonBase):
    pass


class ButtonApplicationUpdate(ButtonBase):
    pass


class LabelApplicationIcon(QLabel):
    pass


class LabelApplicationName(QLabel):
    pass


class LabelApplicationVersion(QLabel):
    pass


class LabelApplicationDescription(QLabel):
    pass


class LabelApplicationSpinner(QLabel):
    pass


class ButtonApplicationVersion(ButtonBase):
    pass


class WidgetApplication(QFrame):
    # application_name, application_version
    sig_application_updated = Signal(object, object)
    sig_status_updated = Signal(object)


# --- Main Widgets
# -----------------------------------------------------------------------------
class ListWidgetApplication(QListWidget):
    """
    Widget that holds the whole list of applications to launch.
    """
    # application_name, application_version
    sig_application_updated = Signal(object, object)
    sig_status_updated = Signal(object)

    def __init__(self, *args, **kwargs):
        super(ListWidgetApplication, self).__init__(*args, **kwargs)

        self._items = []

        # Widget setup
        self.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.setFocusPolicy(Qt.NoFocus)
        self.setFrameStyle(QFrame.Plain)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setMovement(QListWidget.Static)
        self.setResizeMode(QListWidget.Adjust)
        self.setSelectionMode(QAbstractItemView.NoSelection)
        self.setViewMode(QListWidget.IconMode)
        self.timer = QTimer()
        self.timer.timeout.connect(self.repaint)
        self.timer.start(300)

    def addItem(self, item):
        """
        Add an application item to the list.
        """
        super(ListWidgetApplication, self).addItem(item)
        self.setItemWidget(item, item.widget)
        if isinstance(item, ListItemApplication):
            self._items.append(item)
            item.widget.sig_application_updated.connect(
                self.sig_application_updated)
            item.widget.sig_status_updated.connect(
                self.sig_status_updated)

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()

        for item in self._items:
            item.update_style_sheet(style_sheet)
        self.setStyleSheet(style_sheet)

    def clear(self):
        self._items = []
        super(ListWidgetApplication, self).clear()

    def items(self):
        return self._items


class WidgetEmpty(QWidget):
    def sizeHint(self):
        return QSize(0, 0)


class ListItemEmpty(QListWidgetItem):
    def __init__(self):
        super(ListItemEmpty, self).__init__()
        self.widget = QWidget()
        self.setSizeHint(self.widget.sizeHint())


class ListItemApplication(QListWidgetItem):
    """
    Item with custom widget for the applications list.
    """
    ICON_SIZE = 48

    def __init__(self, name=None, description=None, command=None,
                 pixmap=None, version=None, versions=None,
                 path=None, dev_tool=True, prefix=None,
                 is_conda_app=False, packages_widget=None):
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.command = command
        self.dev_tool = dev_tool
        self.installed = False
        self.is_conda_app = is_conda_app
        self.name = name
        self.path = path
        self.pixmap = pixmap if pixmap else QPixmap(ANACONDA_ICON_64_PATH)
        self.prefix = prefix
        self.timeout = 10000  # In miliseconds
        self.version = version
        self.versions = versions
        self.packages_widget = packages_widget

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(name)
        self.label_description = LabelApplicationDescription(description)
#        self.label_update = LabelApplicationUpdate()
        self.button_version = ButtonApplicationVersion(to_text_string(version))
        self.label_spinner = LabelApplicationSpinner()
#        self.label_version = LabelApplicationVersion(to_text_string(version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.movie_spinner = QMovie(SPINNER_WHITE_16_PATH)
        self.timer = QTimer()
        self.widget = WidgetApplication()

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.label_name.setToolTip(description)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.movie_spinner.start()
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setToolTip(description)
        self.label_icon.setPixmap(self.pixmap.scaled(self.ICON_SIZE,
                                                     self.ICON_SIZE,
                                                     Qt.KeepAspectRatio,
                                                     Qt.SmoothTransformation))
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.label_spinner.setVisible(False)
        self.label_spinner.setMinimumWidth(16)
        self.label_spinner.setMinimumHeight(16)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.button_options, 0, Qt.AlignRight)
        layout.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout.addWidget(self.label_description, 0, Qt.AlignCenter)

#        hlayout = QHBoxLayout()
#        hlayout.addWidget(self.label_update)
#        hlayout.addWidget(self.label_version)
#        layout.addLayout(hlayout)
#        layout.addWidget(self.label_version, 0, Qt.AlignCenter)
        layout.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout.addWidget(self.label_spinner, 0, Qt.AlignCenter)
        layout.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget.sizeHint())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _application_launched(self):
        """
        """
        self.button_launch.setDisabled(False)
        update_pointer()

    def _application_installed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_updated(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_removed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    # --- Helpers
    # -------------------------------------------------------------------------
    def _partial_output_ready(self, worker, output, error):
        """
        """
        message = None
        progress = (0, 0)

        if isinstance(output, dict):
            progress = (output.get('progress', None),
                        output.get('maxval', None))
            name = output.get('name', None)
            fetch = output.get('fetch', None)

            if fetch:
                message = "Downloading <b>{0}</b>...".format(fetch)

            if name:
                self._current_action_name = name
                message = "Linking <b>{0}</b>...".format(name)

        logger.debug(message)
        self.widget.sig_status_updated.emit(message)

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.menu_options.setStyleSheet(style_sheet)
        self.menu_versions.setStyleSheet(style_sheet)

    def actions_menu_requested(self):
        """
        Create and display options menu for the currently selected application.
        """
        self.menu_options.clear()
        self.menu_versions.clear()

        # Add versions menu
        versions = self.versions if self.versions else []
        version_actions = []
        for version in reversed(versions):
            action = create_action(
                self.widget,
                version,
                triggered=lambda value, version=version:
                    self.install_application(version=version))

            action.setCheckable(True)
            if self.version == version:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        update_action = create_action(
            self.widget,
            'Update application',
            triggered=lambda: self.update_application())

        if versions and versions[-1] == self.version:
            update_action.setDisabled(True)
        else:
            update_action.setDisabled(False)

        remove_action = create_action(
            self.widget,
            'Remove application',
            triggered=lambda: self.remove_application())
        remove_action.setEnabled(self.installed)

        actions = [update_action, remove_action, None, self.menu_versions]
        add_actions(self.menu_options, actions)
        add_actions(self.menu_versions, version_actions)
        offset = QPoint(self.button_options.width(), 0)
        position = self.button_options.mapToGlobal(QPoint(0, 0))
        self.menu_versions.setEnabled(bool(versions))
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        if self.prefix:
            self.version = self.api.conda_package_version(self.prefix,
                                                          pkg=self.name)
        self.installed = bool(self.version)
        if (self.versions and self.version != self.versions[-1] and
                self.installed):
            self.button_version.setIcon(QIcon(CONDA_MANAGER_UPGRADE_ARROW))
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: #0071a0;}")
            self.button_version.setToolTip(
                'Version {0} available'.format(self.versions[-1]))
        else:
            self.button_version.setIcon(QIcon())
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: black;}")

        self.button_install.setVisible(not self.installed)
        self.button_launch.setVisible(self.installed)

    def set_loading(self, value):
        self.button_launch.setDisabled(value)
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)

        if value:
            self.label_spinner.setMovie(self.movie_spinner)
        else:
            self.label_spinner.setMovie(None)
            if self.version is None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)

        self.label_spinner.setVisible(value)
        self.button_version.setVisible(not value)

    def handle_action_finished(self, worker, output, error):
        if not isinstance(output, dict):
            output = {}
        success = output.get('success', True)

        if error or not success:
            # Error might be harmless if no decoding was possible...
            # Success deserves some sort of messagebox
            logger.error(error)
        self.widget.sig_application_updated.emit(self.name, self.version)

        self.update_status()
        self.set_loading(False)

    def update_versions(self, version=None, versions=None):
        """
        Update button visibility depending on update availability.
        """
        update = versions[-1] != version
        logger.debug(str((self.name, self.dev_tool, self.installed)))

        if self.installed and version:
            self.button_options.setVisible(True)
            self.button_version.setText(version)
            self.button_version.setVisible(True)
        elif not self.installed and versions:
            self.button_install.setEnabled(True)
            self.button_version.setText(versions[-1])
            self.button_version.setVisible(True)

        self.versions = versions
        self.version = version
        self.update_status()

    # --- Public API
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None):
        """
        Update the application on the defined prefix environment.

        This is used for both normal install and specific version install.
        """
        if version:
            self.version = version
        else:
            self.version = self.versions[-1]
            version = self.versions[-1]

        pkg = '{0}={1}'.format(self.name, version)
        pkgs = [pkg]
        logger.debug(str((pkg, self.dev_tool)))

        # Check if environment exists and then create or install
#        is_installed = self.api.conda_package_version(prefix=self.prefix,
#                                                      pkg=self.name)
#        pkgs = [pkg] + self.BASIC_PACKAGES
#        if is_installed:
#            worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
#        else:
#            worker = self.api.conda_create(prefix=self.prefix, pkgs=pkgs)

        worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_installed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Installing application '
                                            '<b>{0}</b>'.format(self.name))

    def launch_application(self):
        """
        Launch application installed in prefix environment.
        """
        if self.command is not None:
            if self.command.startswith('open'):
                command = self.command.replace("${PREFIX}", self.prefix)
            elif self.prefix:
                command = os.sep.join([self.prefix, 'bin', self.command])
            else:
                command = self.command

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            launch(self.path, command)

    def remove_application(self):
        """
        Remove the application from the defined prefix environment.
        """
        pkg = self.name
        pkgs = [pkg]
        logger.debug(str((self.name, self.dev_tool)))
        worker = self.api.conda_remove(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_removed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Removing application '
                                            '<b>{0}</b>'.format(self.name))

    def update_application(self):
        """
        Update the application on the defined prefix environment.
        """
        logger.debug(str((self.name, self.dev_tool, self.installed)))
        self.install_application(version=self.versions[-1])
        self.widget.sig_status_updated.emit('Updating application '
                                            '<b>{0}</b>'.format(self.name))


def test():
    from anaconda_navigator.utils.qthelpers import qapplication
    app = qapplication(test_time=5)
    l = ListWidgetApplication()
    for i in range(300):
        item = ListItemApplication(
            name="Package {0}".format(i),
            description="Scientific PYthon Development EnviRonment"
            )
        l.addItem(item)

    l.show()
    l.update_style_sheet()
    sys.exit(app.exec_())


if __name__ == "__main__":
    test()
