'''Drawer Widget to hold the main window and the menu/hidden section that
can be swiped in from the left. This Menu would be only hidden in phone mode
and visible in Tablet Mode.

This class is specifically in lined to save on start up speed(minimize i/o).
'''

from kivy.app import App
from kivy.factory import Factory
from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger

import gc

# delayed imports
app = None


class Drawer(Factory.RelativeLayout):
    '''Drawer Widget to hold the main window and the menu/hidden section that
    can be swiped in from the left. This Menu would be only hidden in phone mode
    and visible in Tablet Mode.

    '''

    state = OptionProperty('closed',
                            options=('closed', 'open', 'opening', 'closing'))
    '''This indicates the current state the drawer is in.

    :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
    `closed`, `open`, `opening`, `closing`.
    '''

    scroll_timeout = NumericProperty(200)
    '''Timeout allowed to trigger the :data:`scroll_distance`,
    in milliseconds. If the user has not moved :data:`scroll_distance`
    within the timeout, the scrolling will be disabled and the touch event
    will go to the children.

    :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 200 (milliseconds)
    '''

    scroll_distance = NumericProperty('9dp')
    '''Distance to move before scrolling the :class:`Drawer` in pixels.
    As soon as the distance has been traveled, the :class:`Drawer` will
    start to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target
    device's screen.

    :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 20dp.
    '''

    drag_area = NumericProperty('9dp')
    '''The percentage of area on the left edge that triggers the opening of
    the drawer. from 0-1

    :attr:`drag_area` is a `NumericProperty` defaults to 2
    '''

    hidden_widget = ObjectProperty(None)
    ''' This is the widget that is hidden in phone mode on the left side of
    drawer or displayed on the left of the overlay widget in tablet mode.

    :attr:`hidden_widget` is a `ObjectProperty` defaults to None.
    '''

    overlay_widget = ObjectProperty(None)
    '''This a pointer to the default widget that is overlayed either on top or
    to the right of the hidden widget.
    '''

    def __init__(self, **kwargs):
        super(Drawer, self).__init__(**kwargs)

        self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)

    def toggle_drawer(self):
        if app.ui_mode[0] == 't':
            return
        Factory.Animation.cancel_all(self.overlay_widget)
        anim = Factory.Animation(x=self.hidden_widget.width
                            if self.state in ('opening', 'closed') else 0,
                            d=.1, t='linear')
        anim.bind(on_complete = self._complete_drawer_animation)
        anim.start(self.overlay_widget)

    def _re_enable_gc(self, dt):
        global gc
        gc.enable()

    def on_touch_down(self, touch):
        if self.disabled:
            return

        if not self.collide_point(*touch.pos):
            return

        touch.grab(self)

        # disable gc for smooth interaction
        # This is still not enough while wallet is synchronising
        # look into pausing all background tasks while ui interaction like this
        gc.disable()

        global app
        if not app:
            app = App.get_running_app()

        # skip on tablet mode
        if app.ui_mode[0] == 't':
            return super(Drawer, self).on_touch_down(touch)

        state = self.state
        touch.ud['send_touch_down'] = False
        start = 0 #if state[0] == 'c' else self.hidden_widget.right
        drag_area = self.drag_area\
           if self.state[0] == 'c' else\
           (self.overlay_widget.x)

        if touch.x < start or touch.x > drag_area:
            if self.state == 'open':
                self.toggle_drawer()
                return
            return super(Drawer, self).on_touch_down(touch)

        self._touch = touch
        Clock.schedule_once(self._change_touch_mode,
                            self.scroll_timeout/1000.)
        touch.ud['in_drag_area'] = True
        touch.ud['send_touch_down'] = True
        return

    def on_touch_move(self, touch):
        if touch.grab_current is not self:
            return
        self._touch = False
        # skip on tablet mode
        if app.ui_mode[0] == 't':
            return super(Drawer, self).on_touch_move(touch)

        if not touch.ud.get('in_drag_area', None):
            return super(Drawer, self).on_touch_move(touch)

        ov = self.overlay_widget
        ov.x=min(self.hidden_widget.width,
            max(ov.x + touch.dx*2, 0))

        #_anim = Animation(x=x, duration=1/2, t='in_out_quart')
        #_anim.cancel_all(ov)
        #_anim.start(ov)

        if abs(touch.x - touch.ox) < self.scroll_distance:
            return

        touch.ud['send_touch_down'] = False
        Clock.unschedule(self._change_touch_mode)
        self._touch = None
        self.state = 'opening' if touch.dx > 0 else 'closing'
        touch.ox = touch.x
        return

    def _change_touch_mode(self, *args):
        if not self._touch:
            return
        touch = self._touch
        touch.ungrab(self)
        touch.ud['in_drag_area'] = False
        touch.ud['send_touch_down'] = False
        self._touch = None
        super(Drawer, self).on_touch_down(touch)
        return

    def on_touch_up(self, touch):
        if touch.grab_current is not self:
            return

        self._triigger_gc()

        touch.ungrab(self)
        touch.grab_current = None

        # skip on tablet mode
        get  = touch.ud.get
        if app.ui_mode[0] == 't':
            return super(Drawer, self).on_touch_up(touch)

        self.old_x = [1,] * 10
        self.speed = sum((
            (self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.

        if get('send_touch_down', None):
            # touch up called before moving
            Clock.unschedule(self._change_touch_mode)
            self._touch = None
            Clock.schedule_once(
                lambda dt: super(Drawer, self).on_touch_down(touch))
        if get('in_drag_area', None):
            if abs(touch.x - touch.ox) < self.scroll_distance:
                anim_to = (0 if self.state[0] == 'c'
                      else self.hidden_widget.width)
                Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)
                return
            touch.ud['in_drag_area'] = False
            if not get('send_touch_down', None):
                self.toggle_drawer()
        Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))

    def _complete_drawer_animation(self, *args):
        self.state = 'open' if self.state in ('opening', 'closed') else 'closed'

    def add_widget(self, widget, index=1):
        if not widget:
            return

        iget = self.ids.get
        if not iget('hidden_widget') or not iget('overlay_widget'):
            super(Drawer, self).add_widget(widget)
            return

        if not self.hidden_widget:
            self.hidden_widget = self.ids.hidden_widget
        if not self.overlay_widget:
            self.overlay_widget = self.ids.overlay_widget

        if self.overlay_widget.children and self.hidden_widget.children:
            Logger.debug('Drawer: Accepts only two widgets. discarding rest')
            return

        if not self.hidden_widget.children:
            self.hidden_widget.add_widget(widget)
        else:
            self.overlay_widget.add_widget(widget)
            widget.x = 0

    def remove_widget(self, widget):
        if self.overlay_widget.children[0] == widget:
            self.overlay_widget.clear_widgets()
            return
        if widget == self.hidden_widget.children:
            self.hidden_widget.clear_widgets()
            return

    def clear_widgets(self):
        self.overlay_widget.clear_widgets()
        self.hidden_widget.clear_widgets()

if __name__ == '__main__':
    from kivy.app import runTouchApp
    from kivy.lang import Builder
    runTouchApp(Builder.load_string('''
Drawer:
    Button:
    Button
'''))
