/*
 * Copyright (C) 2016 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.
 *
 */

#define LOG_TAG "AAudio"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#include <assert.h>
#include <new>
#include <stdint.h>
#include <utils/Mutex.h>

#include <aaudio/AAudio.h>
#include "HandleTracker.h"

using android::Mutex;

// Handle format is: tgggiiii
// where each letter is 4 bits, t=type, g=generation, i=index

#define TYPE_SIZE           4
#define GENERATION_SIZE    12
#define INDEX_SIZE         16

#define GENERATION_INVALID  0
#define GENERATION_SHIFT    INDEX_SIZE

#define TYPE_MASK           ((1 << TYPE_SIZE) - 1)
#define GENERATION_MASK     ((1 << GENERATION_SIZE) - 1)
#define INDEX_MASK          ((1 << INDEX_SIZE) - 1)

#define SLOT_UNAVAILABLE    (-1)

// Error if handle is negative so type is limited to bottom half.
#define HANDLE_INVALID_TYPE TYPE_MASK

static_assert(HANDLE_TRACKER_MAX_TYPES == (1 << (TYPE_SIZE - 1)),
    "Mismatch between header and cpp.");
static_assert(HANDLE_TRACKER_MAX_HANDLES == (1 << (INDEX_SIZE)),
    "Mismatch between header and cpp.");

HandleTracker::HandleTracker(uint32_t maxHandles)
        : mMaxHandleCount(maxHandles)
        , mHandleHeaders(nullptr)
{
    assert(maxHandles <= HANDLE_TRACKER_MAX_HANDLES);
    // Allocate arrays to hold addresses and validation info.
    mHandleAddresses = (handle_tracker_address_t *)
            new(std::nothrow) handle_tracker_address_t[maxHandles];
    if (mHandleAddresses != nullptr) {
        mHandleHeaders = new(std::nothrow) handle_tracker_header_t[maxHandles];

        if (mHandleHeaders != nullptr) {
            handle_tracker_header_t initialHeader = buildHeader(0, 1);
            // Initialize linked list of free nodes. nullptr terminated.
            for (uint32_t i = 0; i < (maxHandles - 1); i++) {
                mHandleAddresses[i] = &mHandleAddresses[i + 1]; // point to next node
                mHandleHeaders[i] = initialHeader;
            }
            mNextFreeAddress = &mHandleAddresses[0];
            mHandleAddresses[maxHandles - 1] = nullptr;
            mHandleHeaders[maxHandles - 1] = 0;
        } else {
            delete[] mHandleAddresses; // so the class appears uninitialized
            mHandleAddresses = nullptr;
        }
    }
}

HandleTracker::~HandleTracker()
{
    Mutex::Autolock _l(mLock);
    delete[] mHandleAddresses;
    delete[] mHandleHeaders;
    mHandleAddresses = nullptr;
}

bool HandleTracker::isInitialized() const {
    return mHandleAddresses != nullptr;
}

handle_tracker_slot_t HandleTracker::allocateSlot_l() {
    void **allocated = mNextFreeAddress;
    if (allocated == nullptr) {
        return SLOT_UNAVAILABLE;
    }
    // Remove this slot from the head of the linked list.
    mNextFreeAddress = (void **) *allocated;
    return (allocated - mHandleAddresses);
}

handle_tracker_generation_t HandleTracker::nextGeneration_l(handle_tracker_slot_t index) {
    handle_tracker_generation_t generation = (mHandleHeaders[index] + 1) & GENERATION_MASK;
    // Avoid generation zero so that 0x0 is not a valid handle.
    if (generation == GENERATION_INVALID) {
        generation++;
    }
    return generation;
}

