#!/usr/bin/env python 
# encoding: utf-8

"""
A set of utilities to use spike in NMR or FTMS within jupyter


First version MAD june 2017
preliminary and not fully tested !

"""

from __future__ import print_function, division
import unittest
import sys
import os
import os.path as op
import glob

import matplotlib.pylab as plt
import matplotlib.gridspec as gridspec
from ipywidgets import interact, interactive, fixed, interact_manual, Layout, HBox, VBox, Label, Output, Button
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
import numpy as np

from ..File.BrukerNMR import Import_1D
from .. import NPKData
try:
    import spike.plugins.bcorr as bcorr
except:
    print('Baseline correction plugins not installed !')

# REACTIVE modify callback behaviour
# True is good for inline mode / False is better for notebook mode
REACTIVE = True
HEAVY = False

def hidecode(initial='show', message=True):
    """
    this func adds a button to hide/show the code on a jupyter page
    initial is either 'show' or 'hide'
    see: https://stackoverflow.com/questions/27934885/how-to-hide-code-from-cells-in-ipython-notebook-visualized-with-nbviewer/28073228#28073228
    """
    from IPython.display import display, HTML, Markdown
    if initial == 'show':
        init = 'false'
    elif initial == 'hide':
        init = 'true'
    if message:
        msg = "<i>usefull to show/print a clean screen when processing is finished</i>"
    else:
        msg = ""
    display(HTML('''
<style>hr {height: 2px; border: 0;border-top: 1px solid #ccc;margin: 1em 0;padding: 0; }</style>
<script>
code_show=%s; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()">
<input type="submit" style="border:1px solid black; background-color:#DDD" value="hide/show the python code.">
%s
</form>'''%(init, msg)))

def jsalert(msg):
    "send a javascript alert"
    display(Javascript("alert('%s')"%msg))

