/*
 * Copyright (C) 2017 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 "SensorTest.h"
#include <errno.h>

namespace android {
namespace SensorTest {

// SensorTest container class
bool SensorTest::SetUp() {
    if (mManager == nullptr) {
        mManager.reset(
              TestSensorManager::getInstanceForPackage("android.hardware.cts.SensorNativeTest"));
    }
    return mManager == nullptr;
}

void SensorTest::TearDown() {
    if (mManager == nullptr) {
        mManager.reset(nullptr);
    }
}

TestSensorManager::TestSensorManager(const char *package) {
    mManager = ASensorManager_getInstanceForPackage(package);
}

TestSensorManager::~TestSensorManager() {
    for (int channel : mSensorDirectChannel) {
        destroyDirectChannel(channel);
    }
    mSensorDirectChannel.clear();
}

TestSensorManager * TestSensorManager::getInstanceForPackage(const char *package) {
    return new TestSensorManager(package);
}

TestSensor TestSensorManager::getDefaultSensor(int type) {
    return TestSensor(ASensorManager_getDefaultSensor(mManager, type));
}

int TestSensorManager::createDirectChannel(const TestSharedMemory &mem) {
    if (!isValid()) {
        return -EINVAL;
    }
    switch (mem.getType()) {
        case ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY:
            return createSharedMemoryDirectChannel(
                    mem.getSharedMemoryFd(), mem.getSize());
        case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER:
            return createHardwareBufferDirectChannel(
                    mem.getHardwareBuffer(), mem.getSize());
        default:
            return -1;
    }
}

int TestSensorManager::createSharedMemoryDirectChannel(int fd, size_t size) {
    int ret = ASensorManager_createSharedMemoryDirectChannel(mManager, fd, size);
    if (ret > 0) {
        mSensorDirectChannel.insert(ret);
    }
    return ret;
}

int TestSensorManager::createHardwareBufferDirectChannel(
        AHardwareBuffer const *buffer, size_t size) {
    int ret = ASensorManager_createHardwareBufferDirectChannel(mManager, buffer, size);
    if (ret > 0) {
        mSensorDirectChannel.insert(ret);
    }
    return ret;
}

void TestSensorManager::destroyDirectChannel(int channel) {
    if (!isValid()) {
        return;
    }
    ASensorManager_destroyDirectChannel(mManager, channel);
    mSensorDirectChannel.erase(channel);
    return;
}

int TestSensorManager::configureDirectReport(TestSensor sensor, int channel, int rate) {
    if (!isValid()) {
        return -EINVAL;
    }
    return ASensorManager_configureDirectReport(mManager, sensor, channel, rate);
}

char * TestSharedMemory::getBuffer() const {
    return mBuffer;
}

std::vector<ASensorEvent> TestSharedMemory::parseEvents(int64_t lastCounter, size_t offset) const {
    constexpr size_t kEventSize = sizeof(ASensorEvent);
    constexpr size_t kOffsetSize = offsetof(ASensorEvent, version);
    constexpr size_t kOffsetAtomicCounter = offsetof(ASensorEvent, reserved0);

    std::vector<ASensorEvent> events;
    while (offset + kEventSize <= mSize) {
        int64_t atomicCounter = *reinterpret_cast<uint32_t *>(mBuffer + offset + kOffsetAtomicCounter);
        if (atomicCounter <= lastCounter) {
            break;
        }

        int32_t size = *reinterpret_cast<int32_t *>(mBuffer + offset + kOffsetSize);
        if (size != kEventSize) {
            // unknown error, events parsed may be wrong, remove all
            events.clear();
            break;
        }

        events.push_back(*reinterpret_cast<ASensorEvent *>(mBuffer + offset));
        lastCounter = atomicCounter;
        offset += kEventSize;
    }

    return events;
}

TestSharedMemory::TestSharedMemory(int type, size_t size)
        : mType(type), mSize(0), mBuffer(nullptr),
            mSharedMemoryFd(-1), mHardwareBuffer(nullptr) {
    bool success = false;
    switch(type) {
        case ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY: {
            mSharedMemoryFd = ASharedMemory_create("TestSharedMemory", size);
            if (mSharedMemoryFd < 0
                    || ASharedMemory_getSize(mSharedMemoryFd) != size) {
                break;
            }

            mSize = size;
            mBuffer = reinterpret_cast<char *>(::mmap(
                    nullptr, mSize, PROT_READ | PROT_WRITE,
                    MAP_SHARED, mSharedMemoryFd, 0));

            if (mBuffer == MAP_FAILED) {
                mBuffer = nullptr;
                break;
            }
            success = true;
            break;
        }
        case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER: {
            AHardwareBuffer_Desc desc = {
                .width = static_cast<uint32_t>(size),
                .height = 1,
                .layers = 1,
                .usage = AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA
                         | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
                .format = AHARDWAREBUFFER_FORMAT_BLOB
            };

            // allocate
            if (AHardwareBuffer_allocate(&desc, &mHardwareBuffer) == 0) {
                // lock
                if (AHardwareBuffer_lock(mHardwareBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
                                         -1, nullptr, reinterpret_cast<void **>(&mBuffer)) == 0) {
                    if (mBuffer != nullptr) {
                        mSize = size;
                        success = true;
                    }
                }
            }
            break;
        }
        default:
            break;
    }

    if (!success) {
        release();
    }
}

TestSharedMemory::~TestSharedMemory() {
    release();
}

void TestSharedMemory::release() {
    switch(mType) {
        case ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY: {
            if (mBuffer != nullptr) {
                ::munmap(mBuffer, mSize);
                mBuffer = nullptr;
            }
            if (mSharedMemoryFd > 0) {
                ::close(mSharedMemoryFd);
                mSharedMemoryFd = -1;
            }
            mSize = 0;
            break;
        }
        case ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER: {
            if (mHardwareBuffer != nullptr) {
                if (mBuffer != nullptr) {
                    int32_t fence = -1;
                    AHardwareBuffer_unlock(mHardwareBuffer, &fence);
                    mBuffer = nullptr;
                }
                AHardwareBuffer_release(mHardwareBuffer);
                mHardwareBuffer = nullptr;
            }
            mSize = 0;
            break;
        }
        default:
            break;
    }
    if (mSharedMemoryFd > 0 || mSize != 0 || mBuffer != nullptr || mHardwareBuffer != nullptr) {
        ALOGE("TestSharedMemory %p not properly destructed: "
              "type %d, shared_memory_fd %d, hardware_buffer %p, size %zu, buffer %p",
              this, static_cast<int>(mType), mSharedMemoryFd, mHardwareBuffer, mSize, mBuffer);
    }
}

TestSharedMemory* TestSharedMemory::create(int type, size_t size) {
    constexpr size_t kMaxSize = 128*1024*1024; // sensor test should not need more than 128M
    if (size == 0 || size >= kMaxSize) {
        return nullptr;
    }

    auto m = new TestSharedMemory(type, size);
    if (m->mSize != size || m->mBuffer == nullptr) {
        delete m;
        m = nullptr;
    }
    return m;
}
} // namespace SensorTest
} // namespace android