# -*- coding: utf-8 -*-
"""
/***************************************************************************
                              EO Time Series Viewer
                              -------------------
        begin                : 2017-08-04
        git sha              : $Format:%H$
        copyright            : (C) 2017 by HU-Berlin
        email                : benjamin.jakimow@geo.hu-berlin.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
# noinspection PyPep8Naming

import sys, os, re
from qgis.core import *
from collections import OrderedDict
from qgis.gui import QgsDockWidget
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *

import numpy as np
from eotimeseriesviewer import DIR_UI
from eotimeseriesviewer.utils import loadUi, SpatialExtent

PSUTIL_AVAILABLE = False
try:
    import psutil
    PSUTIL_AVAILABLE = True
except:
    pass

def value2str(args, separator = ''):
    return str(args)

class MapLayerRegistryModel(QAbstractTableModel):

        class LayerWrapper(object):
            def __init__(self, lyr):
                assert isinstance(lyr, QgsMapLayer)
                self.lyr = lyr

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

            self.cID = '#'
            self.cPID = 'PID'
            self.cName = 'Name'
            self.cSrc = 'Uri'
            self.cType= 'Type'

            self.mLayers = list()
            self.REG = QgsProject.instance()
            self.REG.layersAdded.connect(self.addLayers)
            self.REG.layersWillBeRemoved.connect(self.removeLayers)
            self.addLayers(self.REG.mapLayers().values())


            s = ""

        def addLayers(self, lyrs):

            lyrs = [l for l in lyrs if isinstance(l, QgsMapLayer)]

            l = len(lyrs)

            if l > 0:
                i = len(self.mLayers)
                self.beginInsertRows(QModelIndex(),i, i+l)
                self.mLayers.extend(lyrs)
                self.endInsertRows()

        #@pyqtSlot(list)
        def removeLayers(self, lyrNames):


            to_remove = [self.REG.mapLayer(name) for name in lyrNames]

            for l in to_remove:
                if l in self.mLayers:
                    i = self.mLayers.index(l)
                    self.beginRemoveRows(QModelIndex(),i,i)
                    self.mLayers.remove(l)
                    self.endRemoveRows()
            #self.reset()

        def columnNames(self):
            return [self.cID, self.cPID, self.cName, self.cType, self.cSrc]

        def headerData(self, col, orientation, role):
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                return self.columnNames()[col]
            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
                return col
            return None

        def sort(self, col, order):
            """Sort table by given column number.
            """
            self.layoutAboutToBeChanged.emit()
            columnName = self.columnNames()[col]
            rev = order == Qt.DescendingOrder
            sortedLyers = None

            if columnName == self.cName:
                sortedLyers = sorted(self.mLayers, key=lambda l: l.name(), reverse=rev)
            elif columnName == self.cSrc:
                sortedLyers = sorted(self.mLayers, key=lambda l: l.source(), reverse=rev)
            elif columnName == self.cID:
                lyrs = self.REG.mapLayers().values()
                sortedLyers = sorted(self.mLayers, key=lambda l: lyrs.index(l), reverse=rev)
            elif columnName == self.cPID:
                sortedLyers = sorted(self.mLayers, key=lambda l: id(l), reverse=rev)
            elif columnName == self.cType:
                types = [QgsVectorLayer, QgsRasterLayer]
                sortedLyers = sorted(self.mLayers, key=lambda l: types.index(type(l)), reverse=rev)

            del self.mLayers[:]
            self.mLayers.extend(sortedLyers)
            self.layoutChanged.emit()

        def rowCount(self, parentIdx=None, *args, **kwargs):
            return len(self.mLayers)

        def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
            return len(self.columnNames())

        def lyr2idx(self, lyr):
            assert isinstance(lyr, QgsMapLayer)
            # return self.createIndex(self.mSpecLib.index(profile), 0)
            # pw = self.mProfileWrappers[profile]
            if not lyr in self.mLayers:
                return None
            return self.createIndex(self.mLayers.index(lyr), 0)

        def idx2lyr(self, index):
            assert isinstance(index, QModelIndex)
            if not index.isValid():
                return None
            return self.mLayers[index.row()]


        def idx2lyrs(self, indices):
            lyrs = [self.idx2lyr(i) for i in indices]
            return [l for l in lyrs if isinstance(l, QgsMapLayer)]

        def data(self, index, role=Qt.DisplayRole):
            if role is None or not index.isValid():
                return None

            columnName = self.columnNames()[index.column()]
            lyr = self.idx2lyr(index)
            value = None
            assert isinstance(lyr, QgsMapLayer)
            if role == Qt.DisplayRole:
                if columnName == self.cPID:
                    value = id(lyr)
                elif columnName == self.cID:
                    value = list(self.REG.mapLayers().values()).index(lyr)
                elif columnName == self.cName:
                    value = lyr.name()
                elif columnName == self.cSrc:
                    value = lyr.source()
                elif columnName in self.cType:
                    value = re.sub('[\'<>]','',str(type(lyr))).split('.')[-1]

            if role == Qt.UserRole:
                value = lyr

            return value

        def flags(self, index):
            if index.isValid():
                columnName = self.columnNames()[index.column()]
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
                return flags
            return None



class DataLoadingModel(QAbstractTableModel):

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

            self.cName = 'Type'
            self.cSamples = 'n'
            self.cAvgAll = u'mean \u0394t(all) [ms]'
            self.cMaxAll = u'max \u0394t(all) [ms]'
            self.cAvg10 = u'mean \u0394t(10) [ms]'
            self.cMax10 = u'max \u0394t(10) [ms]'
            self.cLast = u'last \u0394t [ms]'

            self.mCacheSize = 500
            self.mLoadingTimes = OrderedDict()

        def addTimeDelta(self, name, timedelta):
            assert isinstance(timedelta, np.timedelta64)
            #if timedelta.astype(float) > 0:
            #print(timedelta)
            if name not in self.mLoadingTimes.keys():
                self.mLoadingTimes[name] = []
            to_remove = max(0,len(self.mLoadingTimes[name]) + 1 - self.mCacheSize)
            if to_remove > 0:
                del self.mLoadingTimes[name][0:to_remove]
            self.mLoadingTimes[name].append(timedelta)

            self.layoutChanged.emit()

        def variableNames(self):
            return [self.cName, self.cSamples, self.cLast, self.cMaxAll, self.cAvgAll, self.cMax10, self.cAvg10]

        def headerData(self, col, orientation, role):
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                return self.variableNames()[col]
            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
                return col
            return None

        def sort(self, col, order):
            """Sort table by given column number.
            """
            self.layoutAboutToBeChanged.emit()
            columnName = self.variableNames()[col]
            rev = order == Qt.DescendingOrder
            sortedNames = None
            if columnName == self.cName:
                sortedNames = sorted(self.mLoadingTimes.keys(), reverse=rev)
            elif columnName == self.cSamples:
                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda n: len(self.mLoadingTimes[n]), reverse=rev)
            elif columnName == self.cAvgAll:
                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
                    np.asarray(self.mLoadingTimes[name]).mean(), reverse=rev)
            elif columnName == self.cAvg10:
                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
                    np.asarray(self.mLoadingTimes[name][-10:]).mean(), reverse=rev)

            if sortedNames is not None:
                tmp = OrderedDict([(name, self.mLoadingTimes[name]) for name in sortedNames])
                self.mLoadingTimes.clear()
                self.mLoadingTimes.update(tmp)
                self.layoutChanged.emit()

        def rowCount(self, parentIdx=None, *args, **kwargs):
            return len(self.mLoadingTimes)

        def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
            return len(self.variableNames())

        def type2idx(self, type):
            assert isinstance(type, str)
            if type not in self.mLoadingTimes.keys():
                return None
            return self.createIndex(self.mLoadingTimes.keys().index(type), 0)

        def idx2type(self, index):
            assert isinstance(index, QModelIndex)
            if not index.isValid():
                return None
            return list(self.mLoadingTimes.keys())[index.row()]

        def data(self, index, role=Qt.DisplayRole):
            if role is None or not index.isValid():
                return None

            columnName = self.variableNames()[index.column()]
            name = self.idx2type(index)
            lTimes = self.mLoadingTimes[name]
            value = None
            if role in [Qt.DisplayRole, Qt.EditRole]:
                if columnName == self.cName:
                    value = name
                elif columnName == self.cSamples:
                    value = len(lTimes)

                if len(lTimes) > 0:
                    if columnName == self.cAvg10:
                        value = float(np.asarray(lTimes[-10:]).mean().astype(float))
                    elif columnName == self.cAvgAll:
                        value = float(np.asarray(lTimes[:]).mean().astype(float))
                    elif columnName == self.cMax10:
                        value = float(np.asarray(lTimes[-10:]).max().astype(float))
                    elif columnName == self.cMaxAll:
                        value = float(np.asarray(lTimes[:]).max().astype(float))
                    elif columnName == self.cLast:
                        value = float(lTimes[-1].astype(float))

            if role == Qt.UserRole:
                value = lTimes

            return value

        def flags(self, index):
            if index.isValid():
                columnName = self.variableNames()[index.column()]
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
                return flags
            return None



class SystemInfoDock(QgsDockWidget):


    def __init__(self, parent=None):
        super(SystemInfoDock, self).__init__(parent)
        loadUi(DIR_UI / 'systeminfo.ui', self)

        self.lyrModel = MapLayerRegistryModel()
        self.tableViewMapLayerRegistry.setModel(self.lyrModel)
        self.tableViewMapLayerRegistry.contextMenuEvent = \
            lambda event: self.contextMenuEvent(self.tableViewMapLayerRegistry, event)

        self.dataLoadingModel = DataLoadingModel()

        def resetModel():
            self.dataLoadingModel.mLoadingTimes.clear()
            self.dataLoadingModel.layoutChanged.emit()

        self.tableViewDataLoading.setModel(self.dataLoadingModel)
        self.tableViewDataLoading.contextMenuEvent = \
            lambda event: self.contextMenuEvent(self.tableViewDataLoading, event)

        self.btnResetDataLoadingModel.clicked.connect(resetModel)

        self.labelPSUTIL.setVisible(PSUTIL_AVAILABLE == False)
        if PSUTIL_AVAILABLE:
            self.tableViewSystemParameters.setVisible(True)
            #self.systemInfoModel = SystemInfoModel()
            #self.tableViewSystemParameters.setModel(self.systemInfoModel)
        else:
            self.systemInfoModel = None

    def addTimeDelta(self, type, timedelta):
        self.dataLoadingModel.addTimeDelta(type, timedelta)


    def contextMenuEvent(self, tableView, event):
        assert isinstance(tableView, QTableView)
        menu = QMenu(self)
        a = menu.addAction("Copy selected")
        a.triggered.connect(lambda :self.onCopy2Clipboard(tableView, 'SELECTED', separator=';'))

        a = menu.addAction("Copy table")
        a.triggered.connect(lambda: self.onCopy2Clipboard(tableView, 'TABLE', separator=';'))

        a = menu.addAction('Save to file')
        a.triggered.connect(lambda : self.onSaveToFile(tableView, 'TABLE'))


        menu.popup(QCursor.pos())

    def onCopy2Clipboard(self, tableView, key, separator='\t'):
        lines = self.readTableValues(key, tableView)
        if len(lines) > 0:
            lines = [value2str(l, sep=separator) for l in lines]
            QApplication.clipboard().setText('\n'.join(lines))

    def readTableValues(self, key, tableView):
        assert key in ['SELECTED', 'TABLE']
        txt = None
        model = tableView.model()
        assert isinstance(model, QAbstractTableModel)
        lines = []
        if key == 'SELECTED':
            line = []
            row = None
            for idx in tableView.selectionModel().selectedIndexes():
                if row is None:
                    row = idx.row()
                elif row != idx.row():
                    lines.append(line)
                    line = []
                line.append(model.data(idx, role=Qt.DisplayRole))
            lines.append(line)
        elif key == 'TABLE':
            for row in range(model.rowCount()):
                line = []
                for col in range(model.columnCount()):
                    idx = model.createIndex(row, col)
                    line.append(model.data(idx, role=Qt.DisplayRole))
            lines.append(line)
        return lines

    def onSaveToFile(self, tableView, key):
        lines = self.readTableValues(key, tableView)


        if len(lines) > 0:
            filters = 'Textfile (*.txt);;CSV Table (*.csv)'
            path = QFileDialog.getSaveFileName(parent=None, caption="Save Table to file",
                                               directory='', filter=filters)
            if len(path) > 0:
                ext = os.path.splitext(path)[-1].lower()
                if ext in ['.txt']:
                    lines = [value2str(l, sep=';') for l in lines]
                else:
                    lines = [value2str(l, sep='\t') for l in lines]
                file = open(path, 'w')
                for line in lines:
                    file.write(line + '\n')
                file.flush()
                file.close()
