#!/bin/python
# -*- coding: utf-8 -*-

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

from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
import string, time, re

class outputManager():
    def __init__(self):
        self.lastEcho = ''
    def initialize(self, environment):
        self.env = environment
        self.env['runtime']['settingsManager'].loadDriver(\
          self.env['runtime']['settingsManager'].getSetting('speech', 'driver'), 'speechDriver')
        self.env['runtime']['settingsManager'].loadDriver(\
          self.env['runtime']['settingsManager'].getSetting('sound', 'driver'), 'soundDriver')
        self.env['runtime']['settingsManager'].loadDriver(\
          self.env['runtime']['settingsManager'].getSetting('braille', 'driver'), 'brailleDriver')
    def shutdown(self):
        self.env['runtime']['settingsManager'].shutdownDriver('soundDriver')
        self.env['runtime']['settingsManager'].shutdownDriver('speechDriver')
        self.env['runtime']['settingsManager'].shutdownDriver('brailleDriver')
        
    def presentText(self, text, interrupt=True, soundIcon = '', ignorePunctuation=False, announceCapital=False, flush=True, brailleAlternative = ''):
        if text == '':
            return
        if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'readNumbersAsDigits') and len(text.strip()) > 1:
            text = re.sub(r"(\d)", r"\1 ", text).rstrip()
        self.env['runtime']['debug'].writeDebugOut("presentText:\nsoundIcon:'"+soundIcon+"'\nText:\n" + text ,debug.debugLevel.INFO)
        if self.playSoundIcon(soundIcon, interrupt):
            self.env['runtime']['debug'].writeDebugOut("soundIcon found" ,debug.debugLevel.INFO)
            return
        if (len(text) > 1) and (text.strip(string.whitespace) == ''):
            return
        toAnnounceCapital = announceCapital and text[0].isupper()
        if toAnnounceCapital:
            if self.playSoundIcon('capital', False):
                toAnnounceCapital = False
        self.lastEcho = text
        self.speakText(text, interrupt, ignorePunctuation,toAnnounceCapital)
        if flush:
            if brailleAlternative != '':
                brlText = brailleAlternative
            else:
                brlText = text
            self.brailleText(brlText, flush)
    def getLastEcho(self):
        return self.lastEcho
    def speakText(self, text, interrupt=True, ignorePunctuation=False, announceCapital=False):
        if not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'):
            self.env['runtime']['debug'].writeDebugOut("Speech disabled in outputManager.speakText",debug.debugLevel.INFO)
            return
        if self.env['runtime']['speechDriver'] == None:
            self.env['runtime']['debug'].writeDebugOut("No speechDriver in outputManager.speakText",debug.debugLevel.ERROR)
            return
        if interrupt:
            self.interruptOutput()
        try:
            self.env['runtime']['speechDriver'].setLanguage(self.env['runtime']['settingsManager'].getSetting('speech', 'language'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("setting speech language in outputManager.speakText",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
        
        try:
            self.env['runtime']['speechDriver'].setVoice(self.env['runtime']['settingsManager'].getSetting('speech', 'voice'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("Error while setting speech voice in outputManager.speakText",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)        
        
        try:
            if announceCapital:
                self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'capitalPitch'))            
            else:
                self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("setting speech pitch in outputManager.speakText",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)            
        
        try:
            self.env['runtime']['speechDriver'].setRate(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("setting speech rate in outputManager.speakText",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)            
        
        try:
            self.env['runtime']['speechDriver'].setModule(self.env['runtime']['settingsManager'].getSetting('speech', 'module'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("setting speech module in outputManager.speakText",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)

        try:
            self.env['runtime']['speechDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("setting speech volume in outputManager.speakText ",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)            
        
        try:
            if self.env['runtime']['settingsManager'].getSettingAsBool('general', 'newLinePause'):        
                cleanText = text.replace('\n',' , ')
            else:
                cleanText = text.replace('\n',' ')

            cleanText = self.env['runtime']['textManager'].replaceHeadLines(cleanText)        
            cleanText = self.env['runtime']['punctuationManager'].proceedPunctuation(cleanText, ignorePunctuation) 
            cleanText = re.sub(' +$',' ', cleanText)            
            self.env['runtime']['speechDriver'].speak(cleanText)
            self.env['runtime']['debug'].writeDebugOut("Speak: "+ cleanText,debug.debugLevel.INFO)                
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("\"speak\" in outputManager.speakText ",debug.debugLevel.ERROR)
            self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)            

    def brailleText(self, text='', flush=True):
        if not self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
            return
        if self.env['runtime']['brailleDriver'] == None:
            return
        if flush:
            self.env['output']['nextFlush'] = time.time() + self.getFlushTime(text)
            self.env['output']['messageOffset'] = {'x':0,'y':0}            
            self.env['output']['messageText'] = text
            displayText = self.getBrailleTextWithOffset(self.env['output']['messageText'], self.env['output']['messageOffset'])    
            self.env['runtime']['brailleDriver'].writeText('flush '+ displayText)         
        else:
            if self.env['output']['nextFlush'] < time.time():
                if self.env['output']['messageText'] != '':
                    self.env['output']['messageText'] = ''
                if self.env['output']['messageOffset'] != None:
                    self.env['output']['messageOffset'] = None
                cursor = self.getBrailleCursor()
                x, y, self.env['output']['brlText'] = \
                  line_utils.getCurrentLine(cursor['x'], cursor['y'], self.env['screen']['newContentText'])                
                displayText = self.getBrailleTextWithOffset(self.env['screen']['newContentText'], self.env['output']['cursorOffset'], cursor)    
                self.env['runtime']['brailleDriver'].writeText('notflush ' + displayText)                  
            else:
                displayText = self.getBrailleTextWithOffset(self.env['output']['messageText'], self.env['output']['messageOffset'])    
                self.env['runtime']['brailleDriver'].writeText('flush'+displayText)                          
    def resetSpeechDriver(self):
        try:
            self.env['runtime']['speechDriver'].reset()
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("reset " + str(e),debug.debugLevel.ERROR)
        
    def getBrailleCursor(self):
        if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'REVIEW':
            return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
        if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'MANUAL':
            return self.env['runtime']['cursorManager'].getReviewOrTextCursor()            
        if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'LAST':
            return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
        return self.env['runtime']['cursorManager'].getReviewOrTextCursor()                     
    
    def getFixCursorCell(self):
        size = self.env['runtime']['brailleDriver'].getDeviceSize()[0]
        fixCell = self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'fixCursorOnCell')
        if fixCell <= -1:
            return size[0]
        if fixCell >= size[0]:
            return size[0]
        return fixCell 
    def getActiveOffsetAndText(self):
        if self.env['output']['messageOffset']:
            return self.env['output']['messageOffset'], self.env['output']['messageText']
        if not self.env['output']['cursorOffset']:
            return self.getBrailleCursor(), self.env['screen']['newContentText']
        return self.env['output']['cursorOffset'], self.env['screen']['newContentText']
    def getHorizontalPanSize(self):
        size = self.env['runtime']['brailleDriver'].getDeviceSize()        
        if self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal') <= 0:
            return size[0]
        if self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal') >= size[0]:
            return size[0]
        return self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal')
    def getHorizontalPanLevel(self,offsetChange = 0):
        panned = True
        panSize = self.getHorizontalPanSize()
        offset, text = self.getActiveOffsetAndText()
        currline = text.split('\n')[offset['y']]
        newOffsetStart = (int(offset['x']  / panSize) + offsetChange) * panSize
        if newOffsetStart < 0:
            newOffsetStart = 0
            panned = False
        if newOffsetStart >= len(text):
            newOffsetStart = int((len(text) - panSize - 1) / panSize)
            panned = False
        return newOffsetStart, panned    
    def setPanLeft(self):
        newPan, panned = self.getHorizontalPanLevel(-1)
        if self.env['output']['messageOffset']:
            self.env['output']['messageOffset'] = newPan.copy()
        else:
            self.env['output']['cursorOffset'] = newPan.copy()
        return panned
    def setPanRight(self):
        newPan, panned = self.getHorizontalPanLevel(1)  
        if self.env['output']['messageOffset']:
            self.env['output']['messageOffset'] = newPan.copy()
        else:
            self.env['output']['cursorOffset'] = newPan.copy()
        return panned
    def removePanning(self):
        if self.env['output']['messageOffset']:
            self.env['output']['messageOffset'] = None
        else:
            self.env['output']['cursorOffset'] = None
    def getBrailleTextWithOffset(self, text, offset = None, cursor = None):
        if text == '':
            return ''
        size = self.env['runtime']['brailleDriver'].getDeviceSize()
        offsetText = text

        if cursor and not offset:
            if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'FIXCELL':
                #fix cell
                cursorCell = self.getFixCursorCell()
                offsetStart = cursor['x']
                if offsetStart < size[0]:
                    if offsetStart <= cursorCell:
                        return offsetText[0: size[0]]

                offsetStart -= cursorCell
                if offsetStart >= len(offsetText):
                    offsetStart = len(offsetText) - 1
            else: 
                # page and fallback
                offsetStart = int(cursor['x'] / size[0]) * size[0]
        else:
            if not offset:
                offset = {'x':0,'y':0}
            offsetStart = offset['x']
            if offsetStart >= len(offsetText):
                offsetStart = len(offsetText) - size[0]
        
        if offsetStart < 0:
            offsetStart = 0
        offsetEnd = offsetStart + size[0]
        offsetText = offsetText[offsetStart: offsetEnd]
        return offsetText
    def interruptOutput(self):
        try:
            self.env['runtime']['speechDriver'].cancel()
            self.env['runtime']['debug'].writeDebugOut("Interrupt speech",debug.debugLevel.INFO)
        except:
            pass

    def clearFlushTime(self):
        self.setFlushTime(0.0)

    def setFlushTime(self,newTime):
        self.env['output']['nextFlush'] = newTime

    def getFlushTime(self,text=''):
        if self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') < 0 or \
          self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'NONE':
            return 999999999999
        if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'FIX':
            return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout')
        if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'CHAR':
            return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') * len(text)
        if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'WORD':
            wordsList = text.split(' ')
            return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') * len( list( filter(None, wordsList) ) )

    def playSoundIcon(self, soundIcon = '', interrupt=True):
        if soundIcon == '':
            return False
        soundIcon = soundIcon.upper()
        if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
            self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playSoundIcon",debug.debugLevel.INFO)
            return False  

        try:
            e = self.env['soundIcons'][soundIcon]
        except:
            self.env['runtime']['debug'].writeDebugOut("SoundIcon doesnt exist: " + soundIcon, debug.debugLevel.WARNING)
            return False

        if self.env['runtime']['soundDriver'] == None:
            self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playSoundIcon:  soundDriver not loaded",debug.debugLevel.ERROR)
            return False

        try:
            self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e),debug.debugLevel.ERROR)

        try:
            self.env['runtime']['soundDriver'].playSoundFile(self.env['soundIcons'][soundIcon], interrupt)
            return True
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e),debug.debugLevel.ERROR)
            return False

        return False

    def playFrequence(self, frequence, duration, interrupt=True):
        if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
            self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playFrequence",debug.debugLevel.INFO)
            return False

        if frequence < 1 or frequence > 20000:
            self.env['runtime']['debug'].writeDebugOut("outputManager.playFrequence::Filefrequence is out of range:" + str(frequence),debug.debugLevel.INFO)
            return False

        if self.env['runtime']['soundDriver'] == None:
            self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playFrequence: soundDriver not loaded",debug.debugLevel.ERROR)
            return False

        try:
            self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e),debug.debugLevel.ERROR)
        adjustVolume = 0.0
        try:
            adjustVolume = 1.0 - (frequence / 20000)
        except:
            pass
        if adjustVolume > 9.0:
            adjustVolume = 9.0

        try:
            self.env['runtime']['soundDriver'].playFrequence(frequence, duration, adjustVolume, interrupt)
            return True
        except Exception as e:
            self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e),debug.debugLevel.ERROR)
            return False

        return False

    def tempDisableSpeech(self):
        if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'): 
            self.presentText(_("speech temporary disabled"), soundIcon='SpeechOff', interrupt=True)
            self.env['commandBuffer']['enableSpeechOnKeypress'] = True
            self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled')))
            self.interruptOutput()
    def announceActiveCursor(self, interrupt_p=False):
        if self.env['runtime']['cursorManager'].isReviewMode():
            self.presentText(' review cursor ', interrupt=interrupt_p)
        else:
            self.presentText(' text cursor ', interrupt=interrupt_p)

