#!/usr/bin/python3

# -----------------------------------------------------------------------------
# Copyright (C) 2020  Jonathan Grahl <gn@igh.de>
#
# This file is part of cxmenu.
#
# Cxmenu 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.
#
# Cxmenu 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 cxmenu.  If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------



# ------------------------- Loading Necessary Modules -------------------------
from enum import Enum
import getopt
import logging
import selectors
import sys
import time as t

# ---------------------------- Loading Own Modules ----------------------------
from cxmenutools import button
from cxmenutools import display
from cxmenutools import globals as glb
from cxmenutools.globals import eventtype
from cxmenutools import interval
from cxmenutools import logginghandler
from cxmenutools import menu
from cxmenutools import modules
from cxmenutools import option as opt
from cxmenutools import remotebutton
from cxmenutools import remotedisplay



# -----------------------------------------------------------------------------
class Main():
    """cxmenu application
    Module:      main

    Mainfunction to Control Input and Output of the Beckhoff cx21xx display and
    buttons.
    """


    # -------------------------------------------------------------------------
    SLOWTIMEOUT     = 10
    FASTTIMEOUT     = 2
    MENUTIMEOUT     = 15
    CALLBACKTIMEOUT = 0.2



    # -------------------------------------------------------------------------
    class layer(Enum):
        INTERVAL = 0
        MENU     = 1



    # -------------------------------------------------------------------------
    def __init__(self):
        # ------------ Open Logger to handle different information ------------
        self.log = logging.getLogger('cxmenu')
        self.log.setLevel(logging.DEBUG)

        formatter = logging.Formatter('%(levelname)s - %(name)s - %(message)s')
        loghandler = logginghandler.PufferHandler()
        filehanlder = logging.FileHandler('.cxmenu.log', mode = 'w')
        loghandler.setFormatter(formatter)
        filehanlder.setFormatter(formatter)

        self.log.addHandler(loghandler)
        self.log.addHandler(filehanlder)

        # ------------------------ Implement Selector -------------------------
        self.select = selectors.DefaultSelector()

        # --------------------- Control Passed Arguments ----------------------
        opt.evalOpt()
        if opt.remote:
            self.shell = remotedisplay.RemoteDisplay(self, loghandler)
            self.rbutton = remotebutton.RemoteButton(self,self.shell.getShell())

        else:
            self.rbutton = remotebutton.RemoteButton(self, None)



        # ---------------------- Set a global reference -----------------------
        glb.setMain(self)


        # --------------- Create Objecs for Buttons and Display ---------------
        self.btn = button.Button(self)
        self.mdl = modules.Modules(self)
        self.dsp = display.Display(self)
        self.dsp.write(['\021','']) # backlight on
        if opt.dryrun:
            self.shell.setDryrunInfo()

        # ---------------- Load Modules and Distributes Them ------------------
        self.mdl.importmodules()
        self.intv = interval.Interval(self, self.mdl.getIntervallist())
        self.menu = menu.Menu(self, self.mdl.getMenulist())

        # ------------------------ Layer and Timeouts -------------------------
        self.timeout = 2
        self.starttime = t.time()
        self.endtime = t.time()
        self.menulayer = self.layer.INTERVAL
        self.wait = False

        # ----------self--------------- Handle Callbacks --------------------------
        self.callbackEndtime = t.time()
        self.callbackList = []



    # -------------------------------------------------------------------------
    def getselector(self):
        return self.select



    # -------------------------------------------------------------------------
    def back(self):
        self.menulayer = self.layer.INTERVAL
        self.settimer(self.FASTTIMEOUT)
        self.log.debug('back in main')



    # -------------------------------------------------------------------------
    def buttonevent(self, event):
        self.event = event

        # ----------------------------- INTERVAL ------------------------------
        if self.menulayer == self.layer.INTERVAL:
            if event == eventtype.ENTER:
                pass
                # TODO: Hier die Funktion hinterlegen, dass direkt in das
                #       angezeigte Menü gesprungen werden kann, sofern eins
                #       vorhanden ist.

            elif event == eventtype.LEFT:
                self.intv.skip(-1)
                self.settimer(self.SLOWTIMEOUT)
                self.output()

            elif event == eventtype.RIGHT:
                self.intv.skip(1)
                self.settimer(self.SLOWTIMEOUT)
                self.output()

            elif event == eventtype.UP:
                self.menulayer = self.layer.MENU
                self.menu.enter(self.back)
                self.settimer(self.MENUTIMEOUT)
                self.output()
                self.log.debug('switch to menu')


            elif event == eventtype.DOWN:
                self.settimer(self.SLOWTIMEOUT)
                self.log.debug('waiting...')


        # ------------------------------- MENU --------------------------------
        elif self.menulayer == self.layer.MENU:
            self.settimer(self.MENUTIMEOUT)
            self.menu.buttonevent(event)
            self.output()



    # -------------------------------------------------------------------------
    def settimer(self, timeout):
        self.starttime = t.time()
        self.endtime = self.starttime + timeout



    # -------------------------------------------------------------------------
    # To offer an fast callback of a menu entry without any new event pages
    # can register their method in a callbacklist
    def registerCallback(self, callback):
        self.callbackEndtime = t.time() + self.CALLBACKTIMEOUT
        self.callbackList.append(callback)

    def releaseCallback(self, callback):
        self.callbackList.remove(callback)



    # -------------------------------------------------------------------------
    def nextEventtime(self):
        if self.callbackList:
            return min(self.endtime, self.callbackEndtime)

        else:
            return self.endtime



    # -------------------------------------------------------------------------
    def output(self):
        if self.menulayer == self.layer.INTERVAL:
            self.dsp.write(self.intv.getOutput())

        else:
            self.dsp.write(self.menu.getOutput())



    # -------------------------------------------------------------------------
    def main(self):
        try:
            while True:
                # Refresh the shell output.
                if opt.remote:
                    self.shell.output()

                # Selector is in button.py
                events = self.select.select(self.nextEventtime() - t.time())
                for key, mask in events:
                    callback = key.data
                    callback()

                if t.time() > self.callbackEndtime:
                    for callback in self.callbackList:
                        callback(self.event)
                        self.output()

                    self.callbackEndtime = t.time() + self.CALLBACKTIMEOUT

                if self.menulayer == self.layer.INTERVAL \
                        and t.time() >= self.endtime:
                    self.intv.skip(1)
                    self.output()

                    # Reset to std. timeout for interval.
                    self.settimer(self.FASTTIMEOUT)


                elif self.menulayer == self.layer.MENU \
                        and not self.wait \
                        and t.time() >= self.endtime:
                    self.menulayer = self.layer.INTERVAL
                    self.menu.resetEntryLayer()
                    self.log.debug('back in interval')


        except KeyboardInterrupt:
            if opt.remote:
                # End curses (used for remote mode)
                self.shell.closeRemoteDisplay()

            self.log.debug('Application is closed manually. See you soon.')
            print() # For newline
            print('Aplication is closed manually. See you soon.')
            clearscreen = ['\023   IgH Essen', '']  # \023 -> backlight off
            self.dsp.write(clearscreen)
            self.log.error('Global exception.', exc_info = True)

            exit(0)

        except:
            clearscreen = ['Process failed.', 'More in logfile.']
            self.dsp.write(clearscreen)
            self.log.error('Global exception.', exc_info = True)




# =============================================================================
exec = Main()
if __name__ == "__main__":
    exec.main()


#
