#!/usr/bin/python
# -*- mode: python; coding: utf-8; -*-
# ---------------------------------------------------------------------------#
#
# Copyright (C) 2017 LB
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------------#

from __future__ import division

import logging
import math
import traceback

from kivy.animation import Animation
from kivy.app import App
from kivy.base import EventLoop
from kivy.base import stopTouchApp
from kivy.cache import Cache
from kivy.clock import Clock
from kivy.config import Config
from kivy.core.audio import SoundLoader
from kivy.core.window import Window
from kivy.graphics import Color
from kivy.graphics import Line
from kivy.graphics import Rectangle
from kivy.graphics import Triangle
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
from kivy.uix.actionbar import ActionButton
from kivy.uix.actionbar import ActionPrevious
from kivy.uix.actionbar import ActionView
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.treeview import TreeView
from kivy.uix.treeview import TreeViewLabel
from kivy.uix.widget import Widget
from kivy.utils import platform

from pysollib.kivy.LBase import LBase
from pysollib.kivy.LTask import LTask, LTaskQ
from pysollib.kivy.androidperms import requestStoragePerm
from pysollib.kivy.androidrot import AndroidScreenRotation
from pysollib.resource import CSI

if platform != 'android':
    Config.set('input', 'mouse', 'mouse,multitouch_on_demand')

# =============================================================================


def get_platform():
    return platform

# =============================================================================


def get_menu_size_hint():
    sh = (0.5, 1.0)
    if Window.size[0] < Window.size[1]:
        sh = (1.0, 1.0)
    return sh

# =============================================================================


def set_fullscreen(fullscreen=True):
    if get_platform() == 'android':
        from jnius import autoclass
    else:
        return

    SDLActivity = autoclass('org.libsdl.app.SDLActivity')
    SDLActivity.setWindowStyle(fullscreen)

# =============================================================================


def get_screen_ori():
    if get_platform() == 'android':
        from jnius import autoclass
        from jnius import cast
    else:
        return None

    PythonActivity = autoclass('org.kivy.android.PythonActivity')
    currentActivity = cast('android.app.Activity', PythonActivity.mActivity)

    # Display = autoclass('android.view.Display')
    # WindowManager = autoclass('android.view.WindowManager')

    wm = currentActivity.getWindowManager()
    d = wm.getDefaultDisplay()

    so = None
    if d.getWidth() > d.getHeight():
        so = 'landscape'
    else:
        so = 'portrait'

    logging.info("LApp: ori = %s" % so)
    return so

# =============================================================================


class LPopCommander(LBase):
    def __init__(self, **kw):
        super(LPopCommander, self).__init__()
        self.pop_command = kw['pop_command']

    def pop(self):
        if self.pop_command is not None:
            self.pop_command(0)
            return True
        return False

# =============================================================================


class LAnimationTask(LTask, LBase):
    def __init__(self, spos, widget, **kw):
        super(LAnimationTask, self).__init__(widget.card)
        self.spos = spos
        self.widget = widget

        x = 0.0
        y = 0.0
        duration = 0.2
        transition = 'in_out_quad'
        bindE = None
        bindS = None
        if 'x' in kw:
            x = kw['x']
        if 'y' in kw:
            y = kw['y']
        if 'duration' in kw:
            duration = kw['duration']
        if 'transition' in kw:
            transition = kw['transition']
        if 'bindE' in kw:
            bindE = kw['bindE']
        if 'bindS' in kw:
            bindS = kw['bindS']

        self.delay = duration / 3.0

        self.xdest = x
        self.ydest = y
        self.duration = duration
        self.transition = transition
        self.bindE = bindE
        self.bindS = bindS
        print(self.widget.card)

    def start(self):
        super(LAnimationTask, self).start()

        anim = Animation(
            x=self.xdest, y=self.ydest, duration=self.duration,
            transition=self.transition)

        if self.bindE is not None:
            anim.bind(on_complete=self.bindE)
        if self.bindS is not None:
            anim.bind(on_start=self.bindS)

        self.widget.pos = self.spos
        anim.bind(on_complete=self.stop)
        anim.start(self.widget)

    def updateDestPos(self, pos):
        self.xdest = pos[0]
        self.ydest = pos[1]

# =============================================================================


class LAnimationMgr(object):
    def __init__(self, **kw):
        super(LAnimationMgr, self).__init__()
        self.tasks = []
        self.callbacks = []
        self.taskQ = LTaskQ()

    def checkRunning(self):
        return len(self.tasks) > 0

    def addEndCallback(self, cb):
        self.callbacks.append(cb)

    def taskEnd(self, task, value):
        if value:
            # print('LAnimationMgr: taskEnd = %s %s' % (task, value))
            self.tasks.remove(task)
            if not self.checkRunning():
                # print('LAnimationMgr: taskEnd ->', len(self.callbacks), 'callbacks') # noqa
                for cb in self.callbacks:
                    cb()
                # print('LAnimationMgr: taskEnd -> callbacks done')
                self.callbacks = []

            # print('Clock.get_fps() ->', Clock.get_fps())

    def taskInsert(self, task):
        self.tasks.append(task)
        task.bind(done=self.taskEnd)
        Clock.schedule_once(lambda dt: self.taskQ.taskInsert(task), 0.016)


LAnimationManager = LAnimationMgr()

# =============================================================================


def LAfterAnimation(task, delay=0.0):
    def dotask():   # noqa
        Clock.schedule_once(lambda dt: task())
    def mkcb(task): # noqa
        def cb(dt):
            if LAnimationManager.checkRunning():
                LAnimationManager.addEndCallback(dotask)
            else:
                dotask()
        return cb
    Clock.schedule_once(mkcb(task), delay)

# =============================================================================


LSoundLoader = SoundLoader

# =============================================================================


class LBoxLayout(BoxLayout, LBase):
    def __init__(self, **kw):
        super(LBoxLayout, self).__init__(**kw)

    def winfo_screenwidth(self):
        return self.size[0]

    def winfo_screenheight(self):
        return self.size[1]

# =============================================================================


def addAnchorOffset(pos, size, anchor):
    # print ('MfxCanvas: anchor=%s' % (anchor))
    x = pos[0]
    y = pos[1]
    xa = 0
    ya = 0
    if anchor == "n":
        ya = -1
    elif anchor == "w":
        xa = -1
    elif anchor == "s":
        ya = 1
    elif anchor == "e":
        xa = 1
    elif anchor == "ne":
        ya = -1
        xa = 1
    elif anchor == "nw":
        ya = -1
        xa = -1
    elif anchor == "se":
        ya = 1
        xa = 1
    elif anchor == "sw":
        ya = 1
        xa = -1

    if xa == 0:
        x = x - size[0] / 2.0
    elif xa == 1:
        x = x - size[0]
    if ya == 0:
        y = y - size[1] / 2.0
    elif ya == 1:
        y = y - size[1]
    return (x, y)

# =============================================================================


def LColorToLuminance(color):
    kc = color
    if isinstance(color, str):
        kc = LColorToKivy(color)
    r = kc[0]
    g = kc[1]
    b = kc[2]
    Y = 0.2989*r + 0.5866*g + 0.1145*b
    return Y

# =============================================================================


