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


#ifndef ANDROID_VINTF_HAL_MANIFEST_H
#define ANDROID_VINTF_HAL_MANIFEST_H

#include <utils/Errors.h>
#include <map>
#include <optional>
#include <string>
#include <vector>

#include <hidl/metadata.h>

#include "CheckFlags.h"
#include "FileSystem.h"
#include "HalGroup.h"
#include "KernelInfo.h"
#include "Level.h"
#include "ManifestHal.h"
#include "ManifestInstance.h"
#include "MapValueIterator.h"
#include "SchemaType.h"
#include "SystemSdk.h"
#include "VendorNdk.h"
#include "Version.h"
#include "Vndk.h"
#include "WithFileName.h"
#include "XmlFileGroup.h"
#include "constants.h"

namespace android {
namespace vintf {

struct MatrixHal;
struct CompatibilityMatrix;

namespace details {
using InstancesOfVersion =
    std::map<std::string /* interface */, std::set<std::string /* instance */>>;
using Instances = std::map<Version, InstancesOfVersion>;

class CheckVintfUtils;
class FmOnlyVintfObject;

}  // namespace details

// A HalManifest is reported by the hardware and query-able from
// framework code. This is the API for the framework.
struct HalManifest : public HalGroup<ManifestHal>,
                     public XmlFileGroup<ManifestXmlFile>,
                     public WithFileName {
   public:

    // Construct a device HAL manifest.
    HalManifest() : mType(SchemaType::DEVICE) {}

    bool add(ManifestHal&& hal, std::string* error = nullptr);
    // Move all hals from another HalManifest to this.
    bool addAllHals(HalManifest* other, std::string* error = nullptr);

    // Given a component name (e.g. "android.hardware.camera"),
    // return getHal(name)->transport if the component exist and v exactly matches
    // one of the versions in that component, else EMPTY
    Transport getHidlTransport(const std::string& name, const Version& v,
                               const std::string& interfaceName,
                               const std::string& instanceName) const;

    // Check compatibility against a compatibility matrix. Considered compatible if
    // - framework manifest vs. device compat-mat
    //     - checkIncompatibility for HALs returns only optional HALs
    //     - one of manifest.vndk match compat-mat.vndk
    // - device manifest vs. framework compat-mat
    //     - checkIncompatibility for HALs returns only optional HALs
    //     - manifest.sepolicy.version match one of compat-mat.sepolicy.sepolicy-version
    bool checkCompatibility(const CompatibilityMatrix& mat, std::string* error = nullptr,
                            CheckFlags::Type flags = CheckFlags::DEFAULT) const;

    // Generate a compatibility matrix such that checkCompatibility will return true.
    CompatibilityMatrix generateCompatibleMatrix(bool optional = true) const;

    // Returns all component names.
    std::set<std::string> getHalNames() const;

    // Returns all component names and versions, e.g.
    // "android.hardware.camera.device@1.0", "android.hardware.camera.device@3.2",
    // "android.hardware.nfc@1.0"]
    std::set<std::string> getHalNamesAndVersions() const;

    // Type of the manifest. FRAMEWORK or DEVICE.
    SchemaType type() const;
    void setType(SchemaType type);

    // FCM version that it implements.
    Level level() const;

    // device.mSepolicyVersion. Assume type == device.
    // Abort if type != device.
    const SepolicyVersion& sepolicyVersion() const;

    // framework.mVendorNdks. Assume type == framework.
    // Abort if type != framework.
    const std::vector<VendorNdk>& vendorNdks() const;

    // If the corresponding <xmlfile> with the given version exists,
    // - Return the overridden <path> if it is present,
    // - otherwise the default value: /{system,vendor}/etc/<name>_V<major>_<minor>.xml
    // Otherwise if the <xmlfile> entry does not exist, "" is returned.
    std::string getXmlFilePath(const std::string& xmlFileName, const Version& version) const;

    // Alternative to forEachInstance if you just need a set of instance names instead.
    std::set<std::string> getHidlInstances(const std::string& package, const Version& version,
                                           const std::string& interfaceName) const;
    std::set<std::string> getAidlInstances(const std::string& package, size_t version,
                                           const std::string& interfaceName) const;
    std::set<std::string> getAidlInstances(const std::string& package,
                                           const std::string& interfaceName) const;
    std::set<std::string> getNativeInstances(const std::string& package) const;

    // Return whether instance is in getHidlInstances(...).
    bool hasHidlInstance(const std::string& package, const Version& version,
                         const std::string& interfaceName, const std::string& instance) const;

    // Return whether a given AIDL instance is in this manifest with version >= the given version.
    bool hasAidlInstance(const std::string& package, size_t version,
                         const std::string& interfaceName, const std::string& instance) const;

    // Return whether a given AIDL instance is in this manifest with any version.
    bool hasAidlInstance(const std::string& package, const std::string& interfaceName,
                         const std::string& instance) const;

    // Return whether a given native instance is in getNativeInstances(...).
    bool hasNativeInstance(const std::string& package, const std::string& instance) const;

    // Insert the given instance. After inserting it, the instance will be available via
    // forEachInstance* functions. This modifies the manifest.
    // Return whether this operation is successful.
    bool insertInstance(const FqInstance& fqInstance, Transport transport, Arch arch, HalFormat fmt,
                        std::string* error = nullptr);

    // Add everything from another manifest. If no errors (return true), it is guaranteed
    // that other->empty() == true after execution.
    [[nodiscard]] bool addAll(HalManifest* other, std::string* error = nullptr);

   protected:
    // Check before add()
    bool shouldAdd(const ManifestHal& toAdd, std::string* error) const;
    bool shouldAddXmlFile(const ManifestXmlFile& toAdd) const override;

    bool forEachInstanceOfVersion(
        HalFormat format, const std::string& package, const Version& expectVersion,
        const std::function<bool(const ManifestInstance&)>& func) const override;

    bool forEachNativeInstance(const std::string& package,
                               const std::function<bool(const ManifestInstance&)>& func) const;

   private:
    friend struct HalManifestConverter;
    friend class VintfObject;
    friend class AssembleVintfImpl;
    friend class details::CheckVintfUtils;
    friend struct LibVintfTest;
    friend class details::FmOnlyVintfObject;
    friend std::string dump(const HalManifest &vm);
    friend bool operator==(const HalManifest &lft, const HalManifest &rgt);

    status_t fetchAllInformation(const FileSystem* fileSystem, const std::string& path,
                                 std::string* error = nullptr);

    details::Instances expandInstances(const std::string& name) const;
    // Check if all instances in matrixHal is supported in this manifest.
    bool isCompatible(const details::Instances& instances, const MatrixHal& matrixHal) const;

    // Return a list of error messages (for each <hal> name) that does NOT conform to
    // the given compatibility matrix. It does not contain components that are optional.
    // That is, return empty list iff
    // (instance in matrix) => (instance in manifest).
    std::vector<std::string> checkIncompatibleHals(const CompatibilityMatrix& mat) const;

    void removeHals(const std::string& name, size_t majorVer);

    // Returns a list of instance names that are in this manifest but
    // are not specified in the given matrix, whether the HAL is specified as an optional or
    // required HAL.
    // That is, return empty list iff
    // (instance in manifest) => (instance in matrix).
    std::set<std::string> checkUnusedHals(
        const CompatibilityMatrix& mat,
        const std::vector<HidlInterfaceMetadata>& hidlMetadata) const;

    // Check that manifest has no entries.
    bool empty() const;

    // Alternative to forEachInstance if you just need a set of instance names instead.
    std::set<std::string> getInstances(HalFormat format, const std::string& package,
                                       const Version& version,
                                       const std::string& interfaceName) const;

    // Return whether instance is in getInstances(...).
    bool hasInstance(HalFormat format, const std::string& package, const Version& version,
                     const std::string& interfaceName, const std::string& instance) const;

    // Get the <kernel> tag. Assumes type() == DEVICE.
    // - On host, <kernel> tag only exists for the fully assembled HAL manifest.
    // - On device, this only contain information about level(). Other information should be
    //   looked up via RuntimeInfo.
    const std::optional<KernelInfo>& kernel() const;

    // Merge information of other to this.
    bool mergeKernel(std::optional<KernelInfo>* other, std::string* error = nullptr);

    // Whether the manifest contains information about the kernel for compatibility checks.
    // True if kernel()->checkCompatibility can be called.
    bool shouldCheckKernelCompatibility() const;

    // Helper for shouldAdd(). Check if |hal| has a conflicting major version with this. Return
    // false if hal should not be added, and set |error| accordingly. Return true if check passes.
    bool addingConflictingMajorVersion(const ManifestHal& hal, std::string* error) const;

    // Helper for shouldAdd(). Check if |hal| has a conflicting major version in <fqname> with this.
    // Return false if hal should not be added, and set |error| accordingly. Return true if check
    // passes.
    bool addingConflictingFqInstance(const ManifestHal& hal, std::string* error) const;

    // Inferred kernel level.
    Level inferredKernelLevel() const;

    SchemaType mType;
    Level mLevel = Level::UNSPECIFIED;

    // The metaversion on the source file if the HAL manifest is parsed from an XML file,
    // Otherwise, the object is created programmatically, so default to libvintf meta version.
    Version mSourceMetaVersion = kMetaVersion;

    // entries for device hal manifest only
    struct {
        SepolicyVersion mSepolicyVersion;
        std::optional<KernelInfo> mKernel;
    } device;

    // entries for framework hal manifest only
    struct {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        std::vector<Vndk> mVndks;
#pragma clang diagnostic pop

        std::vector<VendorNdk> mVendorNdks;
        SystemSdk mSystemSdk;
    } framework;
};

} // namespace vintf
} // namespace android

#endif // ANDROID_VINTF_HAL_MANIFEST_H
