/*---------------------------------------------------------------------------*/
/* ios_ringer.cpp                                                            */
/* copyright (c) innovaphone 2012                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#define IOS_RINGER_CPP_

#include <AudioUnit/AudioUnit.h>
#if TARGET_OS_IPHONE
#else
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
#include <stdio.h>
#endif

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/ilib/str.h"
#include "common/ilib/xml.h"
#include "common/interface/time.h"
#include "common/interface/audio.h"
#include "common/interface/ringer.h"
#include "common/interface/socket.h"
#include "common/lib/language.h"
#include "client/lang/myapps_languages.h"
#if TARGET_OS_IPHONE
#else
#include "client/headset_provider/interface/headset_provider_if.h"
#endif
#include "ios_phone.h"
#include "ios_ringer.h"

extern int phoneLanguageId;
extern const char *default_tunes; // iOS and Android provide ring tones this way
extern const char *default_tones; // iOS and Android provide ring tones this way
extern class ios_dsp *the_ios_dsp;

#define FILE_ "ios_ringer.cpp"

USING_NAMESPACE;

void *ios_ringer_context;

/*---------------------------------------------------------------------------*/

IRinger * IRinger::Create(class IIoMux * const iomux, class IInstanceLog * log)
{
    return new ios_ringer(iomux, log);
}

/*---------------------------------------------------------------------------*/
/* ios_ringer members                                                        */
/*---------------------------------------------------------------------------*/

void ios_ringer::IoExec(ios_ringer_query_devices * context)
{
    UpdateDeviceList();
    user->RingerDeviceList(context);
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseRingerComplete();
    }
}

void ios_ringer::IoExec(ios_ringer_query_ringtones * context)
{
    user->RingtoneList(context);
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseRingerComplete();
    }
}

void ios_ringer::IoExec(ios_ringer_query_tones * context)
{
    user->NotificationToneList(context);
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseRingerComplete();
    }
}

void ios_ringer::IoExec(ios_ringer_close * context)
{
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseRingerComplete();
    }
}

class ios_ringer *the_ios_ringer = 0;

ios_ringer::ios_ringer(class IIoMux * const iomux, class IInstanceLog * log)
{
    strcpy(tag, "RINGER");

    the_ios_ringer = this;
    this->iomux = iomux;
    this->log = log;

    ringtone = false;

    ringing = false;

    ringtone_count = 0;
    ringtone_number = 1;
    ringtone_seek(ringtone_count);
    while (ringtone_ptr) {
        ringtone_count++;
        ringtone_seek(ringtone_count);
    }

    tone_count = 0;
    tone_number = 1;
    tone_seek(tone_count);
    while (tone_ptr) {
        tone_count++;
        tone_seek(tone_count);
    }

    ios_ringer_context = this;

    shutDown = false;
    execCnt = 0; 

    StartListenDeviceList();
    UpdateDeviceList();
}

ios_ringer::~ios_ringer()
{
    finit();
}

extern "C" OSStatus createRingtonePlayer(const char *device, const char *melody, bool allow_vibrate);
extern "C" OSStatus createNotificationTonePlayer(const char *device, const char *melody, bool allow_vibrate);
extern "C" OSStatus releaseRingtonePlayer();

void ios_ringer::Initialize(class URinger * const user)
{
    this->user = user;
    init();
}

void ios_ringer::QueryDevices(void * context)
{
    if(!shutDown) {
        execCnt++;
        SetExec(iomux, this, (ios_ringer_query_devices *)context);
    }
}

typedef struct SAudioDevices {
#if TARGET_OS_IPHONE
    const char *id;
    enum RingerDeviceType type;
    unsigned capabilities;
    int nameId;
#else
    char *id;
    enum RingerDeviceType type;
    unsigned capabilities;
    char *name;
    AudioDeviceID deviceID;
#endif
} TAudioDevices;

#if TARGET_OS_IPHONE

static TAudioDevices AudioDevicesTable[] =
{
    { "Ringer", RingerDeviceType::RingerSpeaker,        0, TXT_SPEAKER },
};

