"""Support for the Google Cloud TTS service."""
import logging
import os

import asyncio
import async_timeout
import voluptuous as vol
from google.cloud import texttospeech

from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

CONF_KEY_FILE = 'key_file'
CONF_GENDER = 'gender'
CONF_VOICE = 'voice'
CONF_ENCODING = 'encoding'
CONF_SPEED = 'speed'
CONF_PITCH = 'pitch'
CONF_GAIN = 'gain'
CONF_PROFILES = 'profiles'

SUPPORTED_LANGUAGES = [
    'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-CA', 'fr-FR',
    'it-IT', 'ja-JP', 'ko-KR', 'nb-NO', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT',
    'ru-RU', 'sk-SK', 'sv-SE', 'tr-TR', 'uk-UA',
]
DEFAULT_LANG = 'en-US'

DEFAULT_GENDER = 'NEUTRAL'

VOICE_REGEX = r'[a-z]{2}-[A-Z]{2}-(Standard|Wavenet)-[A-Z]|'
DEFAULT_VOICE = ''

DEFAULT_ENCODING = 'OGG_OPUS'

MIN_SPEED = 0.25
MAX_SPEED = 4.0
DEFAULT_SPEED = 1.0

MIN_PITCH = -20.0
MAX_PITCH = 20.0
DEFAULT_PITCH = 0

MIN_GAIN = -96.0
MAX_GAIN = 16.0
DEFAULT_GAIN = 0

SUPPORTED_PROFILES = [
    "wearable-class-device",
    "handset-class-device",
    "headphone-class-device",
    "small-bluetooth-speaker-class-device",
    "medium-bluetooth-speaker-class-device",
    "large-home-entertainment-class-device",
    "large-automotive-class-device",
    "telephony-class-application",
]

SUPPORTED_OPTIONS = [
    CONF_VOICE,
    CONF_GENDER,
    CONF_ENCODING,
    CONF_SPEED,
    CONF_PITCH,
    CONF_GAIN,
    CONF_PROFILES,
]

GENDER_SCHEMA = vol.All(
    vol.Upper,
    vol.In(texttospeech.enums.SsmlVoiceGender.__members__)
)
VOICE_SCHEMA = cv.matches_regex(VOICE_REGEX)
SCHEMA_ENCODING = vol.All(
    vol.Upper,
    vol.In(texttospeech.enums.AudioEncoding.__members__)
)
SPEED_SCHEMA = vol.All(
    vol.Coerce(float),
    vol.Clamp(min=MIN_SPEED, max=MAX_SPEED)
)
PITCH_SCHEMA = vol.All(
    vol.Coerce(float),
    vol.Clamp(min=MIN_PITCH, max=MAX_PITCH)
)
GAIN_SCHEMA = vol.All(
    vol.Coerce(float),
    vol.Clamp(min=MIN_GAIN, max=MAX_GAIN)
)
PROFILES_SCHEMA = vol.All(
    cv.ensure_list,
    [vol.In(SUPPORTED_PROFILES)]
)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_KEY_FILE): cv.string,
    vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES),
    vol.Optional(CONF_GENDER, default=DEFAULT_GENDER): GENDER_SCHEMA,
    vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): VOICE_SCHEMA,
    vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): SCHEMA_ENCODING,
    vol.Optional(CONF_SPEED, default=DEFAULT_SPEED): SPEED_SCHEMA,
    vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): PITCH_SCHEMA,
    vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA,
    vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA,
})


async def async_get_engine(hass, config):
    """Set up Google Cloud TTS component."""
    key_file = config.get(CONF_KEY_FILE)
    if key_file:
        key_file = hass.config.path(key_file)
        if not os.path.isfile(key_file):
            _LOGGER.error("File %s doesn't exist", key_file)
            return None

    return GoogleCloudTTSProvider(
        hass,
        key_file,
        config.get(CONF_LANG),
        config.get(CONF_GENDER),
        config.get(CONF_VOICE),
        config.get(CONF_ENCODING),
        config.get(CONF_SPEED),
        config.get(CONF_PITCH),
        config.get(CONF_GAIN),
        config.get(CONF_PROFILES)
    )