def LTextureToLuminance(texture):
    b = texture.pixels
    s = 4
    n = len(b)/1000
    if n > 4:
        s = n - n % 4
    n = 0
    ll = 0
    for i in range(0, len(b), int(s)):
        rr = int.from_bytes(b[i:i+1], byteorder='big', signed=False) / 256.0  # noqa
        gg = int.from_bytes(b[i+1:i+2], byteorder='big', signed=False) / 256.0  # noqa
        bb = int.from_bytes(b[i+2:i+3], byteorder='big', signed=False) / 256.0  # noqa
        ll = ll + LColorToLuminance([rr, gg, bb, 1])
        n += 1
    return (ll/n)

# =============================================================================


def LColorToKivy(outline):
    if (outline[0] == '#'):
        outline = outline[1:]
    ou0 = float(int(outline[0:2], 16)) / 256.0
    ou1 = float(int(outline[2:4], 16)) / 256.0
    ou2 = float(int(outline[4:6], 16)) / 256.0
    ou3 = 1.0
    if len(outline) >= 8:
        ou3 = float(int(outline[6:8], 16)) / 256.0
    return ou0, ou1, ou2, ou3

# =============================================================================


def cardfactor(canvas):
    # heuristic to find some sort of 'fontsize' out of the cardset.
    # We take the original small cardsets as reference and calculate
    # a correction factor.

    def pyth(a, b):
        return math.sqrt(a*a+b*b)

    cardscale = 1.0
    try:
        cs = canvas.wmain.app.images.cs
        # print('Cardset:', cs)
        # print('Cardset:', cs.type)

        cardbase = pyth(73, 97)
        if cs.type == CSI.TYPE_FRENCH:
            cardbase = pyth(73, 97)
        elif cs.type == CSI.TYPE_HANAFUDA:
            cardbase = pyth(64, 102)
        elif cs.type == CSI.TYPE_MAHJONGG:
            cardbase = pyth(38, 54)
        elif cs.type == CSI.TYPE_TAROCK:
            cardbase = pyth(80, 80)
        elif cs.type == CSI.TYPE_HEXADECK:
            cardbase = pyth(50, 80)
        elif cs.type == CSI.TYPE_MUGHAL_GANJIFA:
            cardbase = pyth(80, 80)
        elif cs.type == CSI.TYPE_NAVAGRAHA_GANJIFA:
            cardbase = pyth(80, 80)
        elif cs.type == CSI.TYPE_DASHAVATARA_GANJIFA:
            cardbase = pyth(80, 80)
        elif cs.type == CSI.TYPE_TRUMP_ONLY:
            cardbase = pyth(35, 35)

        si = canvas.wmain.app.images.getSize()
        cardsize = pyth(si[0], si[1])
        cardscale = cardsize/cardbase
    except:	 # noqa: E722
        pass
    return cardscale

# =============================================================================


class LText(Widget, LBase):
    text = StringProperty('')

    def __init__(self, canvas, x, y, **kwargs):
        super(LText, self).__init__(**kwargs)
        # super(LText, self).__init__()

        if 'text' not in kwargs:
            kwargs['text'] = 'X'

        font = 'helvetica'
        fontsize = 18.0
        if 'font' in kwargs:
            font = kwargs['font'][0]
            fontsize = kwargs['font'][1]
            del kwargs['font']

        self.anchor = 'nw'
        if 'anchor' in kwargs:
            self.anchor = kwargs['anchor']

        self.text = kwargs['text']
        self.coreFontSize = fontsize
        self.coreFont = font

        # print('LText: font = %s, font_size = %s' % (font, fontsize))
        # print('LText: text = %s' % (self.text))

        kwargs['font'] = font
        kwargs['font_size'] = fontsize * cardfactor(canvas)

        class MyLabel(Label, LBase):
            pass

        self.label = MyLabel(**kwargs)
        self.label.texture_update()
        self.coreSize = self.label.texture_size
        self.corePos = (x, y)
        self.prnt = canvas

        # print('LText: corePos = %s, coreSize = %s'
        # % (self.corePos, self.coreSize))

        self.size = self.label.texture_size

        self.bind(size=self.updateCanvas)
        self.bind(pos=self.updateCanvas)
        self.bind(text=self.updateCanvas)

    def updateCanvas(self, inst, val):
        self.label.text = self.text
        self.label.texture_update()

        self.coreSize = self.label.texture_size
        cp = addAnchorOffset(self.corePos, self.coreSize, self.anchor)
        cs = self.coreSize

        pos, size = self.prnt.CoreToKivy(cp, cs)
        # print('LText: pos = %s, size = %s' % (pos, size))

        color = LColorToKivy(self.prnt._text_color)
        # print('LText: color = %s' % str(color))
        self.canvas.clear()
        with self.canvas:
            Color(color[0], color[1], color[2], color[3])
            Rectangle(texture=self.label.texture, pos=pos, size=size)

# =============================================================================


class LEvent(object):
    def __init__(self):
        self.x = 0
        self.y = 0
        self.cardid = -1
        self.char = False
        pass

# =============================================================================


