/*
 * 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 "Camera3-TagMonitor"
#define ATRACE_TAG ATRACE_TAG_CAMERA
//#define LOG_NDEBUG 0

#include <iostream>
#include <sstream>

#include "TagMonitor.h"

#include <inttypes.h>
#include <utils/Log.h>
#include <camera/VendorTagDescriptor.h>
#include <camera/StringUtils.h>
#include <camera_metadata_hidden.h>
#include <device3/Camera3Stream.h>

namespace android {

TagMonitor::TagMonitor():
        mMonitoringEnabled(false),
        mMonitoringEvents(kMaxMonitorEvents),
        mVendorTagId(CAMERA_METADATA_INVALID_VENDOR_ID)
{}

TagMonitor::TagMonitor(const TagMonitor& other):
        mMonitoringEnabled(other.mMonitoringEnabled.load()),
        mMonitoredTagList(other.mMonitoredTagList),
        mLastMonitoredRequestValues(other.mLastMonitoredRequestValues),
        mLastMonitoredResultValues(other.mLastMonitoredResultValues),
        mLastMonitoredPhysicalRequestKeys(other.mLastMonitoredPhysicalRequestKeys),
        mLastMonitoredPhysicalResultKeys(other.mLastMonitoredPhysicalResultKeys),
        mMonitoringEvents(other.mMonitoringEvents),
        mVendorTagId(other.mVendorTagId) {}

const std::string TagMonitor::kMonitorOption("-m");

const char* TagMonitor::k3aTags =
        "android.control.aeMode, android.control.afMode, android.control.awbMode,"
        "android.control.aeState, android.control.afState, android.control.awbState,"
        "android.control.aePrecaptureTrigger, android.control.afTrigger,"
        "android.control.aeRegions, android.control.awbRegions, android.control.afRegions,"
        "android.control.aeExposureCompensation, android.control.aeLock, android.control.awbLock,"
        "android.control.aeAntibandingMode, android.control.aeTargetFpsRange,"
        "android.control.effectMode, android.control.mode, android.control.sceneMode,"
        "android.control.videoStabilizationMode";

void TagMonitor::parseTagsToMonitor(std::string tagNames) {
    std::lock_guard<std::mutex> lock(mMonitorMutex);

    // Expand shorthands
    ssize_t idx = tagNames.find("3a");
    if (idx != -1) {
        ssize_t end = tagNames.find(",", idx);
        tagNames = tagNames.substr(0, idx) + k3aTags;
        if (end != -1) {
            tagNames += tagNames.substr(end);
        }
    }

    sp<VendorTagDescriptor> vTags =
            VendorTagDescriptor::getGlobalVendorTagDescriptor();
    if ((nullptr == vTags.get()) || (0 >= vTags->getTagCount())) {
        sp<VendorTagDescriptorCache> cache =
                VendorTagDescriptorCache::getGlobalVendorTagCache();
        if (cache.get()) {
            cache->getVendorTagDescriptor(mVendorTagId, &vTags);
        }
    }

    bool gotTag = false;

    std::istringstream iss(tagNames);
    std::string nextTagName;
    while (std::getline(iss, nextTagName, ',')) {
        uint32_t tag;
        status_t res = CameraMetadata::getTagFromName(nextTagName.c_str(), vTags.get(), &tag);
        if (res != OK) {
            ALOGW("%s: Unknown tag %s, ignoring", __FUNCTION__, nextTagName.c_str());
        } else {
            if (!gotTag) {
                mMonitoredTagList.clear();
                gotTag = true;
            }
            mMonitoredTagList.push_back(tag);
        }
        iss >> std::ws;
    }

    if (gotTag) {
        // Got at least one new tag
        mMonitoringEnabled = true;
    }
}

void TagMonitor::disableMonitoring() {
    mMonitoringEnabled = false;
    mLastMonitoredRequestValues.clear();
    mLastMonitoredResultValues.clear();
    mLastMonitoredPhysicalRequestKeys.clear();
    mLastMonitoredPhysicalResultKeys.clear();
    mLastStreamIds.clear();
    mLastInputStreamId = -1;
}

void TagMonitor::monitorMetadata(eventSource source, int64_t frameNumber, nsecs_t timestamp,
        const CameraMetadata& metadata,
        const std::unordered_map<std::string, CameraMetadata>& physicalMetadata,
        const camera3::camera_stream_buffer_t *outputBuffers, uint32_t numOutputBuffers,
        int32_t inputStreamId) {
    if (!mMonitoringEnabled) return;

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

    if (timestamp == 0) {
        timestamp = systemTime(SYSTEM_TIME_BOOTTIME);
    }
    std::unordered_set<int32_t> outputStreamIds;
    for (size_t i = 0; i < numOutputBuffers; i++) {
        const camera3::camera_stream_buffer_t *src = outputBuffers + i;
        int32_t streamId = camera3::Camera3Stream::cast(src->stream)->getId();
        outputStreamIds.emplace(streamId);
    }
    std::string emptyId;
    for (auto tag : mMonitoredTagList) {
        monitorSingleMetadata(source, frameNumber, timestamp, emptyId, tag, metadata,
                outputStreamIds, inputStreamId);

        for (auto& m : physicalMetadata) {
            monitorSingleMetadata(source, frameNumber, timestamp, m.first, tag, m.second,
                    outputStreamIds, inputStreamId);
        }
    }
}

void TagMonitor::monitorSingleMetadata(eventSource source, int64_t frameNumber, nsecs_t timestamp,
        const std::string& cameraId, uint32_t tag, const CameraMetadata& metadata,
        const std::unordered_set<int32_t> &outputStreamIds, int32_t inputStreamId) {

    CameraMetadata &lastValues = (source == REQUEST) ?
            (cameraId.empty() ? mLastMonitoredRequestValues :
                    mLastMonitoredPhysicalRequestKeys[cameraId]) :
            (cameraId.empty() ? mLastMonitoredResultValues :
                    mLastMonitoredPhysicalResultKeys[cameraId]);

    camera_metadata_ro_entry entry = metadata.find(tag);
    if (lastValues.isEmpty()) {
        lastValues = CameraMetadata(mMonitoredTagList.size());
        const camera_metadata_t *metaBuffer =
                lastValues.getAndLock();
        set_camera_metadata_vendor_id(
                const_cast<camera_metadata_t *> (metaBuffer), mVendorTagId);
        lastValues.unlock(metaBuffer);
    }

    camera_metadata_entry lastEntry = lastValues.find(tag);

    // Monitor when the stream ids change, this helps visually see what
    // monitored metadata values are for capture requests with different
    // stream ids.
    if (source == REQUEST) {
        if (inputStreamId != mLastInputStreamId) {
            mMonitoringEvents.emplace(source, frameNumber, timestamp, camera_metadata_ro_entry_t{},
                                      cameraId, std::unordered_set<int>(), inputStreamId);
            mLastInputStreamId = inputStreamId;
        }

        if (outputStreamIds != mLastStreamIds) {
            mMonitoringEvents.emplace(source, frameNumber, timestamp, camera_metadata_ro_entry_t{},
                                      cameraId, outputStreamIds, -1);
            mLastStreamIds = outputStreamIds;
        }
    }
    if (entry.count > 0) {
        bool isDifferent = false;
        if (lastEntry.count > 0) {
            // Have a last value, compare to see if changed
            if (lastEntry.type == entry.type &&
                    lastEntry.count == entry.count) {
                // Same type and count, compare values
                size_t bytesPerValue = camera_metadata_type_size[lastEntry.type];
                size_t entryBytes = bytesPerValue * lastEntry.count;
                int cmp = memcmp(entry.data.u8, lastEntry.data.u8, entryBytes);
                if (cmp != 0) {
                    isDifferent = true;
                }
            } else {
                // Count or type has changed
                isDifferent = true;
            }
        } else {
            // No last entry, so always consider to be different
            isDifferent = true;
        }

        if (isDifferent) {
            ALOGV("%s: Tag %s changed", __FUNCTION__,
                  get_local_camera_metadata_tag_name_vendor_id(
                          tag, mVendorTagId));
            lastValues.update(entry);
            mMonitoringEvents.emplace(source, frameNumber, timestamp, entry, cameraId,
                                      std::unordered_set<int>(), -1);
        }
    } else if (lastEntry.count > 0) {
        // Value has been removed
        ALOGV("%s: Tag %s removed", __FUNCTION__,
              get_local_camera_metadata_tag_name_vendor_id(
                      tag, mVendorTagId));
        lastValues.erase(tag);
        entry.tag = tag;
        entry.type = get_local_camera_metadata_tag_type_vendor_id(tag,
                mVendorTagId);
        entry.count = 0;
        mLastInputStreamId = inputStreamId;
        mLastStreamIds = outputStreamIds;
        mMonitoringEvents.emplace(source, frameNumber, timestamp, entry, cameraId,
                                  std::unordered_set<int>(), -1);
    }
}

void TagMonitor::dumpMonitoredMetadata(int fd) {
    std::lock_guard<std::mutex> lock(mMonitorMutex);

    if (mMonitoringEnabled) {
        dprintf(fd, "     Tag monitoring enabled for tags:\n");
        for (uint32_t tag : mMonitoredTagList) {
            dprintf(fd, "        %s.%s\n",
                    get_local_camera_metadata_section_name_vendor_id(tag,
                            mVendorTagId),
                    get_local_camera_metadata_tag_name_vendor_id(tag,
                            mVendorTagId));
        }
    } else {
        dprintf(fd, "     Tag monitoring disabled (enable with -m <name1,..,nameN>)\n");
    }

    if (mMonitoringEvents.size() == 0) { return; }

    dprintf(fd, "     Monitored tag event log:\n");

    std::vector<std::string> eventStrs;
    dumpMonitoredTagEventsToVectorLocked(eventStrs);
    for (const std::string &eventStr : eventStrs) {
        dprintf(fd, "        %s", eventStr.c_str());
    }
}

void TagMonitor::getLatestMonitoredTagEvents(std::vector<std::string> &out) {
    std::lock_guard<std::mutex> lock(mMonitorMutex);
    dumpMonitoredTagEventsToVectorLocked(out);
}

void TagMonitor::dumpMonitoredTagEventsToVectorLocked(std::vector<std::string> &vec) {
    if (mMonitoringEvents.size() == 0) { return; }

    for (const auto& event : mMonitoringEvents) {
        int indentation = (event.source == REQUEST) ? 15 : 30;
        std::string eventString = fmt::sprintf("f%d:%" PRId64 "ns:%*s%*s",
                event.frameNumber, event.timestamp,
                2, event.cameraId.c_str(),
                indentation,
                event.source == REQUEST ? "REQ:" : "RES:");

        if (!event.outputStreamIds.empty()) {
            eventString += " output stream ids:";
            for (const auto& id : event.outputStreamIds) {
                eventString += fmt::sprintf(" %d", id);
            }
            eventString += "\n";
            vec.emplace_back(eventString);
            continue;
        }

        if (event.inputStreamId != -1) {
            eventString += fmt::sprintf(" input stream id: %d\n", event.inputStreamId);
            vec.emplace_back(eventString);
            continue;
        }

        eventString += fmt::sprintf(
                "%s.%s: ",
                get_local_camera_metadata_section_name_vendor_id(event.tag, mVendorTagId),
                get_local_camera_metadata_tag_name_vendor_id(event.tag, mVendorTagId));

        if (event.newData.empty()) {
            eventString += " (Removed)\n";
        } else {
            eventString += getEventDataString(
                    event.newData.data(), event.tag, event.type,
                    event.newData.size() / camera_metadata_type_size[event.type], indentation + 18);
        }
        vec.emplace_back(eventString);
    }
}

#define CAMERA_METADATA_ENUM_STRING_MAX_SIZE 29

std::string TagMonitor::getEventDataString(const uint8_t* data_ptr, uint32_t tag, int type,
        int count, int indentation) {
    static int values_per_line[NUM_TYPES] = {
        [TYPE_BYTE]     = 16,
        [TYPE_INT32]    = 8,
        [TYPE_FLOAT]    = 8,
        [TYPE_INT64]    = 4,
        [TYPE_DOUBLE]   = 4,
        [TYPE_RATIONAL] = 4,
    };

    size_t type_size = camera_metadata_type_size[type];
    char value_string_tmp[CAMERA_METADATA_ENUM_STRING_MAX_SIZE];
    uint32_t value;

    int lines = count / values_per_line[type];
    if (count % values_per_line[type] != 0) lines++;

    std::ostringstream returnStr;
    int index = 0;
    int j, k;
    for (j = 0; j < lines; j++) {
        returnStr << fmt::sprintf("%*s[", (j != 0) ? indentation + 4 : 0, "");
        for (k = 0;
             k < values_per_line[type] && count > 0;
             k++, count--, index += type_size) {

            switch (type) {
                case TYPE_BYTE:
                    value = *(data_ptr + index);
                    if (camera_metadata_enum_snprint(tag,
                                                     value,
                                                     value_string_tmp,
                                                     sizeof(value_string_tmp))
                        == OK) {
                        returnStr << value_string_tmp;
                    } else {
                        returnStr << fmt::sprintf("%hhu ", *(data_ptr + index));
                    }
                    break;
                case TYPE_INT32:
                    value =
                            *(int32_t*)(data_ptr + index);
                    if (camera_metadata_enum_snprint(tag,
                                                     value,
                                                     value_string_tmp,
                                                     sizeof(value_string_tmp))
                        == OK) {
                        returnStr << value_string_tmp;
                    } else {
                        returnStr << fmt::sprintf("%" PRId32 " ", *(int32_t*)(data_ptr + index));
                    }
                    break;
                case TYPE_FLOAT:
                    returnStr << fmt::sprintf("%0.8f ", *(float*)(data_ptr + index));
                    break;
                case TYPE_INT64:
                    returnStr << fmt::sprintf("%" PRId64 " ", *(int64_t*)(data_ptr + index));
                    break;
                case TYPE_DOUBLE:
                    returnStr << fmt::sprintf("%0.8f ", *(double*)(data_ptr + index));
                    break;
                case TYPE_RATIONAL: {
                    int32_t numerator = *(int32_t*)(data_ptr + index);
                    int32_t denominator = *(int32_t*)(data_ptr + index + 4);
                    returnStr << fmt::sprintf("(%d / %d) ", numerator, denominator);
                    break;
                }
                default:
                    returnStr << "??? ";
            }
        }
        returnStr << "]\n";
    }
    return returnStr.str();
}

template<typename T>
TagMonitor::MonitorEvent::MonitorEvent(eventSource src, uint32_t frameNumber, nsecs_t timestamp,
        const T &value, const std::string& cameraId,
        const std::unordered_set<int32_t> &outputStreamIds,
        int32_t inputStreamId) :
        source(src),
        frameNumber(frameNumber),
        timestamp(timestamp),
        cameraId(cameraId),
        tag(value.tag),
        type(value.type),
        newData(value.data.u8, value.data.u8 + camera_metadata_type_size[value.type] * value.count),
        outputStreamIds(outputStreamIds),
        inputStreamId(inputStreamId) {}

TagMonitor::MonitorEvent::~MonitorEvent() {
}

} // namespace android
