/*
 * Copyright (c) 2017 TOYOTA MOTOR CORPORATION
 *
 * 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.
 */

#include <stdarg.h>
#include <sys/socket.h>
#include <iostream>
#include <algorithm>
#include "libsoundmanager.hpp"

#define ELOG(args,...) _ELOG(__FUNCTION__,__LINE__,args,##__VA_ARGS__)
#ifdef DEBUGMODE
 #define DLOG(args,...) _DLOG(__FUNCTION__,__LINE__,args,##__VA_ARGS__)
#else
 #define DLOG(args,...)
#endif
static void _DLOG(const char* func, const int line, const char* log, ...);
static void _ELOG(const char* func, const int line, const char* log, ...);

using namespace std;

static bool has_verb(const std::string& verb);
static const char API[] = "soundmanager";

static const std::vector<std::string> api_list{
    std::string("connect"),
    std::string("disconnect"),
    std::string("setVolume"),
    std::string("volumeStep"),
    std::string("setSinkMuteState"),
    std::string("getListMainConnections"),
    std::string("getListMainSources"),
    std::string("getListMainSinks"),
    std::string("ackConnect"),
    std::string("ackDisconnect"),
    std::string("ackSetSourceState"),
    std::string("registerSource"),
    std::string("deregisterSource"),
    std::string("subscribe"),
    std::string("unsubscribe"),
    std::string("stream_open"),
    std::string("stream_close"),
    std::string("set_stream_state")
};

static const std::vector<std::string> event_list{
    std::string("asyncSetSourceState"),
    std::string("newMainConnection"),
    std::string("volumeChanged"),
    std::string("removedMainConnection"),
    std::string("sinkMuteStateChanged"),
    std::string("mainConnectionStateChanged"),
    std::string("setRoutingReady"),
    std::string("setRoutingRundown"),
    std::string("asyncConnect"),
    std::string("stream_state_event")
};

static void _onHangupStatic(void *closure, struct afb_wsj1 *wsj)
{
    static_cast<Soundmanager*>(closure)->_onHangup(NULL,wsj);
}

static void _onCallStatic(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg)
{
    /* Soundmanager is not called from other process */
}

static void _onEventStatic(void* closure, const char* event, struct afb_wsj1_msg *msg)
{
    static_cast<Soundmanager*>(closure)->_onEvent(NULL,event,msg);
}

static void _onReplyStatic(void *closure, struct afb_wsj1_msg *msg)
{
    static_cast<Soundmanager*>(closure)->_onReply(NULL,msg);
}

Soundmanager::Soundmanager()
{
}

Soundmanager::~Soundmanager()
{
    if(mploop)
    {
        sd_event_unref(mploop);
    }
    if(sp_websock != NULL)
    {
        afb_wsj1_unref(sp_websock);
    }
}

/**
 * This function is initialization function
 *
 * #### Parameters
 * - port  [in] : This argument should be specified to the port number to be used for websocket
 * - token [in] : This argument should be specified to the token to be used for websocket
 *
 * #### Return
 * Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 *
 */
int Soundmanager::init(int port, const string& token)
{
    int ret;
    if(port > 0 && token.size() > 0)
    {
        mport = port;
        mtoken = token;
    }
    else
    {
        ELOG("port and token should be > 0, Initial port and token uses.");
        return -1;
    }

    ret = initializeWebsocket();
    if(ret != 0 )
    {
        ELOG("Failed to initialize websocket");
        return -1;
    }
    ret = initEvent();
    if(ret != 0 )
    {
        ELOG("Failed to initialize websocket");
        return -1;
    }
    return 0;
}

int Soundmanager::initializeWebsocket()
{
    mploop = NULL;
    onEvent = nullptr;
    onReply = nullptr;
    int ret = sd_event_default(&mploop);
    if(ret < 0)
    {
        ELOG("Failed to create event loop");
        goto END;
    }
    /* Initialize interface from websocket */
    {
    minterface.on_hangup = _onHangupStatic;
    minterface.on_call = _onCallStatic;
    minterface.on_event = _onEventStatic;
    string muri = "ws://localhost:" + to_string(mport) + "/api?token=" + mtoken;
    sp_websock = afb_ws_client_connect_wsj1(mploop, muri.c_str(), &minterface, this);
    }
    if(sp_websock == NULL)
    {
        ELOG("Failed to create websocket connection");
        goto END;
    }

    return 0;
END:
    if(mploop)
    {
        sd_event_unref(mploop);
    }
    return -1;
}