class LLine(Widget, LBase):
    def __init__(self, canvas, args, **kw):
        super(LLine, self).__init__(**kw)

        print('kw = %s%s' % (args, kw))

        lwidth = 10
        fill = '#ee3344'
        ashape = ()
        arrow = 'none'

        self.prnt = canvas
        xmin = 100000
        ymin = 100000
        xmax = -100000
        ymax = -100000
        self.corePoly = []
        if isinstance(args[0], list):
            kww = args[1]
            if ('width' in kww):
                lwidth = kww['width']
            self.lwidth = lwidth
            if ('fill' in kww):
                fill = kww['fill']
            self.fill = fill
            if ('arrowshape' in kw):
                ashape = kw['arrowshape']
            self.ashape = ashape
            if ('arrow' in kw):
                arrow = kw['arrow']
            self.arrow = arrow

            pts = args[0]
            ipts = iter(pts)
            for x, y in zip(ipts, ipts):
                print('%s.%s' % (x, y))
                self.corePoly.append(x)
                self.corePoly.append(y)
                if x < xmin:
                    xmin = x
                if x > xmax:
                    xmax = x
                if y < ymin:
                    ymin = y
                if y > ymax:
                    ymax = y
        else:
            if ('width' in kw):
                lwidth = kw['width']
            self.lwidth = lwidth
            if ('fill' in kw):
                fill = kw['fill']
            self.fill = fill
            if ('arrowshape' in kw):
                ashape = kw['arrowshape']
            self.ashape = ashape
            if ('arrow' in kw):
                arrow = kw['arrow']
            self.arrow = arrow

            for i in range(0, 2):
                x = args[2 * i]
                y = args[2 * i + 1]
                self.corePoly.append(x)
                self.corePoly.append(y)
                if x < xmin:
                    xmin = x
                if x > xmax:
                    xmax = x
                if y < ymin:
                    ymin = y
                if y > ymax:
                    ymax = y

        print('width = %s' % self.lwidth)
        print('color = %s' % self.fill)
        print('arrow = %s' % self.arrow)
        print('ashape = %s' % str(self.ashape))

        self.alist = []
        if self.arrow == 'last':
            self.alist.append(self.corePoly[-2])
            self.alist.append(self.corePoly[-1])
            self.alist.append(self.corePoly[-4])
            self.alist.append(self.corePoly[-3])
        elif self.arrow != 'none':
            self.alist.append(self.corePoly[0])
            self.alist.append(self.corePoly[1])
            self.alist.append(self.corePoly[2])
            self.alist.append(self.corePoly[3])

        self.corePos = (xmin, ymin)
        self.coreSize = (xmax - xmin, ymax - ymin)
        self.pos = self.corePos
        self.size = self.coreSize

        self.bcolor = LColorToKivy(self.fill)

        self.bind(size=self.updateCanvas)
        self.bind(pos=self.updateCanvas)

    def updateCanvas(self, instance, value):
        # size = self.size
        # pos = self.pos

        # Linie:
        poly = None
        poly = []
        dmy, sxy = self.prnt.CoreToKivy(
            (0.0, 0.0), (self.lwidth, self.lwidth))
        wpoly = sxy[1]
        ipts = iter(self.corePoly)
        for x, y in zip(ipts, ipts):
            print('%s.%s' % (x, y))
            xy, dmy = self.prnt.CoreToKivy((x, y))
            poly.append(xy[0])
            poly.append(xy[1])

        def rot(x, y, a):
            x1 = x * math.cos(a) + y * math.sin(a)
            y1 = y * math.cos(a) - x * math.sin(a)
            return (x1, y1)

        # Pfeil:
        PI = 3.1415926
        atrio = None
        atrio = []
        if (len(self.ashape) > 2):
            dx = (self.alist[0] - self.alist[2])
            dy = (self.alist[1] - self.alist[3])
            if (dx == 0.0):
                if (dy > 0.0):
                    ang = -PI / 2.0
                else:
                    ang = PI / 2.0
            else:
                ang = math.atan(dy / dx)
            if (dx > 0.0):
                ang = ang + PI

            # (kante, winkel?)
            x = self.ashape[0] * math.cos(self.ashape[1] * PI / 360.0)
            y = 2.0 * self.ashape[0] * math.sin(self.ashape[1] * PI / 360.0)
            # (länge, breite?)
            # x = self.ashape[0]
            # y = self.ashape[1]
            o = self.ashape[2]
            axy, dmy = self.prnt.CoreToKivy((self.alist[0], self.alist[1]))
            dmy, asxy = self.prnt.CoreToKivy((0, 0), (x, y))
            dmy, aoff = self.prnt.CoreToKivy((0, 0), (o, o))
            print('asxy=%s' % str(asxy))

            x1, y1 = rot(-aoff[0], 0.0, ang)
            atrio.append(x1 + axy[0])
            atrio.append(y1 + axy[1])
            x1, y1 = rot(asxy[0] - aoff[0], asxy[1], ang)
            atrio.append(x1 + axy[0])
            atrio.append(y1 + axy[1])
            x1, y1 = rot(asxy[0] - aoff[0], -asxy[1], ang)
            atrio.append(x1 + axy[0])
            atrio.append(y1 + axy[1])

        self.canvas.clear()
        with self.canvas:
            Color(self.bcolor[0], self.bcolor[1],
                  self.bcolor[2], self.bcolor[3])
            Line(points=poly, width=wpoly, cap='none', joint='bevel')
            if (len(atrio) > 2):
                Triangle(points=atrio)

# =============================================================================


class LRectangle(Widget, LBase):
    def __init__(self, prnt, args, **kw):
        super(LRectangle, self).__init__(**kw)
        self.prnt = prnt

        # print('width   %s' % kw['width'])
        # print('outline %s' % kw['outline'])
        # print('fill    %s' % kw['fill'])

        width = 10.0
        if ('width' in kw):
            width = float(kw['width'])

        bcolor = '#ffa000a0'
        if ('outline') in kw:
            bcolor = kw['outline']
        if (not bcolor or len(bcolor) < 7):
            bcolor = '#ffa000a0'

        fcolor = '#00aaff20'
        if ('fill') in kw:
            fcolor = kw['fill']
        if (not fcolor or len(fcolor) < 7):
            fcolor = '#00aaff20'

        self.group = None
        if 'group' in kw:
            self.group = kw['group']

        xmin = float(args[0])
        ymin = float(args[1])
        xmax = float(args[2])
        ymax = float(args[3])

        # print ('LRectangle: min = %s.%s' % (xmin, ymin))
        # print ('LRectangle: max = %s.%s' % (xmax, ymax))
        # print ('LRectangle: border = %s' % (width))

        self.border = width
        self.fcolor = LColorToKivy(fcolor)
        self.bcolor = LColorToKivy(bcolor)

        self.corePos = (xmin, ymin)
        self.coreSize = (xmax - xmin, ymax - ymin)
        self.pos = self.corePos
        self.size = self.coreSize
        self.topleft = (xmin + width / 2.0, ymin + width / 2.0)
        self.bottomright = (xmax - width / 2.0, ymax - width / 2.0)
        self.poly = None

        self.bind(size=self.updateCanvas)
        self.bind(pos=self.updateCanvas)

    def updateCanvas(self, instance, value):
        # print('LRectangle: updateCanvas')

        pos, size = self.prnt.CoreToKivy(self.corePos, self.coreSize)
        bpos, dmy = self.prnt.CoreToKivy(self.topleft)
        tpos, dmy = self.prnt.CoreToKivy(self.bottomright)

        poly = [bpos[0], bpos[1],
                tpos[0], bpos[1],
                tpos[0], tpos[1],
                bpos[0], tpos[1],
                bpos[0], bpos[1]]

        cf = cardfactor(self.prnt)
        dmy, brd = self.prnt.CoreToKivy(
            (0.0, 0.0), (self.border, self.border))
        border = brd[1] * cf

        self.canvas.clear()
        with self.canvas:
            Color(self.fcolor[0], self.fcolor[1],
                  self.fcolor[2], self.fcolor[3])
            Rectangle(pos=pos, size=size)
            Color(self.bcolor[0], self.bcolor[1],
                  self.bcolor[2], self.bcolor[3])
            Line(points=poly, width=border)

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            if self.group is not None:
                logging.info('LRectangle: self=%s group=%s' %
                             (self, self.group))
                if '<1>' in self.group.bindings:
                    # logging.info('LRectangle: size=%s' % (self.size))
                    ppos, psize = self.group.canvas.KivyToCore(touch.pos)
                    event = LEvent()
                    event.x = ppos[0]
                    event.y = ppos[1]
                    if touch.is_double_tap:
                        self.group.bindings['<Double-1>'](event)
                    else:
                        self.group.bindings['<1>'](event)
                    return True
        return False

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            if self.group is not None:
                logging.info('LRectangle: self=%s group=%s' %
                             (self, self.group))
                if '<ButtonRelease-1>' in self.group.bindings:
                    ppos, psize = self.group.canvas.KivyToCore(touch.pos)
                    event = LEvent()
                    event.x = ppos[0]
                    event.y = ppos[1]
                    self.group.bindings['<ButtonRelease-1>'](event)
                    return True
        return False

# =============================================================================
# Represents a Card as Kivy Window. Will contain an LImage item as child.
# Images are managed in cards.py according to the cards state. Processes
# Events/Action on the card or other images, as LImage is designed to not
# process events. Should not take more than one child (LImage) at a time.


