/*
**
** Copyright (C) 2014, The Android Open Source Project
**
** 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.
*/

#define LOG_TAG "SoundTrigger"
//#define LOG_NDEBUG 0

#include <utils/Log.h>
#include <utils/threads.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>

#include <soundtrigger/SoundTrigger.h>
#include <soundtrigger/ISoundTrigger.h>
#include <soundtrigger/ISoundTriggerHwService.h>
#include <soundtrigger/ISoundTriggerClient.h>
#include <soundtrigger/SoundTriggerCallback.h>

namespace android {

namespace {
    sp<ISoundTriggerHwService> gSoundTriggerHwService;
    const int                  kSoundTriggerHwServicePollDelay = 500000; // 0.5s
    const char*                kSoundTriggerHwServiceName      = "media.sound_trigger_hw";
    Mutex                      gLock;

    class DeathNotifier : public IBinder::DeathRecipient
    {
    public:
        DeathNotifier() {
        }

        virtual void binderDied(const wp<IBinder>& who __unused) {
            ALOGV("binderDied");
            Mutex::Autolock _l(gLock);
            gSoundTriggerHwService.clear();
            ALOGW("Sound trigger service died!");
        }
    };

    sp<DeathNotifier>         gDeathNotifier;
}; // namespace anonymous

const sp<ISoundTriggerHwService>& SoundTrigger::getSoundTriggerHwService()
{
    Mutex::Autolock _l(gLock);
    if (gSoundTriggerHwService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16(kSoundTriggerHwServiceName));
            if (binder != 0) {
                break;
            }
            ALOGW("SoundTriggerHwService not published, waiting...");
            usleep(kSoundTriggerHwServicePollDelay);
        } while(true);
        if (gDeathNotifier == NULL) {
            gDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(gDeathNotifier);
        gSoundTriggerHwService = interface_cast<ISoundTriggerHwService>(binder);
    }
    ALOGE_IF(gSoundTriggerHwService == 0, "no SoundTriggerHwService!?");
    return gSoundTriggerHwService;
}

// Static methods
status_t SoundTrigger::listModules(struct sound_trigger_module_descriptor *modules,
                                 uint32_t *numModules)
{
    ALOGV("listModules()");
    const sp<ISoundTriggerHwService>& service = getSoundTriggerHwService();
    if (service == 0) {
        return NO_INIT;
    }
    return service->listModules(modules, numModules);
}

sp<SoundTrigger> SoundTrigger::attach(const sound_trigger_module_handle_t module,
                                            const sp<SoundTriggerCallback>& callback)
{
    ALOGV("attach()");
    sp<SoundTrigger> soundTrigger;
    const sp<ISoundTriggerHwService>& service = getSoundTriggerHwService();
    if (service == 0) {
        return soundTrigger;
    }
    soundTrigger = new SoundTrigger(module, callback);
    status_t status = service->attach(module, soundTrigger, soundTrigger->mISoundTrigger);

    if (status == NO_ERROR && soundTrigger->mISoundTrigger != 0) {
        IInterface::asBinder(soundTrigger->mISoundTrigger)->linkToDeath(soundTrigger);
    } else {
        ALOGW("Error %d connecting to sound trigger service", status);
        soundTrigger.clear();
    }
    return soundTrigger;
}


status_t SoundTrigger::setCaptureState(bool active)
{
    ALOGV("setCaptureState(%d)", active);
    const sp<ISoundTriggerHwService>& service = getSoundTriggerHwService();
    if (service == 0) {
        return NO_INIT;
    }
    return service->setCaptureState(active);
}

// SoundTrigger
SoundTrigger::SoundTrigger(sound_trigger_module_handle_t module,
                                 const sp<SoundTriggerCallback>& callback)
    : mModule(module), mCallback(callback)
{
}

SoundTrigger::~SoundTrigger()
{
    if (mISoundTrigger != 0) {
        mISoundTrigger->detach();
    }
}


void SoundTrigger::detach() {
    ALOGV("detach()");
    Mutex::Autolock _l(mLock);
    mCallback.clear();
    if (mISoundTrigger != 0) {
        mISoundTrigger->detach();
        IInterface::asBinder(mISoundTrigger)->unlinkToDeath(this);
        mISoundTrigger = 0;
    }
}

