/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#define USE_ERROR
#define USE_TRACE

#include "PLATFORM_API_SolarisOS_Utils.h"
#include "DirectAudio.h"

#if USE_DAUDIO == TRUE


// The default buffer time
#define DEFAULT_PERIOD_TIME_MILLIS 50

///// implemented functions of DirectAudio.h

INT32 DAUDIO_GetDirectAudioDeviceCount() {
    return (INT32) getAudioDeviceCount();
}


INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex,
                                             DirectAudioDeviceDescription* description) {
    AudioDeviceDescription desc;

    if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, TRUE)) {
        description->maxSimulLines = desc.maxSimulLines;
        strncpy(description->name, desc.name, DAUDIO_STRING_LENGTH-1);
        description->name[DAUDIO_STRING_LENGTH-1] = 0;
        strncpy(description->vendor, desc.vendor, DAUDIO_STRING_LENGTH-1);
        description->vendor[DAUDIO_STRING_LENGTH-1] = 0;
        strncpy(description->version, desc.version, DAUDIO_STRING_LENGTH-1);
        description->version[DAUDIO_STRING_LENGTH-1] = 0;
        /*strncpy(description->description, desc.description, DAUDIO_STRING_LENGTH-1);*/
        strncpy(description->description, "Solaris Mixer", DAUDIO_STRING_LENGTH-1);
        description->description[DAUDIO_STRING_LENGTH-1] = 0;
        return TRUE;
    }
    return FALSE;

}

#define MAX_SAMPLE_RATES   20

void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) {
    int fd = -1;
    AudioDeviceDescription desc;
    am_sample_rates_t      *sr;
    /* hardcoded bits and channels */
    int bits[] = {8, 16};
    int bitsCount = 2;
    int channels[] = {1, 2};
    int channelsCount = 2;
    /* for querying sample rates */
    int err;
    int ch, b, s;

    TRACE2("DAUDIO_GetFormats, mixer %d, isSource=%d\n", mixerIndex, isSource);
    if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, FALSE)) {
        fd = open(desc.pathctl, O_RDONLY);
    }
    if (fd < 0) {
        ERROR1("Couldn't open audio device ctl for device %d!\n", mixerIndex);
        return;
    }

    /* get sample rates */
    sr = (am_sample_rates_t*) malloc(AUDIO_MIXER_SAMP_RATES_STRUCT_SIZE(MAX_SAMPLE_RATES));
    if (sr == NULL) {
        ERROR1("DAUDIO_GetFormats: out of memory for mixer %d\n", (int) mixerIndex);
        close(fd);
        return;
    }

    sr->num_samp_rates = MAX_SAMPLE_RATES;
    sr->type = isSource?AUDIO_PLAY:AUDIO_RECORD;
    sr->samp_rates[0] = -2;
    err = ioctl(fd, AUDIO_MIXER_GET_SAMPLE_RATES, sr);
    if (err < 0) {
        ERROR1("  DAUDIO_GetFormats: AUDIO_MIXER_GET_SAMPLE_RATES failed for mixer %d!\n",
               (int)mixerIndex);
        ERROR2(" -> num_sample_rates=%d sample_rates[0] = %d\n",
               (int) sr->num_samp_rates,
               (int) sr->samp_rates[0]);
        /* Some Solaris 8 drivers fail for get sample rates!
         * Do as if we support all sample rates
         */
        sr->flags = MIXER_SR_LIMITS;
    }
    if ((sr->flags & MIXER_SR_LIMITS)
        || (sr->num_samp_rates > MAX_SAMPLE_RATES)) {
#ifdef USE_TRACE
        if ((sr->flags & MIXER_SR_LIMITS)) {
            TRACE1("  DAUDIO_GetFormats: floating sample rate allowed by mixer %d\n",
                   (int)mixerIndex);
        }
        if (sr->num_samp_rates > MAX_SAMPLE_RATES) {
            TRACE2("  DAUDIO_GetFormats: more than %d formats. Use -1 for sample rates mixer %d\n",
                   MAX_SAMPLE_RATES, (int)mixerIndex);
        }
#endif
        /*
         * Fake it to have only one sample rate: -1
         */
        sr->num_samp_rates = 1;
        sr->samp_rates[0] = -1;
    }
    close(fd);

    for (ch = 0; ch < channelsCount; ch++) {
        for (b = 0; b < bitsCount; b++) {
            for (s = 0; s < sr->num_samp_rates; s++) {
                DAUDIO_AddAudioFormat(creator,
                                      bits[b], /* significant bits */
                                      0, /* frameSize: let it be calculated */
                                      channels[ch],
                                      (float) ((int) sr->samp_rates[s]),
                                      DAUDIO_PCM, /* encoding - let's only do PCM */
                                      (bits[b] > 8)?TRUE:TRUE, /* isSigned */
#ifdef VM_LITTLE_ENDIAN
                                      FALSE /* little endian */
#else
                                      (bits[b] > 8)?TRUE:FALSE  /* big endian */
#endif
                                      );
            }
        }
    }
    free(sr);
}