class LImageItem(BoxLayout, LBase):
    def __init__(self, **kw):
        super(LImageItem, self).__init__(**kw)
        self.game = None
        self.card = None
        self.group = None
        self.image_type = "undefined"
        if 'game' in kw:
            self.game = kw['game']
        if 'card' in kw:
            self.card = kw['card']
            self.image_type = "card"
        if 'group' in kw:
            self.group = kw['group']
        if 'image_type' in kw:
            self.image_type = kw['image_type']

        self.dragstart = None
        # ev. noch globales cache für stacks->game und cards->stack
        # einrichten. Aber: stacks hängt vom jeweiligen spiel ab.

    def __str__(self):
        return f'<LImageItem @ {hex(id(self))}>'

    def get_image_type(self):
        return self.image_type

    def send_event_pressed_n(self, event, n):
        if self.group and n in self.group.bindings:
            self.group.bindings[n](event)

    def send_event_pressed(self, touch, event):

        if touch.is_double_tap:
            self.send_event_pressed_n(event, '<Double-1>')
        else:
            button = 'left'
            if 'button' in touch.profile:
                button = touch.button
            if button == 'left':
                self.send_event_pressed_n(event, '<1>')
                return
            if button == 'middle':
                self.send_event_pressed_n(event, '<2>')
                return
            if button == 'right':
                self.send_event_pressed_n(event, '<3>')
                return

    def on_touch_down(self, touch):

        # print('LCardImage: size = %s' % self.size)
        if self.collide_point(*touch.pos):

            if (self.game):
                for stack in self.game.allstacks:
                    for i in range(len(stack.cards)):
                        if stack.cards[i] == self.card:
                            print('LCardImage: stack = %s' % stack)
                            print('LCardImage: touch = %s' % str(touch))
                            print('grab')
                            # grab the touch!
                            touch.grab(self)
                            ppos, psize = self.game.canvas.KivyToCore(
                                touch.pos, self.size)
                            event = LEvent()
                            event.x = ppos[0]
                            event.y = ppos[1]
                            self.dragstart = touch.pos
                            event.cardid = i
                            self.send_event_pressed(touch, event)
                            AndroidScreenRotation.lock(toaster=False)
                            return True

            if self.group is not None:
                print('LCardImage: self=%s group=%s' % (self, self.group))
                if '<1>' in self.group.bindings:
                    ppos, psize = self.group.canvas.KivyToCore(
                        touch.pos, self.size)
                    event = LEvent()
                    event.x = ppos[0]
                    event.y = ppos[1]
                    self.group.bindings['<1>'](event)
                    return True

            if self.card is None:
                return False
            if self.game is None:
                return False

        # print('LCardImage: touch_down on %s' % str(touch.pos))
        return False

    def send_event_released_1(self, event):
        if self.group and '<ButtonRelease-1>' in self.group.bindings:
            self.group.bindings['<ButtonRelease-1>'](event)

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            # release my grabbed touch!
            print('ungrab')
            touch.ungrab(self)
            return True

        if self.collide_point(*touch.pos):

            if (self.game):
                for stack in self.game.allstacks:
                    for i in range(len(stack.cards)):
                        if stack.cards[i] == self.card:
                            print('LCardImage: stack = %s' % stack)
                            ppos, psize = self.game.canvas.KivyToCore(
                                touch.pos, self.size)
                            event = LEvent()
                            event.x = ppos[0]
                            event.y = ppos[1]
                            event.cardid = i
                            self.send_event_released_1(event)
                            return True

            if self.group is not None:
                print('LCardImage: self=%s group=%s' % (self, self.group))
                if '<ButtonRelease-1>' in self.group.bindings:
                    ppos, psize = self.group.canvas.KivyToCore(
                        touch.pos, self.size)
                    event = LEvent()
                    event.x = ppos[0]
                    event.y = ppos[1]
                    self.group.bindings['<ButtonRelease-1>'](event)
                    return True

            if self.card is None:
                return False
            if self.game is None:
                return False

        # print('LCardImage: touch_up on %s' % str(touch.pos))
        return False

    def on_touch_move(self, touch):
        # behandeln nur wenn grabbed
        if touch.grab_current is not self:
            return False
        if 'pos' not in touch.profile:
            return False

        print('LCardImage: touch_move on %s' % str(touch.pos))

        for stack in self.game.allstacks:
            for i in range(len(stack.cards)):
                if stack.cards[i] == self.card:
                    print('LCardImage: stack = %s/%s' % (stack, touch))
                    ppos, psize = self.game.canvas.KivyToCore(
                        touch.pos, self.size)
                    event = LEvent()
                    event.x = ppos[0]
                    event.y = ppos[1]
                    event.cardid = i
                    stack._motionEventHandler(event)
                    return True

        # print('LCardImage: touch_move on %s' % str(touch.pos))
        return False

# =============================================================================
# Treeview


class LTreeRoot(TreeView, LBase):
    def __init__(self, **kw):
        super(LTreeRoot, self).__init__(**kw)
        self.kw = kw

    def closeLastNode(self):
        ret = False
        lastopen = None
        for ti in reversed(self.children):
            if isinstance(ti, LTreeNode):
                if ti.is_open:
                    lastopen = ti

        if lastopen is not None:
            self.toggle_node(lastopen)
            self.select_node(lastopen)
            ret = True

        return ret


