# ========================================================================================================
#
# John Pritchard
#
# Developed from esotk_fringe_obj_mask.py by Mark Neeser
#
# Contains the base class BaseDataPlotterManager and necessary customisations to
# PipelineInteractiveApp to have interactive GUIs with necessary controls that
# allow to navigate the FITS file extensions more easily.
# All interactive python actors for the esotk fringing workflow should use these
# common classes and methods.
#
# Author: Artur Szostak <aszostak@partner.eso.org
# ------------------------------------------------------------------------------
# Small adaptations for GIRAFFE
#
# ChangeLog:
#   2017-03: removed Extension slider etc as GIRAFFE products doe not have extensions...

import os
import sys
import reflex_interactive_app
import reflex_interactive_gui

try:
    # Import non-standard but needed modules.
    import wx
    from wx import xrc
    import numpy
    from astropy.io import fits as pyfits
    import reflex
    import pipeline_product
    import pipeline_display
    import matplotlib
    import_success = True


    def MAD(x):
        """
        Median absolute deviation function; used to scale the images.
        """
        x = numpy.array(x)
        return numpy.median(numpy.abs(x - numpy.median(x)))


    class BaseDataPlotterManager(object):
        """
        Common base class that must be inherited by the different interactive
        fringing GUIs to correctly setup the customised GUI with FITS
        navigation controls.reflex_interactive_gui.py
        """

        def __init__(self):
            self.xrc_file = os.path.join(os.path.dirname(__file__),
                                         'esotk_giraf_gui.xrc')
            self.n_ext = 0
            self.ext_id = 0
            self.science_frames = []
            # The following can only be filled in after initialisation.
            self.fileChoice = None
            self.extSlider = None
            self.figure = None

        def setupExtensionValues(self, products):
            ## Not needed by Giraffe
            pass

        def _setupExtensionValues(self, products):
            """
            This method should be called in the readFitsData() method to update
            the maximum extensions value and the current extension ID. The
            needed values are taken from the PipelineProduct object given by
            'products'.
            """
            self.n_ext = len(products.all_hdu)
            if self.n_ext > 1:
                self.ext_id = 1
            else:
                self.ext_id = 0

        def setupFigure(self, figure):
            self.figure = figure
            pass

        def setupExtensionLabel(self, figure):
            ## Not needed by Giraffe
            pass

        def _setupExtensionLabel(self, figure):
            """
            This method must be called in the addSubplots() method to setup the
            extension label in the Matplotlib figure.
            """
            self.extLabel = figure.text(0.01, 0.97, "Extension 0 of 0")
            # Keep the figure to redraw plots in the event handlers.
            self.figure = figure

        def updateExtensionLabel(self):
            ## Not needed by Giraffe
            pass

        def _updateExtensionLabel(self):
            """
            This function should be called in the plotProductsGraphics() method
            to update the extension label in the Matplotlib figure.
            """
            text = "Extension {0} of {1}".format(self.ext_id, self.n_ext - 1)
            self.extLabel.set_text(text)

        def computeRobustMedMad(self, image):
            """
            Helper method to compute the median and MAD values returned as a
            tuple for the given image.
            """
            med = numpy.median(image)
            mad = MAD(image)
            new_image = image[numpy.abs(image-med) < 3*1.48*mad]
            new_med = numpy.median(new_image)
            new_mad = MAD(new_image)
            return new_med, new_mad

        def onClickNextFile(self, event):
            """
            This event handler is called whenever the "next" button is clicked
            for the file selection. Need to update the selected file and then
            update the plots.
            """
            val = self.fileChoice.GetSelection() + 1
            if val >= self.fileChoice.GetCount():
                return
            self.fileChoice.SetSelection(val)
            self.onFileChange(None)

        def onClickPreviousFile(self, event):
            """
            This event handler is called whenever the "previous" button is
            clicked for the file selection. Need to update the file choice and
            plots, like for onClickNextFile.
            """
            val = self.fileChoice.GetSelection() - 1
            if val < 0:
                return
            self.fileChoice.SetSelection(val)
            self.onFileChange(None)

        def loadProducts(self, index):
            """
            Function to load the products based on the given file selection
            indicated by 'index'. i.e. the index'th frame from the array
            self.science_frames. This method can be overloaded if more than one
            image needs to be loaded.
            """
            frame = self.science_frames[index]
            self.science = pipeline_product.PipelineProduct(frame)

        def onFileChange(self, event):
            """
            This event handler is called whenever the file choice changes from
            the drop down list. We need to update the current loaded file and
            redraw the plots.
            """
            index = self.fileChoice.GetSelection()
            if index >= 0 and index < len(self.science_frames):
                self.loadProducts(index)
                self.plotProductsGraphics()
                self.figure.canvas.draw()

        def onClickNextExt(self, event):
            """
            This event handler is called whenever the "next" button is clicked
            for extensions. Need to update the slider, current extension ID and
            then update the plots.
            """
            val = self.extSlider.GetValue() + 1
            if val > self.extSlider.GetMax():
                return
            self.extSlider.SetValue(val)
            self.ext_id = val
            self.plotProductsGraphics()
            self.figure.canvas.draw()

        def onClickPreviousExt(self, event):
            """
            This event handler is called whenever the "previous" button is
            clicked for extensions. Need to update the slider and plots, like
            for onClickNextExt.
            """
            val = self.extSlider.GetValue() - 1
            if val < self.extSlider.GetMin():
                return
            self.extSlider.SetValue(val)
            self.ext_id = val
            self.plotProductsGraphics()
            self.figure.canvas.draw()

        def onSliderChange(self, event):
            """
            This event handler is called whenever the slider position changes.
            We need to update the current extension ID and the plots.
            """
            self.ext_id = event.GetPosition()
            self.plotProductsGraphics()
            self.figure.canvas.draw()


    class ReflexInteractiveWxApp(reflex_interactive_gui.ReflexInteractiveWxApp):
        """
        This class overrides the onMotion method and adds a 'Display ESOREX log' button...
        """

        def onMotion(self, event):
            if event.inaxes:
                #for attr in dir(self.dataPlotManager):
                #    print attr, '=>', getattr(self.dataPlotManager, attr)
                str=""
                if hasattr(self.dataPlotManager,"subplot_sci_rbnspectra") :
                    if event.inaxes in [self.dataPlotManager.subplot_sci_rbnspectra,] :
                        OBJECT = self.dataPlotManager.sci_rbnspectra_extr.extractedscience.all_hdu[1].data['OBJECT'][int(event.xdata-0.5)]
                        FPS = self.dataPlotManager.sci_rbnspectra_extr.extractedscience.all_hdu[1].data['FPS'][int(event.xdata-0.5)]
                        SSN = self.dataPlotManager.sci_rbnspectra_extr.extractedscience.all_hdu[1].data['SSN'][int(event.xdata-0.5)]
                        OBJECT_SNR = self.dataPlotManager.sci_rbnspectra_extr.SNRarr[int(event.xdata-0.5)]
                        str=" %s, S/N = %5.2f [FPS/SSN=%d/%d, **** col=%d]" %(OBJECT.strip(),OBJECT_SNR,FPS,SSN,int(1.0+event.xdata+0.5))
                self.statusBar.SetStatusText(event.inaxes.format_coord(event.xdata, event.ydata)+str)
            else:
                self.statusBar.SetStatusText((''), 0)


        def bindXrcObjects(self):
            self.Bind(wx.EVT_BUTTON, self.onCont, id=xrc.XRCID('contBtn'))
            self.Bind(wx.EVT_BUTTON, self.onRepeat, id=xrc.XRCID('repeatBtn'))
            self.Bind(wx.EVT_BUTTON, self.onLog, id=xrc.XRCID('logBtn'))
            self.Bind(wx.EVT_BUTTON, self.onHelp, id=xrc.XRCID('helpBtn'))
            self.Bind(wx.EVT_CHECKBOX, self.onSetDisable, self.setDisableCheck)
            self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onFileInfoPaneChanged)
            self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)

            if(self.inter_app.is_init_sop_enable()):
                self.Bind(wx.EVT_CHECKBOX, self.onSetInitSop, self.setInitSopCheck)

        def onLog(self, event):
            #for attr in dir(self.dataPlotManager.sci_rbnspectra_extr.extractedscience):
            #    print attr, '=>', getattr(self.dataPlotManager.sci_rbnspectra_extr.extractedscience, attr)
            log_file = None
            try :
                log_file = open(self.dataPlotManager.esorex_log_file_name)
            except :
                pass
            if log_file is not None :
                log = log_file.read()
                log_file.close()
                import tkinter as tk
                from tkinter.scrolledtext import ScrolledText
                self.root = tk.Tk()
                self.root.title("esorex log file: %s" %(self.dataPlotManager.esorex_log_file_name))
                S = tk.Scrollbar(self.root)
                T = tk.Text(self.root, height=24, width=80)
                S.pack(side=tk.RIGHT, fill=tk.Y)
                T.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.Y)
                S.config(command=T.yview)
                T.config(yscrollcommand=S.set)

                T.insert(tk.END, log)
                tk.mainloop()
            else :
                print(("Failed to open esorex logfile %s" %(self.dataPlotManager.esorex_log_file_name)))

    class PipelineInteractiveApp(reflex_interactive_app.PipelineInteractiveApp, object):
        """
        This class overrides the showGUI method to bind the event handlers for
        the customised GUI layout provided by the esotk_giraf_gui.xrc file.
        """

        def showGUI(self):
            try:
                #from reflex_interactive_gui import ReflexInteractiveWxApp
                from esotk_giraf_gui import ReflexInteractiveWxApp
                self.wxApp = ReflexInteractiveWxApp(self, self.dataPlotManager)

                # Bind event handlers to the additional next button, previous
                # button and file choice widgets.
                self.wxApp.Bind(wx.EVT_BUTTON,
                                self.dataPlotManager.onClickNextFile,
                                id = xrc.XRCID('nextFileButton'))
                self.wxApp.Bind(wx.EVT_BUTTON,
                                self.dataPlotManager.onClickPreviousFile,
                                id = xrc.XRCID('previousFileButton'))
                self.wxApp.Bind(wx.EVT_CHOICE,
                                self.dataPlotManager.onFileChange,
                                id = xrc.XRCID('fileChoice'))

                # Bind event handlers to the additional next button, previous
                # button and slider for extension selection.
                #self.wxApp.Bind(wx.EVT_BUTTON,
                #                self.dataPlotManager.onClickNextExt,
                #                id = xrc.XRCID('nextExtButton'))
                #self.wxApp.Bind(wx.EVT_BUTTON,
                #                self.dataPlotManager.onClickPreviousExt,
                #                id = xrc.XRCID('previousExtButton'))
                #self.wxApp.Bind(wx.EVT_SCROLL_CHANGED,
                #                self.dataPlotManager.onSliderChange,
                #                id = xrc.XRCID('extSlider'))

                # Need to update the slider range and initial value. This has to
                # happen at this point because the dataPlotManager.readFitsData
                # method would have been called by now, with it's n_ext value
                # set.
                #extSlider = xrc.XRCCTRL(self.wxApp.frame, 'extSlider')
                #n_ext = self.dataPlotManager.n_ext
                #if n_ext > 1:
                    # For FITS with multiple extensions the image range is from
                    # extension 1 onwards.
                #    extSlider.SetRange(1, n_ext - 1)
                #else:
                    # For only 1 extension set the range to zero.
                #    extSlider.SetRange(0, 0)
                #extSlider.SetValue(self.dataPlotManager.ext_id)
                #self.dataPlotManager.extSlider = extSlider

                # Need to also update the list of files in the file choice
                # widget. We add a reference to the widget in dataPlotManager so
                # that the event handler methods can access it.
                fileChoice = xrc.XRCCTRL(self.wxApp.frame, 'fileChoice')
                fileChoice.Clear()
                for frame in self.dataPlotManager.science_frames:
                    fileChoice.Append(self.dataPlotManager.frameLabels[frame.name])
                fileChoice.SetSelection(0)
                self.dataPlotManager.fileChoice = fileChoice

                self.wxApp.MainLoop()
            except (ImportError, NameError) as e:
                sys.stderr.write("Error importing modules: {0}\n".format(e))
                raise