typedef struct {
    int fd;
    audio_info_t info;
    int bufferSizeInBytes;
    int frameSize; /* storage size in Bytes */
    /* how many bytes were written or read */
    INT32 transferedBytes;
    /* if transferedBytes exceed 32-bit boundary,
     * it will be reset and positionOffset will receive
     * the offset
     */
    INT64 positionOffset;
} SolPcmInfo;


void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource,
                  int encoding, float sampleRate, int sampleSizeInBits,
                  int frameSize, int channels,
                  int isSigned, int isBigEndian, int bufferSizeInBytes) {
    int err = 0;
    int openMode;
    AudioDeviceDescription desc;
    SolPcmInfo* info;

    TRACE0("> DAUDIO_Open\n");
    if (encoding != DAUDIO_PCM) {
        ERROR1(" DAUDIO_Open: invalid encoding %d\n", (int) encoding);
        return NULL;
    }

    info = (SolPcmInfo*) malloc(sizeof(SolPcmInfo));
    if (!info) {
        ERROR0("Out of memory\n");
        return NULL;
    }
    memset(info, 0, sizeof(SolPcmInfo));
    info->frameSize = frameSize;
    info->fd = -1;

    if (isSource) {
        openMode = O_WRONLY;
    } else {
        openMode = O_RDONLY;
    }

#ifndef __linux__
    /* blackdown does not use NONBLOCK */
    openMode |= O_NONBLOCK;
#endif

    if (getAudioDeviceDescriptionByIndex(mixerIndex, &desc, FALSE)) {
        info->fd = open(desc.path, openMode);
    }
    if (info->fd < 0) {
        ERROR1("Couldn't open audio device for mixer %d!\n", mixerIndex);
        free(info);
        return NULL;
    }
    /* set to multiple open */
    if (ioctl(info->fd, AUDIO_MIXER_MULTIPLE_OPEN, NULL) >= 0) {
        TRACE1("DAUDIO_Open: %s set to multiple open\n", desc.path);
    } else {
        ERROR1("DAUDIO_Open: ioctl AUDIO_MIXER_MULTIPLE_OPEN failed on %s!\n", desc.path);
    }

    AUDIO_INITINFO(&(info->info));
    /* need AUDIO_GETINFO ioctl to get this to work on solaris x86  */
    err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));

    /* not valid to call AUDIO_SETINFO ioctl with all the fields from AUDIO_GETINFO. */
    AUDIO_INITINFO(&(info->info));

    if (isSource) {
        info->info.play.sample_rate = sampleRate;
        info->info.play.precision = sampleSizeInBits;
        info->info.play.channels = channels;
        info->info.play.encoding = AUDIO_ENCODING_LINEAR;
        info->info.play.buffer_size = bufferSizeInBytes;
        info->info.play.pause = 1;
    } else {
        info->info.record.sample_rate = sampleRate;
        info->info.record.precision = sampleSizeInBits;
        info->info.record.channels = channels;
        info->info.record.encoding = AUDIO_ENCODING_LINEAR;
        info->info.record.buffer_size = bufferSizeInBytes;
        info->info.record.pause = 1;
    }
    err = ioctl(info->fd, AUDIO_SETINFO,  &(info->info));
    if (err < 0) {
        ERROR0("DAUDIO_Open: could not set info!\n");
        DAUDIO_Close((void*) info, isSource);
        return NULL;
    }
    DAUDIO_Flush((void*) info, isSource);

    err = ioctl(info->fd, AUDIO_GETINFO, &(info->info));
    if (err >= 0) {
        if (isSource) {
            info->bufferSizeInBytes = info->info.play.buffer_size;
        } else {
            info->bufferSizeInBytes = info->info.record.buffer_size;
        }
        TRACE2("DAUDIO: buffersize in bytes: requested=%d, got %d\n",
               (int) bufferSizeInBytes,
               (int) info->bufferSizeInBytes);
    } else {
        ERROR0("DAUDIO_Open: cannot get info!\n");
        DAUDIO_Close((void*) info, isSource);
        return NULL;
    }
    TRACE0("< DAUDIO_Open: Opened device successfully.\n");
    return (void*) info;
}


