#
# Copyright 2018 Picovoice Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
from collections import namedtuple
from enum import Enum

import numpy as np
from pocketsphinx import get_model_path
from pocketsphinx.pocketsphinx import Decoder

from engines import Porcupine
from engines import snowboydetect
from engines import AudioRecognition, FeatureExtractor

class Engines(Enum):
    POCKET_SPHINX = 'PocketSphinx'
    PORCUPINE = 'Porcupine'
    SNOWBOY = 'Snowboy'
    NYUMAYA = 'Nyumaya'

SensitivityInfo = namedtuple('SensitivityInfo', 'min, max, step')


class Engine(object):
    def process(self, pcm):
        raise NotImplementedError()

    def release(self):
        raise NotImplementedError()

    def __str__(self):
        raise NotImplementedError()

    @staticmethod
    def frame_length(engine_type):
        if engine_type is Engines.NYUMAYA:
            return 1600
        else:
            return 512

    @staticmethod
    def sensitivity_info(engine_type):
        if engine_type is Engines.POCKET_SPHINX:
            return SensitivityInfo(-21, 15, 3)
        elif engine_type is Engines.PORCUPINE:
            return SensitivityInfo(0, 1, 0.1)
        elif engine_type is Engines.SNOWBOY:
            return SensitivityInfo(0, 1, 0.05)
        elif engine_type is Engines.NYUMAYA:
            return SensitivityInfo(0, 1, 0.1)
        else:
            raise ValueError("no sensitivity range for '%s'", engine_type.value)

    @staticmethod
    def create(engine, keyword, sensitivity):
        if engine is Engines.POCKET_SPHINX:
            return PocketSphinxEngine(keyword, sensitivity)
        elif engine is Engines.PORCUPINE:
            return PorcupineEngine(keyword, sensitivity)
        elif engine is Engines.SNOWBOY:
            return SnowboyEngine(keyword, sensitivity)
        elif engine is Engines.NYUMAYA:
            return NyumayaEngine(keyword, sensitivity)
        else:
            ValueError("cannot create engine of type '%s'", engine.value)


class PocketSphinxEngine(Engine):
    def __init__(self, keyword, sensitivity):
        config = Decoder.default_config()
        config.set_string('-logfn', '/dev/null')
        config.set_string('-hmm', os.path.join(get_model_path(), 'en-us'))
        config.set_string('-dict', os.path.join(get_model_path(), 'cmudict-en-us.dict'))
        config.set_string('-keyphrase', keyword if keyword != 'snowboy' else 'snow boy')
        config.set_float('-kws_threshold', 10 ** -sensitivity)

        self._decoder = Decoder(config)
        self._decoder.start_utt()

    def process(self, pcm):
        assert pcm.dtype == np.int16

        self._decoder.process_raw(pcm.tobytes(), False, False)

        detected = self._decoder.hyp()
        if detected:
            self._decoder.end_utt()
            self._decoder.start_utt()

        return detected

    def release(self):
        self._decoder.end_utt()

    def __str__(self):
        return 'PocketSphinx'


class PorcupineEngine(Engine):
    def __init__(self, keyword, sensitivity):
        self._porcupine = Porcupine(
            library_path=os.path.join(self._repo_path, 'lib/linux/x86_64/libpv_porcupine.so'),
            model_path=os.path.join(self._repo_path, 'lib/common/porcupine_params.pv'),
            keyword_paths=[os.path.join(self._repo_path, 'resources/keyword_files/linux/%s_linux.ppn' % keyword.lower())],
            sensitivities=[sensitivity])

    def process(self, pcm):
        assert pcm.dtype == np.int16

        return self._porcupine.process(pcm) == 0

    def release(self):
        self._porcupine.delete()

    def __str__(self):
        return 'Porcupine'

    @property
    def _repo_path(self):
        return os.path.join(os.path.dirname(__file__), 'engines/porcupine')


class SnowboyEngine(Engine):
    def __init__(self, keyword, sensitivity):
        keyword = keyword.lower()
        if keyword == 'alexa':
            model_relative_path = 'engines/snowboy/resources/alexa/alexa-avs-sample-app/alexa.umdl'
        else:
            model_relative_path = 'engines/snowboy/resources/models/%s.umdl' % keyword.replace(' ', '_')

        model_str = os.path.join(os.path.dirname(__file__), model_relative_path).encode()
        resource_filename = os.path.join(os.path.dirname(__file__), 'engines/snowboy/resources/common.res').encode()
        self._snowboy = snowboydetect.SnowboyDetect(resource_filename=resource_filename, model_str=model_str)

        # https://github.com/Kitt-AI/snowboy#pretrained-universal-models

        if keyword == 'jarvis':
            self._snowboy.SetSensitivity(('%f,%f' % (sensitivity, sensitivity)).encode())
        else:
            self._snowboy.SetSensitivity(str(sensitivity).encode())

        if keyword in {'alexa', 'computer', 'jarvis', 'view glass'}:
            self._snowboy.ApplyFrontend(True)
        else:
            self._snowboy.ApplyFrontend(False)

    def process(self, pcm):
        assert pcm.dtype == np.int16

        return self._snowboy.RunDetection(pcm.tobytes()) == 1

    def release(self):
        pass

    def __str__(self):
        return 'Snowboy'

