##########################################################################
##############################  RectSelect  ###############################
###########################################################################

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
import copy
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
                                              NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.patches as patches

import sys
if sys.version_info[0] < 3:
    import Tkinter as tk
else: # Python3
    import tkinter as tk




class RectSelect:
    """
    The class represents the rectangle selection tool
    start and end are coordinate pairs (x,y)
    The functions with names starting with __upd are the callback routines.
    """
	
    def __init__(self, momPanel):

        self.momPanel = momPanel            # One of the plot windows (Panels)
        self.fig = momPanel.fig
        self.ax = momPanel.ax
        self.patch = None
        #self.sel = momPanel.sel             # List of already selected points
		
    def draw(self, start, end):
        """ Draw the rectangle """
        wid = end[0] - start[0]
        hei = end[1] - start[1]
        return patches.Rectangle(start, wid, hei, ec='k', fill=False, fc='c', \
                              ls='dashed', lw=0.7)
		
    def autodraw(self, **opts):
		""" Setup automatic drawing; supports command option """
		self.start = None
        # self.fig.canvas is momPanel.canvas
		self.fig.canvas.mpl_connect('button_press_event', self.__upd_press)
		self.fig.canvas.mpl_connect('motion_notify_event', self.__upd_motion)
		self.fig.canvas.mpl_connect('button_release_event', self.__upd_release)
		self.fig.canvas.mpl_connect('key_press_event', self.__upd_key)
		
		#self._command = opts.pop('command', lambda *args: None)
		#self.rectopts = opts

		
    def __upd_key(self, event):

        if event.key == 'esc':
            if self.patch is not None:
                self.patch.remove()



    def __upd_press(self, event):

        if event.inaxes <> self.ax: return # Beyond data window: ========= >>>
        
        if self.start == None:
            x = event.xdata
            y = event.ydata
            if x <> None and y <> None:
                self.start = [x,y]
            self.fig.canvas.show()



    def __upd_motion(self, event):

        if event.inaxes <> self.ax: return # Beyond data window: ========= >>>

        if self.patch is not None:
            self.patch.remove()

        x = event.xdata
        y = event.ydata
        if self.start <> None and x <> None and y <> None:
            self.patch = self.draw(self.start, [x,y])
            self.ax.add_artist(self.patch)
        self.fig.canvas.show()



    def __upd_release(self, event):
        
        if self.patch is None:    # return  # Click without motion detected
            return

        mom = self.momPanel
        
        extn = self.patch.get_bbox().extents
        ex0, ey0, ex1, ey1 = extn

        if self.patch is not None:
            self.patch.remove()  # Do not leave the dashed rectangle visible
     
        self.start = None
        self.patch = None
        self.fig.canvas.show()
        
        #
        # Make the extents to be always in correct order:
        #     x0 < x1 and y0 < y1
        #
        x0 = ex0
        x1 = ex1
        if ex0 > ex1:
            x0 = ex1
            x1 = ex0
            
        y0 = ey0
        y1 = ey1
        if ey0 > ey1:
            y0 = ey1
            y1 = ey0
            
        #
        # The graph points
        #
        x = mom.xdata[mom.Xscale][mom.Xkey]
        y = mom.ydata[mom.Yscale][mom.Ykey]
        
        #
        # Find the graph points covered by the rectangle patch
        #
        sel = np.where((x >= x0) & (x <= x1) & (y >= y0) & (y <= y1))[0]

        if len(sel) == 0:
            return        # =========== NO POINTS SELECTED =============== >>>

        #
        # Remove all the flagged data points from selection
        #
        sel = np.setdiff1d(sel, mom.iflag)

        if len(sel) == 0:
            return        # =========== NO VALID POINTS ================== >>>
        
        #
        # Undo/Redo in kwrite editor style
        #
        # If the selection/unselection is made after one or more Undo,
        # the history before the current state is wiped out and cannot
        # be Redo-ne
        ## #
        ## # If iSelQueue points not at the last element of the selection queue:
        ## #
        ## if mom.iSelQueue < len(mom.selQueue) - 1:
        ##     # Wipe after iSelQueue
        ##     mom.selQueue = mom.selQueue[:mom.iSelQueue+1] 
        ##     mom.buttonRedo.config(state='disabled') # Gray out Redo

        if mom.SelectingON:
            #
            # Exclude from sel the points already selected 
            #
            if len(mom.sel) > 0:
                sel = np.setdiff1d(sel, mom.sel)
            
            if len(sel) == 0:
                return  # =============== NO NEW SELECTIONS =============== >>>

            #
            # If iSelQueue points not at the last element of the
            # selection queue:
            #
            if mom.iSelQueue < len(mom.selQueue) - 1:
                # Wipe after iSelQueue
                mom.selQueue = mom.selQueue[:mom.iSelQueue+1]
                mom.buttonRedo.config(state='disabled') # Gray out Redo

            #
            # We are here in case user selected some not yet selected points
            #
            # Append the new selection to the Undo/ReDo queue
            #
            mom.selQueue.append({0:'select', 1:copy.copy(sel)})

            #
            # At the very beginning, enable button Undo and option Save
            #
            if mom.iSelQueue == 0:
                mom.buttonUndo.config(state='normal')         # Enable Undo
                mom.menuSelect.entryconfig(2, state='normal') # Enable Save

            mom.iSelQueue = mom.iSelQueue + 1

            #
            # Add the just selected points to the self.sel list of the
            # mother panel keepeng the unique points only
            #
            if len(mom.sel) > 0:
                mom.sel = np.union1d(mom.sel, sel)
            else:
                mom.sel = sel
            


        if mom.unSelectingON:
            #
            # Exclude from sel the points that are not selected 
            #
            if len(mom.sel) == 0:  # If there are no selected points at all,
                sel = np.array([], dtype=np.int64)  # The unselection is empty
            else:                # Otherwise unselect only those selected:
                sel = np.intersect1d(mom.sel, sel)  # Leave only common points

            if len(sel) == 0:
                return  # =============== NO NEW SELECTIONS =============== >>>

            #
            # If iSelQueue points not at the last element of the
            # selection queue:
            #
            if mom.iSelQueue < len(mom.selQueue) - 1:
                # Wipe after iSelQueue
                mom.selQueue = mom.selQueue[:mom.iSelQueue+1]
                mom.buttonRedo.config(state='disabled') # Gray out Redo
            
            #
            # We are here in case user unselected some selected points
            #
            # Append the new UNselection to the Undo/ReDo queue
            #
            mom.selQueue.append({0:'unselect', 1:copy.copy(sel)})

            #
            # At the very beginning, enable button Undo and option Save
            #
            if mom.iSelQueue == 0: 
                mom.buttonUndo.config(state='normal')         # Enable Undo
                mom.menuSelect.entryconfig(2, state='normal') # Enable Save

            mom.iSelQueue = mom.iSelQueue + 1

            #
            # Remove the just selected points from the self.sel list of the
            # mother panel keepeng the unique points only
            #
            if len(mom.sel) > 0:
                mom.sel = np.setdiff1d(mom.sel, sel)
            else:
                mom.sel = sel

        mom.nsel = len(mom.sel)
           
        if len(mom.sel) > 0:
        
            #
            # Find the the new graph points to paint red
            #
            xsel = x[mom.sel]
            ysel = y[mom.sel]
            #
            # Paint the selected points
            #
            if mom.plsel:
                mom.plsel[0].remove()  # Wipe out the red selected points

            mom.plsel = mom.ax.plot(xsel, ysel, 'r.', ms=6.0, mew=0.1)

            self.fig.canvas.show()
          



    def disconnect(self):
        self.fig.canvas.mpl_disconnect(self.__upd_press)
        self.fig.canvas.mpl_disconnect(self.__upd_motion)
        self.fig.canvas.mpl_disconnect(self.__upd_release)
        self.fig.canvas.mpl_disconnect(self.__upd_key)







 

