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

"""
Projects Tab.
"""

# Standard library imports
from __future__ import absolute_import, division, print_function
import os
import shutil
import sys

# Third party imports
from qtpy.compat import getexistingdirectory
from qtpy.QtCore import Signal, QSize, Qt
from qtpy.QtGui import QIcon, QPixmap
from qtpy.QtWidgets import (QHBoxLayout, QFrame, QLabel, QLineEdit,
                            QListWidgetItem, QMessageBox,
                            QPushButton, QVBoxLayout)
import qtawesome as qta

# Local imports
from anaconda_navigator.api import AnacondaAPI
from anaconda_navigator.config import (DEFAULT_PROJECT_PATH, get_home_dir,
                                       get_projects_path)
from anaconda_navigator.config.main import CONF
from anaconda_navigator.utils.qthelpers import update_pointer
from anaconda_navigator.utils.styles import load_style_sheet
from anaconda_navigator.widgets import (WidgetBase, ProjectsListWidget,
                                        MessageCheckBox)
from anaconda_navigator.widgets.projects import ProjectWidget


class ProjectsTab(WidgetBase):
    """
    Projects management tab.
    """
#    sig_apps_changed = Signal(str)
    sig_apps_updated = Signal()
    sig_project_updated = Signal()
    sig_status_updated = Signal(str, int, int, int)

    def __init__(self, parent=None):
        super(ProjectsTab, self).__init__(parent)

        self.api = AnacondaAPI()
        self.current_project_item = None
        self.dev_tool_visible = False
        self.new_project_name_template = "New project {0}"
        self.project_counter = 1   # Counter for new projects
        self.projects_dict = {}

        # Widgets
        self.button_add = QPushButton("Import")
        self.button_create = QPushButton("Create")
        self.button_delete = QPushButton("Delete")
        self.button_duplicate = QPushButton("Duplicate")
        self.label_search = QLabel('')
        self.list_projects = ProjectsListWidget()
        self.project_widget = ProjectWidget(parent=self)
        self.text_search = QLineEdit()
        search_icon = qta.icon('fa.search')

        # Widgets setup
        self.label_search.setPixmap(search_icon.pixmap(16, 16))
        self.text_search.setPlaceholderText("Search Projects")
        self.setObjectName("Tab")
        self.list_projects.setObjectName("ListProjects")

        # Layouts
        projects_layout = QVBoxLayout()

        search_layout = QHBoxLayout()
        search_layout.addWidget(self.text_search)
        search_layout.addWidget(self.label_search)

        projects_layout.addLayout(search_layout)
        projects_layout.addWidget(self.list_projects)

        project_buttons_layout = QHBoxLayout()
        project_buttons_layout.addWidget(self.button_create)
        project_buttons_layout.addWidget(self.button_add)
        project_buttons_layout.addWidget(self.button_duplicate)
        project_buttons_layout.addWidget(self.button_delete)

        projects_layout.addLayout(project_buttons_layout)

        main_layout = QHBoxLayout()
        main_layout.addSpacing(20)
        main_layout.addLayout(projects_layout, 1)
        main_layout.addSpacing(20)
        main_layout.addWidget(self.project_widget, 2)

        self.setLayout(main_layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_project(path=None))
        self.button_create.clicked.connect(
            lambda: self.create_new_project(path=None))
        self.button_delete.clicked.connect(self.delete_project)
        self.button_duplicate.clicked.connect(self.duplicate_project)

        self.list_projects.itemChanged.connect(self.set_app)
        self.list_projects.itemSelectionChanged.connect(
            self.load_project_information)

        self.project_widget.sig_apps_updated.connect(self.sig_apps_updated)
        self.project_widget.sig_project_updated.connect(
            self.sig_project_updated)
        self.project_widget.sig_project_name_updated.connect(
            self.update_project_name)
        self.project_widget.sig_project_icon_updated.connect(
            self.update_project_icon)
        self.project_widget.sig_project_commands_updated.connect(
            self.update_project_commands)

        self.text_search.textChanged.connect(self.filter_projects)

        # Setup
        self.button_duplicate.setDisabled(True)
        self.button_duplicate.setVisible(False)
        self.button_delete.setDisabled(True)
        self.update_style_sheet()

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

        self.list_projects.setFrameStyle(QFrame.NoFrame)
        self.list_projects.setFrameShape(QFrame.NoFrame)
        self.list_projects.setStyleSheet(style_sheet)
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setSizeHint(QSize(60, 60))
        self.list_projects.repaint()

    def setup_tab(self):
        """
            self.update_visibility(disabled=True)

        """
        self.load_projects()
        self.project_widget.setDisabled(True)
        self.toogle_dev_tools(visible=self.dev_tool_visible)
        self.load_last_active_project()

    # --- Helpers
    # -------------------------------------------------------------------------
    def _update_project_numbering(self):
        """
        Update the new project count number based on existing project names.
        """
        new_project_numbering = [0]
        for path in self.projects_dict:
            project = self.projects_dict[path]

            if self.new_project_name_template[:-3] in project.name:
                try:
                    number = int(project.name.split(' ')[-1])
                except Exception:
                    number = 0
                new_project_numbering.append(number)

        self.project_counter = max(new_project_numbering) + 1

    def set_app(self, item):
        """
        Check box changed, set project's is_app property.
        """
        item.project.is_app = item.checkState() == Qt.Checked
        item.project.save(item.path)
        self.sig_apps_updated.emit()

    def filter_projects(self):
        """
        Filter displayed projects by matching search text.
        """
        text = self.text_search.text().lower()
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setHidden(text not in item.text().lower())
        self.toogle_dev_tools(visible=self.dev_tool_visible)

    def load_projects(self):
        """
        Load all projects available in the configuration folder and settings.
        """
        paths = get_projects_path()
        self.projects_dict = self.api.load_projects(paths)
        self._update_project_numbering()
        self.load_project_list()

    def load_last_active_project(self):
        """
        Make sure project selection ("current") persists.
        """
        active_path = CONF.get('main', 'current_project_path',
                               DEFAULT_PROJECT_PATH)

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            if item.path == active_path:
                self.list_projects.setCurrentItem(item)
                self.current_project_item = item
                self.load_project_information()

    def load_project_information(self):
        """
        Update the project widget after a project has been selected.
        """
        # Project Widget update
        item = self.list_projects.currentItem()
        details = item.project
        path = item.path
        self.project_widget.load_project_information(path)
        self.current_project_item = item
        CONF.set('main', 'current_project_path', item.path)

        self.button_delete.setDisabled(details.is_default)
        self.button_duplicate.setDisabled(details.is_default)
        self.project_widget.setDisabled(False)

    def load_project_list(self):
        """
        Populate the project selection list.
        """
        self.list_projects.clear()

        for path in self.projects_dict:
            project = self.projects_dict[path]
            name = project.name

            item = QListWidgetItem(name)

            if getattr(project, 'icon', None):
                icon = self.api.load_icon(path, project)
            else:
                icon = qta.icon('fa.cog')

            item.setIcon(icon)
            item.project = project
            item.path = path
            if project.commands:
                item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable |
                              Qt.ItemIsEnabled)
                if project.is_app:
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)

            self.list_projects.addItem(item)
            self.toogle_dev_tools(visible=self.dev_tool_visible)

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setSizeHint(QSize(item.sizeHint().width(), self._height()))

    def _height(self):
        """
        Get the height for the row in the widget based on OS font metrics.
        """
        return self.fontMetrics().height()*2

    def toogle_dev_tools(self, visible=None):
        """
        Display/Hide development tools project folders.
        """
        if visible is not None:
            self.dev_tool_visible = visible
        else:
            self.dev_tool_visible = not self.dev_tool_visible

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            is_conda_app = item.project.is_conda_app
            if is_conda_app:
                item.setHidden(not self.dev_tool_visible)

    def update_visibility(self, disabled=True):
        """
        Enable/Disabled widgets during operations.
        """
        self.list_projects.setDisabled(disabled)
        self.button_add.setDisabled(disabled)
        self.button_create.setDisabled(disabled)
        self.button_delete.setDisabled(disabled)
        self.button_duplicate.setDisabled(disabled)
        self.project_widget.setDisabled(disabled)

    def update_status(self, message='', timeout=0):
        """
        Update project tab status.
        """
        self.sig_status_updated.emit(message, timeout, -1, -1)

    def update_project_name(self, path, old_name, new_name):
        """
        Update project name item only. Avoid reloading projects.
        """
        item = self.get_item_by_path(path)
        item.setText(new_name)

    def update_project_icon(self, path, old_image_path, new_image_path):
        """
        Update project item icon only. Avoid reloading projects.
        """
        self.api.save_icon(new_image_path, path)
        icon = QIcon(QPixmap(new_image_path))
        item = self.get_item_by_path(path)
        item.setIcon(icon)

    def get_item_by_path(self, path):
        """
        """
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            if item.path == path:
                return item

    def update_project_commands(self, path, commands):
        """
        Update project checkbox icon only. Avoid reloading projects.
        """
        # FIXME: There should be a better way!
        item = self.get_item_by_path(path)
        item.project.commands = commands
        if len(commands) != 0:
            item.setData(Qt.CheckStateRole, item.checkState())
        else:
            item.setData(Qt.CheckStateRole, None)

    # --- Main project tab actions
    # -------------------------------------------------------------------------
    def add_project(self, path=None, new_path=None):
        """
        Add/import existing project given by `path` into `new_path`.
        """
        if path is None:
            path = getexistingdirectory(caption="Select origin project folder "
                                                "to import",
                                        basedir=get_home_dir())
        if path:
            self.update_status('Adding project at {0}'.format(path))
            self.update_visibility(disabled=True)
            update_pointer(Qt.BusyCursor)

            # shutil.copytree does not work if the destionation folder exists
