/*---------------------------------------------------------------------------*/
/* ringer_io.cpp                                                               */
/* copyright (c) innovaphone 2021                                            */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"

#include "common/interface/socket.h"
#include "common/interface/audio.h"
#include "common/interface/media.h"
#include "common/interface/ringer.h"
#include "common/interface/local_storage.h"

#include "common/ilib/json.h"

#include "ringer_io.h"

class IRingerIo * IRingerIo::Create(class IIoMux * const iomux, class IRinger * const ringer, class IInstanceLog * log, class MediaConfiguration* mediaConfiguration)
{
    return new class RingerIo(iomux, ringer, log, mediaConfiguration);
}

/*---------------------------------------------------------------------------*/
/* RingerIo                                                                   */
/*---------------------------------------------------------------------------*/

RingerIo::RingerIo(class IIoMux * const iomux, class IRinger * const ringer, class IInstanceLog * log, class MediaConfiguration* mediaConfiguration)
{
    this->iomux = iomux;
    this->ringer = ringer;
    this->user = NULL;
    this->deviceIoUser = NULL;
    this->ringtoneUser = NULL;
    this->log = log;
    this->shutDown = false;
    this->execCnt = 0;
    this->selectDeviceUser = 0;
    this->softphoneApp = NULL;
    this->mediaConfiguration = mediaConfiguration;
    this->selectedDeviceRemoved = false;
    this->selectedRingtoneRemoved = false;

    ReadSelectedDevices();
    ReadSelectedRingtones();

    if(ringer) ringer->Initialize(this);
}

RingerIo::~RingerIo()
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::~RingerIo", this);

    if (softphoneApp) free(softphoneApp);
    while (selectedRingtones.empty() == false) delete selectedRingtones.front();
    while (devicesList.empty() == false) delete devicesList.front();
    while (ringtonesList.empty() == false) delete ringtonesList.front();
    while (notificationToneList.empty() == false) delete notificationToneList.front();
    while (selectedDevices.empty() == false) delete selectedDevices.front();
}

void RingerIo::Initialize(class URingerIo * const user, class UDeviceIo * const deviceIoUser, class URingtone * const ringtoneUser)
{
    this->user = user;
    this->deviceIoUser = deviceIoUser;
    this->ringtoneUser = ringtoneUser;
}

void RingerIo::ReadSelectedDevices()
{
    while (selectedDevices.empty() == false) delete selectedDevices.front();

    char * ringerSelectedDevicesStr = ILocalStorage::GetItem("ringerSelectedDevices");
    if (ringerSelectedDevicesStr) {
        if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::ReadSelectedDevices (%s)", this, ringerSelectedDevicesStr);
        class json_io jsonDevices(ringerSelectedDevicesStr);
        if (jsonDevices.decode()) {
            word c_last = 0, t, end = 0xFFFF;
            word base = jsonDevices.get_object(JSON_ID_ROOT, 0);
            word devsArray = jsonDevices.get_array(base, "devices");
            while ((t = jsonDevices.get_object(devsArray, c_last)) != end) {
                if (jsonDevices.get_string(t, "device") && jsonDevices.get_string(t, "app")) {
                    class SelectedDeviceIo * dev = new class SelectedDeviceIo(jsonDevices.get_string(t, "app"), jsonDevices.get_string(t, "device"), jsonDevices.get_bool(t, "remote"));
                    selectedDevices.push_back(dev);
                }
            }
        }
        else if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::ReadSelectedDevices failed to decode json", this);
        free(ringerSelectedDevicesStr);
    }
}

void RingerIo::ReadSelectedRingtones()
{
    while (selectedRingtones.empty() == false) delete selectedRingtones.front();

    char * ringerSelectedRingtonesStr = ILocalStorage::GetItem("ringerSelectedRingtones");
    if (ringerSelectedRingtonesStr) {
        if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::ReadSelectedRingtones (%s)", this, ringerSelectedRingtonesStr);
        class json_io jsonRingtones(ringerSelectedRingtonesStr);
        if (jsonRingtones.decode()) {
            word c_last = 0, t, end = 0xFFFF;
            word base = jsonRingtones.get_object(JSON_ID_ROOT, 0);
            word devsArray = jsonRingtones.get_array(base, "ringtones");
            while ((t = jsonRingtones.get_object(devsArray, c_last)) != end) {
                if (jsonRingtones.get_string(t, "ringtone") && jsonRingtones.get_string(t, "app")) {
                    class SelectedDeviceIo * dev = new class SelectedDeviceIo(jsonRingtones.get_string(t, "app"), jsonRingtones.get_string(t, "ringtone"), jsonRingtones.get_bool(t, "remote"));
                    selectedRingtones.push_back(dev);
                }
            }
        }
        else if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::ReadSelectedRingtones failed to decode ringtone json", this);
        free(ringerSelectedRingtonesStr);
    }
}