void ios_ringer::UpdateDeviceList()
{
    AudioDevices = AudioDevicesTable;
    AudioDeviceCount = sizeof(AudioDevicesTable) / sizeof(AudioDevicesTable[0]);
}

void ios_ringer::StartListenDeviceList()
{
}

void ios_ringer::StopListenDeviceList()
{
}

void ios_ringer::IoContextNotify(bool indRX, bool indTX)
{
}

#else

void ios_ringer::UpdateDeviceList()
{
    OSStatus os_status;
    AudioObjectPropertyAddress ioProperties;
    UInt32 ioSize, transportType;
    AudioDeviceID deviceID;
    AudioObjectID *devices;
    AudioBufferList *bufferList;
    CFStringRef str, outputUID;
    CFIndex maxSize;
    const char *p;
    char *name;
    unsigned ioDevice, ioDeviceCount, outputChannels, i;

    ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    ioSize = 0;
    os_status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &ioProperties, 0, NULL, &ioSize);
    if (os_status != noErr) {
        return;
    }
    // calculate the number of device available
    ioDeviceCount = ioSize / (UInt32)(sizeof(AudioObjectID));
    // make space for the devices we are about to get
    devices = (AudioObjectID *)(malloc(ioSize));
    memset(devices, 0, ioSize);
    os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, 0, NULL, &ioSize, devices);
    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
    }
    AudioDevices = (TAudioDevices *)(malloc(ioDeviceCount * sizeof(TAudioDevices)));
    AudioDeviceCount = 0;

    AudioDeviceID outputDeviceID = 0;
    UInt32 param = sizeof(AudioDeviceID);
    ioProperties.mSelector = kAudioHardwarePropertyDefaultSystemOutputDevice;
	os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, 0, NULL, &param, &outputDeviceID);
    if(os_status != noErr) {
        ERR(("%s couldn't get default system output device %i", tag, os_status));
    }

    // iterate over each device gathering information
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        deviceID = devices[ioDevice];

        // get device name
        name = NULL;
        ioProperties.mSelector = kAudioObjectPropertyName;
        ioProperties.mScope = kAudioObjectPropertyScopeGlobal;
        ioSize = sizeof(CFStringRef);
        os_status = AudioObjectGetPropertyData(devices[ioDevice], &ioProperties, 0, NULL, &ioSize, &str);
        if((os_status == noErr) && str) {
            CFIndex maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8) + 1;
            name = (char *)(malloc(maxSize));
            p = CFStringGetCStringPtr(str, kCFStringEncodingUTF8);
            if (p) {
                strcpy(name, p);
            }
            else if (!CFStringGetCString(str, name, maxSize, kCFStringEncodingUTF8)) {
                name[0] = '\0';
            }
            CFRelease(str);
        }

        outputChannels = 0;
        outputUID = NULL;
        // get output device UID
        ioProperties.mSelector = kAudioDevicePropertyDeviceUID;
        ioProperties.mScope = kAudioObjectPropertyScopeOutput;
        ioSize = sizeof(CFStringRef);
        os_status = AudioObjectGetPropertyData(devices[ioDevice], &ioProperties, 0, NULL, &ioSize, &outputUID);
        if((os_status == noErr) && outputUID) {
            // get number of output channels
            ioProperties.mSelector = kAudioDevicePropertyStreamConfiguration;
            ioProperties.mScope = kAudioObjectPropertyScopeOutput;
            ioSize = 0;
            os_status = AudioObjectGetPropertyDataSize(devices[ioDevice], &ioProperties, 0, NULL, &ioSize);
            if((os_status == noErr) && (ioSize != 0)) {
                bufferList = (AudioBufferList *)(malloc(ioSize));
                if(bufferList != NULL) {
                    // get the output stream configuration
                    os_status = AudioObjectGetPropertyData(devices[ioDevice], &ioProperties, 0, NULL, &ioSize, bufferList);
                    if(os_status == noErr) {
                        // count the total number of output channels in the stream
                        for(i = 0; i < bufferList->mNumberBuffers; i++) {
                            outputChannels += bufferList->mBuffers[i].mNumberChannels;
                        }
                    }
                    free(bufferList);
                }
            }
            if (outputChannels == 0) {
                CFRelease(outputUID);
                outputUID = NULL;
            }
        }

        ioProperties.mSelector = kAudioDevicePropertyTransportType;
        ioProperties.mScope = kAudioObjectPropertyScopeGlobal;
        ioSize = sizeof(transportType);
        os_status = AudioObjectGetPropertyData(devices[ioDevice], &ioProperties, 0, NULL, &ioSize, &transportType);
        if ((os_status == noErr) && (transportType != kAudioDeviceTransportTypeAggregate) && name && (outputChannels != 0)) {
            i = AudioDeviceCount;
            if (deviceID == outputDeviceID) {
                while (i > 0) {
                    memcpy(&AudioDevices[i], &AudioDevices[i-1], sizeof(TAudioDevices));
                    i--;
                }
            }
            maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(outputUID), kCFStringEncodingUTF8) + 1;
            AudioDevices[i].id = (char *)(malloc(maxSize));
            p = CFStringGetCStringPtr(outputUID, kCFStringEncodingUTF8);
            if (p) {
                strcpy(AudioDevices[i].id, p);
            }
            else if (!CFStringGetCString(outputUID, AudioDevices[i].id, maxSize - 1, kCFStringEncodingUTF8)) {
                AudioDevices[i].id[0] = '\0';
            }
            CFRelease(outputUID);
            AudioDevices[i].name = name;
            AudioDevices[i].type = RingerDeviceType::RingerSpeaker;
            AudioDevices[i].capabilities = 0;
            AudioDevices[i].deviceID = deviceID;
            AudioDeviceCount++;
        }
        else {
            if (name) {
                free(name);
            }
        }
    }
    free(devices);