#            os.removedirs(new_path)
#            shutil.copytree(path, new_path)
            worker = self.api.add_project(path)
            worker.path = path
            worker.sig_finished.connect(self._project_added)

    def create_new_project(self, path=None):
        """
        Create a new project. If `path` is provided no dialog is openned.
        """
        if path is None:
            path = getexistingdirectory(caption='Select new project folder '
                                                'location',
                                        basedir=get_home_dir())

        # Check that project.yaml does not exists, otherwise warn the user and
        # prompt for action?
        if path:
            name = self.new_project_name_template.format(self.project_counter)
            self.update_status('Creating project "{0}"'.format(name))
            worker = self.api.create_new_project(path=path, name=name)
            worker.sig_finished.connect(self._project_created)
            worker.path = path
            self.update_visibility(disabled=True)
            self.project_counter += 1
            update_pointer(Qt.BusyCursor)

    def delete_project(self):
        """
        Remove project from listing and optionally delete all project files.
        """
        project = self.list_projects.currentItem().project

        # The default project cant be deleted. Enforce extra security for that
        if project.is_default:
            return

        msgbox = MessageCheckBox.warning(
            self,
            "Delete project",
            "Do you really want to delete project <strong>{0}</strong>?"
            "".format(project.name),
            QMessageBox.Yes | QMessageBox.No,
            checkbox_text="Delete all project files"
            )

        reply = msgbox.exec_()
        if reply == MessageCheckBox.Yes:
            self.sig_status_updated.emit('Deleting project '
                                         '"{0}"'.format(project.name),
                                         0, -1, -1)
            item = self.list_projects.takeItem(self.list_projects.currentRow())
            projects_path = list(set(CONF.get('main', 'projects_path', [])))
            projects_path = list(projects_path)
            projects_path.remove(item.path)
            CONF.set('main', 'projects_path', projects_path)

            if msgbox.is_checked():
                if os.path.isdir(item.path):
                    shutil.rmtree(item.path)

            # Select the default project after deletion of a project
            default_item = self.list_projects.item(0)
            self.list_projects.itemClicked.emit(default_item)
            self.sig_status_updated.emit('', 0, -1, -1)
            self.sig_apps_updated.emit()

    def duplicate_project(self, path=None):
        """
        Copy selected project directory into `path`.
        """
        source_path = self.list_projects.currentItem().path
        if path is None:
            path = getexistingdirectory(caption="Select folder destination "
                                                "for project duplication",
                                        basedir=get_home_dir())
        if path:
            shutil.copy2(source_path, path)

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _project_created(self, worker, output, error):
        """
        Callback after worker has finished creating a new project.
        """
        self._update_project_config(path=worker.path)
        self.update_visibility(disabled=False)

        # Select the project after created
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)

            if worker.path == item.path:
                self.list_projects.setCurrentRow(i)
                self.list_projects.itemClicked.emit(item)
        update_pointer()

    def _project_added(self, worker, output, error):
        """
        Callback after worker has finished adding project.
        """
        self._project_created(worker, output, error)

    def _update_project_config(self, path):
        """
        Add given path to the CONF list of known projects.
        """
        projects_path = list(set(CONF.get('main', 'projects_path', [])))
        projects_path = list(projects_path)
        projects_path.append(path)
        CONF.set('main', 'projects_path', projects_path)
        self.load_projects()
        self.update_status('')


def test():
    from anaconda_navigator.utils.qthelpers import qapplication
    app = qapplication()
    w = ProjectsTab()
    w.showMaximized()
    sys.exit(app.exec_())


if __name__ == "__main__":
    test()