void RingerIo::WriteSelectedDevices()
{
    int jsonSize = 128;
    int intSize = 0;
    class SelectedDeviceIo * dev = selectedDevices.front();
    while (dev) {
        jsonSize += strlen(dev->app) + strlen(dev->device) + 40;   // {"app":"","device":"","mode":1},
        intSize += 32;
        dev = dev->goNext();
    }
    char * j = (char *)malloc(jsonSize);
    class json_io jsonDevices(j);
    word base = jsonDevices.add_object(0xffff, 0);
    word a = jsonDevices.add_array(base, "devices");
    dev = selectedDevices.front();
    while (dev) {
        word e = jsonDevices.add_object(a, "selectedDevice");
        jsonDevices.add_string(e, "app", dev->app);
        jsonDevices.add_string(e, "device", dev->device);
        jsonDevices.add_bool(e, "remote", dev->isRemote);
        dev = dev->goNext();
    }
    jsonDevices.encode();
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::WriteSelectedDevices Devices json sizes=%d,%d (%s)", this, jsonSize, intSize, j);
    ILocalStorage::SetItem("ringerSelectedDevices", j);
    free(j);
}

void RingerIo::WriteSelectedRingtones()
{
    int jsonSize = 128;
    int intSize = 0;
    class SelectedDeviceIo* dev = selectedRingtones.front();
    while (dev) {
        jsonSize += strlen(dev->app) + strlen(dev->device) + 40;   // {"app":"","device":"","mode":1},
        intSize += 32;
        dev = dev->goNext();
    }
    char * j = (char *)malloc(jsonSize);
    class json_io jsonRingtones(j);
    word base = jsonRingtones.add_object(0xffff, 0);
    word a = jsonRingtones.add_array(base, "ringtones");
    dev = selectedRingtones.front();
    while (dev) {
        word e = jsonRingtones.add_object(a, "selectedRingtone");
        jsonRingtones.add_string(e, "app", dev->app);
        jsonRingtones.add_string(e, "ringtone", dev->device);
        jsonRingtones.add_bool(e, "remote", dev->isRemote);
        dev = dev->goNext();
    }
    jsonRingtones.encode();
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::WriteSelectedRingtones Devices json sizes=%d,%d (%s)", this, jsonSize, intSize, j);
    ILocalStorage::SetItem("ringerSelectedRingtones", j);
    free(j);
}

void RingerIo::Close(class URingerIo * user)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::Close ringer=%p user=%p remote=%d,%d", this, ringer, user, remoteDeviceUsers.empty(), remoteRingtoneUsers.empty());

    this->shutDown = true;

    if (!this->user) this->user = user;

    WriteSelectedDevices();
    WriteSelectedRingtones();

    if (ringer) ringer->Close();
    else {
        execCnt++;
        SetExec(iomux, this, (RingerIoClose *)NULL);
    }
}

void RingerIo::CloseRingerComplete()
{
    ringer = NULL;
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::CloseRingerComplete execCnt=%d", this, execCnt);
    execCnt++;
    SetExec(iomux, this, (RingerIoClose *)NULL);
}

void RingerIo::IoExec(RingerIoClose * context)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::IoExec execCnt=%d shutDown=%d remoteUsers=%d,%d", this, execCnt, shutDown, remoteDeviceUsers.empty(), remoteRingtoneUsers.empty());

    execCnt--;
    if (shutDown && !execCnt) {
        if(remoteDeviceUsers.empty() && remoteRingtoneUsers.empty()) user->CloseRingerIoComplete();
        else {
            execCnt++;
            SetExec(iomux, this, (RingerIoClose*)NULL);
        }
    }
}

void RingerIo::QueryDevices(void * context)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::QueryDevices context=%p deviceIoUser=%p", this, context, deviceIoUser);

    ReadSelectedDevices();

    if (ringer) ringer->QueryDevices(context);
    else if (mediaConfiguration->isRunningTS) RingerDeviceList(context);
}

void RingerIo::CleanRemoteDevices()
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::CleanRemoteDevices", this);

    if (devicesList.empty() == false) {
        class RingerDeviceItem* dev = devicesList.front();
        while (dev) {
            class RingerDeviceItem* next = dev->goNext();
            if (dev->isRemote) {
                if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, NULL, dev->deviceId);
                if (!selectedDevices.empty()) {
                    class SelectedDeviceIo* sel;
                    for (sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                        if (sel->device && !strcmp(sel->device, dev->deviceId)) {
                            sel->available = false;
                            if (dev->isSelected) {
                                if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::CleanRemoteDevices selected device was removed (%s)", this, dev->deviceId);
                                selectedDeviceRemoved = true;
                                if (selectDeviceUser) selectDeviceUser->RingerDeviceRemoved(dev->deviceId);
                            }
                        }
                    }
                }
                dev->remove();
                delete dev;
            }
            dev = next;
        }
    }
}

void RingerIo::QueryRingtones(void * context)
{
    ReadSelectedRingtones();

    if (ringer) ringer->QueryRingtones(context);
    else if (mediaConfiguration->isRunningTS) RingtoneList(context);
}

void RingerIo::QueryNotificationTones(void * context)
{
    if (ringer) ringer->QueryNotificationTones(context);
    else if (mediaConfiguration->isRunningTS) NotificationToneList(context);
}

