# noinspection PyUnresolvedReferences
import objc
from AppKit import NSSpeechSynthesizer
from Foundation import *
from PyObjCTools import AppHelper
# noinspection PyProtectedMember
from PyObjCTools.AppHelper import PyObjCAppHelperRunLoopStopper

from ..voice import Voice


# noinspection PyUnresolvedReferences
class RunLoopStopper(PyObjCAppHelperRunLoopStopper):
    """
    Overrides PyObjCAppHelperRunLoopStopper to terminate after endLoop.
    """

    def __init__(self):
        self.shouldStop = False

    def init(self):
        return objc.super(RunLoopStopper, self).init()

    def stop(self):
        self.shouldStop = True


# noinspection PyPep8Naming
def buildDriver(proxy):
    return NSSpeechDriver.alloc().initWithProxy(proxy)


# noinspection PyUnresolvedReferences,PyPep8Naming,PyUnusedLocal
class NSSpeechDriver(NSObject):

    def __init__(self):
        self._proxy = None
        self._tts = None
        self._completed = False
        self._current_text = ''

    @objc.python_method
    def initWithProxy(self, proxy):
        try:
            proxy_attr = objc.super(NSSpeechDriver, self).init()
        except AttributeError:
            proxy_attr = self
        if proxy_attr:
            self._proxy = proxy
            self._tts = NSSpeechSynthesizer.alloc().initWithVoice_(None)
            self._tts.setDelegate_(self)
            # default rate
            self._tts.setRate_(200)
            self._completed = True
        return self

    def destroy(self):
        self._tts.setDelegate_(None)
        del self._tts

    def onPumpFirst_(self, timer):
        self._proxy.setBusy(False)

    def startLoop(self):
        # https://github.com/ronaldoussoren/pyobjc/blob/mater/pyobjc-framework-Cocoa/Lib/PyObjCTools/AppHelper.py#L243C25-L243C25  # noqa
        NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
            0.0, self, 'onPumpFirst:', None, False
        )
        runLoop = NSRunLoop.currentRunLoop()
        stopper = RunLoopStopper.alloc().init()
        PyObjCAppHelperRunLoopStopper.addRunLoopStopper_toRunLoop_(stopper, runLoop)
        while stopper.shouldRun():
            nextfire = runLoop.limitDateForMode_(NSDefaultRunLoopMode)
            soon = NSDate.dateWithTimeIntervalSinceNow_(0)  # maxTimeout in runConsoleEventLoop
            if nextfire is not None:
                nextfire = soon.earlierDate_(nextfire)
            if not runLoop.runMode_beforeDate_(NSDefaultRunLoopMode, nextfire):
                stopper.stop()
                break
        PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)

    @staticmethod
    def endLoop():
        AppHelper.stopEventLoop()

    def iterate(self):
        self._proxy.setBusy(False)
        yield

    @objc.python_method
    def say(self, text):
        self._proxy.setBusy(True)
        self._completed = True
        self._proxy.notify('started-utterance')
        self._current_text = text
        self._tts.startSpeakingString_(text)

    def stop(self):
        if self._proxy.isBusy():
            self._completed = False
        self._tts.stopSpeaking()

    @objc.python_method
    def _toVoice(self, attr):
        return Voice(attr.get('VoiceIdentifier'), attr.get('VoiceName'),
                     [attr.get('VoiceLocaleIdentifier', attr.get('VoiceLanguage'))], attr.get('VoiceGender'),
                     attr.get('VoiceAge'))

    @objc.python_method
    def getProperty(self, name):
        if name == 'voices':
            return [self._toVoice(NSSpeechSynthesizer.attributesForVoice_(v))
                    for v in list(NSSpeechSynthesizer.availableVoices())]
        elif name == 'voice':
            return self._tts.voice()
        elif name == 'rate':
            return self._tts.rate()
        elif name == 'volume':
            return self._tts.volume()
        elif name == "pitch":
            print("Pitch adjustment not supported when using NSSS")
        else:
            raise KeyError('unknown property %s' % name)

    @objc.python_method
    def setProperty(self, name, value):
        if name == 'voice':
            # vol/rate gets reset, so store and restore it
            vol = self._tts.volume()
            rate = self._tts.rate()
            self._tts.setVoice_(value)
            self._tts.setRate_(rate)
            self._tts.setVolume_(vol)
        elif name == 'rate':
            self._tts.setRate_(value)
        elif name == 'volume':
            self._tts.setVolume_(value)
        elif name == 'pitch':
            print("Pitch adjustment not supported when using NSSS")
        else:
            raise KeyError('unknown property %s' % name)

    @objc.python_method
    def save_to_file(self, text, filename):
        self._proxy.setBusy(True)
        self._completed = True
        url = Foundation.NSURL.fileURLWithPath_(filename)
        self._tts.startSpeakingString_toURL_(text, url)

    def speechSynthesizer_didFinishSpeaking_(self, tts, success):
        if not self._completed:
            success = False
        else:
            success = True
        self._proxy.notify('finished-utterance', completed=success)
        self._proxy.setBusy(False)

    def speechSynthesizer_willSpeakWord_ofString_(self, tts, rng, text):
        if self._current_text:
            current_word = self._current_text[rng.location:rng.location + rng.length]
        else:
            current_word = "Unknown"

        self._proxy.notify('started-word', name=current_word, location=rng.location,
                           length=rng.length)
