#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.

import signal
import time
import os
import sys

from fenrirscreenreader.core import i18n
from fenrirscreenreader.core import settingsManager
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.eventData import fenrirEventType

class fenrirManager():
    def __init__(self, cliArgs):
        self.isInitialized = False
        try:
            self.environment = settingsManager.settingsManager().initFenrirConfig(cliArgs, self)
            if not self.environment:
                raise RuntimeError('Cannot Initialize. Maybe the configfile is not available or not parseable')
        except RuntimeError:
            raise
            
        self.environment['runtime']['outputManager'].presentText(_("Start Fenrir"), soundIcon='ScreenReaderOn', interrupt=True)
        signal.signal(signal.SIGINT, self.captureSignal)
        signal.signal(signal.SIGTERM, self.captureSignal)
        
        self.isInitialized = True
        self.modifierInput = False
        self.singleKeyCommand = False
        self.command = ''
        self.setProcessName()

    def proceed(self):
        if not self.isInitialized:
            return
        self.environment['runtime']['eventManager'].startMainEventLoop()
        self.shutdown()

    def handleInput(self, event):
        self.environment['runtime']['debug'].writeDebugOut('DEBUG INPUT fenrirMan:' + str(event), debug.debugLevel.INFO)
        
        if not event['Data']:
            event['Data'] = self.environment['runtime']['inputManager'].getInputEvent()
            
        if event['Data']:
            event['Data']['EventName'] = self.environment['runtime']['inputManager'].convertEventName(event['Data']['EventName'])
            self.environment['runtime']['inputManager'].handleInputEvent(event['Data'])
        else:
            return

        if self.environment['runtime']['inputManager'].noKeyPressed(): 
            self.environment['runtime']['inputManager'].clearLastDeepInput()
            
        if self.environment['runtime']['screenManager'].isSuspendingScreen():
            self.environment['runtime']['inputManager'].writeEventBuffer()
        else: 
            if self.environment['runtime']['helpManager'].isTutorialMode():
                self.environment['runtime']['inputManager'].clearEventBuffer()
                self.environment['runtime']['inputManager'].keyEcho(event['Data'])

            if self.environment['runtime']['vmenuManager'].getActive():
                self.environment['runtime']['inputManager'].clearEventBuffer()

            self.detectShortcutCommand()

            if self.modifierInput:
                self.environment['runtime']['inputManager'].clearEventBuffer() 
            if self.singleKeyCommand:
                if self.environment['runtime']['inputManager'].noKeyPressed():
                    self.environment['runtime']['inputManager'].clearEventBuffer() 
            else:
                self.environment['runtime']['inputManager'].writeEventBuffer()
                
        if self.environment['runtime']['inputManager'].noKeyPressed():
            self.modifierInput = False
            self.singleKeyCommand = False
            self.environment['runtime']['inputManager'].writeEventBuffer()
            self.environment['runtime']['inputManager'].handleDeviceGrab()

        if self.environment['input']['keyForeward'] > 0:
            self.environment['input']['keyForeward'] -= 1
            
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onKeyInput')

    def handleByteInput(self, event):
        if not event['Data'] or event['Data'] == b'':
            return
        self.environment['runtime']['byteManager'].handleByteInput(event['Data'])
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onByteInput')

    def handleExecuteCommand(self, event):
        if not event['Data'] or event['Data'] == '':
            return
        currentCommand = event['Data']

        # special modes
        if self.environment['runtime']['helpManager'].isTutorialMode():
            if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'help'):
                self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'help')
                return
        elif self.environment['runtime']['vmenuManager'].getActive():
            if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'vmenu-navigation'):
                self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'vmenu-navigation')
                return

        # default
        self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'commands')

    def handleRemoteIncomming(self, event):
        if not event['Data']:
            return
        self.environment['runtime']['remoteManager'].handleRemoteIncomming(event['Data'])

    def handleScreenChange(self, event):
        self.environment['runtime']['screenManager'].hanldeScreenChange(event['Data'])
        if self.environment['runtime']['vmenuManager'].getActive():
            return
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenChanged')
        self.environment['runtime']['screenDriver'].getCurrScreen()

    def handleScreenUpdate(self, event):
        self.environment['runtime']['screenManager'].handleScreenUpdate(event['Data'])
        
        if time.time() - self.environment['runtime']['inputManager'].getLastInputTime() >= 0.3:
            self.environment['runtime']['inputManager'].clearLastDeepInput()
            
        if (self.environment['runtime']['cursorManager'].isCursorVerticalMove() or 
            self.environment['runtime']['cursorManager'].isCursorHorizontalMove()):
            self.environment['runtime']['commandManager'].executeDefaultTrigger('onCursorChange')
            
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenUpdate')
        self.environment['runtime']['inputManager'].clearLastDeepInput()

    def handlePlugInputDevice(self, event):
        try:
            self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data'])
        except:
            pass
        self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data'])
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True)
        self.environment['runtime']['inputManager'].setLastDetectedDevices(None)

    def handleHeartBeat(self, event):
        self.environment['runtime']['commandManager'].executeDefaultTrigger('onHeartBeat', force=True)

    def detectShortcutCommand(self):
        if self.environment['input']['keyForeward'] > 0:
            return
            
        if len(self.environment['input']['prevInput']) > len(self.environment['input']['currInput']):
            return
            
        if self.environment['runtime']['inputManager'].isKeyPress():
            self.modifierInput = self.environment['runtime']['inputManager'].currKeyIsModifier()
        else:
            if not self.environment['runtime']['inputManager'].noKeyPressed():
                if self.singleKeyCommand:
                    self.singleKeyCommand = len(self.environment['input']['currInput']) == 1
                    
        if not(self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()):
            currentShortcut = self.environment['runtime']['inputManager'].getCurrShortcut()
            self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(currentShortcut)

        if not self.modifierInput:
            if self.environment['runtime']['inputManager'].isKeyPress():
                if self.command != '':
                    self.singleKeyCommand = True

        if not (self.singleKeyCommand or self.modifierInput):
            return

        # fire event
        if self.command != '':
            if self.modifierInput:
                self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command)
                self.command = ''
            else:
                if self.singleKeyCommand:
                    self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command)
                    self.command = ''

    def setProcessName(self, name='fenrir'):
        """Attempts to set the process name to 'fenrir'."""
        try:
            from setproctitle import setproctitle
        except ImportError:
            pass
        else:
            setproctitle(name)
            return True

        try:
            from ctypes import cdll, byref, create_string_buffer
            libc = cdll.LoadLibrary('libc.so.6')
            stringBuffer = create_string_buffer(len(name) + 1)
            stringBuffer.value = bytes(name, 'UTF-8')
            libc.prctl(15, byref(stringBuffer), 0, 0, 0)
            return True
        except:
            pass

        return False

    def shutdownRequest(self):
        try:
            self.environment['runtime']['eventManager'].stopMainEventLoop()
        except:
            pass

    def captureSignal(self, sigInit, frame):
        self.shutdownRequest()

    def shutdown(self):
        self.environment['runtime']['inputManager'].ungrabAllDevices()
        self.environment['runtime']['eventManager'].stopMainEventLoop()
        self.environment['runtime']['outputManager'].presentText(_("Quit Fenrir"), soundIcon='ScreenReaderOff', interrupt=True)
        self.environment['runtime']['eventManager'].cleanEventQueue()
        time.sleep(0.6)
        
        for currentManager in self.environment['general']['managerList']:
            if self.environment['runtime'][currentManager]:
                self.environment['runtime'][currentManager].shutdown()
                del self.environment['runtime'][currentManager]

        self.environment = None