void RingerIo::RingtoneStart(const char *deviceId, int deviceMode, const char *ident)
{
    if (ringer) ringer->RingtoneStart(deviceId, (enum RingerDeviceMode)deviceMode, ident);
}

void RingerIo::RingtoneStop(const char *deviceId)
{
    if (ringer) ringer->RingtoneStop(deviceId);
}

void RingerIo::NotificationTonePlay(const char *deviceId, int deviceMode, const char *ident)
{
    if (ringer) ringer->NotificationTonePlay(deviceId, (enum RingerDeviceMode)deviceMode, ident);
}

void RingerIo::SelectDevice(const char *app, const char *deviceId)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::SelectDevice app=%s deviceId=%s", this, app, deviceId);

    class SelectedDeviceIo * dev = NULL;
    if (!selectedDevices.empty()) {
        // Delete device if already exists and allow a maximum number of 10 devices pro SoftphoneApp
        int numSrcDevices = 0;
        dev = selectedDevices.front();
        while (dev) {
            class SelectedDeviceIo * next = dev->goNext();
            if (dev->app && app && !strcmp(dev->app, app)) {
                if ((dev->device && deviceId && !strcmp(dev->device, deviceId)) || (numSrcDevices >= 10)) {
                    dev->remove();
                    delete dev;
                }
                else numSrcDevices++;
            }
            dev = next;
        }
    }
    // Push the new device at the front of the list
    dev = new class SelectedDeviceIo(app, deviceId);
    selectedDevices.push_front(dev);
    // Check if device is still available
    class RingerDeviceItem* devItem;
    if ((devItem = FindRingerDevice(dev->device)) != NULL) {
        class RingerDeviceItem* temp = NULL;
        for (temp = devicesList.front(); temp; temp = temp->goNext()) {
            temp->isSelected = false;
        }
        dev->available = true;
        dev->type = devItem->type;
        dev->isRemote = devItem->isRemote;
        if (selectDeviceUser) selectDeviceUser->SelectRingerDevice(this, app, deviceId);
        WriteSelectedDevices();
        devItem->isSelected = true;
    }
}

const char * RingerIo::GetDeviceIo(const char * app)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::GetDeviceIo app=%s selectedDevices=%d", this, app ? app : "", !selectedDevices.empty());

    if (softphoneApp) free(softphoneApp);
    softphoneApp = _strdup(app);

    class SelectedDeviceIo * dev = NULL;
    if (!selectedDevices.empty()) {
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            if (dev->available && dev->app && app && !strcmp(dev->app, app)) {
                if (FindRingerDevice(dev->device)) return dev->device;
                else if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::GetDeviceIo dev=%s not available any more", this, dev->device);
            }
        }
    }
    // Return first ringer device
    if (devicesList.empty() == false) {
        class RingerDeviceItem * dev = devicesList.front();
        return dev->deviceId;
    }
    return NULL;
}

class RingerDeviceItem * RingerIo::FindRingerDevice(const char * deviceId)
{
    if (devicesList.empty() == false) {
        class RingerDeviceItem * dev = devicesList.front();
        while (dev) {
            if (!strcmp(deviceId, dev->deviceId)) return dev;
            dev = dev->goNext();
        }
    }
    return NULL;
}

void RingerIo::SelectRingtone(const char *app, const char *ringtone)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::SelectRingtone app=%s ringtone=%s", this, app, ringtone ? ringtone : "-");

    class SelectedDeviceIo* dev = NULL;
    if (!selectedRingtones.empty()) {
        int numSrcRingtones = 0;
        // Delete device if already exists and allow a maximum number of 10 devices pro SoftphoneApp
        dev = selectedRingtones.front();
        while (dev) {
            class SelectedDeviceIo* next = dev->goNext();
            if (dev->app && app && !strcmp(dev->app, app)) {
                if ((dev->device && ringtone && !strcmp(dev->device, ringtone)) || (numSrcRingtones >= 10)) {
                    dev->remove();
                    delete dev;
                }
                else numSrcRingtones++;
            }
            dev = next;
        }
    }
    // Push the new ringtone at the front of the list
    if (ringtone) {
        dev = new class SelectedDeviceIo(app, ringtone);
        selectedRingtones.push_front(dev);
        class RingtoneItem * rItem;
        // Check if ringtone is still available
        if ((rItem = FindRingtone(dev->device, NULL, false)) != NULL) {
            class RingtoneItem* temp = NULL;
            for (temp = ringtonesList.front(); temp; temp = temp->goNext()) {
                temp->isSelected = false;
            }
            dev->available = true;
            dev->isRemote = rItem->isRemote;
            if (selectDeviceUser) selectDeviceUser->SelectRingtone(this, app, ringtone);
            WriteSelectedRingtones();
            rItem->isSelected = true;
        }
        else if (!strcmp(ringtone, "No Ringtone")) {
            dev->available = true;
            WriteSelectedRingtones();
        }
    }
    else {
        // remove entry from selectedRingtones
        while (class SelectedDeviceIo * tone = FindRingtone(app)) {
            delete tone;
        }
        WriteSelectedRingtones();
    }
    if (selectDeviceUser) selectDeviceUser->SelectRingtone(this, app, ringtone);
}