class GoogleCloudTTSProvider(Provider):
    """The Google Cloud TTS API provider."""

    def __init__(
            self,
            hass,
            key_file=None,
            language=DEFAULT_LANG,
            gender=DEFAULT_GENDER,
            voice=DEFAULT_VOICE,
            encoding=DEFAULT_ENCODING,
            speed=1.0,
            pitch=0,
            gain=0,
            profiles=None
    ):
        """Init Google Cloud TTS service."""
        self.hass = hass
        self.name = 'Google Cloud TTS'
        self._language = language
        self._gender = gender
        self._voice = voice
        self._encoding = encoding
        self._speed = speed
        self._pitch = pitch
        self._gain = gain
        self._profiles = profiles

        if key_file:
            self._client = texttospeech \
                .TextToSpeechClient.from_service_account_json(key_file)
        else:
            self._client = texttospeech.TextToSpeechClient()

    @property
    def supported_languages(self):
        """Return list of supported languages."""
        return SUPPORTED_LANGUAGES

    @property
    def default_language(self):
        """Return the default language."""
        return self._language

    @property
    def supported_options(self):
        """Return a list of supported options."""
        return SUPPORTED_OPTIONS

    @property
    def default_options(self):
        """Return a dict including default options."""
        return {
            CONF_GENDER: self._gender,
            CONF_VOICE: self._voice,
            CONF_ENCODING: self._encoding,
            CONF_SPEED: self._speed,
            CONF_PITCH: self._pitch,
            CONF_GAIN: self._gain,
            CONF_PROFILES: self._profiles
        }

    async def async_get_tts_audio(self, message, language, options=None):
        """Load TTS from google."""
        options_schema = vol.Schema({
            vol.Optional(CONF_GENDER, default=self._gender): GENDER_SCHEMA,
            vol.Optional(CONF_VOICE, default=self._voice): VOICE_SCHEMA,
            vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING):
                SCHEMA_ENCODING,
            vol.Optional(CONF_SPEED, default=self._speed): SPEED_SCHEMA,
            vol.Optional(CONF_PITCH, default=self._speed): SPEED_SCHEMA,
            vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA,
            vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA,
        })
        options = options_schema(options)

        _encoding = options[CONF_ENCODING]
        _voice = options[CONF_VOICE]
        if _voice and not _voice.startswith(language):
            language = _voice[:5]

        try:
            # pylint: disable=no-member
            synthesis_input = texttospeech.types.SynthesisInput(
                text=message
            )

            voice = texttospeech.types.VoiceSelectionParams(
                language_code=language,
                ssml_gender=texttospeech.enums.SsmlVoiceGender[
                    options[CONF_GENDER]
                ],
                name=_voice
            )

            audio_config = texttospeech.types.AudioConfig(
                audio_encoding=texttospeech.enums.AudioEncoding[_encoding],
                speaking_rate=options.get(CONF_SPEED),
                pitch=options.get(CONF_PITCH),
                volume_gain_db=options.get(CONF_GAIN),
                effects_profile_id=options.get(CONF_PROFILES),
            )
            # pylint: enable=no-member

            with async_timeout.timeout(10, loop=self.hass.loop):
                response = await self.hass.async_add_executor_job(
                    self._client.synthesize_speech,
                    synthesis_input,
                    voice,
                    audio_config
                )
                return _encoding, response.audio_content

        except asyncio.TimeoutError as ex:
            _LOGGER.error("Timeout for Google Cloud TTS call: %s", ex)
        except Exception as ex:  # pylint: disable=broad-except
            _LOGGER.exception(
                "Error occured during Google Cloud TTS call: %s", ex
            )

        return None, None
