#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Thu Feb  2 08:33:53 2017

@author: anne
"""
qt = 5
# Here we provide the necessary imports.
# The basic GUI widgets are located in QtGui module. 
import sys, h5py
import numpy as np
if qt==4:
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt4 import NavigationToolbar2QT as NavigationToolbar
else:
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import QIcon,QDoubleValidator
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar

from matplotlib.figure import Figure

import time
from matplotlib.animation import FuncAnimation
from gui.about_dialog import Ui_about_dialog

scale = [('nano',-9,'n'),
         ('micro',-6,'$\mu$'),
         ('milli:',-3,'m'),
         ('centi',-2,'c'),
         ('deci',-1,'d'),
         ('-',0,''),
         ('deca',1,'da'),
         ('hecto',2,'h'),
         ('kilo',3,'k'),
         ('mega',6,'M'),
         ('giga',9,'G'),
         ('tera',12,'T')]

appname = 'TaSMET Postprocessor'
def getGuiSettings():
    return QSettings(appname,appname)

 
class MaxCounter(object):
    def __init__(self,max=-1):
        self.a = 0
        self.max = max            
    def __call__(self): 
        rval = self.a
        self.a += 1
        if self.a == self.max+1:
            self.a = 0
        return rval

class StepCounter(object):
    def __init__(self,step=1):
        self.a = 0
        self.step = step
        self.inner = 0
        
    def __call__(self): 
        rval = self.a
        
        self.inner += 1
        
        if self.inner == self.step:
            self.inner = 0
            self.a += 1
     
        return rval    

class TaSMETFigure(Figure):
    
    def __init__(self, width=5, height=4, dpi=100):
        
        Figure.__init__(self, figsize=(width, height), dpi=dpi)
        
        self._axes = self.add_subplot(111)

        # We want the axes cleared every time plot() is called
        
        self._plots = []
        self._grid = False
    def setCanvas(self,canvas):
        self._canvas = canvas

    def addPlot(self,plot,one):
        if one:
            self._plots = [plot]
        else:
            self._plots.append(plot)
        
        self.rePlot()
        
    def rePlot(self):
        self._axes.clear()
        for plot in self._plots:
            self._axes.plot(plot['x'],plot['y'],label=plot['ylabel'])
        
            self._axes.set_ylabel(plot['ylabel'])
            self._axes.set_xlabel(plot['xlabel'])
            
        self._axes.grid(self._grid,'both')
        
        self.canvas.draw()
        
    def setGrid(self,grid):
        self._grid = grid
        self.rePlot()
        
    def clearLastPlot(self):
        if len(self._plots) > 0:
            del self._plots[-1]
        self.rePlot()
    def clearAllPlot(self):
        self._plots = []
        self.rePlot()

    def animate(self, data):
        line = self._axes.get_lines()[0]
        
        max_val = np.max(data)
        min_val = np.min(data)
        
        self._axes.set_ylim((min_val,max_val))
        
        # Number of frames available in a period
        frames_per_period = data.shape[1]

        # Time to pass to show one period
        time_per_period = 1.0
        
        # interval in seconds
        interval = time_per_period/frames_per_period
        
        # We need a max, here because due to a bug intervals lower than 200
        # are going plain wrong in matplotlib
        interval_ms = max(int(interval*1000),200)

        def animate_i(i):
            line.set_ydata(data[:,i%frames_per_period])
            return line,
        
        ani = FuncAnimation(self, 
                            animate_i,
                            interval = max(200,interval_ms),
                            frames = frames_per_period,
                            blit=True,
                            repeat = True,
                            save_count = 300 # High value here as animate_i repeats itself
                            )
        self.canvas.draw()        
#        for i in range(100):
#            print(i)
#            animate_i(i)
#        
class TaSMETCanvas(FigureCanvas):

    def __init__(self, parent, figure):
        
        FigureCanvas.__init__(self, figure)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        
        FigureCanvas.updateGeometry(self)
        figure.setCanvas(self)

class MainWindow(QMainWindow):
    
    def __init__(self, file_):
        QMainWindow.__init__(self)

        # Open the postprocessing file
        try:
            self._file = h5py.File(file_,'r')
        except:
            QMessageBox.warning(self,
            'Error opening file',
            'File %s is not a valid TaSMET Postprocessing file. Closing.' %file_)
            exit(1)

        # Suppress triggering of the GUI changed() callbacks
        self._suppressed = True

        self.setWindowTitle(appname)
        
        # Window icon
        self.setWindowIcon(QIcon('images/tasmet_small.png'))        
        
        self._figure = TaSMETFigure()
        self._canvas= TaSMETCanvas(self,figure=self._figure)        

        self._seg_box = QComboBox()
        self._seg_box.currentIndexChanged.connect(self.segItemChanged)
        
        self._qty_box = QComboBox()
        self._qty_box.currentIndexChanged.connect(self.qtyItemChanged)
        
        
        # Horizontal offset
        self._hor_offset = QLineEdit()
        self._hor_offset.setAlignment(Qt.AlignRight)
        self._hor_offset.setText('0.0')
        self._hor_offset.setMinimumWidth(40)

        # Vertical offset
        self._ver_offset = QLineEdit()
        self._ver_offset.setAlignment(Qt.AlignRight)
        self._ver_offset.setText('0.0')
        self._ver_offset.setMinimumWidth(40)
                             
        offset_validator = QDoubleValidator()
        self._hor_offset.setValidator(offset_validator)
        self._ver_offset.setValidator(offset_validator)
        
        
        self._scale = QComboBox()
        for val in scale:
            self._scale.addItem(val[0] + ' (%0.0e)' %(10**val[1]))
        self._scale.setCurrentIndex(5)
        
        self._hold_button = QPushButton('Hold on')
        self._hold_button.setCheckable(True)
        
        self._vstime = QRadioButton('Plot vs time')
        self._vstime.clicked.connect(self.qtyItemChanged)
        
        self._vspos = QRadioButton('Plot vs position')
        self._vspos.clicked.connect(self.qtyItemChanged)
        
        self._vspos.setChecked(True)
        self._vs_instance = QSpinBox()
        
        self._plot_button = QPushButton('Plot')
        self._plot_button.clicked.connect(self.plot)

        self._output_button = QPushButton('Output')
        self._output_button.clicked.connect(self.output)
        
        self._animate_button = QPushButton('Animate')
        self._animate_button.clicked.connect(self.animate)
                
        
        top = QGridLayout()


                
        col = StepCounter(step=2)
        row = MaxCounter(max=1)
        
        top.addWidget(QLabel('Segment:'),row(),col())
        top.addWidget(QLabel('Quantity:'),row(),col())
        top.addWidget(self._seg_box,row(),col())
        top.addWidget(self._qty_box,row(),col())
        
        top.addWidget(QLabel('Horizontal offset'),row(),col())
        top.addWidget(QLabel('Vertical offset'),row(),col())

        top.addWidget(self._hor_offset,row(),col())
        top.addWidget(self._ver_offset,row(),col())
              
        top.addWidget(QLabel('Scale:'),row(),col())
        row(); col()
        top.addWidget(self._scale,row(),col())
        row(); col()

        radiobox = QHBoxLayout()
        radiobox.addWidget(self._vstime)
        radiobox.addWidget(self._vspos)

        top.addLayout(radiobox,row(),col())
        
        instance_box = QHBoxLayout()
        instance_box.addWidget(QLabel('Time/position instance:'))
        instance_box.addWidget(self._vs_instance)
        
        top.addLayout(instance_box,row(),col())
        
        top.addWidget(self._plot_button,row(),col())
        top.addWidget(self._hold_button,row(),col())
        
        top.addWidget(self._output_button,row(),col())
        top.addWidget(self._animate_button,row(),col())
        
        htop = QHBoxLayout()
        htop.addLayout(top)
        htop.addStretch()

        vlayout = QVBoxLayout()
        vlayout.addLayout(htop)
        navbar = NavigationToolbar(self._canvas, self)
        
        vlayout.addWidget(navbar)
        vlayout.addWidget(self._canvas)
        
        hlayout = QHBoxLayout()
        
        output_layout = QVBoxLayout()        
        output_layout.addWidget(QLabel('Output'))
        
        self._output = QPlainTextEdit()
        self._output.setFixedWidth(300)
        
        self._output.setStyleSheet('font-family: monospace; background-color: white; color: black;')
        
        output_layout.addWidget(self._output)
        
        hlayout.addLayout(vlayout)        
        hlayout.addLayout(output_layout)

        # Put stuff in a widget and put it in the main window
        widget = QWidget()        
        widget.setLayout(hlayout)
        self.setCentralWidget(widget)
    
        # File menu
        file_menu = QMenu('&File', self)
        file_menu.addAction('&Open',self.loadFile)
        file_menu.addAction('&Exit',self.close)        

        # Plot menu
        plot_menu = QMenu('&Plot', self)
        self._grid_option = QAction('Enable grid',plot_menu)
        self._grid_option.setCheckable(True)
        self._grid_option.triggered.connect(self._figure.setGrid)
        plot_menu.addAction(self._grid_option)
        plot_menu.addAction('&Delete last line',self._figure.clearLastPlot)
        plot_menu.addAction('&Delete all lines',self._figure.clearAllPlot)

        # Options menu
        options_menu = QMenu('&Options', self)
        options_menu.addAction('Clear output',self.clearOutput)
        options_menu.addAction('Default output',self.defaultOutput)
        
        # Help menu
        help_menu = QMenu('&Help', self)
        help_menu.addAction('&About', self.about)
        
        # Add menus to the menubar        
        self.menuBar().addMenu(file_menu)
        self.menuBar().addMenu(plot_menu)
        self.menuBar().addMenu(options_menu)
        self.menuBar().addMenu(help_menu)

        # Set window sizes
        settings = getGuiSettings()
        if settings.value('Geometry') is not None:
            self.restoreGeometry(settings.value("Geometry"))
        
        self.initGui()
        self._suppressed = False
    
    def clearOutput(self):
        self._output.setPlainText('')
    
    def defaultOutput(self):
        txt = ''
        nsegments = 0
        for x in self._file:
            if isinstance(self._file[x], h5py.Group):
                nsegments+=1
        basestr = '{:22}: {:>12} [{:2}]\n'
        txt += basestr.format('Number of segments',nsegments,'-')
        txt += basestr.format('Fundamental frequency',self._file.attrs['freq'][0],'Hz')
        txt += basestr.format('Numer of time samples',self._file.attrs['Ns'][0],'-')
        txt += basestr.format('Reference temperature',self._file.attrs['T0'][0],'K')
        txt += basestr.format('Reference pressure',self._file.attrs['p0'][0],'Pa')
        txt += basestr.format('Gas type',u''+self._file.attrs['gas'].decode('utf-8'),'-')
        tinst = self._file.attrs['t']
        txt += '\nTime instances:\n'
        for i in range(tinst.shape[0]):
            txt += '{:2}: {:2e}\n'.format(i,tinst[i])
            
        
        
        self._output.setPlainText(txt)
    
    def output(self):
        print('output')
    
    def disableAll(self):
        self._seg_box.setEnabled(False)
        self._qty_box.setEnabled(False)
        self._vspos.setEnabled(False)
        self._vstime.setEnabled(False)
        self._vs_instance.setEnabled(False)
        self._plot_button.setEnabled(False)
        self._output_button.setEnabled(False)
        self._animate_button.setEnabled(False)        
    
    def animate(self):
        self.disableAll()
        self._hold_button.setChecked(False)
        self.plot()
        try:
            data,datatype = self.getCurrentData()
        except:
            return
        
        self._figure.animate(data[:,:])
        
        self.qtyItemChanged()
    
    def getCurrentData(self):
        """
        Returns the current datatype, None if invalid or not selected        
        """
        segitemno = self._seg_box.currentText()
        itemtxt = self._qty_box.currentText()
        if len(segitemno) == 0:
            return None
        if len(itemtxt)==0:
            return None
        else:
            data = self._file[segitemno][itemtxt]
            datatype = data.attrs['datatype'].decode('utf-8')
            return data,datatype
        
   
    def segItemChanged(self,item=None):
        """
        Update the combobox with quantities
        """
        
        self._suppressed = True
        self._qty_box.clear()
        one = False
        for dataset in self._file[str(item)]:
            self._qty_box.addItem(dataset)
            one = True
        
        # Not at least one plottable item in the list of the selected segment
        self._plot_button.setEnabled(one)
        self._output_button.setEnabled(one)
            
        self._suppressed = False
        self._qty_box.setCurrentIndex(0)
        self.qtyItemChanged()
    
    def qtyItemChanged(self,item=None):
        """
        The quantity item has been changed
        """
        if self._suppressed: return

        try:
            data,datatype = self.getCurrentData()
        except:
            data = None
        
        segitemno = self._seg_box.currentText()
        
        self.disableAll()
        
        self._seg_box.setEnabled(True)
        self._qty_box.setEnabled(True)
        
        if data is None:
            return

                
        self._plot_button.setEnabled(True)
        self._output_button.setEnabled(True)
        
        
        if datatype == 'time':
            self._vstime.setChecked(True)
            self._vspos.setEnabled(False)
            
        elif datatype == 'pos':
            self._vspos.setChecked(True)
            self._vspos.setEnabled(False)
            self._vstime.setEnabled(False)
            self._vs_instance.setEnabled(False)
            self._animate_button.setEnabled(False)
            
        elif datatype == 'postime':
            vspos = self._vspos.isChecked()
            if vspos:
                t = self._file.attrs['t']
                self._vs_instance.setRange(0,len(t)-1)
            else:
                x = self._file[segitemno]['x']                
                self._vs_instance.setRange(0,len(x)-1)
            self._vs_instance.setValue(0)
            
            self._vspos.setEnabled(True)
            self._vstime.setEnabled(True)
            self._vs_instance.setEnabled(True)
            self._animate_button.setEnabled(True)
        
    def initGui(self):
        self.defaultOutput()
        self._suppressed = True
        for x in self._file:
            if isinstance(self._file[x], h5py.Group):
                self._seg_box.addItem(x)
            else:
                print(self._file[x])
        self._suppressed = False
        self.segItemChanged(0)

    def loadFile(self, file=None):
        pass

    def getx(self):
        """
        Returns the x-axis for the current data selected. Returns None if invalid.
        Returns a tuple containing: x-values, x-legend
        """
        try:
            data,datatype = self.getCurrentData()
        except:
            return None
        
        segitemno = self._seg_box.currentText()
        vspos = self._vspos.isChecked()
        
        x = None
        if datatype == 'time' or (datatype == 'postime' and not vspos):
            x = self._file.attrs['t']
            xsymbol = 't'
            xunit = 's'
        elif datatype == 'pos' or (datatype == 'postime' and vspos):
            xset = self._file[segitemno]['x']
            x = xset[:]
            xsymbol = xset.attrs['symbol'].decode('utf-8')
            xunit = xset.attrs['unit'].decode('utf-8')
        
        xlabel = xsymbol + ' [' + xunit + ']'
        
        return x,xlabel
        
    def gety(self):
        """
        Returns the y-axis for the current data selected. Returns None if invalid.
        Returns a tuple containing x-values, x-legend
        """
        try:
            data,datatype = self.getCurrentData()
        except:
            return None
        
        symbol = data.attrs['symbol'].decode('utf-8')
        unit = data.attrs['unit'].decode('utf-8')
        
        vspos = self._vspos.isChecked()
                                                       
        ylabel = symbol + ' [' + unit + ']'
                            
        if datatype == 'time' or datatype == 'pos':
            y = data[:]
        elif datatype == 'postime':
            data_inst  = self._vs_instance.value()
            if self._vspos.isChecked():
                y = data[:,data_inst]
            else:
                y = data[data_inst,:]
                
        return y,ylabel
        
    
    def plot(self):

        plot = {}

        
        hor_offset = float(self._hor_offset.text())
        ver_offset = float(self._ver_offset.text())
        try:
            x,xlabel = self.getx()
        except TypeError:
            return
        
        try:
            y,ylabel = self.gety()
        except TypeError:
            return
        
        plot['x'] = x+hor_offset
        plot['xlabel'] = xlabel
            
        thescale = scale[self._scale.currentIndex()]
        scaleval = thescale[1]
        scaleylabel = '' if scaleval == 0 else ' x $10^{%i}$' %scaleval
        
        plot['y'] = (y+ver_offset)/(10**scaleval)
        plot['ylabel'] = ylabel + ' ' + scaleylabel
        
        self._figure.addPlot(plot, not self._hold_button.isChecked())
        
    def about(self):
        dialog = QDialog(self)
        about_dialog = Ui_about_dialog()
        about_dialog.setupUi(dialog)
        dialog.exec_()
        
    def closeEvent(self,event):
        settings = getGuiSettings()
        settings.setValue("Geometry", self.saveGeometry())
        
        event.accept()

def usage():
    print("Usage:")
    if hasattr(sys,'frozen'):
        pass
    else:
        print('python ' + sys.argv[0] + ' <file>.tasmet.h5')

if __name__ == '__main__':
    if(len(sys.argv) < 2):
        usage()
        exit(-1)
    else:
        file_ = sys.argv[1]
        
    # Every PyQt4 application must create an application object.
    # The application object is located in the QtGui module.
#    try:
#        app = QApplication.instance()
#    except:
    app = QApplication(sys.argv)
    
    # The QWidget widget is the base class of all user interface objects in PyQt4.
    # We provide the default constructor for QWidget. The default constructor has no parent.
    # A widget with no parent is called a window. 
#    w = MainWindow(sys.argv[1])
    w = MainWindow(file_)    
    w.resize(640, 480)  # The resize() method resizes the widget.
    
    w.show()  # The show() method displays the widget on the screen.
    
    sys.exit(app.exec_())  # Finally, we enter the mainloop of the application.