class SelectedDeviceIo * RingerIo::FindRingtone(const char * app)
{
    class SelectedDeviceIo * item = selectedRingtones.front();
    while (item) {
        if (item->app && app && !strcmp(item->app, app)) break;
        item = item->goNext();
    }
    return item;
}

const char * RingerIo::GetRingtone(const char * app)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::GetRingtone app=%s", this, app ? app : "");

    class SelectedDeviceIo * dev = NULL;
    if (!selectedRingtones.empty()) {
        for (dev = selectedRingtones.front(); dev; dev = dev->goNext()) {
            if (dev->app && app && !strcmp(dev->app, app)) {
                break;
            }
        }
    }
    // Check if ringtone still available
    if (dev && FindRingtone(dev->device, NULL, false)) {
        return dev->device;
    }
#if 0 // Do not return first Ringtone available because user may have selected "No Ringtone"
    if (ringtonesList.empty() == false) {
        class RingtoneItem * dev = ringtonesList.front();
        return dev->ident;
    }
#endif
    return NULL;
}

void RingerIo::RemoteMediaIoDeviceAdded(const char* deviceId, int deviceType, unsigned deviceCapabilities, const char* deviceName, bool selected)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteMediaIoDeviceAdded deviceId=%s deviceIoUser=%p", this, deviceId, deviceIoUser);

    class RingerDeviceItem* dev = NULL;
    if (devicesList.empty() == false) {
        dev = devicesList.front();
        while (dev) {
            if (deviceId && dev->deviceId && !strcmp(deviceId, dev->deviceId)) break;
            dev = dev->goNext();
        }
    }
    if (!dev) devicesList.push_back(new RingerDeviceItem(deviceId, (RingerDeviceType)deviceType, deviceCapabilities, deviceName, true));
}

void RingerIo::RemoteMediaIoDeviceRemoved(const char* deviceId)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteMediaIoDeviceRemoved deviceId=%s", this, deviceId);

    if (devicesList.empty() == false) {
        class RingerDeviceItem* dev = devicesList.front();
        while (dev) {
            if (deviceId && dev->deviceId && !strcmp(deviceId, dev->deviceId)) {
                if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, NULL, dev->deviceId);
                if (!selectedDevices.empty()) {
                    class SelectedDeviceIo* sel;
                    for (sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                        if (sel->device && !strcmp(sel->device, dev->deviceId)) {
                            sel->available = false;
                            if (dev->isSelected) {
                                if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteMediaIoDeviceRemoved selected device was removed (%s)", this, dev->deviceId);
                                selectedDeviceRemoved = true;
                            }
                        }
                    }
                }
                dev->remove();
                delete dev;
                return;
            }
            dev = dev->goNext();
        }
    }
}

void RingerIo::RemoteMediaIoQueryDevicesResult()
{
    RingerDeviceList(NULL);
}