class LTreeNode(ButtonBehavior, TreeViewLabel, LBase):

    # def __init__(self, gameview, **kw):
    def __init__(self, **kw):
        self.command = None
        if 'command' in kw:
            self.command = kw['command']
        self.variable = None
        if 'variable' in kw:
            self.variable = kw['variable']
        if 'value' in kw:
            self.value = kw['value']
        if ('text' in kw):
            self.title = kw['text']

        super(LTreeNode, self).__init__(markup=True, **kw)

        if self.variable:
            self.variable.bind(value=self.onVarChange)
            self.onVarChange(self.variable, self.variable.value)

        # self.gameview = gameview
        self.coreFont = self.font_size
        # self.scaleFont(self.gameview.size[1])
        # self.gameview.bind(size=self.scaleFontCB)
        # nicht skalieren!

        self.bind(on_release=self.on_released)
        self.bind(is_selected=self.onSelect)
        self.bind(is_open=self.onOpen)

    def onVarChange(self, instance, value):
        # print('LTreeNode: onVarChange(%s, %s, %s)'
        # % (instance, value, type(value)))
        if type(value) is bool:
            self.setCheck(value)
        if type(value) is int:
            self.setVal(value)
        if type(value) is str:
            self.setVal(value)
        # if type(value) is unicode:
        #     self.setVal(value)

    def setCheck(self, value):
        # print('LTreeNode: setCheck(%s)' % value)
        if value:
            # self.text = '+ '+self.title
            self.text = '[b]+[/b] ' + self.title
        else:
            self.text = '- ' + self.title
        self.texture_update()

    def setVal(self, value):
        # print('LTreeNode: setVal(%s)' % value)
        if value == self.value:
            # fs = str(int(self.font_size+2))
            # print ('%s.%s' % (self.font_size, fs))
            # self.text = '[size='+fs+'][b]'+self.title+'[/b][/size]'
            # self.text = 'o '+self.title
            self.text = '[b]o[/b] ' + self.title
            # self.text = u'\u25cf '+self.title  # unicode filled circle
        else:
            self.text = self.title
            self.text = u'    ' + self.title
            # self.text = u'\u25cb  '+self.title # unicode open circle
        self.texture_update()

    # font skalierung.

    def scaleFont(self, value):
        self.font_size = int(self.coreFont * value / 550.0)

    def scaleFontCB(self, instance, value):
        self.scaleFont(value[1])

    # benutzer interaktion.

    def onSelect(self, instance, val):
        if val:
            print('select %s' % self.title)
        else:
            print('deselect %s' % self.title)
        pass

    def collapseChildren(self, deep=False):

        def cc(p, n):
            for c in n.nodes:
                if c.is_open:
                    cc(p, c)
                    p.toggle_node(c)

        p = self.parent
        if p and isinstance(p, LTreeRoot):
            for n in self.nodes:
                if n.is_open:
                    # n.collapseChildren()   # ginge nur mit LTreeNode!
                    if deep:
                        cc(p, n)            # geht mit allen TreeViewNode
                    p.toggle_node(n)

    def collapseSiblings(self, deep=True):

        def cc(p, n):
            for c in n.nodes:
                if c.is_open:
                    cc(p, c)
                    p.toggle_node(c)

        p = self.parent
        if p and isinstance(p, LTreeRoot):
            # print('expand: LTreeRoot')
            for n in p.root.nodes:
                # print('expand: -> check %s' % n.title)
                if n != self and n.is_open and n.level >= self.level:
                    # print('expand: -> close %s' % n.title)
                    if deep:
                        cc(p, n)
                    p.toggle_node(n)

            pn = self.parent_node
            if pn and isinstance(pn, LTreeNode):
                # print('expand: LTreeNode')
                for n in pn.nodes:
                    # print('expand: -> check %s' % n.title)
                    if n != self and n.is_open and n.level >= self.level:
                        # print('expand: -> close %s' % n.title)
                        if deep:
                            cc(p, n)
                        p.toggle_node(n)

    def onOpen(self, instance, val):
        if val:
            # print('expand %s, %s' % (self.level, self.title))
            self.collapseSiblings(deep=False)
        else:
            # print('collapse %s, %s' % (self.level, self.title))
            pass

    def on_released(self, v):
        if self.command:
            Clock.schedule_once(self.commandCB, 0.1)
        else:
            Clock.schedule_once(self.toggleCB, 0.1)

    def commandCB(self, d):
        self.command()

    def toggleCB(self, d):
        # hier könnte der knoten ev. auch neu aufgebaut werden ?!
        self.parent.toggle_node(self)

# =============================================================================


class LTopLevelContent(BoxLayout, LBase):
    def __init__(self, **kw):
        super(LTopLevelContent, self).__init__(**kw)

        # Macht die Hintergrundfarbe der TopLevel (Dialog-) Fenster.
        with self.canvas.before:
            Color(0.45, 0.5, 0.5, 1.0)
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(pos=self.update_rect)
        self.bind(size=self.update_rect)

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size

    def wm_minsize(self, w, h):
        pass

# =============================================================================


class LTopLine(ButtonBehavior, Label, LBase):

    def __init__(self, **kw):
        super(LTopLine, self).__init__(**kw)
        with self.canvas.before:
            Color(0.45, 0.3, 0.3, 1.0)
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(pos=self.update_rect)
        self.bind(size=self.update_rect)
        self.maxlines = 0
        self.halign = 'center'
        self.valign = 'center'

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size

    def on_size(self, o, s):
        self.text_size = s

    def on_press(self):
        print('press')

    def on_release(self):
        print('release')

# =============================================================================


class LTopLevelBase(BoxLayout, LBase):
    def __init__(self, title='', **kw):
        super(LTopLevelBase, self).__init__(orientation="vertical", **kw)
        self.title = title
        self.titleline = LTopLine(text=title, size_hint=[1.0, 0.15])
        self.content = LTopLevelContent(orientation="vertical", **kw)
        self.add_widget(self.titleline)
        self.add_widget(self.content)

# =============================================================================


class LTopLevel0(LTopLevelBase, LBase):
    def __init__(self, top, title='', **kw):
        super(LTopLevel0, self).__init__(title=title, **kw)

        self.main = top
        self.titleline.bind(on_press=self.onClick)
        self.main.pushWork(self.title, self)

    def onClick(self, event):
        print('LTopLevel: onClick')
        self.main.popWork(self.title)

# =============================================================================


class LTopLevel(LTopLevelBase, LBase):
    def __init__(self, parent, title='', **kw):
        super(LTopLevel, self).__init__(title=title, **kw)
        self.mainwindow = parent

    def processAndroidBack(self):
        ret = False
        # try to collapse the last open tree node
        # the treeview will be located inside of a scrollview
        # (-> menubar.py)
        for c in self.content.children:
            # print("childitem: %s" % str(c))
            if isinstance(c, LScrollView):
                for t in reversed(c.children):
                    # print("  childitem: %s" % str(t))
                    if isinstance(t, LTreeRoot):
                        ret = t.closeLastNode()
            if isinstance(c, BoxLayout):
                for t in reversed(c.children):
                    # print("  childitem: %s" % str(t))
                    if isinstance(t, LPopCommander):
                        ret = t.pop()
                    pass
        return ret

# =============================================================================


class LMenuBar(BoxLayout, LBase):
    def __init__(self, **kw):
        super(LMenuBar, self).__init__(**kw)
        self.menu = None
        self.size_hint = (1.0, 0.08)

    def setMenu(self, menu):
        print('LMenuBar: setMenu %s, %s' % (self, menu))

        # Letztes Menu entfernen
        last = self.menu
        if (last is not None):
            self.remove_widget(last)
            self.menu = None

        # Neues Menu einfügen
        if (menu is not None):
            self.add_widget(menu)
            self.menu = menu
            menu.setBar(self)

    def getMenu(self):
        return self.menu

# =============================================================================


class LMenu(ActionView, LBase):
    def __init__(self, prev, **kw):
        super(LMenu, self).__init__(**kw)

        class MyActionPrev(ActionPrevious, LBase):
            pass

        kw['app_icon'] = 'data/images/icons/48x48/pysol.png'
        kw['with_previous'] = prev
        kw['size_hint'] = (.01, 1)
        self.ap = MyActionPrev(**kw)
        self.add_widget(self.ap)
        self.bar = None
        self.uppermenu = None

    def addItem(self, mi):
        # print ('LMenu: addItem '+str(mi)+' '+str(self.bar))
        mi.setBar(self.bar)
        self.add_widget(mi)

    def setBar(self, bar):
        # print ('LMenu: setBar %s, %s' % (self, bar))
        self.bar = bar

    def prev(self, menu):
        # print ('LMenu: prev = %s' % menu)
        self.uppermenu = menu
        self.ap.bind(on_release=self.upper)
        pass

    def upper(self, event):
        print('upper')
        self.bar.setMenu(self.uppermenu)

    def delete(self, pos, mode):
        # print ('LMenu(%s): delete(%s, %s)' % (self, pos, mode))

        items = []
        menues = []
        for c in self.children:
            if (type(c) is LMenuItem):
                # print ('LMenu: to delete child %s' % c)
                items.append(c)
            elif (type(c) is LMenu):
                # print ('LMenu: to delete child %s' % c)
                menues.append(c)
            else:
                # print ('LMenu: unknown child %s' % c)
                pass

        for c in items:
            # print ('LMenu: delete child %s' % c)
            self.clear_widgets([c])
        for c in menues:
            # print ('LMenu: delete child %s' % c)
            self.clear_widgets([c])
            c.delete(pos, mode)

    # def __str__(self):
    #   return hex(id(self))