aaudio_handle_t HandleTracker::put(handle_tracker_type_t type, void *address)
{
    if (type < 0 || type >= HANDLE_TRACKER_MAX_TYPES) {
        return static_cast<aaudio_handle_t>(AAUDIO_ERROR_OUT_OF_RANGE);
    }
    if (!isInitialized()) {
        return static_cast<aaudio_handle_t>(AAUDIO_ERROR_NO_MEMORY);
    }

    Mutex::Autolock _l(mLock);

    // Find an empty slot.
    handle_tracker_slot_t index = allocateSlot_l();
    if (index == SLOT_UNAVAILABLE) {
        ALOGE("HandleTracker::put() no room for more handles");
        return static_cast<aaudio_handle_t>(AAUDIO_ERROR_NO_FREE_HANDLES);
    }

    // Cycle the generation counter so stale handles can be detected.
    handle_tracker_generation_t generation = nextGeneration_l(index); // reads header table
    handle_tracker_header_t inputHeader = buildHeader(type, generation);

    // These two writes may need to be observed by other threads or cores during get().
    mHandleHeaders[index] = inputHeader;
    mHandleAddresses[index] = address;
    // TODO use store release to enforce memory order with get()

    // Generate a handle.
    aaudio_handle_t handle = buildHandle(inputHeader, index);

    ALOGV("HandleTracker::put(%p) returns 0x%08x", address, handle);
    return handle;
}

handle_tracker_slot_t HandleTracker::handleToIndex(handle_tracker_type_t type,
                                                   aaudio_handle_t handle) const
{
    // Validate the handle.
    handle_tracker_slot_t index = extractIndex(handle);
    if (index >= mMaxHandleCount) {
        ALOGE("HandleTracker::handleToIndex() invalid handle = 0x%08X", handle);
        return static_cast<aaudio_handle_t>(AAUDIO_ERROR_INVALID_HANDLE);
    }
    handle_tracker_generation_t handleGeneration = extractGeneration(handle);
    handle_tracker_header_t inputHeader = buildHeader(type, handleGeneration);
    // We do not need to synchronize this access to mHandleHeaders because it is constant for
    // the lifetime of the handle.
    if (inputHeader != mHandleHeaders[index]) {
        ALOGE("HandleTracker::handleToIndex() inputHeader = 0x%08x != mHandleHeaders[%d] = 0x%08x",
             inputHeader, index, mHandleHeaders[index]);
        return static_cast<aaudio_handle_t>(AAUDIO_ERROR_INVALID_HANDLE);
    }
    return index;
}

handle_tracker_address_t HandleTracker::get(handle_tracker_type_t type, aaudio_handle_t handle) const
{
    if (!isInitialized()) {
        return nullptr;
    }
    handle_tracker_slot_t index = handleToIndex(type, handle);
    if (index >= 0) {
        // We do not need to synchronize this access to mHandleHeaders because this slot
        // is allocated and, therefore, not part of the linked list of free slots.
        return mHandleAddresses[index];
    } else {
        return nullptr;
    }
}

handle_tracker_address_t HandleTracker::remove(handle_tracker_type_t type, aaudio_handle_t handle) {
    if (!isInitialized()) {
        return nullptr;
    }

    Mutex::Autolock _l(mLock);

    handle_tracker_slot_t index = handleToIndex(type,handle);
    if (index >= 0) {
        handle_tracker_address_t address = mHandleAddresses[index];

        // Invalidate the header type but preserve the generation count.
        handle_tracker_generation_t generation = mHandleHeaders[index] & GENERATION_MASK;
        handle_tracker_header_t inputHeader = buildHeader(
                (handle_tracker_type_t) HANDLE_INVALID_TYPE, generation);
        mHandleHeaders[index] = inputHeader;

        // Add this slot to the head of the linked list.
        mHandleAddresses[index] = mNextFreeAddress;
        mNextFreeAddress = (handle_tracker_address_t *) &mHandleAddresses[index];
        return address;
    } else {
        return nullptr;
    }
}

aaudio_handle_t HandleTracker::buildHandle(handle_tracker_header_t typeGeneration,
                                         handle_tracker_slot_t index) {
    return (aaudio_handle_t)((typeGeneration << GENERATION_SHIFT) | (index & INDEX_MASK));
}

handle_tracker_header_t HandleTracker::buildHeader(handle_tracker_type_t type,
                                    handle_tracker_generation_t generation)
{
    return (handle_tracker_header_t) (((type & TYPE_MASK) << GENERATION_SIZE)
        | (generation & GENERATION_MASK));
}

handle_tracker_slot_t HandleTracker::extractIndex(aaudio_handle_t handle)
{
    return handle & INDEX_MASK;
}

handle_tracker_generation_t HandleTracker::extractGeneration(aaudio_handle_t handle)
{
    return (handle >> GENERATION_SHIFT) & GENERATION_MASK;
}
