/*
 * Copyright 2020 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 "EvsUltrasonicsArray.h"

#include <android-base/logging.h>
#include <hidlmemory/mapping.h>
#include <log/log.h>
#include <time.h>
#include <utils/SystemClock.h>
#include <utils/Timers.h>

namespace android {
namespace hardware {
namespace automotive {
namespace evs {
namespace V1_1 {
namespace implementation {

// Arbitrary limit on number of data frames allowed to be allocated
// Safeguards against unreasonable resource consumption and provides a testable limit
const unsigned int kMaximumDataFramesInFlight = 100;

const uint32_t kMaxReadingsPerSensor = 5;
const uint32_t kMaxReceiversCount = 3;

const unsigned int kSharedMemoryMaxSize =
        kMaxReadingsPerSensor * kMaxReceiversCount * 2 * sizeof(float);

// Target frame rate in frames per second.
const int kTargetFrameRate = 10;

namespace {

void fillMockArrayDesc(UltrasonicsArrayDesc& arrayDesc) {
    arrayDesc.maxReadingsPerSensorCount = kMaxReadingsPerSensor;
    arrayDesc.maxReceiversCount = kMaxReceiversCount;

    const int kSensorCount = 3;
    const float kMaxRange = 4000;                // 4 metres.
    const float kAngleOfMeasurement = 0.261799;  // 15 degrees.

    std::vector<UltrasonicSensor> sensors(kSensorCount);

    // Sensor pointing forward on left side of front bumper.
    sensors[0].maxRange = kMaxRange;
    sensors[0].angleOfMeasurement = kAngleOfMeasurement;
    sensors[0].pose = {{1, 0, 0, 0}, {-1000, 2000, 200}};

    // Sensor pointing forward on center of front bumper.
    sensors[1].maxRange = kMaxRange;
    sensors[1].angleOfMeasurement = kAngleOfMeasurement;
    sensors[1].pose = {{1, 0, 0, 0}, {0, 2000, 200}};

    // Sensor pointing forward on right side of front bumper.
    sensors[2].maxRange = kMaxRange;
    sensors[2].angleOfMeasurement = kAngleOfMeasurement;
    sensors[2].pose = {{1, 0, 0, 0}, {1000, 2000, 200}};

    arrayDesc.sensors = sensors;
}

// Struct used by SerializeWaveformData().
struct WaveformData {
    uint8_t receiverId;
    std::vector<std::pair<float, float>> readings;
};

// Serializes data provided in waveformDataList to a shared memory data pointer.
// TODO(b/149950362): Add a common library for serialiazing and deserializing waveform data.
void SerializeWaveformData(const std::vector<WaveformData>& waveformDataList, uint8_t* pData) {
    for (auto& waveformData : waveformDataList) {
        // Set Id
        memcpy(pData, &waveformData.receiverId, sizeof(uint8_t));
        pData += sizeof(uint8_t);

        for (auto& reading : waveformData.readings) {
            // Set the time of flight.
            memcpy(pData, &reading.first, sizeof(float));
            pData += sizeof(float);

            // Set the resonance.
            memcpy(pData, &reading.second, sizeof(float));
            pData += sizeof(float);
        }
    }
}

// Fills dataFrameDesc with mock data.
bool fillMockDataFrame(UltrasonicsDataFrameDesc& dataFrameDesc, sp<IMemory> pIMemory) {
    dataFrameDesc.timestampNs = elapsedRealtimeNano();

    const std::vector<uint8_t> transmittersIdList = {0};
    dataFrameDesc.transmittersIdList = transmittersIdList;

    const std::vector<uint8_t> recvIdList = {0, 1, 2};
    dataFrameDesc.receiversIdList = recvIdList;

    const std::vector<uint32_t> receiversReadingsCountList = {2, 2, 4};
    dataFrameDesc.receiversReadingsCountList = receiversReadingsCountList;

    const std::vector<WaveformData> waveformDataList = {
            {recvIdList[0], {{1000, 0.1f}, {2000, 0.8f}}},
            {recvIdList[1], {{1000, 0.1f}, {2000, 1.0f}}},
            {recvIdList[2], {{1000, 0.1f}, {2000, 0.2f}, {4000, 0.2f}, {5000, 0.1f}}}};

    if (pIMemory.get() == nullptr) {
        return false;
    }

    uint8_t* pData = (uint8_t*)((void*)pIMemory->getPointer());

    pIMemory->update();
    SerializeWaveformData(waveformDataList, pData);
    pIMemory->commit();

    return true;
}

}  // namespace

EvsUltrasonicsArray::EvsUltrasonicsArray(const char* deviceName)
    : mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED) {
    LOG(DEBUG) << "EvsUltrasonicsArray instantiated";

    // Set up mock data for description.
    mArrayDesc.ultrasonicsArrayId = deviceName;
    fillMockArrayDesc(mArrayDesc);

    // Assign allocator.
    mShmemAllocator = IAllocator::getService("ashmem");
    if (mShmemAllocator.get() == nullptr) {
        LOG(ERROR) << "SurroundViewHidlTest getService ashmem failed";
    }
}

sp<EvsUltrasonicsArray> EvsUltrasonicsArray::Create(const char* deviceName) {
    return sp<EvsUltrasonicsArray>(new EvsUltrasonicsArray(deviceName));
}

EvsUltrasonicsArray::~EvsUltrasonicsArray() {
    LOG(DEBUG) << "EvsUltrasonicsArray being destroyed";
    forceShutdown();
}

// This gets called if another caller "steals" ownership of the ultrasonic array.
void EvsUltrasonicsArray::forceShutdown() {
    LOG(DEBUG) << "EvsUltrasonicsArray forceShutdown";

    // Make sure our output stream is cleaned up
    // (It really should be already)
    stopStream();

    // Claim the lock while we work on internal state
    std::lock_guard<std::mutex> lock(mAccessLock);

    // Drop all the data frames we've been using
    for (auto&& dataFrame : mDataFrames) {
        if (dataFrame.inUse) {
            LOG(ERROR) << "Error - releasing data frame despite remote ownership";
        }
        dataFrame.sharedMemory.clear();
    }
    mDataFrames.clear();

    // Put this object into an unrecoverable error state since somebody else
    // is going to own the underlying ultrasonic array now
    mStreamState = DEAD;
}

UltrasonicsArrayDesc EvsUltrasonicsArray::GetMockArrayDesc(const char* deviceName) {
    UltrasonicsArrayDesc ultrasonicsArrayDesc;
    ultrasonicsArrayDesc.ultrasonicsArrayId = deviceName;
    fillMockArrayDesc(ultrasonicsArrayDesc);
    return ultrasonicsArrayDesc;
}

Return<void> EvsUltrasonicsArray::getUltrasonicArrayInfo(getUltrasonicArrayInfo_cb _get_info_cb) {
    LOG(DEBUG) << "EvsUltrasonicsArray getUltrasonicsArrayInfo";

    // Return the description for the get info callback.
    _get_info_cb(mArrayDesc);

    return Void();
}

Return<EvsResult> EvsUltrasonicsArray::setMaxFramesInFlight(uint32_t bufferCount) {
    LOG(DEBUG) << "EvsUltrasonicsArray setMaxFramesInFlight";

    // Lock mutex for performing changes to available frames.
    std::lock_guard<std::mutex> lock(mAccessLock);

    // We cannot function without at least one buffer to send data.
    if (bufferCount < 1) {
        LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested";
        return EvsResult::INVALID_ARG;
    }

    // Update our internal state of buffer count.
    if (setAvailableFrames_Locked(bufferCount)) {
        return EvsResult::OK;
    } else {
        return EvsResult::BUFFER_NOT_AVAILABLE;
    }

    return EvsResult::OK;
}

Return<void> EvsUltrasonicsArray::doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) {
    LOG(DEBUG) << "EvsUltrasonicsArray doneWithFrame";

    std::lock_guard<std::mutex> lock(mAccessLock);

    if (dataFrameDesc.dataFrameId >= mDataFrames.size()) {
        LOG(ERROR) << "ignoring doneWithFrame called with invalid dataFrameId "
                   << dataFrameDesc.dataFrameId << "(max is " << mDataFrames.size() - 1 << ")";
        return Void();
    }

    if (!mDataFrames[dataFrameDesc.dataFrameId].inUse) {
        LOG(ERROR) << "ignoring doneWithFrame called on frame " << dataFrameDesc.dataFrameId
                   << "which is already free";
        return Void();
    }

    // Mark the frame as available
    mDataFrames[dataFrameDesc.dataFrameId].inUse = false;
    mFramesInUse--;

    // If this frame's index is high in the array, try to move it down
    // to improve locality after mFramesAllowed has been reduced.
    if (dataFrameDesc.dataFrameId >= mFramesAllowed) {
        // Find an empty slot lower in the array (which should always exist in this case)
        for (auto&& dataFrame : mDataFrames) {
            if (!dataFrame.sharedMemory.IsValid()) {
                dataFrame.sharedMemory = mDataFrames[dataFrameDesc.dataFrameId].sharedMemory;
                mDataFrames[dataFrameDesc.dataFrameId].sharedMemory.clear();
                return Void();
            }
        }
    }

    return Void();
}

Return<EvsResult> EvsUltrasonicsArray::startStream(
        const ::android::sp<IEvsUltrasonicsArrayStream>& stream) {
    LOG(DEBUG) << "EvsUltrasonicsArray startStream";

    std::lock_guard<std::mutex> lock(mAccessLock);

    if (mStreamState != STOPPED) {
        LOG(ERROR) << "ignoring startStream call when a stream is already running.";
        return EvsResult::STREAM_ALREADY_RUNNING;
    }

    // If the client never indicated otherwise, configure ourselves for a single streaming buffer
    if (mFramesAllowed < 1) {
        if (!setAvailableFrames_Locked(1)) {
            LOG(ERROR)
                    << "Failed to start stream because we couldn't get shared memory data buffer";
            return EvsResult::BUFFER_NOT_AVAILABLE;
        }
    }

    // Record the user's callback for use when we have a frame ready
    mStream = stream;

    // Start the frame generation thread
    mStreamState = RUNNING;
    mCaptureThread = std::thread([this]() { generateDataFrames(); });

    return EvsResult::OK;
}

Return<void> EvsUltrasonicsArray::stopStream() {
    LOG(DEBUG) << "EvsUltrasonicsArray stopStream";

    bool streamStateStopping = false;
    {
        std::lock_guard<std::mutex> lock(mAccessLock);
        if (mStreamState == RUNNING) {
            // Tell the GenerateFrames loop we want it to stop
            mStreamState = STOPPING;
            streamStateStopping = true;
        }
    }

    if (streamStateStopping) {
        // Block outside the mutex until the "stop" flag has been acknowledged
        // We won't send any more frames, but the client might still get some already in flight
        LOG(DEBUG) << "Waiting for stream thread to end...";
        mCaptureThread.join();
    }

    {
        std::lock_guard<std::mutex> lock(mAccessLock);
        mStreamState = STOPPED;
        mStream = nullptr;
        LOG(DEBUG) << "Stream marked STOPPED.";
    }

    return Void();
}

bool EvsUltrasonicsArray::setAvailableFrames_Locked(unsigned bufferCount) {
    if (bufferCount < 1) {
        LOG(ERROR) << "Ignoring request to set buffer count to zero";
        return false;
    }
    if (bufferCount > kMaximumDataFramesInFlight) {
        LOG(ERROR) << "Rejecting buffer request in excess of internal limit";
        return false;
    }

    // Is an increase required?
    if (mFramesAllowed < bufferCount) {
        // An increase is required
        unsigned needed = bufferCount - mFramesAllowed;
        LOG(INFO) << "Number of data frame buffers to add: " << needed;

        unsigned added = increaseAvailableFrames_Locked(needed);
        if (added != needed) {
            // If we didn't add all the frames we needed, then roll back to the previous state
            LOG(ERROR) << "Rolling back to previous frame queue size";
            decreaseAvailableFrames_Locked(added);
            return false;
        }
    } else if (mFramesAllowed > bufferCount) {
        // A decrease is required
        unsigned framesToRelease = mFramesAllowed - bufferCount;
        LOG(INFO) << "Number of data frame buffers to reduce: " << framesToRelease;

        unsigned released = decreaseAvailableFrames_Locked(framesToRelease);
        if (released != framesToRelease) {
            // This shouldn't happen with a properly behaving client because the client
            // should only make this call after returning sufficient outstanding buffers
            // to allow a clean resize.
            LOG(ERROR) << "Buffer queue shrink failed -- too many buffers currently in use?";
        }
    }

    return true;
}

EvsUltrasonicsArray::SharedMemory EvsUltrasonicsArray::allocateAndMapSharedMemory() {
    SharedMemory sharedMemory;

    // Check shared memory allocator is valid.
    if (mShmemAllocator.get() == nullptr) {
        LOG(ERROR) << "Shared memory allocator not initialized.";
        return SharedMemory();
    }

    // Allocate memory.
    bool allocateSuccess = false;
    Return<void> result = mShmemAllocator->allocate(kSharedMemoryMaxSize,
                                                    [&](bool success, const hidl_memory& hidlMem) {
                                                        if (!success) {
                                                            return;
                                                        }
                                                        allocateSuccess = success;
                                                        sharedMemory.hidlMemory = hidlMem;
                                                    });

    // Check result of allocated memory.
    if (!result.isOk() || !allocateSuccess) {
        LOG(ERROR) << "Shared memory allocation failed.";
        return SharedMemory();
    }

    // Map shared memory.
    sharedMemory.pIMemory = mapMemory(sharedMemory.hidlMemory);
    if (sharedMemory.pIMemory.get() == nullptr) {
        LOG(ERROR) << "Shared memory mapping failed.";
        return SharedMemory();
    }

    // Return success.
    return sharedMemory;
}

unsigned EvsUltrasonicsArray::increaseAvailableFrames_Locked(unsigned numToAdd) {
    unsigned added = 0;

    while (added < numToAdd) {
        SharedMemory sharedMemory = allocateAndMapSharedMemory();

        // If allocate and map fails, break.
        if (!sharedMemory.IsValid()) {
            break;
        }

        // Find a place to store the new buffer
        bool stored = false;
        for (auto&& dataFrame : mDataFrames) {
            if (!dataFrame.sharedMemory.IsValid()) {
                // Use this existing entry
                dataFrame.sharedMemory = sharedMemory;
                dataFrame.inUse = false;
                stored = true;
                break;
            }
        }

        if (!stored) {
            // Add a BufferRecord wrapping this handle to our set of available buffers
            mDataFrames.emplace_back(sharedMemory);
        }

        mFramesAllowed++;
        added++;
    }

    return added;
}

unsigned EvsUltrasonicsArray::decreaseAvailableFrames_Locked(unsigned numToRemove) {
    unsigned removed = 0;

    for (auto&& dataFrame : mDataFrames) {
        // Is this record not in use, but holding a buffer that we can free?
        if (!dataFrame.inUse && dataFrame.sharedMemory.IsValid()) {
            // Release buffer and update the record so we can recognize it as "empty"
            dataFrame.sharedMemory.clear();

            mFramesAllowed--;
            removed++;

            if (removed == numToRemove) {
                break;
            }
        }
    }

    return removed;
}

// This is the asynchronous data frame generation thread that runs in parallel with the
// main serving thread. There is one for each active ultrasonic array instance.
void EvsUltrasonicsArray::generateDataFrames() {
    LOG(DEBUG) << "Data frame generation loop started";

    unsigned idx = 0;

    while (true) {
        bool timeForFrame = false;

        nsecs_t startTime = elapsedRealtimeNano();

        // Lock scope for updating shared state
        {
            std::lock_guard<std::mutex> lock(mAccessLock);

            if (mStreamState != RUNNING) {
                // Break out of our main thread loop
                break;
            }

            // Are we allowed to issue another buffer?
            if (mFramesInUse >= mFramesAllowed) {
                // Can't do anything right now -- skip this frame
                LOG(WARNING) << "Skipped a frame because too many are in flight";
            } else {
                // Identify an available buffer to fill
                for (idx = 0; idx < mDataFrames.size(); idx++) {
                    if (!mDataFrames[idx].inUse && mDataFrames[idx].sharedMemory.IsValid()) {
                        // Found an available record, so stop looking
                        break;
                    }
                }
                if (idx >= mDataFrames.size()) {
                    // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed
                    LOG(ERROR) << "Failed to find an available buffer slot";
                } else {
                    // We're going to make the frame busy
                    mDataFrames[idx].inUse = true;
                    mFramesInUse++;
                    timeForFrame = true;
                }
            }
        }

        if (timeForFrame) {
            // Assemble the buffer description we'll transmit below
            UltrasonicsDataFrameDesc mockDataFrameDesc;
            mockDataFrameDesc.dataFrameId = idx;
            mockDataFrameDesc.waveformsData = mDataFrames[idx].sharedMemory.hidlMemory;

            // Fill mock waveform data.
            fillMockDataFrame(mockDataFrameDesc, mDataFrames[idx].sharedMemory.pIMemory);

            // Issue the (asynchronous) callback to the client -- can't be holding the lock
            auto result = mStream->deliverDataFrame(mockDataFrameDesc);
            if (result.isOk()) {
                LOG(DEBUG) << "Delivered data frame id: " << mockDataFrameDesc.dataFrameId;
            } else {
                // This can happen if the client dies and is likely unrecoverable.
                // To avoid consuming resources generating failing calls, we stop sending
                // frames.  Note, however, that the stream remains in the "STREAMING" state
                // until cleaned up on the main thread.
                LOG(ERROR) << "Frame delivery call failed in the transport layer.";

                // Since we didn't actually deliver it, mark the frame as available
                std::lock_guard<std::mutex> lock(mAccessLock);
                mDataFrames[idx].inUse = false;
                mFramesInUse--;

                break;
            }
        }

        // Sleep to generate frames at kTargetFrameRate.
        static const nsecs_t kTargetFrameTimeUs = 1000 * 1000 / kTargetFrameRate;
        const nsecs_t now = elapsedRealtimeNano();
        const nsecs_t workTimeUs = (now - startTime) / 1000;
        const nsecs_t sleepDurationUs = kTargetFrameTimeUs - workTimeUs;
        if (sleepDurationUs > 0) {
            usleep(sleepDurationUs);
        }
    }

    // If we've been asked to stop, send an event to signal the actual end of stream
    EvsEventDesc event;
    event.aType = EvsEventType::STREAM_STOPPED;
    auto result = mStream->notify(event);
    if (!result.isOk()) {
        LOG(ERROR) << "Error delivering end of stream marker";
    }
}

}  // namespace implementation
}  // namespace V1_1
}  // namespace evs
}  // namespace automotive
}  // namespace hardware
}  // namespace android