void RingerIo::RingerDeviceList(void * context)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingerDeviceList context=%p deviceIoUser=%p,%d local devices=%d", this, context, deviceIoUser, !remoteDeviceUsers.empty(), ringer ? ringer->DeviceCount() : 0);

    unsigned i;

    int remoteDevices = 0;
    // let's assume all devices were removed 
    if (devicesList.empty() == false) {
        class RingerDeviceItem * dev = devicesList.front();
        while (dev) {
            if (dev->isRemote == false) dev->wasRemoved = true;
            else {
                dev->isSelected = false;
                remoteDevices++;
            }
            dev = dev->goNext();
        }
    }
    // User may have several SoftphoneApps
    class SelectedDeviceIo* selectedDeviceIoFront = NULL;
    if (selectedDevices.empty() == false) {
        MediaProviderContext* ctx = (MediaProviderContext*)context;
        if (ctx || softphoneApp) {
            class SelectedDeviceIo* dev = selectedDevices.front();
            while (dev) {
                if (dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                    selectedDeviceIoFront = dev;
                    break;
                }
                dev = dev->goNext();
            }
        }
    }
    // Check if any available device is present on the list of selected devices for an specific app
    // context contains the app that made the request
    int firstSelected = -1;
    int selected = -1;
    int devLocalSelected = 0;
    if (selectedDevices.empty() == false) {
        MediaProviderContext * ctx = (MediaProviderContext *)context;
        if (ringer && (ctx || softphoneApp)) {
            class SelectedDeviceIo * dev = selectedDevices.front();
            while (dev) {
                for (i = 0; i < ringer->DeviceCount(); i++) {
                    if (dev->device && ringer->DeviceId(i) && !strcmp(dev->device, ringer->DeviceId(i)) && dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                        if (dev == selectedDeviceIoFront) firstSelected = i;
                        else selected = i;
                        if(log) log->Log(LOG_AUDIO, "RingerIo(%p,%d)::RingerDeviceList selectedDevice (%s) for (%s)", this, i, dev->device, dev->app);
                        break;
                    }
                }
                if (selected >= 0 || firstSelected >= 0) break;
                devLocalSelected++;
                dev = dev->goNext();
            }
        }
    }
    if (remoteDevices == 0 && (!ringer || ringer->DeviceCount() == 0)) {
        if (context && deviceIoUser) deviceIoUser->MediaIoQueryDevicesResult(this, context);
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                h->user->MediaIoQueryDevicesResult(this, context);
                h = h->goNext();
            }
        }
        return;
    }
    // local devices
    for (i = 0; i < (ringer ? ringer->DeviceCount() : 0); i++) {
        class RingerDeviceItem * dev = FindRingerDevice(ringer->DeviceId(i));
        if (dev == NULL) {
            dev = new RingerDeviceItem(ringer->DeviceId(i), ringer->DeviceType(i), ringer->DeviceCapabilities(i), ringer->DeviceName(i));
            devicesList.push_back(dev);
        }
        else {
            dev->wasRemoved = false;
            dev->isSelected = false;
        }
        if ((int)i == firstSelected || (int)i == selected) dev->isSelected = true;
        if (deviceIoUser) deviceIoUser->MediaIoDeviceAdded(this, context, ringer->DeviceId(i), ringer->DeviceType(i), ringer->DeviceCapabilities(i), ringer->DeviceName(i), dev->isSelected);
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                h->user->MediaIoDeviceAdded(this, context, ringer->DeviceId(i), ringer->DeviceType(i), ringer->DeviceCapabilities(i), ringer->DeviceName(i), dev->isSelected);
                h = h->goNext();
            }
        }
    }
    // remote devices
    if (deviceIoUser) {
        if (firstSelected == -1) {
            MediaProviderContext* ctx = (MediaProviderContext*)context;
            class RingerDeviceItem* dev;
            if (ctx || softphoneApp) {
                int devRemoteSelected = 0;
                class SelectedDeviceIo* sel = selectedDevices.front();
                while (sel) {
                    class RingerDeviceItem* dev = devicesList.front();
                    while (dev) {
                        if (dev->isRemote) {
                            if (dev->deviceId && sel->device && !strcmp(dev->deviceId, sel->device) && sel->app && ((ctx && ctx->app && !strcmp(sel->app, ctx->app)) || (softphoneApp && !strcmp(sel->app, softphoneApp)))) {
                                if (sel == selectedDevices.front() || (devRemoteSelected < devLocalSelected)) {
                                    firstSelected = 1;
                                    dev->isSelected = true;
                                }
                                if(log) log->Log(LOG_AUDIO, "RingerIo(%p,%d)::RingerDeviceList remote selectedDevice (%s) for (%s) selected=%d %d<%d", this, i, sel->device, sel->app, firstSelected, devRemoteSelected, devLocalSelected);
                                break;
                            }
                        }
                        dev = dev->goNext();
                    }
                    if (firstSelected >= 0) break;
                    devRemoteSelected++;
                    sel = sel->goNext();
                }
            }
            dev = devicesList.front();
            while (dev) {
                if (dev->isRemote) deviceIoUser->MediaIoDeviceAdded(this, context, dev->deviceId, dev->type, dev->capabilities, dev->name, dev->isSelected);
                dev = dev->goNext();
            }
        }
        else {
            class RingerDeviceItem* dev = devicesList.front();
            while (dev) {
                if (dev->isRemote) deviceIoUser->MediaIoDeviceAdded(this, context, dev->deviceId, dev->type, dev->capabilities, dev->name, dev->isSelected);
                dev = dev->goNext();
            }
        }
    }
    if (context && deviceIoUser) deviceIoUser->MediaIoQueryDevicesResult(this, context);
    if (!remoteDeviceUsers.empty()) {
        class UDeviceIoHelper* h = remoteDeviceUsers.front();
        while (h) {
            h->user->MediaIoQueryDevicesResult(this, context);
            h = h->goNext();
        }
    }
    // Check which selected devices are still available
    if (!selectedDevices.empty()) {
        class SelectedDeviceIo* dev;
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            dev->available = false;
        }
        if (!devicesList.empty()) {
            class RingerDeviceItem* dev = devicesList.front();
            while (dev) {
                for (class SelectedDeviceIo* sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                    if (sel->device && dev->deviceId && !strcmp(sel->device, dev->deviceId)) {
                        if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingerDeviceList selected (%s) available", this, sel->device);
                        sel->available = true;
                        sel->type = dev->type;
                    }
                }
                dev = dev->goNext();
            }
        }
    }
    // Report removed device to frontend
    if (devicesList.empty() == false) {
        class RingerDeviceItem * dev = devicesList.front();
        while (dev) {
            class RingerDeviceItem * nextDevice = dev->goNext();
            if (dev->wasRemoved) {
                // Audio triggers a QueryDevices(NULL) call if a dev is added or removed
                if (!context) {
                    if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, context, dev->deviceId);
                    if (!remoteDeviceUsers.empty()) {
                        class UDeviceIoHelper* h = remoteDeviceUsers.front();
                        while (h) {
                            h->user->MediaIoDeviceRemoved(this, context, dev->deviceId);
                            h = h->goNext();
                        }
                    }
                }
                if (dev->isSelected) {
                    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingerDeviceList selected device was removed (%s)", this, dev->deviceId);
                    selectedDeviceRemoved = true;
                }
                dev->remove();
                delete dev;
            }
            dev = nextDevice;
        }
    }

    if (selectedDeviceRemoved) {
        if (deviceIoUser && !context) {
            if (!selectedDevices.empty()) {
                class SelectedDeviceIo* dev = NULL;
                for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
                    if (dev->available && dev->app && softphoneApp && !strcmp(dev->app, softphoneApp)) {
                        class RingerDeviceItem* devItem;
                        if ((devItem = FindRingerDevice(dev->device)) != NULL) {
                            if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingerDeviceList selected new device after removal (%s)", this, devItem->deviceId);
                            if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, context, devItem->deviceId);
                            if (deviceIoUser) deviceIoUser->MediaIoDeviceAdded(this, context, devItem->deviceId, devItem->type, devItem->capabilities, devItem->name, true);
                            devItem->isSelected = true;
                            break;
                        }
                    }
                }
            }
        }
        selectedDeviceRemoved = false;
    }
    if (selectDeviceUser) selectDeviceUser->RingerDeviceList();
}

