/*
 * Copyright (C) 2019 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 "Camera3-BufUtils"
#define ATRACE_TAG ATRACE_TAG_CAMERA
//#define LOG_NDEBUG 0
//#define LOG_NNDEBUG 0  // Per-frame verbose logging

#include <inttypes.h>

#include <utils/Log.h>

#include "device3/BufferUtils.h"

namespace android {
namespace camera3 {

camera3_buffer_status_t mapHidlBufferStatus(hardware::camera::device::V3_2::BufferStatus status) {
    using hardware::camera::device::V3_2::BufferStatus;

    switch (status) {
        case BufferStatus::OK: return CAMERA3_BUFFER_STATUS_OK;
        case BufferStatus::ERROR: return CAMERA3_BUFFER_STATUS_ERROR;
    }
    return CAMERA3_BUFFER_STATUS_ERROR;
}

void BufferRecords::takeInflightBufferMap(BufferRecords& other) {
    std::lock_guard<std::mutex> oLock(other.mInflightLock);
    std::lock_guard<std::mutex> lock(mInflightLock);
    if (mInflightBufferMap.size() > 0) {
        ALOGE("%s: inflight map is set in non-empty state!", __FUNCTION__);
    }
    mInflightBufferMap = std::move(other.mInflightBufferMap);
    other.mInflightBufferMap.clear();
}

void BufferRecords::takeRequestedBufferMap(BufferRecords& other) {
    std::lock_guard<std::mutex> oLock(other.mRequestedBuffersLock);
    std::lock_guard<std::mutex> lock(mRequestedBuffersLock);
    if (mRequestedBufferMap.size() > 0) {
        ALOGE("%s: requested buffer map is set in non-empty state!", __FUNCTION__);
    }
    mRequestedBufferMap = std::move(other.mRequestedBufferMap);
    other.mRequestedBufferMap.clear();
}

void BufferRecords::takeBufferCaches(BufferRecords& other, const std::vector<int32_t>& streams) {
    std::lock_guard<std::mutex> oLock(other.mBufferIdMapLock);
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    if (mBufferIdMaps.size() > 0) {
        ALOGE("%s: buffer ID map is set in non-empty state!", __FUNCTION__);
    }
    for (auto streamId : streams) {
        mBufferIdMaps.insert({streamId, std::move(other.mBufferIdMaps.at(streamId))});
    }
    other.mBufferIdMaps.clear();
}

std::pair<bool, uint64_t> BufferRecords::getBufferId(
        const buffer_handle_t& buf, int streamId) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);

    BufferIdMap& bIdMap = mBufferIdMaps.at(streamId);
    auto it = bIdMap.find(buf);
    if (it == bIdMap.end()) {
        bIdMap[buf] = mNextBufferId++;
        ALOGV("stream %d now have %zu buffer caches, buf %p",
                streamId, bIdMap.size(), buf);
        return std::make_pair(true, mNextBufferId - 1);
    } else {
        return std::make_pair(false, it->second);
    }
}

void BufferRecords::tryCreateBufferCache(int streamId) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    if (mBufferIdMaps.count(streamId) == 0) {
        mBufferIdMaps.emplace(streamId, BufferIdMap{});
    }
}

void BufferRecords::removeInactiveBufferCaches(const std::set<int32_t>& activeStreams) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    for(auto it = mBufferIdMaps.begin(); it != mBufferIdMaps.end();) {
        int streamId = it->first;
        bool active = activeStreams.count(streamId) > 0;
        if (!active) {
            it = mBufferIdMaps.erase(it);
        } else {
            ++it;
        }
    }
}

uint64_t BufferRecords::removeOneBufferCache(int streamId, const native_handle_t* handle) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    uint64_t bufferId = BUFFER_ID_NO_BUFFER;
    auto mapIt = mBufferIdMaps.find(streamId);
    if (mapIt == mBufferIdMaps.end()) {
        // streamId might be from a deleted stream here
        ALOGI("%s: stream %d has been removed",
                __FUNCTION__, streamId);
        return BUFFER_ID_NO_BUFFER;
    }
    BufferIdMap& bIdMap = mapIt->second;
    auto it = bIdMap.find(handle);
    if (it == bIdMap.end()) {
        ALOGW("%s: cannot find buffer %p in stream %d",
                __FUNCTION__, handle, streamId);
        return BUFFER_ID_NO_BUFFER;
    } else {
        bufferId = it->second;
        bIdMap.erase(it);
        ALOGV("%s: stream %d now have %zu buffer caches after removing buf %p",
                __FUNCTION__, streamId, bIdMap.size(), handle);
    }
    return bufferId;
}

std::vector<uint64_t> BufferRecords::clearBufferCaches(int streamId) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    std::vector<uint64_t> ret;
    auto mapIt = mBufferIdMaps.find(streamId);
    if (mapIt == mBufferIdMaps.end()) {
        ALOGE("%s: streamId %d not found!", __FUNCTION__, streamId);
        return ret;
    }
    BufferIdMap& bIdMap = mapIt->second;
    ret.reserve(bIdMap.size());
    for (const auto& it : bIdMap) {
        ret.push_back(it.second);
    }
    bIdMap.clear();
    return ret;
}

bool BufferRecords::isStreamCached(int streamId) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    return mBufferIdMaps.find(streamId) != mBufferIdMaps.end();
}

bool BufferRecords::verifyBufferIds(
        int32_t streamId, std::vector<uint64_t>& bufIds) {
    std::lock_guard<std::mutex> lock(mBufferIdMapLock);
    camera3::BufferIdMap& bIdMap = mBufferIdMaps.at(streamId);
    if (bIdMap.size() != bufIds.size()) {
        ALOGE("%s: stream ID %d buffer cache number mismatch: %zu/%zu (service/HAL)",
                __FUNCTION__, streamId, bIdMap.size(), bufIds.size());
        return false;
    }
    std::vector<uint64_t> internalBufIds;
    internalBufIds.reserve(bIdMap.size());
    for (const auto& pair : bIdMap) {
        internalBufIds.push_back(pair.second);
    }
    std::sort(bufIds.begin(), bufIds.end());
    std::sort(internalBufIds.begin(), internalBufIds.end());
    for (size_t i = 0; i < bufIds.size(); i++) {
        if (bufIds[i] != internalBufIds[i]) {
            ALOGE("%s: buffer cache mismatch! Service %" PRIu64 ", HAL %" PRIu64,
                    __FUNCTION__, internalBufIds[i], bufIds[i]);
            return false;
        }
    }
    return true;
}

void BufferRecords::getInflightBufferKeys(
        std::vector<std::pair<int32_t, int32_t>>* out) {
    std::lock_guard<std::mutex> lock(mInflightLock);
    out->clear();
    out->reserve(mInflightBufferMap.size());
    for (auto& pair : mInflightBufferMap) {
        uint64_t key = pair.first;
        int32_t streamId = key & 0xFFFFFFFF;
        int32_t frameNumber = (key >> 32) & 0xFFFFFFFF;
        out->push_back(std::make_pair(frameNumber, streamId));
    }
    return;
}

status_t BufferRecords::pushInflightBuffer(
        int32_t frameNumber, int32_t streamId, buffer_handle_t *buffer) {
    std::lock_guard<std::mutex> lock(mInflightLock);
    uint64_t key = static_cast<uint64_t>(frameNumber) << 32 | static_cast<uint64_t>(streamId);
    mInflightBufferMap[key] = buffer;
    return OK;
}

status_t BufferRecords::popInflightBuffer(
        int32_t frameNumber, int32_t streamId,
        /*out*/ buffer_handle_t **buffer) {
    std::lock_guard<std::mutex> lock(mInflightLock);

    uint64_t key = static_cast<uint64_t>(frameNumber) << 32 | static_cast<uint64_t>(streamId);
    auto it = mInflightBufferMap.find(key);
    if (it == mInflightBufferMap.end()) return NAME_NOT_FOUND;
    if (buffer != nullptr) {
        *buffer = it->second;
    }
    mInflightBufferMap.erase(it);
    return OK;
}

