from __future__ import absolute_import
from __future__ import print_function
def log_invocation(fn='/tmp/reflex_invoc.log'):
    """ log invocation of actor for easy reexecution with:
        eval $(cat /tmp/reflex_invoc.log)
    """
    import os, sys
    #if 'REFLEX_DEBUG' not in os.environ:
    #    return
    try:
        with open(fn, 'w') as f:
            path = ""
            s = " ".join(['"%s"' % x for x in sys.argv])
            try:
                import reflex
                path = '"%s:"' % os.path.dirname(reflex.__file__)
            except ImportError:
                pass
            f.write('PYTHONPATH=%s$PYTHONPATH python %s\n' % (path, s)) 
    except:
        pass

# import the needed modules
try:
  import reflex
  try:
    from astropy.io import fits as pyfits
  except ImportError:
    import pyfits
  import_sucess = True
  import reflex_plot_widgets
  import pipeline_product

#NOTE for developers: 
# -If you want to modify the current script to cope
#  with different parameters, this is the function to modify:
#  setInteractiveParameters()
# -If you want to modify the current script to read different data from
#  the input FITS, this is the function to modify:
#  readFitsData()                  (from class DataPlotterManager) 
# -If you want to modify the current script to modify the plots (using the same
#  data),  this is the function to modify:
#  plotProductsGraphics()          (from class DataPlotterManager)
# -If you want to modify the text that appears in the "Help" button,
#  this is the function to modify:
#  setWindowHelp()
# -If you want to modify the title of the window, modify this function:
#  setWindowTitle()


  #This class deals with the specific details of data reading and final plotting.
  class DataPlotterManager:
    # This function will read all the columns, images and whatever is needed
    # from the products. The variables , self.plot_x, self.plot_y, etc...
    # are used later in function plotProductsGraphics().
    # Add/delete these variables as you need (only that plotProductsGraphics()
    # has to use the same names).
    # You can also create some additional variables (like statistics) after
    # reading the files.
    # If you use a control variable (self.xxx_found), you can modify 
    # later on the layout of the plotting window based on the presence of 
    # given input files. 
    # sof contains all the set of frames
    def readFitsData(self, fitsFiles):
      #Control variable to check if the interesting files where at the input
      self.line_table_mos_found = False

      #Read all the products
      frames = dict()
      for frame in fitsFiles:
        if frame == '' :
          continue
        category = frame.category
        frames[category] = frame

      if "FIB_LINE_TABLE_REDL" in frames and \
         "FIB_LINE_TABLE_REDU" in frames :  
        self.line_table_mos_found = True  

      if self.line_table_mos_found == True :
        frame_redl = frames["FIB_LINE_TABLE_REDL"]
        frame_redu = frames["FIB_LINE_TABLE_REDU"]
        self.next=len(pyfits.open(frame_redl.name))
        self.nfibres = (self.next-1) // 3
        nf=(self.next-1) // 3
        table = pipeline_product.PipelineProduct(frame_redl)
        wlen = table.all_hdu[0].header['ESO INS GRAT2 WLEN']
        is_simcal = table.all_hdu[0].header['ESO OCS SIMCAL']
        print('wlen=',wlen)
        print("is_simcal=",is_simcal)
        if wlen == 520 :
           fibre_min=2
        if wlen == 580 :
           if is_simcal == 1 :
             fibre_min=1
           else :
             fibre_min=2

        if wlen == 860 :
           fibre_min=2

        self.wlen=wlen
        self.fibre_min=fibre_min
        self.list_used_fibres = []
        self.list_line_tab = []
        self.list_objects = []

        for fib_id in range(fibre_min,nf+fibre_min) :
           fib_cnt=fib_id-fibre_min
           self.list_used_fibres.append(fib_id)
           ext_id=fib_cnt*3+1
           self.list_line_tab.append(flames_plot_common.PlotableMosLineTable(frame_redl, frame_redu,ext_id,fib_id,fib_cnt))        
        self.list_objects = []

           #print "Process fibre", fib_id, "out of", nf, "use data from ext id=", ext_id




    def plotWidgets(self) :
        widgets = list()

        # Files Selector radiobutton
        labels = self.list_used_fibres
        self.radiobutton = reflex_plot_widgets.InteractiveRadioButtons(self.fibre_selector, self.setFibreSelectCallBack, labels, 0, 
                title="Fibre id\n (Mouse \n left \n button)")
        widgets.append(self.radiobutton)

        return widgets

    def setFibreSelectCallBack(self, fibre_id) :
        fibre_id = int(fibre_id)
        self.subplot_res_wave.cla()
        self.subplot_res_order.cla()
        self.subplot_fwhm_wave.cla()
        self.subplot_y_x.cla()
        self.subtext_lineinfo.cla()

        self.displayTables(fibre_id-self.fibre_min)

    def displayTables(self,fibre_id):

        #Residuals vs wave
        self.plotScatterRESvsWAVE(fibre_id)

        #Residuals vs order
        if self.line_table_mos_found == True :
           self.plotScatterRESvsORDER(fibre_id)

           #FWHM vs wavelenth
           if self.line_table_mos_found == True :
              self.plotScatterFWHMvsWAVE(fibre_id)

              #Y vs X
              self.plotScatterYvsX(fibre_id)

              #Fith subpanel: a text box
              self.list_line_tab[fibre_id].plotLinesText(self.subtext_lineinfo)
        
           else :
              #Data not found info
              displayDataNotFound()

    # This function creates all the subplots. It is responsible for the plotting 
    # layouts. 
    # There can different layouts, depending on the availability of data
    # Note that subplot(I,J,K) means the Kth plot in a IxJ grid 
    # Note also that the last one is actually a box with text, no graphs.
    def addSubplots(self, figure):
      if self.line_table_mos_found == True:

        ncols=2
        nraws=3
        self.subplot_res_wave  = figure.add_subplot(nraws,ncols,1)
        self.subplot_res_order = figure.add_subplot(nraws,ncols,2)
        self.subplot_fwhm_wave = figure.add_subplot(nraws,ncols,3)
        self.subplot_y_x       = figure.add_subplot(nraws,ncols,4)
        self.subtext_lineinfo  = figure.add_subplot(nraws,1,3)
      else : 
        self.subtext_nodata    = figure.add_subplot(1,1,1)

      self.fibre_selector = figure.add_axes([0.89, 0.12, 0.10, 0.15])


    def plotScatterTooltips(self):
      tooltip ="""Plot of the wavelength residual [Ang] as a function of wavelength [Ang] for the wavelength calibration model. 
Blue points represent line identifications which were used in the wavelength solution, and red points represent line identifications that were clipped from the wavelength solution. 
Lines that were detected in the arc frame but not identified in the line catalogue are not plotted. 
The solid and dashed horizontal lines represent the median value and the +-1 sigma range respectively."""
      self.tooltip_res_wave=tooltip

      tooltip ="""Plot of the wavelength residual [Ang] as a function of order for the wavelength calibration model. 
Blue points represent line identifications which were used in the wavelength solution, and red points represent line identifications that were clipped from the wavelength solution. 
Lines that were detected in the arc frame but not identified in the line catalogue are not plotted. 
The solid and dashed horizontal lines represent the median value and the +-1 sigma range respectively."""
      self.tooltip_res_order=tooltip

      tooltip ="""Plot of the line FWHM (pix) as a function of wavelength [Ang] for identified lines that were used in the wavelength solution (blue points) and all remaining detected lines (red points). 
The solid and dashed horizontal lines represent the median value and the +-1 sigma range respectively."""
      self.tooltip_fwhm_wave=tooltip

      tooltip ="""Plot of the measured line Y coordinate (pix) versus the measured line X coordinate (pix) for identified lines that were used in the wavelength solution (blue points) and all remaining detected lines (red points).
This plot shows the distribution of detected arc lines on the wavelength calibration arc frame."""
      self.tooltip_y_x=tooltip


    def plotScatterRESvsWAVE(self,fibre_id):
      print("fibre_id=",fibre_id)
      title = 'Wave Res: Mean=%#.3g Med=%#.3g RMS=%#.3g'% (self.list_line_tab[fibre_id].mean_all_residual,self.list_line_tab[fibre_id].median_all_residual,self.list_line_tab[fibre_id].std_all_residual) 
      self.title_res_wave=title
      print("Mean=", self.list_line_tab[fibre_id].mean_all_residual)
      self.list_line_tab[fibre_id].plotRESvsWAVE(self.subplot_res_wave, title, self.tooltip_res_wave,fibre_id)


    def plotScatterRESvsORDER(self,fibre_id):
      title = 'Wave Res: Mean=%#.3g Med=%#.3g RMS=%#.3g'% (self.list_line_tab[fibre_id].mean_all_residual,self.list_line_tab[fibre_id].median_all_residual,self.list_line_tab[fibre_id].std_all_residual) 
      self.title_res_order=title

      self.list_line_tab[fibre_id].plotRESvsORDER(self.subplot_res_order, title, self.tooltip_res_order,fibre_id)

    def plotScatterFWHMvsWAVE(self,fibre_id):
      print("fibere id", fibre_id)
      title = 'FWHM: Mean=%#.3g Med=%#.3g RMS=%#.3g'% (self.list_line_tab[fibre_id].mean_all_fwhm,self.list_line_tab[fibre_id].median_all_fwhm,self.list_line_tab[fibre_id].std_all_fwhm) 
      self.title_fwhm_wave=title

      self.list_line_tab[fibre_id].plotFWHMvsWAVE(self.subplot_fwhm_wave, title, self.tooltip_fwhm_wave,fibre_id)

    def plotScatterYvsX(self,fibre_id):
      title   = 'Arcline Positions' 
      tooltip ="""Plot of the measured line Y coordinate (pix) versus the measured line X coordinate (pix) for identified lines that were used in the wavelength solution (blue points) and all remaining detected lines (red points).
This plot shows the distribution of detected arc lines on the wavelength calibration arc frame."""
      self.title_y_x=title
      self.list_line_tab[fibre_id].plotYvsX(self.subplot_y_x, title, self.tooltip_y_x)

    def displayDataNotFound(self):
      #Data not found info
      self.subtext_nodata.set_axis_off()
      self.text_nodata = 'Line prediction not found in the products (PRO.CATG=FIB_LINE_TABLE_REDL or FIB_LINE_TABLE_REDU)' 
      self.subtext_nodata.text(0.1, 0.6, self.text_nodata, color='#11557c', fontsize=18,
                                 ha='left', va='center', alpha=1.0)
      self.subtext_nodata.tooltip='Line tables not found in the products'

    # This is the function that makes the plots.
    # Add new plots or delete them using the given scheme.
    # The data has been already stored in self.plot_x, self.plot_xdif, etc ...
    # It is mandatory to add a tooltip variable to each subplot.
    # One might be tempted to merge addSubplots() and plotProductsGraphics().
    # There is a reason not to do it: addSubplots() is called only once at
    # startup, while plotProductsGraphics() is called always there is a resize.
    def plotProductsGraphics(self):
      if self.line_table_mos_found == True :
     
        if self.line_table_mos_found == True :
          
          self.plotScatterTooltips()
          fibre_id=self.list_used_fibres[0]
          self.displayTables(fibre_id-self.fibre_min)
  
    # This function specifies which are the parameters that should be presented
    # in the window to be edited.
    # Note that the parameter has to be also in the in_sop port (otherwise it 
    # won't appear in the window) 
    # The descriptions are used to show a tooltip. They should match one to one
    # with the parameter list 
    # Note also that parameters have to be prefixed by the 'recipe name:'
    def setInteractiveParameters(self):
      paramList = list()
      paramList.append(reflex.RecipeParameter(recipe='flames_cal_wavecal',displayName='range',group='wavecal',description='Width (pix) of search window is 2*range + 1. This parameter is automatically adjusted according to binning'))
      paramList.append(reflex.RecipeParameter(recipe='flames_cal_wavecal',displayName='minlines',group='wavecal',description='Minimum number of lines to detect. If zero, the default value (1100 for BLUE/REDL chips; 1000 for REDU chip) is used'))
      paramList.append(reflex.RecipeParameter('flames_cal_wavecal',displayName='maxlines',group='wavecal',description='Maximum number of lines to detect. If zero, the default value (1600 for BLUE/REDL chip; 1400 for REDU chip) is used'))
      paramList.append(reflex.RecipeParameter(recipe='uves_cal_wavecal',displayName='alpha',group='wavecal',description='The parameter that controls the distance to the nearest neighbours'))
      paramList.append(reflex.RecipeParameter(recipe='flames_cal_wavecal',displayName='degree',group='wavecal',description='Degrees of the global 2d dispersion polynomial. If a negative number is specified, the polynomial degrees are automatically selected by starting from (1, 1) and inreasing the degrees as long as the RMS residual decreases significantly'))
      paramList.append(reflex.RecipeParameter(recipe='flames_cal_wavecal',displayName='tolerance',group='wavecal',description='Tolerance of fit. If positive, the tolerance is in pixel units. If negative, abs(tolerance) is in wavelength units. Lines with residuals worse than the tolerance are excluded from the final fit. Unlike in previous versions, this parameter is not corrected for CCD binning. This rejection based on the absolute residual in pixel can be effectively disabled by setting the tolerance to a very large number (e.g. 9999). In that case outliers will be rejected using only kappa sigma clipping'))
      paramList.append(reflex.RecipeParameter(recipe='flames_cal_wavecal',displayName='kappa',group='wavecal',description='Lines with residuals more then kappa stdev are rejected from the final fit'))

      return paramList

    def setWindowHelp(self):
      help_text = """
In this window, the user should aim to improve the wavelength calibration solution by minimising the RMS statistic for the wavelength residuals (shown in the titles of the upper two plots) while keeping a reasonable number of emission lines used in the final solution (reported as "Final no. of fitted lines")."""
      return help_text

    def setWindowTitle(self):
      title = 'Uves Interactive Wavelength Calibration'
      return title

except ImportError:
  import_sucess = 'false'
  print("Error importing modules pyfits, wx, matplotlib, numpy")

#This is the 'main' function
if __name__ == '__main__':
  log_invocation()

  # import reflex modules
  import reflex_interactive_app
  import sys

  # import UVES reflex modules
  import flames_plot_common

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

  #Check if import failed or not
  if import_sucess == 'false' :
    interactive_app.setEnableGUI('false')

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

  # print outputs
  interactive_app.print_outputs()

  sys.exit()