class NyumayaEngine(Engine):
    def __init__(self, keyword, sensitivity):
        #logging.info("INIT NYUMAYA")
        keyword = keyword.lower()
        model_relative_path = 'engines/nyumaya_audio_recognition/models/Hotword/%s_v1.0.0.premium' % keyword

        model_str = os.path.join(os.path.dirname(__file__), model_relative_path)
        libpath="engines/nyumaya_audio_recognition/lib/linux_x86_64/libnyumaya_premium.so.1.0.0"

        self._extractor = FeatureExtractor(libpath)
        self._detector = AudioRecognition(libpath)
        keywordId = self._detector.addModel(model_str,sensitivity)

    def process(self, pcm):
        assert pcm.dtype == np.int16
        #logging.info(len(pcm))
        features = self._extractor.signalToMel(pcm.tobytes(),1.0)
        return self._detector.runDetection(features) == 1

    def release(self):
        pass

    def __str__(self):
        return 'Nyumaya'

[((35, 18, 35, 65), 'collections.namedtuple', 'namedtuple', ({(35, 29, 35, 46): '"""SensitivityInfo"""', (35, 48, 35, 64): '"""min, max, step"""'}, {}), "('SensitivityInfo', 'min, max, step')", False, 'from collections import namedtuple\n'), ((84, 17, 84, 41), 'pocketsphinx.pocketsphinx.Decoder.default_config', 'Decoder.default_config', ({}, {}), '()', False, 'from pocketsphinx.pocketsphinx import Decoder\n'), ((91, 24, 91, 39), 'pocketsphinx.pocketsphinx.Decoder', 'Decoder', ({(91, 32, 91, 38): 'config'}, {}), '(config)', False, 'from pocketsphinx.pocketsphinx import Decoder\n'), ((147, 24, 147, 109), 'engines.snowboydetect.SnowboyDetect', 'snowboydetect.SnowboyDetect', (), '', False, 'from engines import snowboydetect\n'), ((181, 26, 181, 51), 'engines.FeatureExtractor', 'FeatureExtractor', ({(181, 43, 181, 50): 'libpath'}, {}), '(libpath)', False, 'from engines import AudioRecognition, FeatureExtractor\n'), ((182, 25, 182, 50), 'engines.AudioRecognition', 'AudioRecognition', ({(182, 42, 182, 49): 'libpath'}, {}), '(libpath)', False, 'from engines import AudioRecognition, FeatureExtractor\n'), ((134, 28, 134, 53), 'os.path.dirname', 'os.path.dirname', ({(134, 44, 134, 52): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((178, 33, 178, 58), 'os.path.dirname', 'os.path.dirname', ({(178, 49, 178, 57): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((86, 47, 86, 63), 'pocketsphinx.get_model_path', 'get_model_path', ({}, {}), '()', False, 'from pocketsphinx import get_model_path\n'), ((87, 48, 87, 64), 'pocketsphinx.get_model_path', 'get_model_path', ({}, {}), '()', False, 'from pocketsphinx import get_model_path\n'), ((116, 25, 116, 93), 'os.path.join', 'os.path.join', ({(116, 38, 116, 53): 'self._repo_path', (116, 55, 116, 92): '"""lib/linux/x86_64/libpv_porcupine.so"""'}, {}), "(self._repo_path, 'lib/linux/x86_64/libpv_porcupine.so')", False, 'import os\n'), ((117, 23, 117, 86), 'os.path.join', 'os.path.join', ({(117, 36, 117, 51): 'self._repo_path', (117, 53, 117, 85): '"""lib/common/porcupine_params.pv"""'}, {}), "(self._repo_path, 'lib/common/porcupine_params.pv')", False, 'import os\n'), ((145, 33, 145, 58), 'os.path.dirname', 'os.path.dirname', ({(145, 49, 145, 57): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((146, 41, 146, 66), 'os.path.dirname', 'os.path.dirname', ({(146, 57, 146, 65): '__file__'}, {}), '(__file__)', False, 'import os\n')]