import string

import wx
from .standard_colours import colours

class SliderControl(wx.Slider):
    """ A custom slider control."""

    def __init__(self, parent, id, value=0.0, min_value=0.0, max_value=100.0, font=None, border_frame=True,
                 size=wx.DefaultSize):
        int_value=int(100*(value-min_value)/(max_value-min_value))
        wx.Slider.__init__(self, parent, id, value=int_value, minValue=0, maxValue=100, size=size)
        self.min_value=min_value
        self.max_value=max_value
        self.value=float(value)

        self.border_frame=border_frame
        self.scroll_callback=None

    def bind_handlers(self):
        self.Bind(wx.EVT_CHAR, self.OnChar)
        self.Bind(wx.EVT_SLIDER, self.OnChange)

    def unbind_handlers(self):
        self.Unbind(wx.EVT_SLIDER)

    def OnChar(self, event):
        if chr(event.GetKeyCode())==' ':
            self.GetParent().GetParent().SaveEditControlValue()
            self.GetParent().GetParent().EnableCellEditControl(False)
            return
        else:
            event.Skip()

    def OnChange(self, event):
        self.value=self.GetValue()

        if self.scroll_callback is not None:
            self.scroll_callback(self.value)

    def set_slider_position(self):
        if self.min_value==self.max_value:
            int_value=50
        else:
            int_value=int(100*(self.value-self.min_value)/(self.max_value-self.min_value))
        wx.Slider.SetValue(self, int_value)

    def SetValue(self, value):
        self.value=value
        self.set_slider_position()

    def GetValue(self):
        int_value=wx.Slider.GetValue(self)
        return int_value/100.*(self.max_value-self.min_value)+self.min_value

    def SetMaxValue(self, value):
        self.max_value=value
        self.set_slider_position()

    def SetMinValue(self, value):
        self.min_value=value
        self.set_slider_position()

    def SetScrollCallback(self, func):
        """ Set a callback that is called on a scroll event.

        func should only take one parameter which is the value, i.e. func(value).
        """
        self.scroll_callback=func

class NumberValidator(wx.Validator):
    """Validaator to handle numerical values, accepts also 'e' as exponent value as input"""

    def __init__(self):
        wx.Validator.__init__(self)
        self.Bind(wx.EVT_CHAR, self.OnChar)

    def Clone(self):
        return NumberValidator()

    def Validate(self, win):
        tc=self.GetWindow()
        val=tc.GetValue()

        try:
            val=float(val)
        except:
            return False
        else:
            return True

    def OnChar(self, event):
        if event.AltDown() or event.ControlDown():
            event.Skip()
            return
        key=event.GetKeyCode()
        tc=self.GetWindow()
        val=tc.GetValue()
        pos=tc.GetInsertionPoint()

        if event.GetUnicodeKey()!=wx.WXK_NONE:
            # an actual character key was pressed

            if chr(key) in string.digits:
                if len(val)==0:
                    event.Skip()
                    return
                if pos==0 and val[0]=='-':
                    if not wx.Validator_IsSilent():
                        wx.Bell()
                    return
                event.Skip()
                return

            if chr(key)=='.' and '.' not in val:
                if 'e' in val:
                    # Check so that the . ends up correctly relative e
                    if pos>val.index('e'):
                        if not wx.Validator_IsSilent():
                            wx.Bell()
                        return
                if len(val)==0:
                    event.Skip()
                    return
                if pos==0 and val[0]=='-':
                    if not wx.Validator_IsSilent():
                        wx.Bell()
                    return

                event.Skip()
                return

            if chr(key)=='e' and 'e' not in val:
                if '.' in val:
                    # Check so that the e ends up correctly relative .
                    if pos<=val.index('.'):
                        if not wx.Validator_IsSilent():
                            wx.Bell()
                        return
                if len(val)==0:
                    event.Skip()
                    return
                if pos==0 and val[0]=='-':
                    if not wx.Validator_IsSilent():
                        wx.Bell()
                    return
                event.Skip()
                return

            if chr(key)=='-':
                if val[0]!='-' and pos==0:
                    event.Skip()
                elif val[pos-1]=='e' and '-' not in val[pos-1:]:
                    event.Skip()
                elif val[0]=='-':
                    tc.SetValue(val[1:])
                    tc.SetInsertionPoint(max(0,pos-1))
                else:
                    tc.SetValue('-'+val)
                    tc.SetInsertionPoint(pos+1)
                return

            if chr(key)==' ' and self.Validate(None):
                tc.GetParent().GetParent().SaveEditControlValue()
                tc.GetParent().GetParent().EnableCellEditControl(False)
                return

        if key in [wx.WXK_TAB, wx.WXK_RETURN, wx.WXK_BACK, wx.WXK_ESCAPE,
                     wx.WXK_DOWN, wx.WXK_UP, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_NUMPAD_LEFT,
                     wx.WXK_NUMPAD_RIGHT, wx.WXK_DELETE, wx.WXK_BACK, wx.WXK_HOME, wx.WXK_END]:
            event.Skip()
            return

        if not wx.Validator.IsSilent():
            wx.Bell()

        # Does not allow the event to propagate (kills it)
        return