class FileChooser:
    """a simple file chooser for Jupyter - obsolete - not used"""
    def __init__(self, base=None, filetype=['fid','ser'], mode='r', show=True):
        if base is None:
            self.curdir = "/"
        else:
            self.curdir = base
        self.filetype = filetype
        self.mode = mode
        self.wfile = widgets.Text(layout=Layout(width='70%'),description='File to load')
        self.ldir = widgets.Label(value="Chosen dir:  "+self.curdir)
        self.wdir = widgets.Select(
            options=self.dirlist(),
            description='Choose Dir',
            layout=Layout(width='70%'))
        if mode=='r':
            self.wchooser = widgets.Select(
                options=self.filelist(),
                description='Choose File',
                layout=Layout(width='70%'))
            self.wchooser.observe(self.wob)
            self.wfile.disabled=True   # not mofiable in read mode
        elif mode=="w":
            self.wfile.description = 'File to create'
            self.wfile.disabled=False
            self.wchooser = widgets.Select(
                options=self.filelist(),
                description='Files',
                layout=Layout(width='70%'))
        else:
            raise Exception('Only "r" and  "w" modes supported')
        self.wsetdir = Button(description='❯',layout=Layout(width='20%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='descend in directory')
        self.wup = Button(description='❮',layout=Layout(width='20%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='up to previous directory')
        self.wsetdir.on_click(self.setdir)
        self.wup.on_click(self.updir)
        if show: self.show()
    def filelist(self):
        fl = []
        if self.mode == 'r':
            if type(self.filetype) is str:
                fl = glob.glob(op.join(self.curdir,self.filetype))
            elif type(self.filetype) in (tuple, list):
                for f in self.filetype:
                    fl += glob.glob(op.join(self.curdir,f))
            else:
                raise Exception('TypeError, filetype should be either a string or a list')
        else:   # 'w'
            fl = [f for f in glob.glob(op.join(self.curdir,'*')) if op.isfile(f)]
            self.wfile.value = op.join(self.curdir,self.filetype)
        if fl == []:
            fl = [" "]
        return fl
    def dirlist(self):
        base = self.curdir
        return [d for d in glob.glob(op.join(base,'*')) if (op.isdir(d) and not d.endswith('__pycache__'))]
    def wob(self, e):
        self.wfile.value = self.wchooser.value
    def updir(self, e):
        self.curdir = op.dirname(self.curdir)
        self.ldir.value = "Chosen dir:  "+self.curdir
        self.wdir.options = self.dirlist()
        self.wchooser.options = self.filelist()
    def setdir(self, e):
        self.curdir = self.wdir.value
        self.ldir.value = "Chosen dir:  "+self.curdir
        self.wdir.options = self.dirlist()
        self.wchooser.options = self.filelist()
    def show(self):
        display(VBox(
            [   self.ldir,
            HBox( [self.wdir, VBox([self.wup,self.wsetdir])] ),    
                self.wchooser,
                self.wfile
            ])
        )
        return self
    @property
    def file(self):
        "the chosen complete filename"
        return self.wfile.value
    @property
    def dirname(self):
        "the final dirname containing the chosen file"
        if op.isdir(self.wfile.value):
            return op.basename(self.wfile.value)
        else:
            return op.basename(op.dirname(self.wfile.value))
    @property
    def nmrname(self):
        "the final dirname containing the chosen file for TopSpin files"
        if op.isdir(self.wfile.value):
            return op.join(
                op.basename(op.dirname(self.wfile.value)), self.dirname)
        else:
            return op.join(
                op.basename(op.dirname(op.dirname(self.wfile.value))), self.dirname)
    @property
    def basename(self):
        "the basename of the chosen file"
        return op.basename(self.wfile.value)

class Show1D(object):
    """
    An interactive display, 1D NMR
        Show1D(spectrum)
    to be developped for peaks and integrals
    """
    def __init__(self, data, title=None, figsize=None):
        self.data = data
        self.title = title
        if figsize is None:
            figsize = (10,5)
        self.done = Button(description="Done", button_style='success',layout=Layout(width='10%'))
        self.scale = widgets.FloatSlider(description='scale:', value=1.0, min=1.0, max=100, step=0.1,
                            layout=Layout(width='50%'), continuous_update=REACTIVE)
        for widg in (self.scale,):
            widg.observe(self.ob)
        fi,ax = plt.subplots(figsize=figsize)
        self.ax = ax
        self.data.display(figure=self.ax, title=self.title)
        self.xb = self.ax.get_xbound()
        self.done.on_click(self.on_done)
        self.Box = HBox([self.scale, self.done])
        display( self.Box )
    def on_done(self, b):
        self.close()
    def close(self):
        for w in [ self.Box, self.done, self.scale]:
            w.close()
    def ob(self, event):
        "observe events and display"
        if event['name']=='value':
            self.disp()
    def disp(self):
        self.xb = self.ax.get_xbound()
        self.ax.clear()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title)
        self.ax.set_xbound(self.xb)

class baseline1D(Show1D):
    def __init__(self, data, figsize=None):
        super().__init__( data, figsize=figsize)
        self.data.real()
        ppos = self.data.axis1.itop(self.data.axis1.size/2)
        #self.ax.plot([ppos,ppos], self.ax.get_ybound())
        self.select = widgets.FloatSlider(description='select:', min=0.0, max=1.0, step=0.002, layout=Layout(width='100%'),
                                         value=0.5, readout=False, continuous_update=REACTIVE)
        self.selfine = widgets.FloatSlider(description='fine:', min=0.0, max=1.0, step=0.01, layout=Layout(width='70%'),
                                         value=0.5, readout=False, continuous_update=REACTIVE)
        self.bsl_points = []
        for w in [self.select, self.selfine]:
            w.observe(self.ob)
        bsize = '15%'
#        self.apply = widgets.Button(description="Apply", button_style='success', layout=Layout(width=2*bsize))
#        self.apply.on_click(self.on_apply)
        self.done.description = 'Apply'
        self.cancel = widgets.Button(description="Abort", button_style='warning', layout=Layout(width=2*bsize))
        self.cancel.on_click(self.on_cancel)
        self.Box.add_class("hidden")
        self.auto = widgets.Button(description="auto", button_style='success', layout=Layout(width=bsize))
        self.auto.on_click(self.on_auto)
        self.set = widgets.Button(description="add", button_style='success', layout=Layout(width=bsize))
        self.set.on_click(self.on_set)
        self.unset = widgets.Button(description="rem", button_style='warning', layout=Layout(width=bsize))
        self.unset.on_click(self.on_unset)
        self.toshow = widgets.Dropdown( options=['baseline', 'corrected', 'points'],  description='Display:')
        self.toshow.observe(self.ob)
        self.Box = HBox([self.scale, self.done,self.cancel])
        display(self.Box)
        display( HBox( [
                        VBox([self.select, self.selfine], layout=Layout(width='50%')),
                        VBox([  HBox([self.set, self.auto, self.toshow], layout=Layout(width='100%')),
                                self.unset
                              ], layout=Layout(width='50%')) 
                        ], layout=Layout(width='100%'))
                )
        self.disp()
    def close(self):
        for w in [ self.select, self.selfine, self.auto, self.set, self.unset, self.cancel, self.toshow]:
            w.close()
        super().close()
    def on_done(self, e):
        print('Applied correction:\n',self.bsl_points)
        self.data.set_buffer( self.data.get_buffer() - self.correction() )
        super().disp()
        self.close()
    def on_auto(self, e):
        "automatically set baseline points"
        self.bsl_points = [self.data.axis1.itop(x) for x in bcorr.autopoints(self.data)]
        self.disp()
    def on_cancel(self, e):
        self.close()
        self.ax.clear()
        self.data.display(new_fig=False, figure=self.ax)
    def on_set(self, e):
        "add baseline points at selector"
        self.bsl_points.append( self.selpos() )
        self.selfine.value = 0.5
        self.disp()
    def on_unset(self, e):
        "remove baseline points closest from selector"
        here = self.selpos()
        distclose = np.inf
        pclose = np.NaN
        for i,p in enumerate(self.bsl_points):
            if abs(p-here)< distclose:
                pclose = p
                distclose = abs(p-here)
        self.bsl_points.remove(pclose)
        self.disp()
    def selpos(self):
        "returns selector pos in ppm"
        pos = self.select.value + self.selfine.value/100
        return (1-pos)*(self.data.axis1.itop(0)-self.data.axis1.itop(self.data.size1))+self.data.axis1.itop(self.data.size1)
    def correction(self):
        "returns the correction to apply as a numpy array"
        ibsl_points = [int(self.data.axis1.ptoi(x)) for x in self.bsl_points]
        x = np.arange(self.data.size1)
        if len(self.bsl_points) == 1 :
            value = self.data[ibsl_points[0]] * np.ones( self.data.size1 )
        elif len(self.bsl_points) < 4 :
            corr = bcorr._linear_interpolate(self.data.get_buffer(), ibsl_points)
            value = corr(x)
        else:
            corr = bcorr._spline_interpolate(self.data.get_buffer(), ibsl_points)
            value = corr(x)
        return value
    def corrected(self):
        value = self.data.copy()
        value.set_buffer( value.get_buffer() - self.correction() )
        return value
    def disp(self):
        self.xb = self.ax.get_xbound()
        super().disp()
        if len(self.bsl_points)>0:
            if self.toshow.value == 'baseline':
                ( self.data.copy()-self.corrected() ).display(new_fig=False, figure=self.ax, color='r')
            elif self.toshow.value == 'corrected':
                self.ax.clear()
                self.corrected().display(new_fig=False, figure=self.ax, color='r', scale=self.scale.value)
        ppos = self.selpos()
        self.ax.plot([ppos,ppos], self.ax.get_ybound())# [0,self.data.absmax/self.scale.value])
        y = []
        for point in self.bsl_points:
            if self.toshow.value == 'corrected':
                y.append(0)
            else:
                loc = int(self.data.axis1.ptoi(point))
                y.append(self.data[loc])
        if y:
            self.ax.scatter(self.bsl_points, y, c='r', marker='o')
        self.ax.set_xbound(self.xb)

class Show1Dplus(Show1D):
    def __init__(self, data, figsize=None, title=None):
        super().__init__( data, figsize=figsize, title=title)
        self.scaleint = widgets.FloatSlider(value=0.5, min=0.1, max=10, step=0.05,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.offset = widgets.FloatSlider(value=0.3, min=0.0, max=1.0, step=0.01,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.peaks = widgets.Checkbox(value=False, layout=Layout(width='15%'))
        self.integ = widgets.Checkbox(value=False, layout=Layout(width='15%'))

        for widg in (self.scaleint, self.offset, self.peaks, self.integ):
            widg.observe(self.ob)
        self.fullBox = VBox([  HBox([Label('Integral scale:'),self.scaleint,Label('offset:'),self.offset]),
                        HBox([Label('Show Peaks'),self.peaks,Label('integrals'),self.integ])
                    ])
        display(self.fullBox)
    def on_done(self, e):
        self.fullBox.close()
        super().close()
    def disp(self):
        self.xb = self.ax.get_xbound()
        self.ax.clear()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title)
        if self.integ.value:
            try:
                self.data.display_integral(label=True, integscale=self.scaleint.value, integoff=self.offset.value, figure=self.ax)
            except:
                print('no or wrong integrals')
                pass
        if self.peaks.value:
            try:
                self.data.display_peaks(peak_label=True, figure=self.ax, scale=self.scale.value)
            except:
                print('no or wrong peaklist')
                pass
        self.ax.set_xbound(self.xb)


class baseline2D_F2(baseline1D):
    def __init__(self, data, figsize=None):
        self.data2D = data
        super().__init__( self.data2D.projF2, figsize=figsize)
    def on_done(self, e):
        super().on_done(e)
        ibsl_points = [int(self.data2D.axis2.ptoi(x)) for x in self.bsl_points]
        self.data2D.bcorr(method='spline', xpoints=ibsl_points)

class Show2D(object):
    """
    A display for 2D NMR with a scale cursor
    Show2D(spectrum) where spectrum is a NPKData object
    - special display for DOSY.
    """
    def __init__(self, data, title=None, figsize=None):
        self.data = data
        self.isDOSY =  isinstance(data.axis1, NPKData.LaplaceAxis)
        try:
            self.proj2 = data.projF2
        except:
            self.proj2 = data.proj(axis=2).real()
        try:
            self.proj1 = data.projF1
        except:
            self.proj1 = data.proj(axis=1).real()
        self.title = title
        self.scale = widgets.FloatLogSlider(description='scale:', value=1.0, min=-1, max=3,  base=10, step=0.01,
                            layout=Layout(width='80%'), continuous_update=HEAVY)
        self.posview = widgets.Checkbox(value=True,description='Positive')
        self.negview = widgets.Checkbox(value=False,description='Negative')
        for w in (self.scale, self.posview, self.negview):
            w.observe(self.ob)
        grid = {'height_ratios':[1,4],'hspace':0,'wspace':0}
        if self.isDOSY:
            fsize = (10,5)
            grid['width_ratios']=[7,1]
        else:
            fsize = (8,8)
            grid['width_ratios']=[4,1]
#        fig, self.axarr = plt.subplots(2, 1, sharex=True, figsize=fsize, gridspec_kw=grid)
        fig = plt.figure(figsize=fsize, constrained_layout=False)
        spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=fig, **grid)
        axarr = np.empty((2,2), dtype=object)
        axarr[0,0] = fig.add_subplot(spec2[0, 0])
        axarr[1,0] = fig.add_subplot(spec2[1, 0],sharex=axarr[0, 0])
        axarr[1,1] = fig.add_subplot(spec2[1, 1],sharey=axarr[1, 0])
        self.top_ax = axarr[0,0]
        self.spec_ax = axarr[1,0]
        self.side_ax = axarr[1,1]
        self.Box = HBox( [self.scale, self.posview, self.negview])
        display( self.Box )
        self.disp(new=True)
    def on_done(self, b):
        self.scale.close()
    def ob(self, event):
        "observe events and display"
        if event['name'] != 'value':
            return
        self.disp()
    def disp(self,new=False):
        if new:
            self.proj2.display(figure=self.top_ax, title=self.title)
            xb = self.top_ax.get_xbound()
            dataxis = self.proj1.axis1.itoc( self.proj1.axis1.points_axis() )
            self.side_ax.plot(self.proj1.get_buffer(),dataxis)
            yb = self.side_ax.get_ybound()
        else:
            yb = self.side_ax.get_ybound()
            xb = self.top_ax.get_xbound()
            self.spec_ax.clear()
        if self.posview.value:
            self.data.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax)
        if self.negview.value:
            self.data.display(scale=-self.scale.value, new_fig=False,
                figure=self.spec_ax, mpldic={'cmap':'Wistia'})
        self.spec_ax.set_xbound(xb)
        self.spec_ax.set_ybound(yb)


class Phaser1D(object):
    """
    An interactive phaser in 1D NMR

        Phaser1D(spectrum)

    best when in %matplotlib inline

    """
    def __init__(self, data):
        self.scale = widgets.FloatSlider(description='scale:', min=1, max=100, step=0.5,
                            layout=Layout(width='30%'), continuous_update=REACTIVE)
        self.p0 = widgets.FloatSlider(description='P0:',min=-180, max=180, step=0.1,
                            layout=Layout(width='100%'), continuous_update=REACTIVE)
        self.p1 = widgets.FloatSlider(description='P1:',min=-360, max=360, step=1.0,
                            layout=Layout(width='100%'), continuous_update=REACTIVE)
        self.pivot = widgets.FloatSlider(description='pivot:', min=0.0, max=1.0, step=0.01, layout=Layout(width='30%'),
                                         value=0.5, readout=False, continuous_update=REACTIVE)
        for w in [self.p0, self.p1, self.scale, self.pivot]:
            w.observe(self.ob)
        self.button = widgets.Button(description="Ok", button_style='success')
        self.button.on_click(self.on_Apply)
        self.cancel = widgets.Button(description="Cancel", button_style='warning')
        self.cancel.on_click(self.on_cancel)
        display( VBox([ HBox([self.button, self.cancel]),
                        HBox([self.scale, self.pivot]),
                        self.p0, self.p1]) )
        fi,ax = plt.subplots()
        self.ax = ax
        self.data = data
        self.data.display(figure=self.ax)
        ppos = self.data.axis1.itop(self.data.axis1.size/2)
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
        self.xb = self.ax.get_xbound()  # initialize zoom
    def close_all(self):
        for w in [self.p0, self.p1, self.scale, self.button, self.cancel, self.pivot]:
            w.close()
    def on_cancel(self, b):
        self.close_all()
        self.ax.clear()
        self.data.display(new_fig=False, figure=self.ax)
    def on_Apply(self, b):
        lp0, lp1 = self.ppivot() # get centered values
        if lp0 != 0 or lp1 != 0:
            print("Applied: phase(%.1f,  %.1f)"%(lp0, lp1))
            self.data.phase(lp0, lp1).display(new_fig=False, figure=self.ax)
        else:
            print("no applied phase")
        self.close_all()
#        self.ax.clear()
    def ppivot(self):
        "converts from pivot values to centered ones"
        pp = 1.0-self.pivot.value
        return (self.p0.value + (pp-0.5)*self.p1.value, self.p1.value)
    def ob(self, event):
        "observe changes and start phasing"
        if event['name']=='value':
            self.phase()
    def phase(self):
        "apply phase and display"
        self.xb = self.ax.get_xbound()   # get current zoom
        self.ax.clear()
        lp0, lp1 = self.ppivot() # get centered values
        self.data.copy().phase(lp0, lp1).display(scale=self.scale.value, new_fig=False, figure=self.ax)
        ppos = (1-self.pivot.value)*(self.data.axis1.itop(0)-self.data.axis1.itop(self.data.size1))+self.data.axis1.itop(self.data.size1)
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
        self.ax.set_xbound( self.xb )
        #z = self.zm
        #self.ax.set_xlim(left=z[0], right=z[1] )

class Phaser2D(object):
    """
    An interactive phaser in 2D NMR

        Phaser2D(spec)

    best when in %matplotlib inline

    """
    def __init__(self, data):
        self.data = data
        self.scale = widgets.FloatLogSlider(description='scale:', value=1.0, min=-1, max=2,  base=10, step=0.01,
                            layout=Layout(width='80%'), continuous_update=HEAVY)
        self.F1p0 = widgets.FloatSlider(min=-180, max=180, step=0.1, description='F1 p0',continuous_update=HEAVY)
        self.F1p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='F1 p1',continuous_update=HEAVY)
        self.F2p0 = widgets.FloatSlider(min=-180, max=180, step=0.1, description='F2 p0',continuous_update=HEAVY)
        self.F2p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='F2 p1',continuous_update=HEAVY)
        for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.scale]:
            w.observe(self.ob)
        self.button = widgets.Button(description="Apply correction",button_style='success')
        self.button.on_click(self.on_Apply)
        self.cancel = widgets.Button(description="Cancel",button_style='warning')
        self.cancel.on_click(self.on_cancel)