class RingtoneItem * RingerIo::FindRingtone(const char * ident, const char * title, bool checkTitle)
{
    if (ringtonesList.empty() == false) {
        class RingtoneItem * rt = ringtonesList.front();
        while (rt) {
            if (ident && rt->ident && !strcmp(ident, rt->ident)) {
                if (!checkTitle) return rt;
                else if (title && rt->title && !strcmp(title, rt->title)) return rt;
            }
            rt = rt->goNext();
        }
    }
    return NULL;
}

void RingerIo::RemoteRingerIoRingtoneAdded(const char* ident, const char* title, bool selected)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteRingerIoRingtoneAdded ident=%s title=%s selected=%d", this, ident ? ident : "", title ? title : "", selected);

    class RingtoneItem* r = NULL;
    if (ringtonesList.empty() == false) {
        r = ringtonesList.front();
        while (r) {
            if (ident && r->ident && !strcmp(ident, r->ident)) break;
            r = r->goNext();
        }
    }
    if (!r) ringtonesList.push_back(new RingtoneItem(ident, title, true));
    else {
        if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteRingerIoRingtoneAdded i (%s) remote=%d", this, ident ? ident : "", r->isRemote);
        r->isRemote = true;
    }
}

void RingerIo::RemoteRingerIoRingtoneRemoved(const char* ident, const char* title)
{
    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteRingerIoRingtoneRemoved ident=%s", this, ident);

    if (ringtonesList.empty() == false) {
        class RingtoneItem* r = ringtonesList.front();
        while (r) {
            if (ident && r->ident && !strcmp(ident, r->ident)) {
                if (ringtoneUser) ringtoneUser->RingerIoRingtoneRemoved(this, NULL, r->ident, r->title);
                if (!selectedRingtones.empty()) {
                    class SelectedDeviceIo* sel;
                    for (sel = selectedRingtones.front(); sel; sel = sel->goNext()) {
                        if (sel->device && !strcmp(sel->device, r->ident)) {
                            sel->available = false;
                            if (r->isSelected) {
                                if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RemoteRingerIoRingtoneRemoved selected device was removed (%s)", this, r->ident);
                                selectedRingtoneRemoved = true;
                            }
                            break;
                        }
                    }
                }
                r->remove();
                delete r;
                return;
            }
            r = r->goNext();
        }
    }
}

void RingerIo::RemoteRingerIoQueryRingtonesResult()
{
    RingtoneList(NULL);
}

void RingerIo::RemoteRingerIoNotificationToneAdded(const char* ident, const char* title, bool selected)
{
    // Not needed
}

void RingerIo::RemoteRingerIoNotificationToneRemoved(const char* ident, const char* title)
{
    // Not needed
}

void RingerIo::RemoteRingerIoQueryNotificationTonesResult()
{
    // Not needed
}

