/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include <stdint.h>
#include <iostream>
#include <fstream>
#include <utils/String8.h>
#include "Log.h"
#include "StringUtil.h"
#include "audio/Buffer.h"

Buffer::Buffer(size_t capacity, size_t size, bool stereo)
    : mCapacity(capacity),
      mSize(size),
      mHandled(0),
      mStereo(stereo)
{
    mData = new char[capacity];
    //LOGV("Buffer %d data %x", capacity, (unsigned int)mData);
    // assume 4bytes alignment
    ASSERT(((long)mData & 0x3) == 0);
    // filling with zero just to make valgrind happy.
    // Otherwise, valgrind will complain about uninitialized data for all captured data
    memset(mData, capacity, 0);
};

Buffer::~Buffer()
{
    delete[] mData;
    //LOGV("~Buffer %d", mCapacity);
}

void Buffer::changeToMono(ConvertOption option)
{
    size_t newSize = mSize/2;
    int16_t* data = reinterpret_cast<int16_t*>(mData);
    if (option == EKeepCh0) {
        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
            int16_t l = data[i * 2];
            data[i] = l;
        }
    } else if (option == EKeepCh1) {
        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
            int16_t r = data[i * 2 + 1];
            data[i] = r;
        }
    } else { // average
        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
            int16_t l = data[i * 2];
            int16_t r = data[i * 2 + 1];
            int16_t avr = (int16_t)(((int32_t)l + (int32_t)r)/2);
            data[i] = avr;
        }
    }
    mSize = newSize;
    mHandled /= 2;
    mStereo = false;
}

bool Buffer::changeToStereo()
{
    //TODO ChangeToStereo
    return false;
}

const char* EXTENSION_S16_STEREO = ".r2s";
const char* EXTENSION_S16_MONO = ".r2m";
Buffer* Buffer::loadFromFile(const android::String8& filename)
{
    bool stereo;
    if (StringUtil::endsWith(filename, EXTENSION_S16_STEREO)) {
        stereo = true;
    } else if (StringUtil::endsWith(filename, EXTENSION_S16_MONO)) {
        stereo = false;
    } else {
        LOGE("Buffer::loadFromFile specified file %s has unknown extension.", filename.string());
        return NULL;
    }
    std::ifstream file(filename.string(),  std::ios::in | std::ios::binary |
            std::ios::ate);
    if (!file.is_open()) {
        LOGE("Buffer::loadFromFile cannot open file %s.", filename.string());
        return NULL;
    }
    size_t size = file.tellg();
    Buffer* buffer = new Buffer(size, size, stereo);
    if (buffer == NULL) {
        return NULL;
    }
    file.seekg(0, std::ios::beg);
    file.read(buffer->mData, size); //TODO handle read error
    file.close();
    return buffer;
}

bool Buffer::saveToFile(const android::String8& filename)
{
    android::String8 filenameWithExtension(filename);
    if (isStereo()) {
        filenameWithExtension.append(EXTENSION_S16_STEREO);
    } else {
        filenameWithExtension.append(EXTENSION_S16_MONO);
    }
    std::ofstream file(filenameWithExtension.string(),  std::ios::out | std::ios::binary |
            std::ios::trunc);
    if (!file.is_open()) {
        LOGE("Buffer::saveToFile cannot create file %s.",
                filenameWithExtension.string());
        return false;
    }
    file.write(mData, mSize);
    bool writeOK = true;
    if (file.rdstate() != std::ios_base::goodbit) {
        LOGE("Got error while writing file %s %x", filenameWithExtension.string(), file.rdstate());
        writeOK = false;
    }
    file.close();
    return writeOK;
}

bool Buffer::operator == (const Buffer& b) const
{
    if (mStereo != b.mStereo) {
        LOGD("stereo mismatch %d %d", mStereo, b.mStereo);
        return false;
    }
    if (mSize != b.mSize) {
        LOGD("size mismatch %d %d", mSize, b.mSize);
        return false;
    }
    for (size_t i = 0; i < mSize; i++) {
        if (mData[i] != b.mData[i]) {
            LOGD("%d %x vs %x", i, mData[i], b.mData[i]);
            return false;
        }
    }
    return true;
}