#       interact(self.phase, scale=self.scale, F1p0=self.F1p0, F1p1=self.F1p1, F2p0=self.F2p0, F2p1=self.F2p1)
        display(VBox([self.scale,
            HBox([VBox([self.F1p0, self.F1p1], layout=Layout(width='40%')), VBox([self.F2p0, self.F2p1],  layout=Layout(width='40%'))], layout=Layout(width='80%'))
            ], layout=Layout(width='100%')))
        display(HBox([self.button, self.cancel]))
        fi,ax = plt.subplots()
        self.ax = ax
        self.display()
        #self.data.display(figure=self.ax)
    def ob(self, event):
        "observe changes and start phasing"
        if event['name']=='value':
            self.phase()
    def close(self):
        for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.scale, self.button, self.cancel]:
            w.close()
    def on_cancel(self, b):
        print("No action")
        self.ax.clear()
        self.data.display(figure=self.ax,scale=self.scale.value)
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
        self.close()
    def on_Apply(self, b):
        print("Applied: phase(%.1f,%.1f,axis='F1').phase(%.1f,%.1f,axis='F')"%(self.F1p0.value, self.F1p1.value, self.F2p0.value, self.F2p1.value))
        self.data.phase(self.F2p0.value, self.F2p1.value, axis='F2').phase(self.F1p0.value, self.F1p1.value, axis='F1')
        self.data.display(figure=self.ax,scale=self.scale.value)
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
        self.close()
    def display(self,todisplay=None):
        "display either the current data or the one provided - red and blue"
        self.ax.clear()
        if not todisplay:
            todisplay = self.data
        todisplay.display(scale=self.scale.value, new_fig=False, figure=self.ax,color='blue')
        todisplay.display(scale=-self.scale.value, new_fig=False, figure=self.ax, color='red')
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
    def phase(self):
        "compute phase and display"
        dp = self.data.copy().phase(self.F2p0.value, self.F2p1.value, axis='F2').phase(self.F1p0.value, self.F1p1.value, axis='F1')
        self.display(dp)
    # def phase(self, scale, F1p0, F1p1, F2p0, F2p1):
    #     self.data.copy().phase(F1p0,F1p1,axis='F1').phase(F2p0,F2p1,axis='F2').display(scale=scale);