struct sd_event* Soundmanager::getEventLoop(){
    return mploop;
}

int Soundmanager::initEvent(){
    /* subscribe most important event for sound right */
    return subscribe(string("asyncSetSourceState"));
}

/**
 * This function register callback function for reply/event message from sound manager
 *
 * #### Parameters
 * - event_cb  [in] : This argument should be specified to the callback for subscribed event
 * - reply_cb  [in] : This argument should be specified to the reply callback for call function
 * - hangup_cb [in] : This argument should be specified to the hangup callback for call function. nullptr is defaulty set.
 *
 * #### Return
 *
 * #### Note
 * Event callback is invoked by sound manager for event you subscribed.
 * If you would like to get event, please call subscribe function before/after this function
 */
void Soundmanager::registerCallback(
    void (*event_cb)(const string& event, struct json_object* event_contents),
    void (*reply_cb)(struct json_object* reply_contents),
    void (*hangup_cb)(void))
{
    onEvent = event_cb;
    onReply = reply_cb;
    onHangup = hangup_cb;
}

/**
 * This function calls registerSource of Audio Manager via WebSocket
 * registerSource is registration as source for policy management
 *
 * #### Parameters
 * - audio_role [in] : This argument should be specified to the source name (e.g. "MediaPlayer" which is role)
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function must be called to get source ID
 * sourceID is returned by async reply function.
 * {
 * "response":{
 *   "verb":"registerSource",
 *   "error":0,
 *   "sourceID":101
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 *
 *
 */
int Soundmanager::registerSource(const string& audio_role)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jsn = json_object_new_string(audio_role.c_str());
    json_object_object_add(j_obj, "audio_role", jsn);
    return this->call(__FUNCTION__, j_obj);
}

/**
 * This function calls connect of Audio Manager via WebSocket
 * connect is to get sound right
 *
 * #### Parameters
 * - sourceID [in] : This argument should be specified to the sourceID as int. This parameter is returned value of registerSource
 * - sinkID   [in] : This argument should be specified to the sinkID as int. ID is specified by AudioManager
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function must be called to get source right
 * connectionID is returned by reply event.
 * Response is like here.
 * {
 * "response":{
 *   "verb":"connect",
 *   "error":0,
 *   "mainConnectionID":2
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 * }
 */
int Soundmanager::connect(int source_id, int sink_id)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jsource = json_object_new_int(source_id);
    struct json_object* jsink = json_object_new_int(sink_id);
    json_object_object_add(j_obj, "sourceID", jsource);
    json_object_object_add(j_obj, "sinkID", jsink);
    return this->call(__FUNCTION__, j_obj);
}

/**
 * This function calls the connect of Audio Manager via WebSocket
 * This function is overload of connect
 * Instead of sinkID as the number, abstract sink_name will be supported.
 * For now, "default" is only supported
 *
 * #### Parameters
 * - source_id   [in] : This argument should be specified to the sourceID as int. This parameter is returned value of registerSource
 * - sink_name   [in] : This argument should be specified to the sinkID as string. ID is aliased by SoundManager (e.g: "default")
 *
 * #### Rreturn
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * For now, aliase(hardware abstraction) like "DriverZone:Speaker" is under hard consideration
 * Just "default" is usable.
 *
 */
int Soundmanager::connect(int source_id, const string& sink_name)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jsource = json_object_new_int(source_id);
    //struct json_object* jsink = json_object_new_int(1);
    struct json_object* jsink = json_object_new_string(sink_name.c_str());
    json_object_object_add(j_obj, "sourceID", jsource);
    json_object_object_add(j_obj, "sinkID", jsink);
    return this->call(__FUNCTION__, j_obj);
}

/**
 * This function calls the disconnect of Audio Manager via WebSocket
 *
 * #### Parameters
 * - connection_id  [in] : This parameter is returned value of connect
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * Response is like here
 * {
 * "response":{
 *   "verb":"disconnect",
 *   "error":0
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 * }
 *
 */
int Soundmanager::disconnect(int connection_id)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jconnection = json_object_new_int(connection_id);
    json_object_object_add(j_obj, "mainConnectionID", jconnection);
    return this->call(__FUNCTION__, j_obj);
}