int DAUDIO_Start(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int err, modified;
    audio_info_t audioInfo;

    TRACE0("> DAUDIO_Start\n");

    AUDIO_INITINFO(&audioInfo);
    err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
    if (err >= 0) {
        // unpause
        modified = FALSE;
        if (isSource && audioInfo.play.pause) {
            audioInfo.play.pause = 0;
            modified = TRUE;
        }
        if (!isSource && audioInfo.record.pause) {
            audioInfo.record.pause = 0;
            modified = TRUE;
        }
        if (modified) {
            err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
        }
    }

    TRACE1("< DAUDIO_Start %s\n", (err>=0)?"success":"error");
    return (err >= 0)?TRUE:FALSE;
}

int DAUDIO_Stop(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int err, modified;
    audio_info_t audioInfo;

    TRACE0("> DAUDIO_Stop\n");

    AUDIO_INITINFO(&audioInfo);
    err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
    if (err >= 0) {
        // pause
        modified = FALSE;
        if (isSource && !audioInfo.play.pause) {
            audioInfo.play.pause = 1;
            modified = TRUE;
        }
        if (!isSource && !audioInfo.record.pause) {
            audioInfo.record.pause = 1;
            modified = TRUE;
        }
        if (modified) {
            err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
        }
    }

    TRACE1("< DAUDIO_Stop %s\n", (err>=0)?"success":"error");
    return (err >= 0)?TRUE:FALSE;
}

void DAUDIO_Close(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;

    TRACE0("DAUDIO_Close\n");
    if (info != NULL) {
        if (info->fd >= 0) {
            DAUDIO_Flush(id, isSource);
            close(info->fd);
        }
        free(info);
    }
}

#ifndef USE_TRACE
/* close to 2^31 */
#define POSITION_MAX 2000000000
#else
/* for testing */
#define POSITION_MAX 1000000
#endif

void resetErrorFlagAndAdjustPosition(SolPcmInfo* info, int isSource, int count) {
    audio_info_t audioInfo;
    audio_prinfo_t* prinfo;
    int err;
    int offset = -1;
    int underrun = FALSE;
    int devBytes = 0;

    if (count > 0) {
        info->transferedBytes += count;

        if (isSource) {
            prinfo = &(audioInfo.play);
        } else {
            prinfo = &(audioInfo.record);
        }
        AUDIO_INITINFO(&audioInfo);
        err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
        if (err >= 0) {
            underrun = prinfo->error;
            devBytes = prinfo->samples * info->frameSize;
        }
        AUDIO_INITINFO(&audioInfo);
        if (underrun) {
            /* if an underrun occurred, reset */
            ERROR1("DAUDIO_Write/Read: Underrun/overflow: adjusting positionOffset by %d:\n",
                   (devBytes - info->transferedBytes));
            ERROR1("    devBytes from %d to 0, ", devBytes);
            ERROR2(" positionOffset from %d to %d ",
                   (int) info->positionOffset,
                   (int) (info->positionOffset + info->transferedBytes));
            ERROR1(" transferedBytes from %d to 0\n",
                   (int) info->transferedBytes);
            prinfo->samples = 0;
            info->positionOffset += info->transferedBytes;
            info->transferedBytes = 0;
        }
        else if (info->transferedBytes > POSITION_MAX) {
            /* we will reset transferedBytes and
             * the samples field in prinfo
             */
            offset = devBytes;
            prinfo->samples = 0;
        }
        /* reset error flag */
        prinfo->error = 0;

        err = ioctl(info->fd, AUDIO_SETINFO, &audioInfo);
        if (err >= 0) {
            if (offset > 0) {
                /* upon exit of AUDIO_SETINFO, the samples parameter
                 * was set to the previous value. This is our
                 * offset.
                 */
                TRACE1("Adjust samplePos: offset=%d, ", (int) offset);
                TRACE2("transferedBytes=%d -> %d, ",
                       (int) info->transferedBytes,
                       (int) (info->transferedBytes - offset));
                TRACE2("positionOffset=%d -> %d\n",
                       (int) (info->positionOffset),
                       (int) (((int) info->positionOffset) + offset));
                info->transferedBytes -= offset;
                info->positionOffset += offset;
            }
        } else {
            ERROR0("DAUDIO: resetErrorFlagAndAdjustPosition ioctl failed!\n");
        }
    }
}

// returns -1 on error
int DAUDIO_Write(void* id, char* data, int byteSize) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int ret = -1;

    TRACE1("> DAUDIO_Write %d bytes\n", byteSize);
    if (info!=NULL) {
        ret = write(info->fd, data, byteSize);
        resetErrorFlagAndAdjustPosition(info, TRUE, ret);
        /* sets ret to -1 if buffer full, no error! */
        if (ret < 0) {
            ret = 0;
        }
    }
    TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret);
    return ret;
}