class AvProc1D:
    "Detailed 1D NMR Processing"
    def __init__(self, filename=""):
        self.wfile = widgets.Text(description='File to process',layout=Layout(width='80%'), value=filename)
        self.wapod = widgets.Dropdown(
            options=['None', 'apod_sin (sine bell)', 'apod_em (Exponential)', 'apod_gm (Gaussian)', 'gaussenh (Gaussian Enhacement)', 'kaiser'],
            value='apod_sin (sine bell)',
            description='Apodisation')
        self.wpapod_Hz = widgets.FloatText(
            value=1.0,
            min=0, # max exponent of base
            max=30, # min exponent of base
            description='Width in Hz',
            layout=Layout(width='15%'),
            disabled = True)
        self.wpapod_enh = widgets.FloatText(
            value=2.0,
            min=0.0, # max exponent of base
            max=5.0, # min exponent of base
            description='strength',
            layout=Layout(width='15%'),
            step=1,
            disabled = True)
        self.wpapod_sin = widgets.FloatText(
            value=0.0,
            min=0, # max exponent of base
            max=0.5, # min exponent of base
            description='bell shape',
            layout=Layout(width='15%'),
            step=0.01,
            tooltip='value is the maximum of the bell, 0 is pure cosine, 0.5 is pure sine',
            disabled = False)
        self.wzf = widgets.Dropdown(
            options=[0, 1, 2, 4, 8],
            value=1,
            description='Zero-Filling')
        self.wphase0 = widgets.FloatText(
            value=0, description='Phase : P0', layout=Layout(width='20%'), disabled = True)
        self.wphase1 = widgets.FloatText(
            value=0, description='P1', layout=Layout(width='20%'), disabled = True)
        self.wapmin = widgets.Checkbox(
            value=True, description='AutoPhasing', tooltip='Perform AutoPhasing')
        self.wapmin.observe(self.apmin_select)
        self.wbcorr = widgets.Checkbox(
            value=False, description='Baseline Correction', tooltip='Perform AutoPhasing')
        self.wapod.observe(self.apod_select)
        self.bapod = widgets.Button(description='Show effect on FID')
        self.bapod.on_click(self.show_apod)
        self.bdoit = widgets.Button(description='Process')
        self.bdoit.on_click(self.process)
        self.show()
        fi,ax = plt.subplots()
        self.ax = ax
        if os.path.exists(filename):
            self.load()
            #self.data.set_unit('sec')
            self.display()
    def apod_select(self, e):
        test = self.wapod.value.split()[0]
        self.wpapod_sin.disabled = True
        self.wpapod_Hz.disabled = True
        self.wpapod_enh.disabled = True
        if test == "apod_sin":
            self.wpapod_sin.disabled = False
        if test in ('apod_em', 'apod_gm','gaussenh'):
            self.wpapod_Hz.disabled = False
        if test == 'gaussenh':
            self.wpapod_enh.disabled = False
    def apmin_select(self, e):
        for w in self.wphase0, self.wphase1:
            w.disabled = self.wapmin.value
    def load(self):
        self.data = Import_1D(self.wfile.value)
    def apod(self):
        func = self.wapod.value.split()[0]
        todo = None
        if func == 'apod_sin':
            todo = 'self.data.apod_sin(%f)'%(self.wpapod_sin.value,)
        elif func in ('apod_em', 'apod_gm'):
            todo = 'self.data.%s(%f)'%(func, self.wpapod_Hz.value)
        elif func == 'gaussenh':
            todo = 'self.data.gaussenh(%f,enhancement=%f)'%(self.wpapod_Hz.value, self.wpapod_enh.value)
        if todo is not None:
            eval(todo)
        return self.data
    def show_apod(self, e):
        self.load()
        self.apod()
        self.display()
    def process(self, e):
        self.load()
        self.apod().zf(self.wzf.value).ft_sim().bk_corr().set_unit('ppm')
        if self.wapmin.value:
            self.data.apmin()
            self.wphase0.value = round(self.data.axis1.P0,1)
            self.wphase1.value = self.data.axis1.P1
        else:
            self.data.phase(self.wphase0.value, self.wphase1.value)
        self.display()
    def display(self):
        self.ax.clear()
        self.data.display(figure=self.ax)
    def show(self):
        display(
            VBox([self.wfile,
                HBox([self.wapod, self.wpapod_sin, self.wpapod_Hz, self.wpapod_enh, self.bapod]),
                self.wzf,
                HBox([self.wapmin, self.wphase0, self.wphase1]),
#                self.wbcorr,
                self.bdoit]) )

