/*
 * 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.
 */

#define LOG_TAG "libvintf"
#include <android-base/logging.h>

#include "HalManifest.h"

#include <dirent.h>

#include <mutex>
#include <set>

#include <android-base/strings.h>

#include "parse_string.h"
#include "parse_xml.h"
#include "utils.h"
#include "CompatibilityMatrix.h"

namespace android {
namespace vintf {

using details::Instances;
using details::InstancesOfVersion;
using details::mergeField;

// Check <version> tag for all <hal> with the same name.
bool HalManifest::shouldAdd(const ManifestHal& hal) const {
    if (!hal.isValid()) {
        return false;
    }
    if (hal.isOverride()) {
        return true;
    }
    auto existingHals = mHals.equal_range(hal.name);
    std::set<size_t> existingMajorVersions;
    for (auto it = existingHals.first; it != existingHals.second; ++it) {
        for (const auto& v : it->second.versions) {
            // Assume integrity on existingHals, so no check on emplace().second
            existingMajorVersions.insert(v.majorVer);
        }
    }
    for (const auto& v : hal.versions) {
        if (!existingMajorVersions.emplace(v.majorVer).second /* no insertion */) {
            return false;
        }
    }
    return true;
}

// Remove elements from "list" if p(element) returns true.
template <typename List, typename Predicate>
static void removeIf(List& list, Predicate predicate) {
    for (auto it = list.begin(); it != list.end();) {
        if (predicate(*it)) {
            it = list.erase(it);
        } else {
            ++it;
        }
    }
}

void HalManifest::removeHals(const std::string& name, size_t majorVer) {
    removeIf(mHals, [&name, majorVer](auto& existingHalPair) {
        auto& existingHal = existingHalPair.second;
        if (existingHal.name != name) {
            return false;
        }
        auto& existingVersions = existingHal.versions;
        removeIf(existingVersions, [majorVer](const auto& existingVersion) {
            return existingVersion.majorVer == majorVer;
        });
        return existingVersions.empty();
    });
}

bool HalManifest::add(ManifestHal&& halToAdd) {
    if (halToAdd.isOverride()) {
        if (halToAdd.isDisabledHal()) {
            // Special syntax when there are no instances at all. Remove all existing HALs
            // with the given name.
            mHals.erase(halToAdd.name);
        }
        // If there are <version> tags, remove all existing major versions that causes a conflict.
        for (const Version& versionToAdd : halToAdd.versions) {
            removeHals(halToAdd.name, versionToAdd.majorVer);
        }
    }

    return HalGroup::add(std::move(halToAdd));
}

bool HalManifest::shouldAddXmlFile(const ManifestXmlFile& xmlFile) const {
    auto existingXmlFiles = getXmlFiles(xmlFile.name());
    for (auto it = existingXmlFiles.first; it != existingXmlFiles.second; ++it) {
        if (xmlFile.version() == it->second.version()) {
            return false;
        }
    }
    return true;
}

std::set<std::string> HalManifest::getHalNames() const {
    std::set<std::string> names{};
    for (const auto &hal : mHals) {
        names.insert(hal.first);
    }
    return names;
}

std::set<std::string> HalManifest::getHalNamesAndVersions() const {
    std::set<std::string> names{};
    forEachInstance([&names](const ManifestInstance& e) {
        names.insert(toFQNameString(e.package(), e.version()));
        return true;
    });
    return names;
}

Transport HalManifest::getTransport(const std::string &package, const Version &v,
            const std::string &interfaceName, const std::string &instanceName) const {
    Transport transport{Transport::EMPTY};
    forEachInstanceOfInterface(package, v, interfaceName, [&](const auto& e) {
        if (e.instance() == instanceName) {
            transport = e.transport();
        }
        return transport == Transport::EMPTY;  // if not found, continue
    });
    if (transport == Transport::EMPTY) {
        LOG(DEBUG) << "HalManifest::getTransport(" << mType << "): Cannot find "
                   << toFQNameString(package, v, interfaceName, instanceName);
    }
    return transport;
}

bool HalManifest::forEachInstanceOfVersion(
    const std::string& package, const Version& expectVersion,
    const std::function<bool(const ManifestInstance&)>& func) const {
    for (const ManifestHal* hal : getHals(package)) {
        bool cont = hal->forEachInstance([&](const ManifestInstance& manifestInstance) {
            if (manifestInstance.version().minorAtLeast(expectVersion)) {
                return func(manifestInstance);
            }
            return true;
        });
        if (!cont) return false;
    }
    return true;
}

// indent = 2, {"foo"} => "foo"
// indent = 2, {"foo", "bar"} => "\n  foo\n  bar";
template <typename Container>
void multilineIndent(std::ostream& os, size_t indent, const Container& lines) {
    if (lines.size() == 1) {
        os << *lines.begin();
        return;
    }
    for (const auto& line : lines) {
        os << "\n";
        for (size_t i = 0; i < indent; ++i) os << " ";
        os << line;
    }
}

// For each hal in mat, there must be a hal in manifest that supports this.
std::vector<std::string> HalManifest::checkIncompatibleHals(const CompatibilityMatrix& mat) const {
    std::vector<std::string> ret;
    for (const MatrixHal &matrixHal : mat.getHals()) {
        if (matrixHal.optional) {
            continue;
        }

        std::set<FqInstance> manifestInstances;
        std::set<FqInstance> manifestInstancesNoPackage;
        std::set<Version> versions;
        for (const ManifestHal* manifestHal : getHals(matrixHal.name)) {
            manifestHal->forEachInstance([&](const auto& manifestInstance) {
                manifestInstances.insert(manifestInstance.getFqInstance());
                manifestInstancesNoPackage.insert(manifestInstance.getFqInstanceNoPackage());
                return true;
            });
            manifestHal->appendAllVersions(&versions);
        }

        if (!matrixHal.isCompatible(manifestInstances, versions)) {
            std::ostringstream oss;
            oss << matrixHal.name << ":\n    required: ";
            multilineIndent(oss, 8, android::vintf::expandInstances(matrixHal));
            oss << "\n    provided: ";
            if (manifestInstances.empty()) {
                multilineIndent(oss, 8, versions);
            } else {
                multilineIndent(oss, 8, manifestInstancesNoPackage);
            }

            ret.insert(ret.end(), oss.str());
        }
    }
    return ret;
}

std::set<std::string> HalManifest::checkUnusedHals(const CompatibilityMatrix& mat) const {
    std::set<std::string> ret;

    forEachInstance([&ret, &mat](const auto& manifestInstance) {
        const auto& fqInstance = manifestInstance.getFqInstance();
        if (!mat.matchInstance(fqInstance.getPackage(), fqInstance.getVersion(),
                               fqInstance.getInterface(), fqInstance.getInstance())) {
            ret.insert(fqInstance.string());
        }
        return true;
    });

    return ret;
}

static bool checkVendorNdkCompatibility(const VendorNdk& matVendorNdk,
                                        const std::vector<VendorNdk>& manifestVendorNdk,
                                        std::string* error) {
    // For pre-P vendor images, device compatibility matrix does not specify <vendor-ndk>
    // tag. Ignore the check for these devices.
    if (matVendorNdk.version().empty()) {
        return true;
    }
    for (const auto& vndk : manifestVendorNdk) {
        if (vndk.version() != matVendorNdk.version()) {
            continue;
        }
        // version matches, check libraries
        std::vector<std::string> diff;
        std::set_difference(matVendorNdk.libraries().begin(), matVendorNdk.libraries().end(),
                            vndk.libraries().begin(), vndk.libraries().end(),
                            std::inserter(diff, diff.begin()));
        if (!diff.empty()) {
            if (error != nullptr) {
                *error = "Vndk libs incompatible for version " + matVendorNdk.version() +
                         ". These libs are not in framework manifest:";
                for (const auto& name : diff) {
                    *error += " " + name;
                }
            }
            return false;
        }
        return true;
    }

    // no match is found.
    if (error != nullptr) {
        *error = "Vndk version " + matVendorNdk.version() + " is not supported. " +
                 "Supported versions in framework manifest are:";
        for (const auto& vndk : manifestVendorNdk) {
            *error += " " + vndk.version();
        }
    }
    return false;
}

static bool checkSystemSdkCompatibility(const SystemSdk& matSystemSdk,
                                        const SystemSdk& manifestSystemSdk, std::string* error) {
    SystemSdk notSupported = matSystemSdk.removeVersions(manifestSystemSdk);
    if (!notSupported.empty()) {
        if (error) {
            *error =
                "The following System SDK versions are required by device "
                "compatibility matrix but not supported by the framework manifest: [" +
                base::Join(notSupported.versions(), ", ") + "]. Supported versions are: [" +
                base::Join(manifestSystemSdk.versions(), ", ") + "].";
        }
        return false;
    }
    return true;
}

bool HalManifest::checkCompatibility(const CompatibilityMatrix &mat, std::string *error) const {
    if (mType == mat.mType) {
        if (error != nullptr) {
            *error = "Wrong type; checking " + to_string(mType) + " manifest against "
                    + to_string(mat.mType) + " compatibility matrix";
        }
        return false;
    }
    auto incompatibleHals = checkIncompatibleHals(mat);
    if (!incompatibleHals.empty()) {
        if (error != nullptr) {
            *error = "HALs incompatible.";
            if (mat.level() != Level::UNSPECIFIED)
                *error += " Matrix level = " + to_string(mat.level()) + ".";
            if (level() != Level::UNSPECIFIED)
                *error += " Manifest level = " + to_string(level()) + ".";
            *error += " The following requirements are not met:\n";
            for (const auto& e : incompatibleHals) {
                *error += e + "\n";
            }
        }
        return false;
    }
    if (mType == SchemaType::FRAMEWORK) {
        if (!checkVendorNdkCompatibility(mat.device.mVendorNdk, framework.mVendorNdks, error)) {
            return false;
        }

        if (!checkSystemSdkCompatibility(mat.device.mSystemSdk, framework.mSystemSdk, error)) {
            return false;
        }
    } else if (mType == SchemaType::DEVICE) {
        bool sepolicyMatch = false;
        for (const auto &range : mat.framework.mSepolicy.sepolicyVersions()) {
            if (range.supportedBy(device.mSepolicyVersion)) {
                sepolicyMatch = true;
                break;
            }
        }
        if (!sepolicyMatch) {
            if (error != nullptr) {
                *error = "Sepolicy version " + to_string(device.mSepolicyVersion)
                        + " doesn't satisify the requirements.";
            }
            return false;
        }

        if (!!kernel() && !kernel()->matchKernelRequirements(mat.framework.mKernels, error)) {
            return false;
        }
    }

    return true;
}

CompatibilityMatrix HalManifest::generateCompatibleMatrix() const {
    CompatibilityMatrix matrix;

    forEachInstance([&matrix](const ManifestInstance& e) {
        matrix.add(MatrixHal{
            .format = e.format(),
            .name = e.package(),
            .optional = true,
            .versionRanges = {VersionRange{e.version().majorVer, e.version().minorVer}},
            .interfaces = {{e.interface(), HalInterface{e.interface(), {e.instance()}}}}});
        return true;
    });
    if (mType == SchemaType::FRAMEWORK) {
        matrix.mType = SchemaType::DEVICE;
        // VNDK does not need to be added for compatibility
    } else if (mType == SchemaType::DEVICE) {
        matrix.mType = SchemaType::FRAMEWORK;
        matrix.framework.mSepolicy = Sepolicy(0u /* kernelSepolicyVersion */,
                {{device.mSepolicyVersion.majorVer, device.mSepolicyVersion.minorVer}});
    }

    return matrix;
}

status_t HalManifest::fetchAllInformation(const FileSystem* fileSystem, const std::string& path,
                                          std::string* error) {
    return details::fetchAllInformation(fileSystem, path, gHalManifestConverter, this, error);
}

SchemaType HalManifest::type() const {
    return mType;
}

void HalManifest::setType(SchemaType type) {
    mType = type;
}

Level HalManifest::level() const {
    return mLevel;
}

Version HalManifest::getMetaVersion() const {
    return mMetaVersion;
}

const Version &HalManifest::sepolicyVersion() const {
    CHECK(mType == SchemaType::DEVICE);
    return device.mSepolicyVersion;
}

const std::vector<VendorNdk>& HalManifest::vendorNdks() const {
    CHECK(mType == SchemaType::FRAMEWORK);
    return framework.mVendorNdks;
}

std::string HalManifest::getXmlFilePath(const std::string& xmlFileName,
                                        const Version& version) const {
    using std::literals::string_literals::operator""s;
    auto range = getXmlFiles(xmlFileName);
    for (auto it = range.first; it != range.second; ++it) {
        const ManifestXmlFile& manifestXmlFile = it->second;
        if (manifestXmlFile.version() == version) {
            if (!manifestXmlFile.overriddenPath().empty()) {
                return manifestXmlFile.overriddenPath();
            }
            return "/"s + (type() == SchemaType::DEVICE ? "vendor" : "system") + "/etc/" +
                   xmlFileName + "_V" + std::to_string(version.majorVer) + "_" +
                   std::to_string(version.minorVer) + ".xml";
        }
    }
    return "";
}

bool operator==(const HalManifest &lft, const HalManifest &rgt) {
    return lft.mType == rgt.mType && lft.mLevel == rgt.mLevel && lft.mHals == rgt.mHals &&
           lft.mXmlFiles == rgt.mXmlFiles &&
           (lft.mType != SchemaType::DEVICE ||
            (lft.device.mSepolicyVersion == rgt.device.mSepolicyVersion &&
             lft.device.mKernel == rgt.device.mKernel)) &&
           (lft.mType != SchemaType::FRAMEWORK ||
            (
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
                lft.framework.mVndks == rgt.framework.mVndks &&
#pragma clang diagnostic pop
                lft.framework.mVendorNdks == rgt.framework.mVendorNdks &&
                lft.framework.mSystemSdk == rgt.framework.mSystemSdk));
}

// Alternative to forEachInstance if you just need a set of instance names instead.
std::set<std::string> HalManifest::getInstances(const std::string& halName, const Version& version,
                                                const std::string& interfaceName) const {
    std::set<std::string> ret;
    (void)forEachInstanceOfInterface(halName, version, interfaceName, [&ret](const auto& e) {
        ret.insert(e.instance());
        return true;
    });
    return ret;
}

// Return whether instance is in getInstances(...).
bool HalManifest::hasInstance(const std::string& halName, const Version& version,
                              const std::string& interfaceName, const std::string& instance) const {
    bool found = false;
    (void)forEachInstanceOfInterface(halName, version, interfaceName,
                                     [&found, &instance](const auto& e) {
                                         found |= (instance == e.instance());
                                         return !found;  // if not found, continue
                                     });
    return found;
}

bool HalManifest::insertInstance(const FqInstance& fqInstance, Transport transport, Arch arch,
                                 HalFormat format, std::string* error) {
    for (ManifestHal& hal : getHals()) {
        if (hal.name == fqInstance.getPackage() && hal.format == format &&
            hal.transport() == transport && hal.arch() == arch) {
            return hal.insertInstance(fqInstance, error);
        }
    }

    ManifestHal hal;
    hal.name = fqInstance.getPackage();
    hal.format = format;
    hal.transportArch = TransportArch(transport, arch);
    if (!hal.insertInstance(fqInstance, error)) return false;
    return add(std::move(hal));
}

bool HalManifest::empty() const {
    HalManifest emptyManifest;
    emptyManifest.setType(type());
    return (*this) == emptyManifest;
}

const std::optional<KernelInfo>& HalManifest::kernel() const {
    return device.mKernel;
}

bool HalManifest::addAll(HalManifest* other, std::string* error) {
    if (other->mMetaVersion.majorVer != mMetaVersion.majorVer) {
        if (error) {
            *error = "Cannot merge manifest version " + to_string(mMetaVersion) + " and " +
                     to_string(other->mMetaVersion);
        }
        return false;
    }
    mMetaVersion.minorVer = std::max(mMetaVersion.minorVer, other->mMetaVersion.minorVer);

    if (type() != other->type()) {
        if (error) {
            *error = "Cannot add a " + to_string(other->type()) + " manifest to a " +
                     to_string(type()) + " manifest";
        }
        return false;
    }

    if (!addAllHals(other, error)) {
        return false;
    }

    if (!addAllXmlFiles(other, error)) {
        return false;
    }

    if (!mergeField(&mLevel, &other->mLevel, Level::UNSPECIFIED)) {
        if (error) {
            *error = "Conflicting target-level: " + to_string(level()) + " vs. " +
                     to_string(other->level());
        }
        return false;
    }

    if (type() == SchemaType::DEVICE) {
        if (!mergeField(&device.mSepolicyVersion, &other->device.mSepolicyVersion)) {
            if (error) {
                *error = "Conflicting sepolicy version: " + to_string(sepolicyVersion()) + " vs. " +
                         to_string(other->sepolicyVersion());
            }
            return false;
        }

        if (!mergeField(&device.mKernel, &other->device.mKernel)) {
            // If fails, both have values.
            if (error) {
                *error = "Conflicting kernel: " + to_string(device.mKernel->version()) + " vs. " +
                         to_string(other->device.mKernel->version());
            }
            return false;
        }
    } else if (type() == SchemaType::FRAMEWORK) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        framework.mVndks.insert(framework.mVndks.end(), other->framework.mVndks.begin(),
                                other->framework.mVndks.end());
        other->framework.mVndks.clear();
#pragma clang diagnostic pop

        framework.mVendorNdks.insert(framework.mVendorNdks.end(),
                                     other->framework.mVendorNdks.begin(),
                                     other->framework.mVendorNdks.end());
        other->framework.mVendorNdks.clear();

        framework.mSystemSdk.addAll(&other->framework.mSystemSdk);
    } else {
        LOG(FATAL) << "unknown SchemaType: "
                   << static_cast<std::underlying_type_t<SchemaType>>(type());
    }

    if (!other->empty()) {
        if (error) {
            *error =
                "Cannot add another manifest because it contains extraneous entries that "
                "are not recognized.";
        }
        return false;
    }

    return true;
}

} // namespace vintf
} // namespace android