void RingerIo::RingtoneList(void * context)
{
    unsigned i;
    int remoteRingtones = 0;
    // let's assume all tones were removed 
    if (ringtonesList.empty() == false) {
        class RingtoneItem * rt = ringtonesList.front();
        while (rt) {
            if (rt->isRemote == false) rt->wasRemoved = true;
            else {
                rt->isSelected = false;
                remoteRingtones++;
            }
            rt = rt->goNext();
        }
    }
    // User may have several SoftphoneApps
    class SelectedDeviceIo* selectedRingtoneFront = NULL;
    if (selectedRingtones.empty() == false) {
        MediaProviderContext* ctx = (MediaProviderContext*)context;
        if (ctx || softphoneApp) {
            class SelectedDeviceIo* dev = selectedRingtones.front();
            while (dev) {
                if (dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                    selectedRingtoneFront = dev;
                    break;
                }
                dev = dev->goNext();
            }
        }
    }
    // Check if any available device is present on the list of selected devices for an specific app
    // context contains the app that made the request
    int firstSelected = -1;
    int selected = -1;
    int rtLocalSelected = 0;
    if (selectedRingtones.empty() == false) {
        MediaProviderContext * ctx = (MediaProviderContext *)context;
        if (ringer && (ctx || softphoneApp)) {
            class SelectedDeviceIo * dev = selectedRingtones.front();
            while (dev) {
                if (dev->device && !strcmp(dev->device, "No Ringtone") && dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                    if (dev == selectedRingtoneFront) firstSelected = ringer->RingtoneCount();
                    else selected = ringer->RingtoneCount();
                    if (log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingtoneList selectedRingtone (%s) for (%s) i=%d", this, dev->device, dev->app, ringer->RingtoneCount());
                }
                else {
                    for (i = 0; i < ringer->RingtoneCount(); i++) {
                        if (dev->device && ringer->RingtoneIdent(i) && !strcmp(dev->device, ringer->RingtoneIdent(i)) && dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                            if (dev == selectedRingtoneFront) firstSelected = i;
                            else selected = i;
                            if (log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingtoneList selectedRingtone (%s) for (%s) i=%d", this, dev->device, dev->app, i);
                            break;
                        }
                    }
                }
                if (selected >= 0 || firstSelected >= 0) break;
                rtLocalSelected++;
                dev = dev->goNext();
            }
        }
        else if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingtoneList no context provided", this);
    }
    if (remoteRingtones == 0 && (!ringer || ringer->RingtoneCount() == 0)) {
        if (context && ringtoneUser) ringtoneUser->RingerIoQueryRingtonesResult(this, context);
        if (!remoteRingtoneUsers.empty()) {
            class URingtoneHelper* h = remoteRingtoneUsers.front();
            while (h) {
                h->user->RingerIoQueryRingtonesResult(this, context);
                h = h->goNext();
            }
        }
        return;
    }
    // local ringtones
    for (i = 0; i < (ringer ? ringer->RingtoneCount() : 0); i++) {
        class RingtoneItem* rt = FindRingtone(ringer->RingtoneIdent(i), ringer->RingtoneTitle(i), true);
        if (rt == NULL) {
            rt = new RingtoneItem(ringer->RingtoneIdent(i), ringer->RingtoneTitle(i));
            ringtonesList.push_back(rt);
        }
        else {
            rt->wasRemoved = false;
            rt->isSelected = false;
        }
        if ((int)i == firstSelected || (int)i == selected) rt->isSelected = true;
        if (ringtoneUser) ringtoneUser->RingerIoRingtoneAdded(this, context, ringer->RingtoneIdent(i), ringer->RingtoneTitle(i), rt->isSelected);
        if (!remoteRingtoneUsers.empty()) {
            class URingtoneHelper* h = remoteRingtoneUsers.front();
            while (h) {
                h->user->RingerIoRingtoneAdded(this, context, ringer->RingtoneIdent(i), ringer->RingtoneTitle(i), rt->isSelected);
                h = h->goNext();
            }
        }
    }
    // remote ringtones
    if (ringtoneUser) {
        if (firstSelected == -1) {
            MediaProviderContext* ctx = (MediaProviderContext*)context;
            class RingtoneItem* rItem;
            if (ctx || softphoneApp) {
                int devRemoteSelected = 0;
                class SelectedDeviceIo* sel = selectedRingtones.front();
                while (sel) {
                    rItem = ringtonesList.front();
                    while (rItem) {
                        if (rItem->isRemote) {
                            if (rItem->ident && sel->device && !strcmp(rItem->ident, sel->device) && sel->app && ((ctx && ctx->app && !strcmp(sel->app, ctx->app)) || (softphoneApp && !strcmp(sel->app, softphoneApp)))) {
                                if (sel == selectedRingtones.front() || (devRemoteSelected < rtLocalSelected)) {
                                    firstSelected = 1;
                                    rItem->isSelected = true;
                                }
                                if(log) log->Log(LOG_AUDIO, "RingerIo(%p,%d)::RingtoneList remote selectedDevice (%s) for (%s) selected=%d %d<%d", this, i, sel->device, sel->app, firstSelected, devRemoteSelected, rtLocalSelected);
                                break;
                            }
                        }
                        rItem = rItem->goNext();
                    }
                    if (firstSelected >= 0) break;
                    devRemoteSelected++;
                    sel = sel->goNext();
                }
            }
            rItem = ringtonesList.front();
            while (rItem) {
                if (rItem->isRemote) ringtoneUser->RingerIoRingtoneAdded(this, context, rItem->ident, rItem->title, rItem->isSelected);
                rItem = rItem->goNext();
            }
        }
        else {
            class RingtoneItem* rItem = ringtonesList.front();
            while (rItem) {
                if (rItem->isRemote) ringtoneUser->RingerIoRingtoneAdded(this, context, rItem->ident, rItem->title, rItem->isSelected);
                rItem = rItem->goNext();
            }
        }
    }
    if (context && ringtoneUser) ringtoneUser->RingerIoQueryRingtonesResult(this, context);
    if (!remoteRingtoneUsers.empty()) {
        class URingtoneHelper* h = remoteRingtoneUsers.front();
        while (h) {
            h->user->RingerIoQueryRingtonesResult(this, context);
            h = h->goNext();
        }
    }
    // Check which selected ringtones are still available
    if (!selectedRingtones.empty()) {
        class SelectedDeviceIo* dev;
        for (dev = selectedRingtones.front(); dev; dev = dev->goNext()) {
            dev->available = false;
        }
        if (!selectedRingtones.empty()) {
            class RingtoneItem* r = ringtonesList.front();
            while (r) {
                for (class SelectedDeviceIo* sel = selectedRingtones.front(); sel; sel = sel->goNext()) {
                    if (sel->device && r->ident && !strcmp(sel->device, r->ident)) {
                        if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingtoneList selected (%s) available", this, sel->device);
                        sel->available = true;
                        break;
                    }
                }
                r = r->goNext();
            }
        }
    }
    // Report removed ringtone to frontend
    if (ringtonesList.empty() == false) {
        class RingtoneItem* r = ringtonesList.front();
        while (r) {
            class RingtoneItem* nextRingtone = r->goNext();
            if (r->wasRemoved) {
                // Ringer triggers a QueryRingtones(NULL) call if a dev is added or removed
                if (!context) {
                    if (ringtoneUser) ringtoneUser->RingerIoRingtoneRemoved(this, context, r->ident, r->title);
                    if (!remoteRingtoneUsers.empty()) {
                        class URingtoneHelper* h = remoteRingtoneUsers.front();
                        while (h) {
                            h->user->RingerIoRingtoneRemoved(this, context, r->ident, r->title);
                            h = h->goNext();
                        }
                    }
                }
                if (r->isSelected) {
                    if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingetoneList selected device was removed (%s)", this, r->ident);
                    selectedRingtoneRemoved = true;
                }
                r->remove();
                delete r;
            }
            r = nextRingtone;
        }
    }
    if (selectedRingtoneRemoved) {
        if (deviceIoUser && !context) {
            if (!selectedRingtones.empty()) {
                class SelectedDeviceIo* dev = NULL;
                for (dev = selectedRingtones.front(); dev; dev = dev->goNext()) {
                    if (dev->available && dev->app && softphoneApp && !strcmp(dev->app, softphoneApp)) {
                        class RingtoneItem* rItem;
                        if ((rItem = FindRingtone(dev->device, NULL, false)) != NULL) {
                            if(log) log->Log(LOG_AUDIO, "RingerIo(%p)::RingtoneList selected new device after removal (%s)", this, rItem->ident);
                            if (ringtoneUser) ringtoneUser->RingerIoRingtoneRemoved(this, context, rItem->ident, rItem->title);
                            if (ringtoneUser) ringtoneUser->RingerIoRingtoneAdded(this, context, rItem->ident, rItem->title, true);
                            rItem->isSelected = true;
                            break;
                        }
                    }
                }
            }
        }
        selectedRingtoneRemoved = false;
    }
    if (selectDeviceUser) selectDeviceUser->RingtoneList();
}

class RingtoneItem * RingerIo::FindNotificationTone(const char * ident, const char * title)
{
    if (notificationToneList.empty() == false) {
        class RingtoneItem * rt = notificationToneList.front();
        while (rt) {
            if (ident && title && rt->ident && rt->title && !strcmp(ident, rt->ident) && !strcmp(title, rt->title)) return rt;
            rt = rt->goNext();
        }
    }
    return NULL;
}

void RingerIo::NotificationToneList(void * context)
{
    unsigned i;
    // let's assume all tones were removed 
    if (notificationToneList.empty() == false) {
        class RingtoneItem * rt = notificationToneList.front();
        while (rt) {
            rt->wasRemoved = true;
            rt = rt->goNext();
        }
    }
    int selected = -1;
    for (i = 0; i < (ringer ? ringer->NotificationToneCount() : 0); i++) {
        class RingtoneItem * rt = FindNotificationTone(ringer->NotificationToneIdent(i), ringer->NotificationToneTitle(i));
        if (rt == NULL) {
            notificationToneList.push_back(new RingtoneItem(ringer->NotificationToneIdent(i), ringer->NotificationToneTitle(i)));
        }
        else {
            rt->wasRemoved = false;
        }
        if ((rt == NULL) || context) {
            if (ringtoneUser) ringtoneUser->RingerIoNotificationToneAdded(this, context, ringer->NotificationToneIdent(i), ringer->NotificationToneTitle(i), ((int)i == selected));
        }
    }
    if (context) {
        if (ringtoneUser) ringtoneUser->RingerIoQueryNotificationTonesResult(this, context);
    }
    if (notificationToneList.empty() == false) {
        class RingtoneItem * rt = notificationToneList.front();
        while (rt) {
            class RingtoneItem * nextNotificationTone = rt->goNext();
            if (rt->wasRemoved) {
                // Audio triggers a QueryDevices(NULL) call if a device is removed or plugged in
                if (!context) {
                    if (ringtoneUser) ringtoneUser->RingerIoNotificationToneRemoved(this, context, rt->ident, rt->title);
                }
                rt->remove();
                delete rt;
            }
            rt = nextNotificationTone;
        }
    }
}
