# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals

from elementmaker import E

from dom import unique_id
from gettext import gettext as _
from book_list.globals import get_session_data
from modals import create_custom_dialog, error_dialog
from widgets import create_button


def escaper():
    doc = document.implementation.createDocument("", "", None)
    el = doc.createElement("temp")
    el.textContent = "temp"
    el = el.firstChild
    ser =  new XMLSerializer()  # noqa
    return def(text):
        el.nodeValue = text
        return ser.serializeToString(el)


class Client:

    min_rate = 0.1
    max_rate = 2

    def __init__(self):
        self.stop_requested_at = None
        self.status = {'synthesizing': False, 'paused': False}
        self.queue = v'[]'
        self.last_reached_mark = None
        self.onevent = def():
            pass
        data = get_session_data().get('tts_backend')
        self.current_voice_uri = data.voice or ''
        self.current_rate = data.rate or None

    def create_utterance(self, text_or_ssml, wrap_in_ssml):
        if wrap_in_ssml:
            text_or_ssml = (
                '<?xml version="1.0"?>\n<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"><s>' +
                text_or_ssml +
                '</s></speak>')
        ut = new window.SpeechSynthesisUtterance(text_or_ssml)
        ut.onstart = self.utterance_started
        ut.onpause = self.utterance_paused
        ut.onend = self.utterance_ended
        ut.onerror = self.utterance_failed
        ut.onmark = self.utterance_mark_reached
        ut.onresume = self.utterance_resumed
        if self.current_voice_uri:
            for voice in window.speechSynthesis.getVoices():
                if voice.voiceURI is self.current_voice_uri:
                    ut.voice = voice
                    break
        if self.current_rate:
            ut.rate = self.current_rate
        self.queue.push(ut)
        return ut

    def utterance_started(self, event):
        self.status = {'synthesizing': True, 'paused': False}
        self.onevent('begin')

    def utterance_paused(self, event):
        self.status = {'synthesizing': True, 'paused': True}
        self.onevent('pause')

    def utterance_ended(self, event):
        self.status = {'synthesizing': False, 'paused': False}
        if self.stop_requested_at? and window.performance.now() - self.stop_requested_at < 1000:
            self.stop_requested_at = None
            return
        self.queue.splice(0, 1)
        if self.queue.length:
            window.speechSynthesis.speak(self.queue[0])
        else:
            self.onevent('end')

    def utterance_failed(self, event):
        self.status = {'synthesizing': False, 'paused': False}
        self.queue = v'[]'
        error_dialog(_('Speaking failed'), _(
            'An error has occurred with speech synthesis: ' + event.error))
        self.onevent('cancel')

    def utterance_mark_reached(self, event):
        self.last_reached_mark = event.name
        self.onevent('mark', int(event.name))

    def utterance_resumed(self, event):
        self.status = {'synthesizing': True, 'paused': False}
        self.onevent('resume')

    def pause(self):
        window.speechSynthesis.pause()

    def resume(self):
        window.speechSynthesis.resume()

    def resume_after_configure(self):
        if self.queue.length:
            window.speechSynthesis.speak(self.queue[0])

    def stop(self):
        self.queue = v'[]'
        self.stop_requested_at = window.performance.now()
        window.speechSynthesis.cancel()
        self.status = {'synthesizing': False, 'paused': False}

    def speak_simple_text(self, text):
        self.stop()
        while text.length > 32766:
            self.create_utterance(text[:32766])
            text = text[32766:]
        if text:
            self.create_utterance(text)
        if self.queue.length:
            window.speechSynthesis.speak(self.queue[0])

    def speak_marked_text(self, text_segments, onevent):
        self.stop()
        self.onevent = onevent
        buf = v'[]'
        size = 0
        limit = 2048

        def commit():
            nonlocal buf, size
            text = buf.join('')
            if text.length:
                self.create_utterance(text)
            buf = v'[]'
            size = 0

        for x in text_segments:
            if jstype(x) is 'number':
                # Currently the sad sack brosers dont support SSML
                # https://github.com/WICG/speech-api/issues/37
                # buf.push()
                # markup = '<mark name="' + x + '"/>'
                continue
            else:
                if x.length > limit:
                    commit()
                    while x.length:
                        self.create_utterance(x[:limit])
                        x = x[limit:]
                    continue
            if size + x.length > limit:
                commit()
            buf.push(x)
            size += x.length
        commit()
        if self.queue.length:
            window.speechSynthesis.speak(self.queue[0])

    def faster(self):
        self.change_rate(steps=1)

    def slower(self):
        self.change_rate(steps=-1)

    def apply_settings(self):
        sd = get_session_data()
        sd.set('tts_backend', {'voice': self.current_voice_uri, 'rate': self.current_rate})
        existing = self.queue
        if self.queue and self.queue.length:
            if self.status.paused:
                window.speechSynthesis.resume()
            self.stop()
            for ut in existing:
                self.create_utterance(ut.text)

    def change_rate(self, steps=1):
        rate = current_rate = (self.current_rate or 1) * 10
        step_size = 2
        rate += steps * step_size
        rate /= 10
        rate = max(self.min_rate, min(rate, self.max_rate))
        if rate is not current_rate:
            self.current_rate = rate
            self.apply_settings()
            self.resume_after_configure()

    def configure(self):
        voice_id = unique_id()
        rate_id = unique_id()
        default_voice = None

        def restore_defaults():
            document.getElementById(voice_id).selectedIndex = -1
            document.getElementById(rate_id).value = 10

        create_custom_dialog(_('Configure Text-to-Speech'), def (parent_div, close_modal):
                nonlocal default_voice
                select = E.select(size='5', id=voice_id)
                voices = window.speechSynthesis.getVoices()
                voices.sort(def (a, b):
                    a = a.name.toLowerCase()
                    b = b.name.toLowerCase()
                    return -1 if a < b else (0 if a is b else 1)
                )
                for voice in voices:
                    dflt = ''
                    if voice.default:
                        default_voice = voice.voiceURI
                        dflt = '-- {}'.format(_('default'))
                    option = E.option(f'{voice.name} ({voice.lang}){dflt}', value=voice.voiceURI)
                    if (self.current_voice_uri and voice.voiceURI is self.current_voice_uri) or (not self.current_voice_uri and voice.default):
                        option.setAttribute('selected', 'selected')
                    select.appendChild(option)
                parent_div.appendChild(E.div(_('Speed of speech:')))
                parent_div.appendChild(E.input(type='range', id=rate_id, min=(self.min_rate * 10) + '', max=(self.max_rate * 10) + '', value=((self.current_rate or 1) * 10) + ''))
                parent_div.appendChild(E.div(_('Pick a voice below:')))
                parent_div.appendChild(select)
                if select.options.selectedIndex? and select.options[select.options.selectedIndex]:
                    select.options[select.options.selectedIndex].scrollIntoView()
                parent_div.appendChild(E.div(
                        style='margin: 1rem; display: flex; justify-content: space-between; align-items: flex-start',
                        create_button(_('Restore defaults'), action=restore_defaults),
                        create_button(_('Close'), action=close_modal)
                ))

            , on_close=def():
                voice = document.getElementById(voice_id).value
                rate = int(document.getElementById(rate_id).value) / 10
                if rate is 1:
                    rate = None
                if voice is default_voice:
                    voice = ''
                changed = voice is not self.current_voice_uri or rate is not self.current_rate
                if changed:
                    self.current_voice_uri = voice
                    self.current_rate = rate
                    self.apply_settings()

                self.onevent('configured')
        )