# =============================================================================


class LMenuItem(ActionButton, LBase):

    def __init__(self, menu, **kw):
        super(LMenuItem, self).__init__(**kw)
        # super(LMenuItem, self).__init__()
        self.bar = None
        self.submenu = None
        self.menu = menu
        self.menu.addItem(self)
        self.minimum_width = '200sp'
        if 'command' in kw:
            self.setCommand(kw['command'])
        if 'submenu' in kw:
            self.setSubMenu(kw['submenu'])

    def setBar(self, bar):
        # print ('LMenuItem: setBar %s, %s' % (self, bar))
        self.bar = bar

    def onClick(self, event):
        # print('LMenuItem: onClick')
        # print('LMenuItem: submenu vorh: '+str(self.submenu))
        self.bar.setMenu(self.submenu)
        return True

    def setSubMenu(self, submenu):
        # print('LMenuItem: setSubMenu')
        self.submenu = submenu
        # print('LMenuItem: setSubMenu: '+str(self.submenu))
        self.submenu.prev(self.menu)
        self.submenu.setBar(self.bar)
        self.bind(on_release=self.onClick)
        pass

    def setCommand(self, cmd):
        print('LMenuItem: setCommand')
        self.bind(on_release=cmd)

    # def __str__(self):
    #   return hex(id(self))

# =============================================================================


class LScrollView(ScrollView, LBase):
    def __init__(self, **kw):
        super(LScrollView, self).__init__(**kw)
        self.delayDown = False
        self.touch = None

    def delayReset(self, dt):
        if not self.delayDown:
            return
        self.delayDown = False
        ScrollView.on_touch_down(self, self.touch)

    # Scroll ist original viel zu flink auf den Touchgeräten.
    # Wir versuchen das hier etwas abzuschwächen.

    def on_touch_down(self, touch):
        self.delayDown = True
        self.touch = touch
        Clock.schedule_once(self.delayReset, 0.15)

    def on_touch_up(self, touch):
        if self.delayDown:
            ScrollView.on_touch_down(self, self.touch)
        self.delayDown = False
        return ScrollView.on_touch_up(self, touch)

    def on_touch_move(self, touch):
        return ScrollView.on_touch_move(self, touch)

# =============================================================================


class LWorkWindow(Widget):
    def __init__(self):
        super(LWorkWindow, self).__init__()

        # beispiel zu canvas (hintergrund)
        with self.canvas.before:
            Color(0, 1, 1, 0.4)
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(pos=self.update_rect)
        self.bind(size=self.update_rect)

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size

    def on_touch_down(self, touch):
        print('LWorkWindow: touch_down on %s' % str(touch.pos))
        # return True

# =============================================================================


class LTkBase:
    # Tk Emulation needs.
    def __init__(self):
        self.title = "default title"
        self.icontitle = "default title"
        logging.info("LTkBase: __init__()")
        self.sleeping = False
        self.in_loop = False
        self.screenSize = (1000, 1000)

    def cget(self, strg):
        return False

    def wm_title(self, strg):
        self.title = strg
        logging.info("LTkBase: wm_title %s" % strg)
        if (self.app):
            # self.app.top.topLine.text = strg
            self.app.top.getMenu().ap.title = strg

    def wm_iconname(self, strg):
        self.icontitle = strg
        logging.info("LTkBase: wm_iconname %s" % strg)

    def eval_screen_dim(self, size):
        self.screenSize = size
        if get_platform() == 'android':
            from jnius import autoclass
            from jnius import cast
        else:
            return

        PythonActivity = autoclass('org.kivy.android.PythonActivity')
        currentActivity = cast(
            'android.app.Activity', PythonActivity.mActivity)
        wm = currentActivity.getWindowManager()
        d = wm.getDefaultDisplay()

        self.screenSize = (d.getWidth(), d.getHeight())

    def winfo_screenwidth(self):
        logging.info("LTkBase: winfo_screenwidth %s" % str(self.size[0]))
        return self.size[0]

    def winfo_screenheight(self):
        logging.info("LTkBase: winfo_screenheight %s" % str(self.size[1]))
        return self.size[1]

    def winfo_screendepth(self):
        return 32

    def wm_minsize(self, x, y):
        pass

    def option_add(self, a, b, c):
        pass

    def option_get(self, a, b):
        return 0

    def wm_withdraw(self):
        logging.info("LTkBase: wm_withdraw")
        pass

    def busyUpdate(self):
        print('LTkBase: busyUpdate()')
        pass

    def grid_columnconfigure(self, a, weight):
        pass

    def grid_rowconfigure(self, a, weight):
        pass

    def connectApp(self, app):
        logging.info("LTkBase: connectApp %s" % str(app))
        self.app = app
        pass

    def wm_geometry(self, val):
        logging.info("LTkBase: wm_geometry %s" % str(val))
        pass

    def update_idletasks(self):
        # logging.info("LTkBase: update_idletasks")
        try:
            if len(EventLoop.event_listeners) > 0:
                self.in_loop = True
                EventLoop.idle()
                self.in_loop = False
            else:
                logging.info("LTkBase: update_idletasks: terminating")
        except Exception:
            self.in_loop = False
            logging.info("LTkBase: update_idletasks: exception")

    def wm_state(self):
        return ""

    def wm_deiconify(self):
        pass

    def mainloop(self):
        logging.info("LTkBase: mainloop")
        pass

    def quit(self):
        logging.info("LTkBase: quit")
        stopTouchApp()

    def interruptSleep(self):
        # logging.info('LTkBase: interruptSleep')
        self.update_idletasks()
        # self.sleep_var = 1
        return

    def mainquit(self):
        logging.info('LTkBase: mainquit')
        lapp = App.get_running_app()
        lapp.mainloop.send(None)    # Spielprozess verlassen
        return

    def onWakeUp(self, dt):
        self.sleeping = False

    def sleep(self, seconds):
        logging.info('LTkBase: sleep %s seconds' % seconds)
        self.sleeping = True
        Clock.schedule_once(self.onWakeUp, seconds)
        while self.sleeping:
            # time.sleep(0.05)
            self.in_loop = True
            EventLoop.idle()
            self.in_loop = False

    def waitCondition(self, condition, swallow=False, pickup=False):
        # logging.info('LTkBase: wait condition start')
        while condition():
            self.in_loop = True
            if swallow:  # eat picked input up
                for provider in EventLoop.input_providers:
                    provider.update(dispatch_fn=lambda *x: None)
            EventLoop.idle()
            if pickup:  # pick input from os
                if EventLoop.window:
                    EventLoop.window.mainloop()
            self.in_loop = False
        # logging.info('LTkBase: wait condition end')

    def waitAnimation(self, swallow=False, pickup=False):
        self.waitCondition(LAnimationManager.checkRunning,
                           swallow=swallow,
                           pickup=pickup)

    def tkraise(self):
        pass

    def winfo_ismapped(self):
        return True
        # ???

# =============================================================================