status_t SoundTrigger::loadSoundModel(const sp<IMemory>& modelMemory,
                                sound_model_handle_t *handle)
{
    Mutex::Autolock _l(mLock);
    if (mISoundTrigger == 0) {
        return NO_INIT;
    }

    return mISoundTrigger->loadSoundModel(modelMemory, handle);
}

status_t SoundTrigger::unloadSoundModel(sound_model_handle_t handle)
{
    Mutex::Autolock _l(mLock);
    if (mISoundTrigger == 0) {
        return NO_INIT;
    }
    return mISoundTrigger->unloadSoundModel(handle);
}

status_t SoundTrigger::startRecognition(sound_model_handle_t handle,
                                        const sp<IMemory>& dataMemory)
{
    Mutex::Autolock _l(mLock);
    if (mISoundTrigger == 0) {
        return NO_INIT;
    }
    return mISoundTrigger->startRecognition(handle, dataMemory);
}

status_t SoundTrigger::stopRecognition(sound_model_handle_t handle)
{
    Mutex::Autolock _l(mLock);
    if (mISoundTrigger == 0) {
        return NO_INIT;
    }
    return mISoundTrigger->stopRecognition(handle);
}

// BpSoundTriggerClient
void SoundTrigger::onRecognitionEvent(const sp<IMemory>& eventMemory)
{
    Mutex::Autolock _l(mLock);
    if (eventMemory == 0 || eventMemory->pointer() == NULL) {
        return;
    }

    if (mCallback != 0) {
        mCallback->onRecognitionEvent(
                (struct sound_trigger_recognition_event *)eventMemory->pointer());
    }
}

void SoundTrigger::onSoundModelEvent(const sp<IMemory>& eventMemory)
{
    Mutex::Autolock _l(mLock);
    if (eventMemory == 0 || eventMemory->pointer() == NULL) {
        return;
    }

    if (mCallback != 0) {
        mCallback->onSoundModelEvent(
                (struct sound_trigger_model_event *)eventMemory->pointer());
    }
}

void SoundTrigger::onServiceStateChange(const sp<IMemory>& eventMemory)
{
    Mutex::Autolock _l(mLock);
    if (eventMemory == 0 || eventMemory->pointer() == NULL) {
        return;
    }

    if (mCallback != 0) {
        mCallback->onServiceStateChange(
                *((sound_trigger_service_state_t *)eventMemory->pointer()));
    }
}

//IBinder::DeathRecipient
void SoundTrigger::binderDied(const wp<IBinder>& who __unused) {
    Mutex::Autolock _l(mLock);
    ALOGW("SoundTrigger server binder Died ");
    mISoundTrigger = 0;
    if (mCallback != 0) {
        mCallback->onServiceDied();
    }
}

status_t SoundTrigger::stringToGuid(const char *str, sound_trigger_uuid_t *guid)
{
    if (str == NULL || guid == NULL) {
        return BAD_VALUE;
    }

    int tmp[10];

    if (sscanf(str, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
            tmp, tmp+1, tmp+2, tmp+3, tmp+4, tmp+5, tmp+6, tmp+7, tmp+8, tmp+9) < 10) {
        return BAD_VALUE;
    }
    guid->timeLow = (uint32_t)tmp[0];
    guid->timeMid = (uint16_t)tmp[1];
    guid->timeHiAndVersion = (uint16_t)tmp[2];
    guid->clockSeq = (uint16_t)tmp[3];
    guid->node[0] = (uint8_t)tmp[4];
    guid->node[1] = (uint8_t)tmp[5];
    guid->node[2] = (uint8_t)tmp[6];
    guid->node[3] = (uint8_t)tmp[7];
    guid->node[4] = (uint8_t)tmp[8];
    guid->node[5] = (uint8_t)tmp[9];

    return NO_ERROR;
}

status_t SoundTrigger::guidToString(const sound_trigger_uuid_t *guid, char *str, size_t maxLen)
{
    if (guid == NULL || str == NULL) {
        return BAD_VALUE;
    }

    snprintf(str, maxLen, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
            guid->timeLow,
            guid->timeMid,
            guid->timeHiAndVersion,
            guid->clockSeq,
            guid->node[0],
            guid->node[1],
            guid->node[2],
            guid->node[3],
            guid->node[4],
            guid->node[5]);

    return NO_ERROR;
}

}; // namespace android