/**
 * This function calls the ackSetSourceState of Audio Manager via WebSocket
 *
 * #### Parameters
 * - handle    [in] : This parameter is returned value of ackSetSourceState
 * - errno     [in] : If you have some errors, input ohter than 0. 0 means acknowledge
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function must be called when application get asyncSetSourceState event
 * Input handle number attached in asyncSetSourceState and error number(0 is acknowledge)
 */
int Soundmanager::ackSetSourceState(int handle, int error)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jhandle = json_object_new_int(handle);
    struct json_object* jerrno = json_object_new_int(error);
    json_object_object_add(j_obj, "handle", jhandle);
    json_object_object_add(j_obj, "error", jerrno);
    return this->call(__FUNCTION__, j_obj);
}

/**
 * This function calls the getListMainSources of Audio Manager via WebSocket.
 * Get source list like here.
 * {
 * "response":{
 *   "verb":"getListMainSources",
 *   "sources":[
 *     {
 *       "sourceID":102,
 *       "sourceName":"radio",
 *       "availability":1,
 *       "availabilityReason":0,
 *       "sourceClassID":101
 *     }
 *   ]
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"Success to get main source list"
 * }
 *
 *
 * #### Parameters
 * NULL
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 */
int Soundmanager::getListMainSources(){
    if(!sp_websock)
    {
        return -1;
    }
    return this->call(__FUNCTION__, json_object_new_object());
}

/**
 * This function calls the getListMainSinks of Audio Manager via WebSocket.
 * Get sink list like here.
 * {
 * "response":{
 *   "verb":"getListMainSinks",
 *   "sinks":[
 *     {
 *       "sinkID":1,
 *       "sinkName":"rsnd-dai.0-ak4642-hifi#Analog#Stereo",
 *       "availability":1,
 *       "availabilityReason":0,
 *       "volume":100,
 *       "muteState":2,
 *       "sinkClassID":101
 *     },
 *     {
 *       "sinkID":2,
 *       "sinkName":"Microchip#MOST:0#Multichannel",
 *       "availability":1,
 *       "availabilityReason":0,
 *       "volume":100,
 *       "muteState":2,
 *       "sinkClassID":101
 *     }
 *   ]
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"Success to get main sink list",
 *   "uuid":"ba34fdc7-5c9e-436e-90ca-62abe6a7c4d3"
 * }
 *
 * #### Parameters
 * NULL
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 */
int Soundmanager::getListMainSinks(){
    if(!sp_websock)
    {
        return -1;
    }
    return this->call(__FUNCTION__, json_object_new_object());
}

/**
 * This function calls the getListMainConnections of Audio Manager via WebSocket.
 * Connection is established with success of "connect".
 * Get main connection list like here
 * {
 *   "response":{
 *     "verb":"getListMainConnections",
 *     "connections":[
 *       {
 *         "mainConnectionID":1,
 *         "sourceID":101,
 *         "sinkID":1,
 *         "delay":-1,
 *         "connectionState":2
 *       }
 *     ]
 *   },
 *   "jtype":"afb-reply",
 *   "request":{
 *     "status":"success",
 *     "info":"Success to get main connection list"
 *   }
 * }
 *
 * #### Parameters
 * NULL
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 */
int Soundmanager::getListMainConnections(){
    if(!sp_websock)
    {
        return -1;
    }
    return this->call(__FUNCTION__, json_object_new_object());
}

/**
 * This function calls the stream_open via WebSocket.
 * stream_id which is same meaning as sourceID is returned with success.
 * Sound Manager bypasses registerSource.
 *
 * #### Parameters
 * - audio_role  [in] : This argument should be specified to the source name
 * - endpoint_id [in] : This argument should be specified to the sinkID as int. ID is specified by AudioManager
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * Response is like here
 * {
 * "response":{
 *   "verb":"stream_open",
 *   "error":0,
 *   "stream_id":102
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 * }
 *
 */
int Soundmanager::streamOpen(const std::string& audio_role, int endpoint_id){
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jsource = json_object_new_string(audio_role.c_str());
    struct json_object* jsink = json_object_new_int(endpoint_id);
    json_object_object_add(j_obj, "audio_role", jsource);
    json_object_object_add(j_obj, "endpoint_id", jsink);
    return this->call("stream_open", j_obj);
}

/**
 * This function is overload function.
 * "default" is only supported for now.
 */