void BufferRecords::popInflightBuffers(
        const std::vector<std::pair<int32_t, int32_t>>& buffers) {
    for (const auto& pair : buffers) {
        int32_t frameNumber = pair.first;
        int32_t streamId = pair.second;
        popInflightBuffer(frameNumber, streamId, nullptr);
    }
}

status_t BufferRecords::pushInflightRequestBuffer(
        uint64_t bufferId, buffer_handle_t* buf, int32_t streamId) {
    std::lock_guard<std::mutex> lock(mRequestedBuffersLock);
    auto pair = mRequestedBufferMap.insert({bufferId, {streamId, buf}});
    if (!pair.second) {
        ALOGE("%s: bufId %" PRIu64 " is already inflight!",
                __FUNCTION__, bufferId);
        return BAD_VALUE;
    }
    return OK;
}

// Find and pop a buffer_handle_t based on bufferId
status_t BufferRecords::popInflightRequestBuffer(
        uint64_t bufferId,
        /*out*/ buffer_handle_t** buffer,
        /*optional out*/ int32_t* streamId) {
    if (buffer == nullptr) {
        ALOGE("%s: buffer (%p) must not be null", __FUNCTION__, buffer);
        return BAD_VALUE;
    }
    std::lock_guard<std::mutex> lock(mRequestedBuffersLock);
    auto it = mRequestedBufferMap.find(bufferId);
    if (it == mRequestedBufferMap.end()) {
        ALOGE("%s: bufId %" PRIu64 " is not inflight!",
                __FUNCTION__, bufferId);
        return BAD_VALUE;
    }
    *buffer = it->second.second;
    if (streamId != nullptr) {
        *streamId = it->second.first;
    }
    mRequestedBufferMap.erase(it);
    return OK;
}

void BufferRecords::getInflightRequestBufferKeys(
        std::vector<uint64_t>* out) {
    std::lock_guard<std::mutex> lock(mRequestedBuffersLock);
    out->clear();
    out->reserve(mRequestedBufferMap.size());
    for (auto& pair : mRequestedBufferMap) {
        out->push_back(pair.first);
    }
    return;
}


} // camera3
} // namespace android