class SpinCtrl(wx.TextCtrl):
    def __init__(self, parent, id=wx.NewId(), pos=wx.DefaultPosition, size=wx.DefaultSize, value=0.0,
                 min_value=None, max_value=None, steps=20, digits=6, name="SpinCtrl"):
        self.value=value
        self.digits=digits
        self.min_value=min_value
        self.max_value=max_value
        self.steps=steps
        self.value_change_func=None
        wx.TextCtrl.__init__(self, parent, id=id, pos=pos, size=size, name=name,
                             style=wx.TE_RIGHT,
                             value="%.*g"%(self.digits, self.value),
                             validator=NumberValidator())

        self.Layout()
        # self._spin=wx.SpinButton(self, wx.NewId(), pos=pos, size=wx.Size(20, self.GetSize().GetHeight()-8),
        #                          style=wx.SP_ARROW_KEYS | wx.SP_VERTICAL, name=name+"_SpinButton")
        # self._spin.Bind(wx.EVT_SPIN_UP, self.eh_spin_up,
        #                 id=self._spin.GetId())
        # self._spin.Bind(wx.EVT_SPIN_DOWN, self.eh_spin_down,
        #                 id=self._spin.GetId())

        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        self.Bind(wx.EVT_CHAR, self.OnChar)

    def step(self, dir_up=True):
        """Steps the value up/down on increment

        :param dir_up: stepping direction
        :return:
        """
        inc=(self.max_value-self.min_value)*1.0/self.steps
        val=self.value
        if dir_up:
            val+=inc
        else:
            val-=inc

        # Check so that the bounds are kept
        val=max(val, self.min_value)
        val=min(val, self.max_value)

        self.SetValue(val)

        if self.value_change_func is not None:
            self.value_change_func(val)

    def eh_spin_up(self, event):
        self.step(dir_up=True)
        if self.value_change_func is not None:
            self.value_change_func(self.GetValue())
        event.Skip()

    def eh_spin_down(self, event):
        self.step(dir_up=False)
        if self.value_change_func is not None:
            self.value_change_func(self.GetValue())
        event.Skip()

    def OnMouseWheel(self, event):
        if event.GetWheelRotation()>0 and not event.IsWheelInverted():
            self.step(dir_up=True)
        else:
            self.step(dir_up=False)

    def OnChar(self, event):
        # print event.GetKeyCode()
        if event.GetKeyCode()==wx.WXK_UP:
            self.step(dir_up=True)
        elif event.GetKeyCode()==wx.WXK_DOWN:
            self.step(dir_up=False)
        else:
            event.Skip()

    def SetValue(self, value):
        try:
            self.value=float(value)
            wx.TextCtrl.SetValue(self, "%.*g"%(self.digits, self.value))
        except ValueError:
            wx.TextCtrl.SetValue(self, value)

    def GetValue(self):
        try:
            self.value=float(wx.TextCtrl.GetValue(self))
        except ValueError:
            pass
        return wx.TextCtrl.GetValue(self)

    def SetValueChangeCallback(self, func):
        """ Sets a callback function to execute when the value has changed
        :param func:
        :return:
        """
        self.value_change_func=func

    def SetRange(self, min_value, max_value):
        """ Sets the range of the control

        :param min_value:
        :param max_value:
        :return:
        """
        if max_value<min_value:
            tmp_value=min_value
            min_value=max_value
            max_value=tmp_value
        self.min_value=float(min_value)
        self.max_value=float(max_value)