class LStack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def push(self, key, item):
        self.items.append((key, item))

    def pop(self, key):
        for i in range(len(self.items)):
            t = self.items[i]
            if (t[0] == key):
                self.items.pop(i)
                return t[1]
        return None

    def peek(self, key):
        for i in range(len(self.items)):
            t = self.items[i]
            if (t[0] == key):
                return t
        return None

    def size(self):
        return len(self.items)

# =============================================================================


class LMainWindow(BoxLayout, LTkBase):

    longPress = NumericProperty(0)

    def __init__(self, **kw):
        super(LMainWindow, self).__init__(orientation='vertical')
        LTkBase.__init__(self)
        self.menuArea = LMenuBar()
        self.workContainer = LBoxLayout(orientation='horizontal')
        self.workContainerO = LBoxLayout(orientation='horizontal')
        self.workContainer1 = LBoxLayout(orientation='vertical')
        self.workArea = None
        self.toolBar = None
        self.toolBarPos = 0
        self.bindings = {}
        self._w = '.'

        self.add_widget(self.menuArea)
        self.add_widget(self.workContainerO)
        self.workContainerO.add_widget(self.workContainer)

        self.workStack = LStack()
        self.app = None

        '''
        from kivy.graphics import opengl_utils
        print('OPENGL support:')
        print(opengl_utils.gl_get_extensions())
        '''

        # self.touches = []

        with self.canvas.before:
            Color(0.15, 0.15, 0.15, 1)
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(pos=self.update_rect)
        self.bind(size=self.update_rect)

    def update_rect(self, *args):
        self.pos = (0, 0)
        self.rect.pos = self.pos
        self.rect.size = self.size

    def on_motion(self, m):
        print('on_motion', m)
        pass

    # Events.

    def on_touch_down(self, touch):
        ret = False
        if super().on_touch_down(touch):
            return True

        # print(dir(touch))

        # multitouch detection
        '''
        #print("MainWindow touch_down",touch.ox,touch.oy)
        #print("MainWindow touch_down",touch.sx,touch.sy)
        #print("MainWindow touch_down",touch.px,touch.py)
        self.touches.append(touch)
        print("touches cnt = ",len(self.touches))
        '''
        # multiclick detection

        if touch.is_double_tap:
            # print('Touch is a double tap !')
            # print(' - interval is', touch.double_tap_time)
            # print(' - distance betw. previous is', touch.double_tap_distance)
            AndroidScreenRotation.unlock()

        '''
        if touch.is_triple_tap:
            print('Touch is a triple tap !')
            print(' - interval is', touch.triple_tap_time)
            print(' - distance between previous is', touch.triple_tap_distance)
        '''
        # (Eventloop reentrancy check)
        if self.in_loop:
            return ret

        # (demo mode stop - nur auf spielfläche)
        if '<KeyPress>' in self.bindings:
            pgs = self.workStack.peek('playground')
            if pgs:
                pg = pgs[1]
                if pg.collide_point(*touch.pos):
                    event = LEvent()
                    event.char = True
                    self.bindings['<KeyPress>'](event)

        # standard notifikation:
        for c in self.children:
            ret = c.on_touch_down(touch)
            if ret:
                break
        return ret

    def on_touch_up(self, touch):
        ret = False
        if super().on_touch_up(touch):
            return True

        # long press only on playground.
        pgs = self.workStack.peek('playground')
        if pgs:
            pg = pgs[1]
            if pg.collide_point(*touch.pos):
                if (touch.time_end-touch.time_start) > 2.5:
                    self.longPress = touch.time_end

        # standard notifikation:
        for c in self.children:
            ret = c.on_touch_up(touch)
            if ret:
                break

        # multitouch support
        '''
        self.touches = [xx for xx in self.touches if xx != touch]
        print("touches cnt = ",len(self.touches))
        '''
        return ret

    def on_longPress(self, instance, timestamp):
        print('longPressed at {time}'.format(time=timestamp))

    # Menubar:

    def setMenu(self, menu):
        self.menuArea.setMenu(menu)

    def getMenu(self):
        return self.menuArea.getMenu()

    # Toolbar:

    def setTool(self, toolbar, pos=0):
        if (toolbar is not None):
            self.toolBar = toolbar
        self.toolBarPos = pos
        self.rebuildContainer()

    # Workarea:

    def removeContainer(self):
        self.workContainer.clear_widgets()
        self.workContainerO.clear_widgets()
        self.workContainer1.clear_widgets()

    def buildContainer(self):
        # (hbox)
        if self.toolBar is not None and self.toolBarPos == 3:
            self.workContainerO.add_widget(self.toolBar)
        self.workContainerO.add_widget(self.workContainer1)
        if self.toolBar is not None and self.toolBarPos == 4:
            self.workContainerO.add_widget(self.toolBar)
        # (vbox)
        if self.toolBar is not None and self.toolBarPos == 1:
            self.workContainer1.add_widget(self.toolBar)
        self.workContainer1.add_widget(self.workContainer)
        if self.toolBar is not None and self.toolBarPos == 2:
            self.workContainer1.add_widget(self.toolBar)
        # (workcontainer)
        for w in self.workStack.items:
            self.workContainer.add_widget(w[1])

    def rebuildContainer(self):
        LAfterAnimation(self.removeContainer)
        LAfterAnimation(self.buildContainer)

    def pushWork(self, key, widget):
        if (widget):
            self.workStack.push(key, widget)
            self.rebuildContainer()

    def popWork(self, key):
        w = None
        if self.workStack.size() > 0:
            w = self.workStack.pop(key)
            self.rebuildContainer()
        return w

    def setWork(self, key, widget):
        self.pushWork(key, widget)

    def getWork(self, key):
        return self.workStack.peek(key)

    def processAndroidBack(self):
        ret = False
        # try to close currently open popup windows, one by one
        r = range(len(self.workStack.items))
        rr = reversed(r)
        for i in rr:
            t = self.workStack.items[i]
            # print("stackkey:  %s" % str(t[0]))
            # print("stackitem: %s" % str(t[1]))
            if t[0] == 'playground':
                pass
            else:
                if isinstance(t[1], LTopLevel):
                    ret = t[1].processAndroidBack()
                if not ret:
                    self.popWork(t[0])
                    ret = True
            if ret:
                break
        return ret

# =============================================================================


