"""
This module contains the google asr speech recognizer
"""
import logging
from queue import Queue
from threading import Thread
from typing import Any, Union

import numpy as np  # type: ignore
from google.cloud import speech  # type: ignore
from google.oauth2 import service_account  # type: ignore

from spokestack.context import SpeechContext


_LOG = logging.getLogger(__name__)


class GoogleSpeechRecognizer:
    """Transforms speech into text using Google's ASR.

    Args:
        language (str): The language of given audio as a
                        [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt)
                        language tag. Example: "en-US"
        credentials (Union[None, str, dict]): Dictionary of Google API credentials
                                              or path to credentials. if set to None
                                              credentials will be pulled from the
                                              environment variable:
                                              GOOGLE_APPLICATION_CREDENTIALS
        sample_rate (int): sample rate of the input audio (Hz)
        **kwargs (optional): additional keyword arguments
    """

    def __init__(
        self,
        language: str,
        credentials: Union[None, str, dict] = None,
        sample_rate: int = 16000,
        **kwargs,
    ) -> None:
        if credentials:
            if isinstance(credentials, str):
                credentials = service_account.Credentials.from_service_account_file(
                    credentials
                )
            elif isinstance(credentials, dict):
                credentials = service_account.Credentials.from_service_account_info(
                    credentials
                )
            else:
                raise ValueError(
                    "Invalid Credentials: Only dict, str, or None accepted"
                )

        self._client = speech.SpeechClient(credentials=credentials)
        self._config = speech.StreamingRecognitionConfig(
            config=speech.RecognitionConfig(
                encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
                sample_rate_hertz=sample_rate,
                language_code=language,
                enable_automatic_punctuation=True,
            ),
            interim_results=True,
        )
        self._queue: Queue = Queue()
        self._thread: Any = None

    def __call__(self, context: SpeechContext, frame: np.ndarray) -> None:
        """Main entry point.

        Args:
            context (SpeechContext): current state of the speech pipeline
            frame (np.ndarray): numpy array of PCM-16 audio.

        Returns: None

        """
        if self._thread is None and context.is_active:
            self._begin(context)
        if self._thread is not None and not context.is_active:
            self._commit()
        if context.is_active:
            self._send(frame)

    def _begin(self, context) -> None:
        self._thread = Thread(
            target=self._receive,
            args=(context,),
        )
        self._thread.start()

    def _receive(self, context):
        for response in self._client.streaming_recognize(self._config, self._drain()):
            for result in response.results[:1]:
                for alternative in result.alternatives[:1]:
                    context.transcript = alternative.transcript
                    context.confidence = alternative.confidence
                    if context.transcript:
                        context.event("partial_recognize")

                if result.is_final:
                    if context.transcript:
                        context.event("recognize")
                        _LOG.debug("recognize event")
                    else:
                        context.event("timeout")
                        _LOG.debug("timeout event")

    def _drain(self):
        while data := self._queue.get():
            yield data

    def _commit(self) -> None:
        self._queue.put(None)
        self._thread.join()
        self._thread = None

    def _send(self, frame) -> None:
        self._queue.put(speech.StreamingRecognizeRequest(audio_content=frame.tobytes()))

    def reset(self) -> None:
        """ resets recognizer """
        if self._thread:
            self._queue.put(None)
            self._thread.join()
            self._thread = None

    def close(self) -> None:
        """ closes recognizer """
        self._client = None