/*
    for (i = 0; i < AudioDeviceCount; i++) {
        ERR(("%s RingerDevices[%u] %x %u %s", tag, i, AudioDevices[i].capabilities, AudioDevices[i].deviceID, AudioDevices[i].name));
    }
*/
}

static OSStatus AOPropertyListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
{
    class ios_ringer *thiz = (class ios_ringer *) inClientData;
    UInt32 x;

    for (x = 0; x < inNumberAddresses; x++) {
        switch (inAddresses[x].mSelector) {
    /*
     * These are the other types of notifications we might receive, however, they are beyond
     * the scope of this sample and we ignore them.
     *
        case kAudioHardwarePropertyDefaultInputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default input device changed\n");
            break;

        case kAudioHardwarePropertyDefaultOutputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default output device changed\n");
            break;

        case kAudioHardwarePropertyDefaultSystemOutputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default system output device changed\n");
            break;
    */
        case kAudioHardwarePropertyDevices:
            {
                byte buf[1] = {0};
                write(thiz->device_change_pipefd[1], buf, sizeof(buf));
            }
            break;

        default:
            break;
        }
    }
    return noErr;
}

void ios_ringer::StartListenDeviceList()
{
    long flags;
    unsigned int i;

    AudioDevices = NULL;
    AudioDeviceCount = 0;
    device_change_pipe_created = false;
    if(!device_change_pipe_created) {
        if(pipe(device_change_pipefd) < 0) {
            ERR(("%s cannot create device change pipe", tag));
        }
        else {
            device_change_pipe_created = true;
            for(i = 0; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
                flags = fcntl(device_change_pipefd[i], F_GETFL);
                if(flags == -1) {
                    ERR(("%s cannot fcntl(F_GETFL)", tag));
                    flags = 0;
                }
                flags |= O_NONBLOCK;
                if(fcntl(device_change_pipefd[i], F_SETFL, flags) == -1) {
                    ERR(("%s cannot fcntl(F_SETFL)", tag));
                }
            }
            iomux->FDUnblock(device_change_pipefd[0]);
            iomux->FDAdd(device_change_pipefd[0], this, false);
        }
    }
    // install kAudioHardwarePropertyDevices notification listener
    AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &ioProperties, AOPropertyListenerProc, this);
}