int Soundmanager::streamOpen(const std::string& audio_role, const std::string& endpoint_id){
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jsource = json_object_new_string(audio_role.c_str());
    struct json_object* jsink = json_object_new_string(endpoint_id.c_str());
    json_object_object_add(j_obj, "audio_role", jsource);
    json_object_object_add(j_obj, "endpoint_id", jsink);
    return this->call("stream_open", j_obj);
}

/**
 * This function calls the stream_close via WebSocket
 *
 * #### Parameters
 * - stream_id  [in] : This parameter is returned value of stream_open
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * Response is like here
 * {
 * "response":{
 *   "verb":"stream_close",
 *   "error":0
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 * }
 *
 */
int Soundmanager::streamClose(int stream_id){
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jstream = json_object_new_int(stream_id);
    json_object_object_add(j_obj, "stream_id", jstream);
    return this->call("stream_close", j_obj);
}

/**
 * This function calls set_stream_state via WebSocket.
 * Sound Manager bypasses connect/disconnect judging with mute state.
 * If mute is 0, which means unmute, Sound Manager calls connect, but
 * Sound Manager doesn't return mainConnectionID because Sound Manager stores
 * and remember mainConnectionID for stream_id inside itself using afb-binder function.
 * If mute is 1, which means mute, Sound Manager calls disconnect.
 *
 * After set_source_state with unmute(call connect),
 * "stream_state_event" is published.
 * This event is same as "asyncSetSourceState" and means authorization to output sound.
 *
 * The json message of "stream_state_event" is like here.
 * {
 * "event":"soundmanager\/stream_state_event",
 * "data":{
 *   "handle":2,
 *   "sourceID":101,
 *   "sourceState":"on",
 *   "event_name":"ahl_stream_state_event",
 *   "stream_id":101,
 *   "state_event":1
 * },
 * "jtype":"afb-event"
 * }
 *
 * stream_id is same as sourceID.
 * state_event is same as sourceState but type is different.
 *
 * This event is same as asyncSetSourceState but application doesn't have to call
 * "ackSetSourceState" because Sound Manager send it to Audio MAnager on behalf of applicaiton.
 * if the application uses high level API.
 *
 * Not recommend to confuse connect/disconnect with set_stream_state
 * and confuse registerSource with stream_open even if those are same meanings because
 * the processes of those functions inside Sound Manager are a little different.
 *
 * #### Parameters
 * - streamID [in] : This parameter is returned value of stream_open
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function must be called to get source right.
 * If an application uses "connect" instead of "set_stream_state",
 * it must send "ackSetSourceState" otherwise connection will be removed.
 *
 * Response of this funcion is like here.
 * {
 * "response":{
 *   "verb":"set_stream_state",
 *   "error":0
 * },
 * "jtype":"afb-reply",
 * "request":{
 *   "status":"success",
 *   "info":"OK"
 * }
 * }
 *
 */
int Soundmanager::setStreamState(int stream_id, int mute_state){
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    struct json_object* jstream = json_object_new_int(stream_id);
    struct json_object* jmute = json_object_new_int(mute_state);
    json_object_object_add(j_obj, "stream_id", jstream);
    json_object_object_add(j_obj, "mute", jmute);
    return this->call("set_stream_state", j_obj);
}

/**
 * This function calls the API of Audio Manager via WebSocket
 *
 * #### Parameters
 * - verb [in] : This argument should be specified to the API name (e.g. "connect")
 * - arg  [in] : This argument should be specified to the argument of API. And this argument expects JSON object
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * To call Audio Manager's APIs, the application should set its function name, arguments to JSON format.
 *
 */
int Soundmanager::call(const string& verb, struct json_object* arg)
{
    int ret = -1;
    if(!sp_websock)
    {
        return ret;
    }
    if (!has_verb(verb))
    {
        ELOG("verb doesn't exit");
        return ret;
    }
    ret = afb_wsj1_call_j(sp_websock, API, verb.c_str(), arg, _onReplyStatic, this);
    if (ret < 0) {
        ELOG("Failed to call verb:%s",verb.c_str());
    }
    return ret;
}

/**
 * This function calls the API of Audio Manager via WebSocket
 * This function is overload function of "call"
 *
 * #### Parameters
 * - verb [in] : This argument should be specified to the API name (e.g. "connect")
 * - arg  [in] : This argument should be specified to the argument of API. And this argument expects JSON object
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * To call Audio Manager's APIs, the application should set its function name, arguments to JSON format.
 *
 */