except ImportError:
    import_success = False
    PipelineInteractiveApp = reflex_interactive_app.PipelineInteractiveApp


def runInteractiveApp(dataManagerClass):
    """
    This helper function runs the python interactive application to provide an
    interactive GUI for viewing esotk fringing workflow output.
    One must provide the DataPlotterManager class to use as the data interface
    in the 'dataManagerClass' parameter. The DataPlotterManager class should
    inherit from BaseDataPlotterManager to automatically provide necessary GUI
    event handlers and support methods.
    """

    # Create interactive application.
    interactive_app = PipelineInteractiveApp(enable_init_sop = True)

    # Check if import failed or not.
    if import_success == False:
        # Note: we have to print this message after interactive_app is created
        # or else this message will not be shown in the Reflex error message.
        sys.stderr.write("Error importing one of the modules wx, reflex," \
                         " numpy, astropy.io/pyfits, pylab or matplotlib.\n")
        interactive_app.setEnableGUI(False)

    # Open the interactive window if enabled.
    if interactive_app.isGUIEnabled():
        # Get the specific functions for this window.
        dataPlotManager = dataManagerClass()
        interactive_app.setPlotManager(dataPlotManager)
        interactive_app.showGUI()
    else:
        interactive_app.passProductsThrough()

    # Print outputs. This is parsed by the Reflex python actor to get the
    # results. Do not remove.
    interactive_app.print_outputs()

    if import_success == False:
        sys.exit(1)