void ios_ringer::StopListenDeviceList()
{
    unsigned int i;

    // remove kAudioHardwarePropertyDevices notification listener
    AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &ioProperties, AOPropertyListenerProc, this);
    if(device_change_pipe_created) {
        iomux->FDClose(this);
        for(i = 1; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
            close(device_change_pipefd[i]);
        }
        device_change_pipe_created = false;
    }
    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
        AudioDevices = NULL;
        AudioDeviceCount = 0;
    }
}

void ios_ringer::IoContextNotify(bool indRX, bool indTX)
{
    byte buf[1];

    while(read(device_change_pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {
        if(!shutDown) {
            execCnt++;
            SetExec(iomux, this, (ios_ringer_query_devices *)NULL);
        }
        break;
    }
}

#endif

unsigned ios_ringer::DeviceCount()
{
    return AudioDeviceCount;
}

const char * ios_ringer::DeviceId(unsigned deviceNumber)
{
    return AudioDevices[deviceNumber].id;
}

enum RingerDeviceType ios_ringer::DeviceType(unsigned deviceNumber)
{
    return AudioDevices[deviceNumber].type;
}

unsigned ios_ringer::DeviceCapabilities(unsigned deviceNumber)
{
    return AudioDevices[deviceNumber].capabilities;
}

const char * ios_ringer::DeviceName(unsigned deviceNumber)
{
#if TARGET_OS_IPHONE
    return MYAPPS_LANG_STRING(AudioDevices[deviceNumber].nameId, phoneLanguageId);
#else
    return AudioDevices[deviceNumber].name;
#endif
}

const char *ios_ringer::tone_title_from_ident(const char *ident, const char *tones, char *title_buffer)
{
    const char *p;
    unsigned i, ident_len;

    if (!ident) {
        return 0;
    }
    if (str::n_icmp(ident, "file://", 7) == 0) {
        strcpy(title_buffer, ident);
        return title_buffer;
    }
    ident_len = strlen(ident);
    p = tones;
    i = 0;
    do {
        p = strstr(p, "<tune tag='");
        if (p) {
            p += 11;
            i = 0;
            while (p[i] != '\'') {
                i++;
            }
        }
    }
    while (p && ((i != ident_len) || (memcmp(p, ident, ident_len) != 0)));
    if (!p) {
        return 0;
    }
    p = strstr(p, "' title='");
    if (!p) {
        return 0;
    }
    p += 9;
    i = 0;
    while (p[i] != '\'') {
        i++;
    }
    memcpy(title_buffer, p, i);
    title_buffer[i] = '\0';
    xml_io::xml_to_str(title_buffer, title_buffer);
    return title_buffer;
}

void ios_ringer::QueryRingtones(void * context)
{
    if(!shutDown) {
        execCnt++;
        SetExec(iomux, this, (ios_ringer_query_ringtones *)context);
    }
}

void ios_ringer::ringtone_seek(unsigned num)
{
    if (num < ringtone_number) {
        ringtone_number = 0;
        ringtone_ptr = strstr(default_tunes, "<tune tag='");
        if (ringtone_ptr) {
            ringtone_ptr += 11;
        }
    }
    while ((ringtone_number < num) && ringtone_ptr) {
        ringtone_number++;
        ringtone_ptr = strstr(ringtone_ptr, "<tune tag='");
        if (ringtone_ptr) {
            ringtone_ptr += 11;
        }
    }
}
    
const char * ios_ringer::RingtoneTitle(unsigned ringtoneNumber)
{
    const char *p;
    unsigned int i;

    ringtone_seek(ringtoneNumber);
    if (!ringtone_ptr) {
        return 0;
    }
    p = strstr(ringtone_ptr, "' title='");
    if (!p) {
        return 0;
    }
    p += 9;
    i = 0;
    while (p[i] != '\'') {
        i++;
    }
    memcpy(ringtone_title, p, i);
    ringtone_title[i] = '\0';
    xml_io::xml_to_str(ringtone_title, ringtone_title);
    return ringtone_title;
}

const char * ios_ringer::RingtoneIdent(unsigned ringtoneNumber)
{
    unsigned int i;

    ringtone_seek(ringtoneNumber);
    if (!ringtone_ptr) {
        return 0;
    }
    i = 0;
    while (ringtone_ptr[i] != '\'') {
        i++;
    }
    memcpy(ringtone_ident, ringtone_ptr, i);
    ringtone_ident[i] = '\0';
    xml_io::xml_to_str(ringtone_ident, ringtone_ident);
    return ringtone_ident;
}

void ios_ringer::RingtoneStart(const char *deviceId, enum RingerDeviceMode deviceMode, const char *ident)
{
    char title_buffer[4096];

    if(ringtone) {
        releaseRingtonePlayer();
    }
    if (!ringing) {
        ringing = true;
        if (the_ios_dsp) {
            the_ios_dsp->codec_mode_changed(ringing);
        }
    }
    createRingtonePlayer((deviceId && strchr(deviceId, '\n')) ? strchr(deviceId, '\n') + 1 : deviceId, tone_title_from_ident(ident, default_tunes, title_buffer), true);
    ringtone = true;
}

void ios_ringer::RingtoneStop(const char *deviceId)
{
    if(ringtone) {
        releaseRingtonePlayer();
        ringtone = false;
    }
    if (ringing) {
        ringing = false;
        if (the_ios_dsp) {
            the_ios_dsp->codec_mode_changed(ringing);
        }
    }
}

void ios_ringer::QueryNotificationTones(void * context)
{
    if(!shutDown) {
        execCnt++;
        SetExec(iomux, this, (ios_ringer_query_tones *)context);
    }
}

void ios_ringer::tone_seek(unsigned num)
{
    if (num < tone_number) {
        tone_number = 0;
        tone_ptr = strstr(default_tones, "<tone tag='");
        if (tone_ptr) {
            tone_ptr += 11;
        }
    }
    while ((tone_number < num) && tone_ptr) {
        tone_number++;
        tone_ptr = strstr(tone_ptr, "<tone tag='");
        if (tone_ptr) {
            tone_ptr += 11;
        }
    }
}
    
const char * ios_ringer::NotificationToneTitle(unsigned toneNumber)
{
    const char *p;
    unsigned int i;

    tone_seek(toneNumber);
    if (!tone_ptr) {
        return 0;
    }
    p = strstr(tone_ptr, "' title='");
    if (!p) {
        return 0;
    }
    p += 9;
    i = 0;
    while (p[i] != '\'') {
        i++;
    }
    memcpy(tone_title, p, i);
    tone_title[i] = '\0';
    xml_io::xml_to_str(tone_title, tone_title);
    return tone_title;
}

const char * ios_ringer::NotificationToneIdent(unsigned toneNumber)
{
    unsigned int i;

    tone_seek(toneNumber);
    if (!tone_ptr) {
        return 0;
    }
    i = 0;
    while (tone_ptr[i] != '\'') {
        i++;
    }
    memcpy(tone_ident, tone_ptr, i);
    tone_ident[i] = '\0';
    xml_io::xml_to_str(tone_ident, tone_ident);
    return tone_ident;
}

void ios_ringer::NotificationTonePlay(const char *deviceId, enum RingerDeviceMode deviceMode, const char *ident)
{
    char title_buffer[4096];

    if(ringtone) {
        releaseRingtonePlayer();
    }
    createNotificationTonePlayer((deviceId && strchr(deviceId, '\n')) ? strchr(deviceId, '\n') + 1 : deviceId, tone_title_from_ident(ident, default_tones, title_buffer), true);
    ringtone = true;
}

void ios_ringer::init()
{
}

void ios_ringer::finit()
{
}

void ios_ringer::Close()
{
    shutDown = true;
    StopListenDeviceList();
    execCnt++;
    SetExec(iomux, this, (ios_ringer_close *)0);
}

/*---------------------------------------------------------------------------*/
