import wx
import logging
import matplotlib.cm
import numpy as np
import properties

p = properties.Properties.getInstance()

slider_width = 30
s_off = slider_width/2

class ColorBarPanel(wx.Panel):
    '''
    A HORIZONTAL colo
r bar and value axis drawn on a panel.
    '''
    def __init__(self, parent, map, local_extents=[0.,1.], global_extents=None, 
                 ticks=5, **kwargs):
        '''
        map -- a colormap name from matplotlib.cm
        local_extents -- local min and max values of the measurement
        global_extents -- min and max values of the measurement
        ticks -- # of ticks to display values for on th
e bar
                 1 or 0 will draw no ticks
        labelformat -- a valid format string for the values displayed
                       on the value axis 
        '''
        wx.Panel.__init__(self, parent, **kwargs)
        self.ticks = ticks
        self.labelformat = '%.3f'
        self.low_slider = wx.Button(self, -1, '[', pos=(0,-1), size=(slider_width,-1))
        self.high_slider = wx.Button(self, -1, ']', pos=(self.Size[0],-1), size=(slider_width,-1))
        self.ClearNotifyWindows()
        self.SetMap(map)
        self.interval = list(local_extents)
        self.local_extents = local_extents
        self.global_extents = list(local_extents)
        self.clipmode = 'rescale'
        
        self.low_slider.SetToolTipString('')
        self.low_slider.GetToolTip().Enable(True)
        
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.low_slider.Bind(wx.EVT_LEFT_DOWN, self.OnClipSliderLeftDown)
        self.low_slider.Bind(wx.EVT_MOTION, self.OnClipSliderMotion)
        self.high_slider.Bind(wx.EVT_LEFT_DOWN, self.OnClipSliderLeftDown)
        self.high_slider.Bind(wx.EVT_MOTION, self.OnClipSliderMotion)
        
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_SIZE, self.OnResize)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
    
    def OnLeftDown(self, evt):
        # Get the slider closest to the click point.
        if abs(self.low_slider.GetPositionTuple()[0] - evt.GetX()) < abs(self.high_slider.GetPositionTuple()[0] - evt.GetX()):
            self.cur_slider = self.low_slider
        else:
            self.cur_slider = self.high_slider
        self.cur_slider.SetPosition((evt.GetX() - s_off, -1))
        self.xo = 0
        self.UpdateInterval()
    
    def OnMotion(self, evt):
        if not evt.Dragging() or not evt.LeftIsDown():
            return
        self.cur_slider.SetPosition((evt.GetX() - s_off, -1))
        self.UpdateInterval()
    
    def OnClipSliderLeftDown(self, evt):
        self.cur_slider = evt.EventObject
        self.xo = evt.GetX()
    
    def OnClipSliderMotion(self, evt):
        slider = evt.EventObject
        if not evt.Dragging() or not evt.LeftIsDown():
            return
        slider.SetPosition((slider.GetPositionTuple()[0] + evt.GetX() - self.xo - s_off, -1))
        self.xo = 0
        self.UpdateInterval()
        
    def ClearNotifyWindows(self):
        self.notify_windows = []
    
    def AddNotifyWindow(self, win):
        self.notify_windows += [win]
    
    def ResetInterval(self):
        ''' Sets clip interval to the extents of the colorbar. '''
        self.interval = list(self.global_extents)
        self.low_slider.SetPosition((0-s_off,-1))
        self.high_slider.SetPosition((self.Size[0]-s_off,-1))
        for win in self.notify_windows:
            win.SetClipInterval(self.GetLocalInterval(), self.local_extents, self.clipmode)
        self.Refresh()
    
    def UpdateInterval(self):
        ''' Calculates the interval values w.r.t. the current extents
        and clipping slider positions. '''
        range = self.global_extents[1]-self.global_extents[0]
        w = float(self.Size[0])
        if range > 0 and w > 0:
            self.interval[0] = self.global_extents[0] + ((self.low_slider.GetPositionTuple()[0] + s_off) / w * range)
            self.interval[1] = self.global_extents[0] + ((self.high_slider.GetPositionTuple()[0] + s_off) / w * range)
        
            self.low_slider.SetToolTipString(str(self.global_extents[0] + ((self.low_slider.GetPositionTuple()[0] + s_off) / w * range)))
            self.high_slider.SetToolTipString(str(self.global_extents[0] + ((self.high_slider.GetPositionTuple()[0] + s_off) / w * range)))
        else:
            self.interval = list(self.local_extents)

        self.UpdateLabelFormat()
        
        for win in self.notify_windows:
            win.SetClipInterval(self.GetLocalInterval(), self.local_extents, self.clipmode)
        self.Refresh()
        