class LApp(App):

    # Handling of android return key
    def key_input(self, window, key, scancode, codepoint, modifier):
        if key == 27:
            # Back key of Android.
            lapp = App.get_running_app()
            app = lapp.app
            if app is None:
                return False  # delegate

            # redirect to mainwindow to close popups, tree nodes
            # and html pages.
            if (self.mainWindow.processAndroidBack()):
                return True  # consumed

            # redirect to game to undo last step
            app.menubar.mUndo()
            return True     # consumed
        else:
            return False    # delegate

    def __init__(self, args):
        super(LApp, self).__init__()
        self.args = args
        self.title = "PySolFC"
        self.baseWindow = FloatLayout()

    def build(self):
        class MyLabel(Label, LBase):
            def __init__(self, **kw):
                super(MyLabel, self).__init__(**kw)
                with self.canvas.before:
                    Color(0.05, 0.05, 0.05, 1)
                    self.rect = Rectangle(pos=self.pos, size=self.size)
                self.bind(pos=self.update_rect)
                self.bind(size=self.update_rect)

            def update_rect(self, *args):
                self.rect.pos = self.pos
                self.rect.size = self.size

        # self.startLabel = MyLabel(text="PySolFC", color=[0.9,0.9,0.9,1]) # noqa
        # self.baseWindow.add_widget(self.startLabel)

        return self.baseWindow

    def app_start(self, *args):
        logging.info("LApp: app_start")
        logging.info('top = %s' % str(self.baseWindow))

        self.mainWindow = LMainWindow()
        Cache.register('LAppCache', limit=10)
        Cache.append('LAppCache', 'baseWindow', self.baseWindow, timeout=0)
        Cache.append('LAppCache', 'mainWindow', self.mainWindow, timeout=0)
        Cache.append('LAppCache', 'mainApp', self, timeout=0)
        self.startCode = 0
        Window.bind(on_keyboard=self.key_input)

        from pysollib.app import Application
        from pysollib.main import pysol_init

        self.app = app = Application()
        app.top = self.baseWindow
        self.startCode = pysol_init(app, self.args)
        if self.startCode > 0:
            logging.info("LApp: on_start fails")
            return

        self.mainloop = self.app.mainproc()  # Einrichten
        logging.info("LApp: mainproc initialised sending start signal")
        self.mainloop.send(None)             # Spielprozess starten
        logging.info("LApp: app_start processed, returned to kivy mainloop")

        self.baseWindow.add_widget(self.mainWindow,index=1) # noqa
        self.baseWindow.opacity = 0
        anim = Animation(opacity=1,duration=0.7) # noqa
        Clock.schedule_once(lambda dt: anim.start(self.baseWindow),0.3) # noqa
        Clock.schedule_once(lambda dt: set_fullscreen(True),0.0) # noqa

    def on_start(self):
        logging.info("LApp: on_start")

        # Window.update_viewport()
        # There is still a black screen gap between android splash
        # and displayed app window. But seems to depend on the device
        # used. There are actual discussions running on that. Some
        # suggest its a SDL2 issue.

        debug = False
        if debug:
            # Investigation of the black screen gap on start.
            # For testing we introduce a button. The app will start
            # when the button is pressed.

            # It gets more and more clear that the black screen is
            # screen refresh problem. only arising, when in buildozer.spec
            # fullscreen = 1 is set.
            # Without fullscreen, Initialisation works perfectly and
            # the start button is displayed right after the splash has been
            # removed.
            # In fullscreen mode the button is still there but not
            # visible. It can be pressed to start the app.
            # When the (black) device is rotated the button is displayed
            # because this initiates a screen refresh.
            # In the SDLActivity there are interface functions available.
            # One of these allows to switch between normal and fullscreen.
            # If this function is used, then the complete behaviour of
            # fullscreen is much better. Fullscreen is stable
            # this way and does not switch back occasionally.

            # The result of that investigation is:
            # - App will now be built with fullscreen = 0 in buildozer.spec
            # - Fullscreen is switched on at runtime after the app has fully
            #   started using the SDLActivity command.

            from kivy.uix.button import Button
            self.mybutton = Button(text='startbutton')
            def addbutton(dt):  # noqa
                self.baseWindow.add_widget(self.mybutton)
            def delaystart(*args): # noqa
                self.baseWindow.remove_widget(self.mybutton)
                Clock.schedule_once(self.app_start, 0.5)
            self.mybutton.bind(on_press=delaystart)
            Clock.schedule_once(addbutton, 1.5)
        else:
            Clock.schedule_once(self.app_start, 0.0)
            # self.app_start(0)

        # Android: Request missing android permissions.
        requestStoragePerm()
        logging.info("LApp: on_start processed")

        # NOTE: The Kivy Eventloop starts after this call
        # to process input and events. (NOT EARLIER!). This is
        # also the point, where the android splash screen will be
        # removed.

    def on_stop(self):
        # Achtung wird u.U. 2 mal aufgerufen !!!
        logging.info("LApp: on_stop")
        if self.startCode > 0:
            return
        # lapp: erweiterte klasse dieser (mit pysolfc app members).
        lapp = App.get_running_app()
        lapp.app.menubar.mHoldAndQuit()

    def on_pause(self):
        logging.info("LApp: on_pause")
        # return True: wenn wir wirklich in pause gehen. Dann wird auch
        # resume aufgerufen falls die app wieder aktiviert wird.
        # return False: app wird gestoppt (on_stop wird aufgerufen)
        if self.startCode > 0:
            return False

        pauseSupport = True
        # True ist die bessere Variante.

        lapp = App.get_running_app()
        app = lapp.app
        if app is None:
            return

        logging.info("LApp: on_pause - pause on")
        # set pause
        if not app.game.pause:
            app.game.doPause()

        logging.info("LApp: on_pause - savegame")
        # save game
        try:
            app.game.gstats.holded = 1
            app.game._saveGame(app.fn.holdgame)
            app.opt.game_holded = app.game.id
            app.opt.last_gameid = app.game.id
        except Exception:
            traceback.print_exc()
            pass
        # save options
        try:
            app.saveOptions()
        except Exception:
            traceback.print_exc()
            pass
        # save statistics
        try:
            app.saveStatistics()
        except Exception:
            traceback.print_exc()
            pass
        logging.info("LApp: on_pause - gamesaved")

        logging.info("LApp: on_pause, Window.size=%s" % str(Window.size))

        return pauseSupport

    def on_resume(self):
        logging.info("LApp: on_resume")

        lapp = App.get_running_app()
        app = lapp.app
        if app is None:
            return

        AndroidScreenRotation.unlock(toaster=False)

        so = get_screen_ori()
        go = so  # flake8: F841 nonsense!
        so = go
        logging.info("LApp: on_resume, Window.size=%s" % str(Window.size))
        # ANM:
        # kivy.core.window.Window hat hier u.U. eine falsche dimension
        # und unterscheidet sich vom display (-> in get_screen_ori).
        # Eine korrektur der Parameter von Window kann hier wie skizziert
        # durchgeführt werden und führt auch zu den korrekten 'on_size'
        # Notifikationen. Allerdings wird später (nach diesem Aufruf)
        # eine weitere Notifikation erhalten, welche das Fenster u.U.
        # wieder falsch aufstellt. (woher kommt die und warum ist sie
        # oft falsch ?)

        # Gelegentlich beobachtet: Schwarzer Bilschirm nach resume. Und
        # das bleibt dann so auf ewig. Aber die app läuft stabil. Die
        # einzige Interaktion mit der App ist über die Android buttons
        # für hintrgrund und resume. Diese funktioneren gemäss logcat
        # einwandfrei. Daher versuchen wir ... um den graphik context
        # wieder zu aktivieren/auszurichten:

        # gemäss einer neueren Antwort auf kivy issue 3671 gehört auch
        # update-viewport zu den nützlichen massnahmen.

        Clock.schedule_once(lambda dt: Window.update_viewport(),0.0) # noqa
        Clock.schedule_once(lambda dt: self.mainWindow.rebuildContainer(), 0.5)
        Clock.schedule_once(lambda dt: set_fullscreen(True),1.0) # noqa

        # Pause modus abschalten nach resume:
        if app.game.pause:
            Clock.schedule_once(self.makeEndPauseCmd(app), 3.0)

    def makeEndPauseCmd(self, app):
        def endPauseCmd(dt):
            if app.game.pause:
                logging.info("LApp: on_resume - pause off")
                app.game.doPause()
        return endPauseCmd