int Soundmanager::call(const char* verb, struct json_object* arg)
{
    int ret = -1;
    if(!sp_websock)
    {
        return ret;
    }
    if (!has_verb(string(verb)))
    {
        ELOG("verb doesn't exit");
        return ret;
    }
    ret = afb_wsj1_call_j(sp_websock, API, verb, arg, _onReplyStatic, this);
    if (ret < 0) {
        ELOG("Failed to call verb:%s",verb);
    }
    return ret;
}

/**
 * Register callback function for each event
 *
 * #### Parameters
 * - event_name [in] : This argument should be specified to the event name
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function enables to get an event to your callback function.
 * Regarding the list of event name, please refer to CommandSender API and RountingSender API.
 *
 */
int Soundmanager::subscribe(const string& event_name)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str()));

    int ret = afb_wsj1_call_j(sp_websock, API, "subscribe", j_obj, _onReplyStatic, this);
    if (ret < 0) {
        ELOG("Failed to call verb:%s",__FUNCTION__);
    }
    return ret;
}

/**
 * Unregister callback function for each event
 *
 * #### Parameters
 * - event_name [in] : This argument should be specified to the event name
 *
 * #### Return
 * - Returns 0 on success or -1 in case of transmission error.
 *
 * #### Note
 * This function disables to get an event to your callback function.
 *
 */
int Soundmanager::unsubscribe(const string& event_name)
{
    if(!sp_websock)
    {
        return -1;
    }
    struct json_object* j_obj = json_object_new_object();
    json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str()));

    int ret = afb_wsj1_call_j(sp_websock, API, "unsubscribe", j_obj, _onReplyStatic, this);
    if (ret < 0) {
        ELOG("Failed to call verb:%s",__FUNCTION__);
    }
    return ret;
}

/************* Callback Function *************/

void Soundmanager::_onHangup(void *closure, struct afb_wsj1 *wsj)
{
    DLOG("%s called", __FUNCTION__);
    if(onHangup != nullptr)
    {
        onHangup();
    }
}

void Soundmanager::_onCall(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg)
{
}

/*
* event is like "soundmanager/newMainConnection"
* msg is like {"event":"soundmanager\/newMainConnection","data":{"mainConnectionID":3,"sourceID":101,"sinkID":100,"delay":0,"connectionState":4},"jtype":"afb-event"})}
*               ^key^   ^^^^^^^^^^^^ value ^^^^^^^^^^^^
* so you can get
    event name : struct json_object obj = json_object_object_get(msg,"event")
*/
void Soundmanager::_onEvent(void *closure, const char *event, struct afb_wsj1_msg *msg)
{
    /* check event is for us */
    string ev = string(event);
    if (ev.find(API) == string::npos) {
        /* It's not us */
        return;
    }
    struct json_object* ev_contents = afb_wsj1_msg_object_j(msg);
    if((onEvent != nullptr))
    {
        onEvent(ev, ev_contents);
    }
    else{}

    json_object_put(ev_contents);
}

void Soundmanager::_onReply(void *closure, struct afb_wsj1_msg *msg)
{
    struct json_object* reply = afb_wsj1_msg_object_j(msg);
    if(onReply != nullptr)
    {
        onReply(reply);
    }
    json_object_put(reply);
}

/* Internal Function in libsoundmanager */

static void _ELOG(const char* func, const int line, const char* log, ...)
{
    char *message;
    va_list args;
    va_start(args, log);
    if (log == NULL || vasprintf(&message, log, args) < 0)
        message = NULL;
    cerr << "[ERROR: soundmanager]" << func << "(" << line << "):" << message << endl;
    va_end(args);
    free(message);
}

static void _DLOG(const char* func, const int line, const char* log, ...)
{
    char *message;
    va_list args;
    va_start(args, log);
    if (log == NULL || vasprintf(&message, log, args) < 0)
        message = NULL;
    cout << "[DEBUG: soundmanager]" << func << "(" << line << "):" << message << endl;
    va_end(args);
    free(message);
}

static bool has_verb(const string& verb)
{
    if(find(api_list.begin(), api_list.end(), verb) != api_list.end())
        return true;
    else
        return false;
}