# TODO: To be added.  Not sure how to treat intervals that are outside 
#       the current extents, do we resize the extents?  This could get
#       ugly and confusing.
##    def SetInterval(self, interval):
##        ''' '''
##        self.interval = interval
##        self.low_slider.SetPosition((0-s_off,-1))                
##        self.high_slider.SetPosition((self.Size[0]-s_off,-1))
##        for win in self.notify_windows:
##            win.SetClipInterval(self.GetInterval(), self.clipmode)
##        self.Refresh()
        
    def GetGlobalInterval(self):
        ''' Returns the interval clipped on the value axis. '''
        return self.interval
    
    def GetLocalInterval(self):
        ''' Returns the interval clipped on the local color bar. 
        If either part is outside the local_extents, the extent is returned.
        '''
        return (max(self.interval[0], self.local_extents[0]),
                min(self.interval[1], self.local_extents[1]))
    
    def GetGlobalExtents(self):
        return self.global_extents
        
    def GetLocalExtents(self):
        return self.local_extents
    
    def GetClipMode(self):
        return self.clipmode
        
    def SetMap(self, map):
        ''' Sets the colormap that is displayed.
        map should be the string name of a colormap from matplotlib.cm'''
        self.cm = matplotlib.cm.get_cmap(map)
        self.Refresh()
        
    def SetLocalExtents(self, local_extents):
        #''' Sets the value axis min and max. Accepts a 2-tuple.'''
        self.local_extents = local_extents
        if self.local_extents[0] < self.global_extents[0]:
            self.global_extents[0] = self.local_extents[0]
        if self.local_extents[1] > self.global_extents[1]:
            self.global_extents[1] = self.local_extents[1]
        self.UpdateInterval()
        
    def SetGlobalExtents(self, global_extents):
        self.global_extents = list(global_extents)
        self.UpdateInterval()
        
    def SetTicks(self, ticks):
        ''' Sets the number of tick marks displayed by the ColorBarPanel.
        1 or 0 will draw no ticks'''
        self.ticks = ticks
        self.Refresh()
    
    def UpdateLabelFormat(self):
        ''' Selects a number format based on the step value between ticks ''' 
        range = self.global_extents[1] - self.global_extents[0]
        step = range / self.ticks
        if 0 < step < 0.001:
            self.labelformat = '%.3e'
        else:
            self.labelformat = '%.3f'
        
    def OnToggleClipMode(self, evt):
        if self.clipmode == 'clip':
            self.clipmode = 'rescale'
        else:
            self.clipmode = 'clip'
        for win in self.notify_windows:
            win.SetClipInterval(self.GetLocalInterval(), self.local_extents, self.clipmode)
        self.Refresh()
        
    def OnRightDown(self, evt):
        popupMenu = wx.Menu()
        popupMenu.SetTitle('Colorbar')
        reset = popupMenu.AppendItem(wx.MenuItem(popupMenu, -1, 'Reset sliders'))
        self.Bind(wx.EVT_MENU, lambda(evt):self.ResetInterval(), reset)
        if self.clipmode == 'clip':
            bracket_mode = popupMenu.AppendItem(wx.MenuItem(popupMenu, -1, 'Value bracketing: RESCALE'))
        else:
            bracket_mode = popupMenu.AppendItem(wx.MenuItem(popupMenu, -1, 'Value bracketing: CLIP'))
        self.Bind(wx.EVT_MENU, self.OnToggleClipMode, bracket_mode)
        
        aggmethod = self.Parent.aggregationMe