# coding: utf-8
# /*##########################################################################
# MIT License
#
# Copyright (c) 2018 DAXS developers.
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""This module provides base classes to implement models for
spectroscopy data.
"""

from __future__ import absolute_import, division

__authors__ = ['Marius Retegan', 'Mauro Rovezzi']
__license__ = 'MIT'

import os

import silx.io
from silx.gui import qt

from .items import ExperimentItem, GroupItem, FileItem, ScanItem

from sloth.utils.logging import getLogger
logger = getLogger('sloth.gui.daxs.view')


class HorizontalHeaderView(qt.QHeaderView):

    def __init__(self, parent=None):
        super(HorizontalHeaderView, self).__init__(qt.Qt.Horizontal, parent)

        # Some properties
        self.setStretchLastSection(True)
        # self.setSectionsMovable(True)

        # Context menu
        self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)

    def showContextMenu(self, position):
        menu = qt.QMenu('Horizontal Header View Menu', self)

        section = self.logicalIndexAt(position)

        action = qt.QAction('Add', self, triggered=self.append)
        menu.addAction(action)

        action = qt.QAction('Remove', self,
                            triggered=lambda: self.remove(section))
        menu.addAction(action)

        menu.exec_(self.mapToGlobal(position))

    def append(self):
        pass

    def remove(self, section):
        model = self.model()
        if not model.header[section].removable:
            logger.info('The selected column cannot be removed')
            return
        model.setHeaderData(section, orientation=qt.Qt.Horizontal, value=None)
        view = self.parent()
        view.setItemsDelegates()


class TreeView(qt.QTreeView):

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

        # Header
        headerView = HorizontalHeaderView()
        self.setHeader(headerView)

        # Context menu
        self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)

        # Selection mode
        self.setSelectionMode(qt.QAbstractItemView.ExtendedSelection)

    def setModel(self, model):
        super(TreeView, self).setModel(model)
        self.setItemsDelegates()

    def setItemsDelegates(self):
        if self.model() is None:
            return

        header = self.model().header
        for i, _ in enumerate(header):
            delegate = header.delegate(i)
            if delegate is not None:
                self.setItemDelegateForColumn(i, delegate(parent=self))

    def showContextMenu(self, position):
        menu = qt.QMenu('Tree View Menu', self)

        action = qt.QAction(
            'Add Experiment', self, triggered=self.addExperiment)
        menu.addAction(action)

        # Get the index under the cursor.
        index = self.indexAt(position)
        item = self.model().itemFromIndex(index)

        if isinstance(item, ExperimentItem) or isinstance(item, GroupItem):
            action = qt.QAction('Add Group', self, triggered=self.addGroup)
            menu.addAction(action)

            action = qt.QAction('Load Files', self, triggered=self.loadFiles)
            menu.addAction(action)

        # If there are selected indexes, they can be removed or checked.
        if self.selectedIndexes():
            menu.addSeparator()
            action = qt.QAction(
                'Toggle Selected', self, triggered=self.toggleSelected)
            menu.addAction(action)
            action = qt.QAction(
                'Remove Selected', self, triggered=self.removeSelected)
            menu.addAction(action)

        if isinstance(item, ScanItem) and index.column() > 0:
            menu.addSeparator()
            action = qt.QAction(
                'Copy Value to Selected', self,
                triggered=lambda: self.copyValueToSelected(index))
            menu.addAction(action)

            action = qt.QAction(
                'Copy Value to Toggled', self,
                triggered=lambda: self.copyValueToToggled(index))
            menu.addAction(action)

        menu.exec_(self.mapToGlobal(position))

    def loadFiles(self):
        paths, _ = qt.QFileDialog.getOpenFileNames(
            self, 'Select Files to Load', os.getcwd(),
            'Data Files (*.spec *.hdf5 *.h5);; All Files (*)')

        if not paths:
            return

        parent = self.selectionModel().selectedRows().pop()
        parentItem = self.model().itemFromIndex(parent)
        for path in paths:
            self.addFile(path, parentItem)

    def addExperiment(self, name=None):
        rootItem = self.model().rootItem
        row = rootItem.childCount()
        if name is None or not name:
            name = 'Experiment{}'.format(row)
        item = ExperimentItem(name=name, parentItem=rootItem)
        self.model().appendRow(item)
        return item

    def addGroup(self, name=None, parentItem=None):
        """Add a generic GroupItem at a given parentItem"""
        # Add the file to the last added item.
        if parentItem is None:
            parent = self.selectionModel().selectedRows().pop()
            parentItem = self.model().itemFromIndex(parent)
        row = parentItem.childCount()

        if name is None or not name:
            name = 'Group{}'.format(row)

        item = GroupItem(name, parentItem)
        self.model().appendRow(item)

    def addFile(self, path=None, parentItem=None):
        if path is None:
            return

        # Add the file to the last added experiment item.
        if parentItem is None:
            parentItem = self.model().rootItem.lastChild()

        try:
            data = silx.io.open(path)
        except OSError as e:
            logger.warning(e)
            return

        # Create a tree item for the file and add it to the experiment item.
        name, _ = os.path.splitext(os.path.basename(path))
        item = FileItem(name, parentItem)
        self.model().appendRow(item)

        # Create a tree item for each scan. The parent item is now the
        # previous file item.
        # TODO: Make this more "intelligent" by using the command to
        # set better defaults for x, signal, etc.
        parentItem = item
        for scan in data:
            item = ScanItem(name=scan, parentItem=parentItem, data=data[scan])
            self.model().appendRow(item)

    def selectedItems(self):
        indexes = self.selectionModel().selectedRows()
        items = [self.model().itemFromIndex(index) for index in indexes]
        return items

    def scanItems(self):
        for index in self.model().visitModel():
            item = self.model().itemFromIndex(index)
            if isinstance(item, ScanItem):
                yield item

    def toggleSelected(self):
        for item in self.selectedItems():
            index = self.model().indexFromItem(item)
            try:
                if item.isChecked:
                    self.model().setData(
                        index, qt.Qt.Unchecked, qt.Qt.CheckStateRole)
                else:
                    self.model().setData(
                        index, qt.Qt.Checked, qt.Qt.CheckStateRole)
            except AttributeError:
                pass

    def removeSelected(self):
        items = self.selectedItems()
        parentItems = dict()
        for item in items:
            parentItem = item.parentItem
            remove = True
            while parentItem is not self.model().rootItem:
                if parentItem in items:
                    remove = False
                    break
                parentItem = parentItem.parentItem
            # If an ancestors is selected for removal, pass the item.
            if not remove:
                continue

            # Get the parent item for the current item.
            parentItem = item.parentItem
            if parentItem not in parentItems:
                parentItems[parentItem] = list()
            # Create a list with the positions of the children that are
            # going to be removed.
            parentItems[parentItem].append(item.childPosition())

        # Remove the rows from the parent.
        for parentItem in parentItems:
            rows = parentItems[parentItem]
            parent = self.model().indexFromItem(parentItem)
            for row in reversed(sorted(rows)):
                self.model().removeRow(row, parent)

    def copyValueToToggled(self, indexAt):
        """Copy the value under the cursor to the toggled items."""
        indexes = self.model().visitModel(columns=True)
        for index in indexes:
            item = self.model().itemFromIndex(index)
            if not item.isChecked or index == indexAt:
                continue
            elif index.column() == indexAt.column():
                value = self.model().data(indexAt)
                self.model().setData(index, value)

    def copyValueToSelected(self, indexAt):
        """Copy the value under the cursor to the selected indexes."""
        indexes = self.selectionModel().selectedIndexes()
        for index in indexes:
            if index == indexAt:
                continue
            elif index.column() == indexAt.column():
                value = self.model().data(indexAt)
                self.model().setData(index, value)