class NMRPeaker1D(Show1D):
    "a peak-picker for NMR experiments"
    def __init__(self, data, figsize=None):
        super().__init__( data, figsize=figsize)
        self.Box.add_class("hidden")
        self.data = data.real()
        self.thresh = widgets.FloatLogSlider(value=20.0,
            min=-1, max=2.0, base=10, step=0.01, layout=Layout(width='30%'),
            continuous_update=False, readout=True, readout_format='.2f')
        self.thresh.observe(self.pickpeak)
        self.peak_mode = widgets.Dropdown(options=['marker', 'bar'],value='marker',description='show as')
        self.peak_mode.observe(self.ob)
        self.bprint = widgets.Button(description="Print", layout=Layout(width='10%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='Print to screen')
        self.bprint.on_click(self.pkprint)
        self.out = Output(layout={'border': '1px solid red'})
        self.done = widgets.Button(description="Ok", button_style='success')
        self.done.on_click(self.on_done)
        self.cancel = widgets.Button(description="Cancel", button_style='warning')
        self.cancel.on_click(self.on_cancel)
        self.Box = HBox([self.done,self.cancel])
        self.Box2 = VBox([self.Box,
                            HBox([Label('threshold - % largest signal'), self.thresh, self.peak_mode, self.bprint] ) ])
        display(self.Box2 )
        display(self.out )
        self.pp()
        self.display()
    def on_cancel(self, b):
        self.Box2.close()
        self.out.close()
        del self.data.peaks
        self.ax.clear()
        self.data.display(new_fig=False, figure=self.ax)
    def on_done(self, b):
        pkm = self.peak_mode.value
        self.Box2.close()
        self.ax.clear()
        self.data.display(figure=self.ax )
        self.data.display_peaks(peak_mode=pkm,figure=self.ax )
        self.data.mplfigure.annotate('%d peaks detected'%len(self.data.peaks) ,(0.05,0.95), xycoords='figure fraction')
    def pkprint(self,event):
        self.out.clear_output(wait=True)
        with self.out:
            display(HTML(self.data.pk2pandas().to_html()))
    def _pkprint(self,event):
        self.out.clear_output(wait=True)
        with self.out:
            print(self.pklist())
    def pklist(self):
        "creates peaklist for printing or exporting"
        text = ["ppm\tInt.(%)\twidth (Hz)"]
        data = self.data
        intmax = max(data.peaks.intens)/100
        for pk in data.peaks:
            ppm = data.axis1.itop(pk.pos)
            width = 2*pk.width*data.axis1.specwidth/data.size1
            l = "%.3f\t%.1f\t%.2f"%(ppm, pk.intens/intmax, width)
            text.append(l)
        return "\n".join(text)
    def ob(self, event):
        if event['name']=='value':
            self.display()        
    def display(self):
        "interactive wrapper to peakpick"
        self.xb = self.ax.get_xbound()
        self.yb = self.ax.get_ybound()
        super().disp()
        x = [self.data.axis1.itoc(z) for z in (0, self.data.size1) ]
        y = [self.data.absmax*self.thresh.value/100]*2
        self.ax.plot(x,y,':r')
        try:
            self.data.display_peaks(peak_label=True, peak_mode=self.peak_mode.value, figure=self.ax)
        except:
            pass
        self.ax.set_xbound(self.xb)
        self.ax.set_ybound(self.yb)
    def pickpeak(self, event):
        "interactive wrapper to peakpick"
        if event['name']=='value':
            self.pp()
    def pp(self):
        "do the peak-picking calling pp().centroid()"
        #self.spec.clear_output(wait=True)
        th = self.data.absmax*self.thresh.value/100
        zm = None
        self.data.set_unit('ppm').peakpick(threshold=th, verbose=False, zoom=zm).centroid()
        self.display()
        self.ax.annotate('%d peaks detected'%len(self.data.peaks) ,(0.05,0.95), xycoords='figure fraction')
    def _pp(self):
        "do the peak-picking calling pp().centroid()"
        self.ax.clear()
        self.data.set_unit('m/z').peakpick(autothresh=self.thresh.value, verbose=False).centroid()
        self.data.display(new_fig=False, figure=self.ax)
        x = [self.data.axis1.lowmass, self.data.axis1.highmass]
        y = [self.data.peaks.threshold]*2
        self.ax.plot(x,y,':r')
        self.data.display_peaks(peak_label=True, figure=self.ax)
        self.ax.annotate('%d peaks detected'%len(self.data.peaks) ,(0.05,0.95), xycoords='figure fraction')

class NMRIntegrate(object):
    "an integrator for NMR experiments"
    def __init__(self, npkd):
        self.npkd = npkd.real().integrate()
        self.bias = widgets.FloatSlider(
            description='bias', layout=Layout(width='30%'),
            value=0.0,min=-10.0, max=10.0, step=0.1,
            continuous_update=True, readout=True, readout_format='.1f')
        self.sep = widgets.FloatSlider(
            description='peak separation', layout=Layout(width='30%'),
            value=3.0,min=0.0, max=10.0, step=0.1,
            continuous_update=True, readout=False, readout_format='.1f')
        self.wings = widgets.FloatSlider(
            description='width', layout=Layout(width='30%'),
            value=5.0,min=0.5, max=10.0, step=0.1,
            continuous_update=True, readout=False, readout_format='.1f')
        self.bias.observe(self.integrate)
        self.sep.observe(self.integrate)
        self.wings.observe(self.integrate)
        self.Ok = widgets.Button(description="Ok", button_style='success')
        self.Ok.on_click(self.on_Apply)
        self.cancel = widgets.Button(description="Cancel", button_style='warning')
        self.cancel.on_click(self.on_cancel)
        self.bprint = widgets.Button(description="Print", layout=Layout(width='10%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='Print to screen')
        self.bprint.on_click(self.print)
        self.entry = widgets.IntText(value=0,description='Entry',min=0,layout=Layout(width='15%'))
        self.value = widgets.FloatText(value=100,description='Value',layout=Layout(width='15%'))
        self.set = widgets.Button(description="Set", button_style='success', layout=Layout(width='7%'))
        self.set.on_click(self.set_value)
        self.out = Output(layout={'border': '1px solid red'})
        self.Box = VBox([
            HBox([self.Ok, self.cancel]),
            HBox([self.bias, self.sep, self.wings,] ),
            HBox([self.entry, self.value, self.set, self.bprint] ),])
        display( self.Box)
        self.fig, self.ax = plt.subplots()
        self.npkd.display(figure=self.ax)
        display( self.out)
        self.int()
        self.display()
    def on_cancel(self, b):
        self.Box.close()
        self.out.close()
        self.ax.clear()
        self.npkd.display(figure=self.ax)
    def on_Apply(self, b):
        self.Box.close()
        self.ax.clear()
        self.npkd.display(figure=self.ax)
        self.npkd.display_integral(label=True, figure=self.ax, labelyposition=0.01, regions=False)
    def set_value(self,b):
        self.npkd.integral_calibrate(self.entry.value, self.value.value)
        self.display()
    def print(self,event):
        self.out.clear_output(wait=True)
        with self.out:
            display(HTML( self.npkd.integrals.to_pandas().to_html() ))
    def integrate(self, event):
        "integrate from event"
        if event['name']=='value':
            self.int()
    def int(self):
        "do the integration"
        try:
            calib = self.npkd.integrals.calibration
        except:
            calib = None
        self.npkd.set_unit('ppm').integrate(separation=self.sep.value, wings=self.wings.value,
            bias=self.npkd.absmax*self.bias.value/100)
        self.npkd.integrals.calibrate(calibration=calib)
        self.display()
    def ob(self, event):
        if event['name']=='value':
            self.display()
    def display(self):
        "refresh display from event"
        self.xb = self.ax.get_xbound()
        self.yb = self.ax.get_ybound()
        self.ax.clear()
        self.npkd.display(new_fig=False, figure=self.ax)
        try:
            self.npkd.display_integral(label=True, figure=self.ax, labelyposition=0.01, regions=False)
        except:
            pass
        self.ax.set_xbound(self.xb)
        self.ax.set_ybound(self.yb)


#if __name__ == '__main__':
#    unittest.main()    