// returns -1 on error
int DAUDIO_Read(void* id, char* data, int byteSize) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int ret = -1;

    TRACE1("> DAUDIO_Read %d bytes\n", byteSize);
    if (info != NULL) {
        ret = read(info->fd, data, byteSize);
        resetErrorFlagAndAdjustPosition(info, TRUE, ret);
        /* sets ret to -1 if buffer full, no error! */
        if (ret < 0) {
            ret = 0;
        }
    }
    TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret);
    return ret;
}


int DAUDIO_GetBufferSize(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    if (info) {
        return info->bufferSizeInBytes;
    }
    return 0;
}

int DAUDIO_StillDraining(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    audio_info_t audioInfo;
    audio_prinfo_t* prinfo;
    int ret = FALSE;

    if (info!=NULL) {
        if (isSource) {
            prinfo = &(audioInfo.play);
        } else {
            prinfo = &(audioInfo.record);
        }
        /* check error flag */
        AUDIO_INITINFO(&audioInfo);
        ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
        ret = (prinfo->error != 0)?FALSE:TRUE;
    }
    return ret;
}


int getDevicePosition(SolPcmInfo* info, int isSource) {
    audio_info_t audioInfo;
    audio_prinfo_t* prinfo;
    int err;

    if (isSource) {
        prinfo = &(audioInfo.play);
    } else {
        prinfo = &(audioInfo.record);
    }
    AUDIO_INITINFO(&audioInfo);
    err = ioctl(info->fd, AUDIO_GETINFO, &audioInfo);
    if (err >= 0) {
        /*TRACE2("---> device paused: %d  eof=%d\n",
               prinfo->pause, prinfo->eof);
        */
        return (int) (prinfo->samples * info->frameSize);
    }
    ERROR0("DAUDIO: getDevicePosition: ioctl failed!\n");
    return -1;
}

int DAUDIO_Flush(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int err = -1;
    int pos;

    TRACE0("DAUDIO_Flush\n");
    if (info) {
        if (isSource) {
            err = ioctl(info->fd, I_FLUSH, FLUSHW);
        } else {
            err = ioctl(info->fd, I_FLUSH, FLUSHR);
        }
        if (err >= 0) {
            /* resets the transferedBytes parameter to
             * the current samples count of the device
             */
            pos = getDevicePosition(info, isSource);
            if (pos >= 0) {
                info->transferedBytes = pos;
            }
        }
    }
    if (err < 0) {
        ERROR0("ERROR in DAUDIO_Flush\n");
    }
    return (err < 0)?FALSE:TRUE;
}

int DAUDIO_GetAvailable(void* id, int isSource) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int ret = 0;
    int pos;

    if (info) {
        /* unfortunately, the STREAMS architecture
         * seems to not have a method for querying
         * the available bytes to read/write!
         * estimate it...
         */
        pos = getDevicePosition(info, isSource);
        if (pos >= 0) {
            if (isSource) {
                /* we usually have written more bytes
                 * to the queue than the device position should be
                 */
                ret = (info->bufferSizeInBytes) - (info->transferedBytes - pos);
            } else {
                /* for record, the device stream should
                 * be usually ahead of our read actions
                 */
                ret = pos - info->transferedBytes;
            }
            if (ret > info->bufferSizeInBytes) {
                ERROR2("DAUDIO_GetAvailable: available=%d, too big at bufferSize=%d!\n",
                       (int) ret, (int) info->bufferSizeInBytes);
                ERROR2("                     devicePos=%d, transferedBytes=%d\n",
                       (int) pos, (int) info->transferedBytes);
                ret = info->bufferSizeInBytes;
            }
            else if (ret < 0) {
                ERROR1("DAUDIO_GetAvailable: available=%d, in theory not possible!\n",
                       (int) ret);
                ERROR2("                     devicePos=%d, transferedBytes=%d\n",
                       (int) pos, (int) info->transferedBytes);
                ret = 0;
            }
        }
    }

    TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret);
    return ret;
}

INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int ret;
    int pos;
    INT64 result = javaBytePos;

    if (info) {
        pos = getDevicePosition(info, isSource);
        if (pos >= 0) {
            result = info->positionOffset + pos;
        }
    }

    //printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result);
    return result;
}


void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
    SolPcmInfo* info = (SolPcmInfo*) id;
    int ret;
    int pos;

    if (info) {
        pos = getDevicePosition(info, isSource);
        if (pos >= 0) {
            info->positionOffset = javaBytePos - pos;
        }
    }
}

int DAUDIO_RequiresServicing(void* id, int isSource) {
    // never need servicing on Solaris
    return FALSE;
}

void DAUDIO_Service(void* id, int isSource) {
    // never need servicing on Solaris
}


#endif // USE_DAUDIO
