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

#include "AST.h"
#include "Coordinator.h"
#include "Interface.h"
#include "Scope.h"

#include <android-base/logging.h>
#include <hidl-hash/Hash.h>
#include <hidl-util/FQName.h>
#include <hidl-util/Formatter.h>
#include <hidl-util/StringHelper.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include <vector>

using namespace android;

enum class OutputMode {
    NEEDS_DIR,   // -o output option expects a directory
    NEEDS_FILE,  // -o output option expects a file
    NEEDS_SRC,   // for changes inside the source tree itself
    NOT_NEEDED   // does not create files
};

enum class GenerationGranularity {
    PER_PACKAGE,  // Files generated for each package
    PER_FILE,     // Files generated for each hal file
    PER_TYPE,     // Files generated for each hal file + each type in HAL files
};

// Represents a file that is generated by an -L option for an FQName
struct FileGenerator {
    using ShouldGenerateFunction = std::function<bool(const FQName& fqName)>;
    using FileNameForFQName = std::function<std::string(const FQName& fqName)>;
    using GetFormatter = std::function<Formatter(void)>;
    using GenerationFunction =
            std::function<status_t(const FQName& fqName, const Coordinator* coordinator,
                                   const GetFormatter& getFormatter)>;

    ShouldGenerateFunction mShouldGenerateForFqName;  // If generate function applies to this target
    FileNameForFQName mFileNameForFqName;             // Target -> filename
    GenerationFunction mGenerationFunction;           // Function to generate output for file

    std::string getFileName(const FQName& fqName) const {
        return mFileNameForFqName ? mFileNameForFqName(fqName) : "";
    }

    status_t getOutputFile(const FQName& fqName, const Coordinator* coordinator,
                           Coordinator::Location location, std::string* file) const {
        if (!mShouldGenerateForFqName(fqName)) {
            return OK;
        }

        return coordinator->getFilepath(fqName, location, getFileName(fqName), file);
    }

    status_t appendOutputFiles(const FQName& fqName, const Coordinator* coordinator,
                               Coordinator::Location location,
                               std::vector<std::string>* outputFiles) const {
        if (location == Coordinator::Location::STANDARD_OUT) {
            return OK;
        }

        if (mShouldGenerateForFqName(fqName)) {
            std::string fileName;
            status_t err = getOutputFile(fqName, coordinator, location, &fileName);
            if (err != OK) return err;

            if (!fileName.empty()) {
                outputFiles->push_back(fileName);
            }
        }
        return OK;
    }

    status_t generate(const FQName& fqName, const Coordinator* coordinator,
                      Coordinator::Location location) const {
        CHECK(mShouldGenerateForFqName != nullptr);
        CHECK(mGenerationFunction != nullptr);

        if (!mShouldGenerateForFqName(fqName)) {
            return OK;
        }

        return mGenerationFunction(fqName, coordinator, [&] {
            return coordinator->getFormatter(fqName, location, getFileName(fqName));
        });
    }

    // Helper methods for filling out this struct
    static bool generateForTypes(const FQName& fqName) {
        const auto names = fqName.names();
        return names.size() > 0 && names[0] == "types";
    }
    static bool generateForInterfaces(const FQName& fqName) { return !generateForTypes(fqName); }
    static bool alwaysGenerate(const FQName&) { return true; }
};

// Represents a -L option, takes a fqName and generates files
struct OutputHandler {
    using ValidationFunction = std::function<bool(
        const FQName& fqName, const Coordinator* coordinator, const std::string& language)>;

    std::string mKey;                 // -L in Android.bp
    std::string mDescription;         // for display in help menu
    OutputMode mOutputMode;           // how this option interacts with -o
    Coordinator::Location mLocation;  // how to compute location relative to the output directory
    GenerationGranularity mGenerationGranularity;   // what to run generate function on
    ValidationFunction mValidate;                   // if a given fqName is allowed for this option
    std::vector<FileGenerator> mGenerateFunctions;  // run for each target at this granularity

    const std::string& name() const { return mKey; }
    const std::string& description() const { return mDescription; }

    status_t generate(const FQName& fqName, const Coordinator* coordinator) const;
    status_t validate(const FQName& fqName, const Coordinator* coordinator,
                      const std::string& language) const {
        return mValidate(fqName, coordinator, language);
    }

    status_t writeDepFile(const FQName& fqName, const Coordinator* coordinator) const;

   private:
    status_t appendTargets(const FQName& fqName, const Coordinator* coordinator,
                           std::vector<FQName>* targets) const;
    status_t appendOutputFiles(const FQName& fqName, const Coordinator* coordinator,
                               std::vector<std::string>* outputFiles) const;
};

// Helper method for GenerationGranularity::PER_TYPE
// IFoo -> IFoo, types.hal (containing Bar, Baz) -> types.Bar, types.Baz
static status_t appendPerTypeTargets(const FQName& fqName, const Coordinator* coordinator,
                                     std::vector<FQName>* exportedPackageInterfaces) {
    CHECK(fqName.isFullyQualified());
    if (fqName.name() != "types") {
        exportedPackageInterfaces->push_back(fqName);
        return OK;
    }

    AST* typesAST = coordinator->parse(fqName);
    if (typesAST == nullptr) {
        fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
        return UNKNOWN_ERROR;
    }

    std::vector<NamedType*> rootTypes = typesAST->getRootScope().getSubTypes();
    for (const NamedType* rootType : rootTypes) {
        if (rootType->isTypeDef()) continue;

        FQName rootTypeName(fqName.package(), fqName.version(), "types." + rootType->definedName());
        exportedPackageInterfaces->push_back(rootTypeName);
    }
    return OK;
}

status_t OutputHandler::appendTargets(const FQName& fqName, const Coordinator* coordinator,
                                      std::vector<FQName>* targets) const {
    switch (mGenerationGranularity) {
        case GenerationGranularity::PER_PACKAGE: {
            targets->push_back(fqName.getPackageAndVersion());
        } break;
        case GenerationGranularity::PER_FILE: {
            if (fqName.isFullyQualified()) {
                targets->push_back(fqName);
                break;
            }
            status_t err = coordinator->appendPackageInterfacesToVector(fqName, targets);
            if (err != OK) return err;
        } break;
        case GenerationGranularity::PER_TYPE: {
            if (fqName.isFullyQualified()) {
                status_t err = appendPerTypeTargets(fqName, coordinator, targets);
                if (err != OK) return err;
                break;
            }

            std::vector<FQName> packageInterfaces;
            status_t err = coordinator->appendPackageInterfacesToVector(fqName, &packageInterfaces);
            if (err != OK) return err;
            for (const FQName& packageInterface : packageInterfaces) {
                err = appendPerTypeTargets(packageInterface, coordinator, targets);
                if (err != OK) return err;
            }
        } break;
        default:
            CHECK(!"Should be here");
    }

    return OK;
}

status_t OutputHandler::generate(const FQName& fqName, const Coordinator* coordinator) const {
    std::vector<FQName> targets;
    status_t err = appendTargets(fqName, coordinator, &targets);
    if (err != OK) return err;

    for (const FQName& fqName : targets) {
        for (const FileGenerator& file : mGenerateFunctions) {
            status_t err = file.generate(fqName, coordinator, mLocation);
            if (err != OK) return err;
        }
    }

    return OK;
}

status_t OutputHandler::appendOutputFiles(const FQName& fqName, const Coordinator* coordinator,
                                          std::vector<std::string>* outputFiles) const {
    std::vector<FQName> targets;
    status_t err = appendTargets(fqName, coordinator, &targets);
    if (err != OK) return err;

    for (const FQName& fqName : targets) {
        for (const FileGenerator& file : mGenerateFunctions) {
            err = file.appendOutputFiles(fqName, coordinator, mLocation, outputFiles);
            if (err != OK) return err;
        }
    }

    return OK;
}

status_t OutputHandler::writeDepFile(const FQName& fqName, const Coordinator* coordinator) const {
    std::vector<std::string> outputFiles;
    status_t err = appendOutputFiles(fqName, coordinator, &outputFiles);
    if (err != OK) return err;

    // No need for dep files
    if (outputFiles.empty()) {
        return OK;
    }

    // Depfiles in Android for genrules should be for the 'main file'. Because hidl-gen doesn't have
    // a main file for most targets, we are just outputting a depfile for one single file only.
    const std::string forFile = outputFiles[0];

    return coordinator->writeDepFile(forFile);
}

// Use an AST function as a OutputHandler GenerationFunction
static FileGenerator::GenerationFunction astGenerationFunction(void (AST::*generate)(Formatter&)
                                                                   const = nullptr) {
    return [generate](const FQName& fqName, const Coordinator* coordinator,
                      const FileGenerator::GetFormatter& getFormatter) -> status_t {
        AST* ast = coordinator->parse(fqName);
        if (ast == nullptr) {
            fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
            return UNKNOWN_ERROR;
        }

        if (generate == nullptr) return OK;  // just parsing AST

        Formatter out = getFormatter();
        if (!out.isValid()) {
            return UNKNOWN_ERROR;
        }

        (ast->*generate)(out);

        return OK;
    };
}

// Common pattern: single file for package or standard out
static FileGenerator singleFileGenerator(
    const std::string& fileName, const FileGenerator::GenerationFunction& generationFunction) {
    return {
        FileGenerator::alwaysGenerate, [fileName](const FQName&) { return fileName; },
        generationFunction,
    };
}

static status_t generateJavaForPackage(const FQName& fqName, const Coordinator* coordinator,
                                       const FileGenerator::GetFormatter& getFormatter) {
    AST* ast;
    std::string limitToType;
    FQName typeName;

    // See appendPerTypeTargets.
    // 'a.b.c@1.0::types.Foo' is used to compile 'Foo' for Java even though in
    // the rest of the compiler, this type is simply called 'a.b.c@1.0::Foo'.
    // However, here, we need to disambiguate an interface name and a type in
    // types.hal in order to figure out what to parse, so this legacy behavior
    // is kept.
    if (fqName.name().find("types.") == 0) {
        limitToType = fqName.name().substr(strlen("types."));

        ast = coordinator->parse(fqName.getTypesForPackage());

        const auto& names = fqName.names();
        CHECK(names.size() == 2 && names[0] == "types") << fqName.string();
        typeName = FQName(fqName.package(), fqName.version(), names[1]);
    } else {
        ast = coordinator->parse(fqName);
        typeName = fqName;
    }
    if (ast == nullptr) {
        fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
        return UNKNOWN_ERROR;
    }

    Type* type = ast->lookupType(typeName, &ast->getRootScope());
    CHECK(type != nullptr) << typeName.string();
    if (!type->isJavaCompatible()) {
        return OK;
    }

    Formatter out = getFormatter();
    if (!out.isValid()) {
        return UNKNOWN_ERROR;
    }

    ast->generateJava(out, limitToType);
    return OK;
};

static status_t dumpDefinedButUnreferencedTypeNames(const FQName& packageFQName,
                                                    const Coordinator* coordinator) {
    std::vector<FQName> packageInterfaces;
    status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces);
    if (err != OK) return err;

    std::set<FQName> unreferencedDefinitions;
    std::set<FQName> unreferencedImports;
    err = coordinator->addUnreferencedTypes(packageInterfaces, &unreferencedDefinitions,
                                            &unreferencedImports);
    if (err != OK) return err;

    for (const auto& fqName : unreferencedDefinitions) {
        std::cerr
            << "VERBOSE: DEFINED-BUT-NOT-REFERENCED "
            << fqName.string()
            << std::endl;
    }

    for (const auto& fqName : unreferencedImports) {
        std::cerr
            << "VERBOSE: IMPORTED-BUT-NOT-REFERENCED "
            << fqName.string()
            << std::endl;
    }

    return OK;
}

static std::string makeLibraryName(const FQName &packageFQName) {
    return packageFQName.string();
}

static status_t isPackageJavaCompatible(const FQName& packageFQName, const Coordinator* coordinator,
                                        bool* compatible) {
    std::vector<FQName> todo;
    status_t err =
        coordinator->appendPackageInterfacesToVector(packageFQName, &todo);

    if (err != OK) {
        return err;
    }

    std::set<FQName> seen;
    for (const auto &iface : todo) {
        seen.insert(iface);
    }

    // Form the transitive closure of all imported interfaces (and types.hal-s)
    // If any one of them is not java compatible, this package isn't either.
    while (!todo.empty()) {
        const FQName fqName = todo.back();
        todo.pop_back();

        AST *ast = coordinator->parse(fqName);

        if (ast == nullptr) {
            return UNKNOWN_ERROR;
        }

        if (!ast->isJavaCompatible()) {
            *compatible = false;
            return OK;
        }

        std::set<FQName> importedPackages;
        ast->getImportedPackages(&importedPackages);

        for (const auto &package : importedPackages) {
            std::vector<FQName> packageInterfaces;
            status_t err = coordinator->appendPackageInterfacesToVector(
                    package, &packageInterfaces);

            if (err != OK) {
                return err;
            }

            for (const auto &iface : packageInterfaces) {
                if (seen.find(iface) != seen.end()) {
                    continue;
                }

                todo.push_back(iface);
                seen.insert(iface);
            }
        }
    }

    *compatible = true;
    return OK;
}

static bool packageNeedsJavaCode(
        const std::vector<FQName> &packageInterfaces, AST *typesAST) {
    if (packageInterfaces.size() == 0) {
        return false;
    }

    // If there is more than just a types.hal file to this package we'll
    // definitely need to generate Java code.
    if (packageInterfaces.size() > 1
            || packageInterfaces[0].name() != "types") {
        return true;
    }

    CHECK(typesAST != nullptr);

    // We'll have to generate Java code if types.hal contains any non-typedef
    // type declarations.

    std::vector<NamedType*> subTypes = typesAST->getRootScope().getSubTypes();
    for (const auto &subType : subTypes) {
        if (!subType->isTypeDef()) {
            return true;
        }
    }

    return false;
}

bool validateIsPackage(const FQName& fqName, const Coordinator*,
                       const std::string& /* language */) {
    if (fqName.package().empty()) {
        fprintf(stderr, "ERROR: Expecting package name\n");
        return false;
    }

    if (fqName.version().empty()) {
        fprintf(stderr, "ERROR: Expecting package version\n");
        return false;
    }

    if (!fqName.name().empty()) {
        fprintf(stderr,
                "ERROR: Expecting only package name and version.\n");
        return false;
    }

    return true;
}

bool isHidlTransportPackage(const FQName& fqName) {
    return fqName.package() == gIBaseFqName.package() ||
           fqName.package() == gIManagerFqName.package();
}

bool isSystemProcessSupportedPackage(const FQName& fqName) {
    // Technically, so is hidl IBase + IServiceManager, but
    // these are part of libhidlbase.
    return fqName.inPackage("android.hardware.graphics.common") ||
           fqName.inPackage("android.hardware.graphics.mapper") ||
           fqName.string() == "android.hardware.renderscript@1.0" ||
           fqName.string() == "android.hidl.memory.token@1.0" ||
           fqName.string() == "android.hidl.memory@1.0" ||
           fqName.string() == "android.hidl.safe_union@1.0";
}

bool isCoreAndroidPackage(const FQName& package) {
    return package.inPackage("android.hidl") ||
           package.inPackage("android.system") ||
           package.inPackage("android.frameworks") ||
           package.inPackage("android.hardware");
}

// Keep the list of libs which are used by VNDK core libs and should be part of
// VNDK libs
static const std::vector<std::string> vndkLibs = {
        "android.hardware.audio.common@2.0",
        "android.hardware.configstore@1.0",
        "android.hardware.configstore@1.1",
        "android.hardware.graphics.allocator@2.0",
        "android.hardware.graphics.allocator@3.0",
        "android.hardware.graphics.allocator@4.0",
        "android.hardware.graphics.bufferqueue@1.0",
        "android.hardware.graphics.bufferqueue@2.0",
        "android.hardware.media.bufferpool@2.0",
        "android.hardware.media.omx@1.0",
        "android.hardware.media@1.0",
        "android.hardware.memtrack@1.0",
        "android.hardware.soundtrigger@2.0",
        "android.hidl.token@1.0",
        "android.system.suspend@1.0",
};

bool isVndkCoreLib(const FQName& fqName) {
    return std::find(vndkLibs.begin(), vndkLibs.end(), fqName.string()) != vndkLibs.end();
}

status_t hasVariantFile(const FQName& fqName, const Coordinator* coordinator,
                        const std::string& fileName, bool* isVariant) {
    const auto fileExists = [](const std::string& file) {
        struct stat buf;
        return stat(file.c_str(), &buf) == 0;
    };

    std::string path;
    status_t err =
            coordinator->getFilepath(fqName, Coordinator::Location::PACKAGE_ROOT, fileName, &path);
    if (err != OK) return err;

    const bool exists = fileExists(path);

    if (exists) {
        coordinator->onFileAccess(path, "r");
    }

    *isVariant = exists;
    return OK;
}

status_t isSystemExtPackage(const FQName& fqName, const Coordinator* coordinator,
                            bool* isSystemExt) {
    return hasVariantFile(fqName, coordinator, ".hidl_for_system_ext", isSystemExt);
}

status_t isOdmPackage(const FQName& fqName, const Coordinator* coordinator, bool* isOdm) {
    return hasVariantFile(fqName, coordinator, ".hidl_for_odm", isOdm);
}

static status_t generateAndroidBpForPackage(const FQName& packageFQName,
                                            const Coordinator* coordinator,
                                            const FileGenerator::GetFormatter& getFormatter) {
    CHECK(!packageFQName.isFullyQualified() && packageFQName.name().empty());

    std::vector<FQName> packageInterfaces;

    status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces);

    if (err != OK) {
        return err;
    }

    std::set<FQName> importedPackagesHierarchy;
    std::vector<const Type *> exportedTypes;
    AST* typesAST = nullptr;

    for (const auto& fqName : packageInterfaces) {
        AST* ast = coordinator->parse(fqName);

        if (ast == nullptr) {
            fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());

            return UNKNOWN_ERROR;
        }

        if (fqName.name() == "types") {
            typesAST = ast;
        }

        ast->getImportedPackagesHierarchy(&importedPackagesHierarchy);
        ast->appendToExportedTypesVector(&exportedTypes);
    }

    bool needsJavaCode = packageNeedsJavaCode(packageInterfaces, typesAST);

    bool genJavaConstants = needsJavaCode && !exportedTypes.empty();

    bool isJavaCompatible;
    err = isPackageJavaCompatible(packageFQName, coordinator, &isJavaCompatible);
    if (err != OK) return err;
    bool genJavaLibrary = needsJavaCode && isJavaCompatible;

    bool isCoreAndroid = isCoreAndroidPackage(packageFQName);

    bool isVndkCore = isVndkCoreLib(packageFQName);
    bool isVndkSp = isSystemProcessSupportedPackage(packageFQName);

    bool isSystemExtHidl;
    err = isSystemExtPackage(packageFQName, coordinator, &isSystemExtHidl);
    if (err != OK) return err;
    bool isSystemExt = isSystemExtHidl || !isCoreAndroid;

    bool isForOdm;
    err = isOdmPackage(packageFQName, coordinator, &isForOdm);
    if (err != OK) return err;

    std::string packageRoot;
    err = coordinator->getPackageRoot(packageFQName, &packageRoot);
    if (err != OK) return err;

    Formatter out = getFormatter();
    if (!out.isValid()) {
        return UNKNOWN_ERROR;
    }

    out << "// This file is autogenerated by hidl-gen -Landroidbp.\n\n";

    out << "hidl_interface ";
    out.block([&] {
        out << "name: \"" << makeLibraryName(packageFQName) << "\",\n";
        if (!coordinator->getOwner().empty()) {
            out << "owner: \"" << coordinator->getOwner() << "\",\n";
        }
        out << "root: \"" << packageRoot << "\",\n";
        if (isVndkCore || isVndkSp) {
            out << "vndk: ";
            out.block([&]() {
                out << "enabled: true,\n";
                if (isVndkSp) {
                    out << "support_system_process: true,\n";
                }
            }) << ",\n";
        }
        if (isSystemExt) {
            out << "system_ext_specific: true,\n";
        }
        if (isForOdm) {
            out << "odm_available: true,\n";
        }
        (out << "srcs: [\n").indent([&] {
           for (const auto& fqName : packageInterfaces) {
               out << "\"" << fqName.name() << ".hal\",\n";
           }
        }) << "],\n";
        if (!importedPackagesHierarchy.empty()) {
            (out << "interfaces: [\n").indent([&] {
               for (const auto& fqName : importedPackagesHierarchy) {
                   out << "\"" << fqName.string() << "\",\n";
               }
            }) << "],\n";
        }
        // Explicity call this out for developers.
        out << "gen_java: " << (genJavaLibrary ? "true" : "false") << ",\n";
        if (genJavaConstants) {
            out << "gen_java_constants: true,\n";
        }
   }).endl();

    return OK;
}

static status_t generateAndroidBpImplForPackage(const FQName& packageFQName,
                                                const Coordinator* coordinator,
                                                const FileGenerator::GetFormatter& getFormatter) {
    const std::string libraryName = makeLibraryName(packageFQName) + "-impl";

    std::vector<FQName> packageInterfaces;

    status_t err =
        coordinator->appendPackageInterfacesToVector(packageFQName,
                                                     &packageInterfaces);

    if (err != OK) {
        return err;
    }

    std::set<FQName> importedPackages;

    for (const auto &fqName : packageInterfaces) {
        AST *ast = coordinator->parse(fqName);

        if (ast == nullptr) {
            fprintf(stderr,
                    "ERROR: Could not parse %s. Aborting.\n",
                    fqName.string().c_str());

            return UNKNOWN_ERROR;
        }

        ast->getImportedPackages(&importedPackages);
    }

    Formatter out = getFormatter();
    if (!out.isValid()) {
        return UNKNOWN_ERROR;
    }

    out << "// FIXME: your file license if you have one\n\n";
    out << "cc_library_shared {\n";
    out.indent([&] {
        out << "// FIXME: this should only be -impl for a passthrough hal.\n"
            << "// In most cases, to convert this to a binderized implementation, you should:\n"
            << "// - change '-impl' to '-service' here and make it a cc_binary instead of a\n"
            << "//   cc_library_shared.\n"
            << "// - add a *.rc file for this module.\n"
            << "// - delete HIDL_FETCH_I* functions.\n"
            << "// - call configureRpcThreadpool and registerAsService on the instance.\n"
            << "// You may also want to append '-impl/-service' with a specific identifier like\n"
            << "// '-vendor' or '-<hardware identifier>' etc to distinguish it.\n";
        out << "name: \"" << libraryName << "\",\n";
        if (!coordinator->getOwner().empty()) {
            out << "owner: \"" << coordinator->getOwner() << "\",\n";
        }
        out << "relative_install_path: \"hw\",\n";
        if (coordinator->getOwner().empty()) {
            out << "// FIXME: this should be 'vendor: true' for modules that will eventually be\n"
                   "// on AOSP.\n";
        }
        out << "proprietary: true,\n";
        out << "srcs: [\n";
        out.indent([&] {
            for (const auto &fqName : packageInterfaces) {
                if (fqName.name() == "types") {
                    continue;
                }
                out << "\"" << fqName.getInterfaceBaseName() << ".cpp\",\n";
            }
        });
        out << "],\n"
            << "shared_libs: [\n";
        out.indent([&] {
            out << "\"libhidlbase\",\n"
                << "\"libutils\",\n"
                << "\"" << makeLibraryName(packageFQName) << "\",\n";

            for (const auto &importedPackage : importedPackages) {
                if (isHidlTransportPackage(importedPackage)) {
                    continue;
                }

                out << "\"" << makeLibraryName(importedPackage) << "\",\n";
            }
        });
        out << "],\n";
    });
    out << "}\n";

    return OK;
}

bool validateForSource(const FQName& fqName, const Coordinator* coordinator,
                       const std::string& language) {
    if (fqName.package().empty()) {
        fprintf(stderr, "ERROR: Expecting package name\n");
        return false;
    }

    if (fqName.version().empty()) {
        fprintf(stderr, "ERROR: Expecting package version\n");
        return false;
    }

    const std::string &name = fqName.name();
    if (!name.empty()) {
        if (name.find('.') == std::string::npos) {
            return true;
        }

        if (language != "java" || name.find("types.") != 0) {
            // When generating java sources for "types.hal", output can be
            // constrained to just one of the top-level types declared
            // by using the extended syntax
            // android.hardware.Foo@1.0::types.TopLevelTypeName.
            // In all other cases (different language, not 'types') the dot
            // notation in the name is illegal in this context.
            return false;
        }

        return true;
    }

    if (language == "java") {
        bool isJavaCompatible;
        status_t err = isPackageJavaCompatible(fqName, coordinator, &isJavaCompatible);
        if (err != OK) return false;

        if (!isJavaCompatible) {
            fprintf(stderr,
                    "ERROR: %s is not Java compatible. The Java backend does NOT support union "
                    "types. In addition, vectors of arrays are limited to at most one-dimensional "
                    "arrays and vectors of {vectors,interfaces,memory} are not supported.\n",
                    fqName.string().c_str());
            return false;
        }
    }

    return true;
}

bool validateForFormat(const FQName& fqName, const Coordinator* coordinator,
                       const std::string& format) {
    CHECK_EQ(format, "format");

    if (!validateForSource(fqName, coordinator, format)) return false;

    std::vector<FQName> packageInterfaces;

    if (fqName.isFullyQualified()) {
        packageInterfaces.push_back(fqName);
    } else {
        status_t err = coordinator->appendPackageInterfacesToVector(fqName, &packageInterfaces);
        if (err != OK) return err;
    }

    bool destroysInformation = false;

    for (const auto& fqName : packageInterfaces) {
        AST* ast = coordinator->parse(fqName);

        if (ast->getUnhandledComments().size() > 0) {
            destroysInformation = true;
            for (const auto& i : ast->getUnhandledComments()) {
                std::cerr << "Unrecognized comment at " << i->location() << std::endl;
                Formatter err(stderr);
                err.indent();
                i->emit(err);
                err.unindent();
                err.endl();
            }
        }
    }

    if (destroysInformation) {
        std::cerr << "\nhidl-gen does not support comments at these locations, and formatting "
                     "the file would destroy them. HIDL doc comments need to be multiline comments "
                     "(/*...*/) before specific elements. This will also cause them to be emitted "
                     "in output files in the correct locations so that IDE users or people "
                     "inspecting generated source can see them in the correct location. Formatting "
                     "the file would destroy these comments.\n";
        return false;
    }

    return true;
}

FileGenerator::GenerationFunction generateExportHeaderForPackage(bool forJava) {
    return [forJava](const FQName& packageFQName, const Coordinator* coordinator,
                     const FileGenerator::GetFormatter& getFormatter) -> status_t {
        CHECK(!packageFQName.package().empty() && !packageFQName.version().empty() &&
              packageFQName.name().empty());

        std::vector<FQName> packageInterfaces;

        status_t err = coordinator->appendPackageInterfacesToVector(
                packageFQName, &packageInterfaces);

        if (err != OK) {
            return err;
        }

        std::vector<const Type *> exportedTypes;

        for (const auto &fqName : packageInterfaces) {
            AST *ast = coordinator->parse(fqName);

            if (ast == nullptr) {
                fprintf(stderr,
                        "ERROR: Could not parse %s. Aborting.\n",
                        fqName.string().c_str());

                return UNKNOWN_ERROR;
            }

            ast->appendToExportedTypesVector(&exportedTypes);
        }

        if (exportedTypes.empty()) {
            fprintf(stderr,
                    "ERROR: -Ljava-constants (Android.bp: gen_java_constants) requested for %s, "
                    "but no types declare @export.",
                    packageFQName.string().c_str());
            return UNKNOWN_ERROR;
        }

        Formatter out = getFormatter();
        if (!out.isValid()) {
            return UNKNOWN_ERROR;
        }

        std::string packagePath;
        err = coordinator->getPackagePath(packageFQName, false /* relative */,
                                          false /* sanitized */, &packagePath);
        if (err != OK) return err;

        out << "// This file is autogenerated by hidl-gen. Do not edit manually.\n"
            << "// Source: " << packageFQName.string() << "\n"
            << "// Location: " << packagePath << "\n\n";

        std::string guard;
        if (forJava) {
            out << "package " << packageFQName.javaPackage() << ";\n\n";
            out << "public class Constants {\n";
            out.indent();
        } else {
            guard = "HIDL_GENERATED_";
            guard += StringHelper::Uppercase(packageFQName.tokenName());
            guard += "_";
            guard += "EXPORTED_CONSTANTS_H_";

            out << "#ifndef "
                << guard
                << "\n#define "
                << guard
                << "\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n";
        }

        for (const auto &type : exportedTypes) {
            type->emitExportedHeader(out, forJava);
        }

        if (forJava) {
            out.unindent();
            out << "}\n";
        } else {
            out << "#ifdef __cplusplus\n}\n#endif\n\n#endif  // "
                << guard
                << "\n";
        }

        return OK;
    };
}

static status_t generateHashOutput(const FQName& fqName, const Coordinator* coordinator,
                                   const FileGenerator::GetFormatter& getFormatter) {
    CHECK(fqName.isFullyQualified());

    AST* ast = coordinator->parse(fqName, {} /* parsed */,
                                  Coordinator::Enforce::NO_HASH /* enforcement */);

    if (ast == nullptr) {
        fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());

        return UNKNOWN_ERROR;
    }

    Formatter out = getFormatter();
    if (!out.isValid()) {
        return UNKNOWN_ERROR;
    }

    out << Hash::getHash(ast->getFilename()).hexString() << " " << fqName.string() << "\n";

    return OK;
}

static status_t generateFunctionCount(const FQName& fqName, const Coordinator* coordinator,
                                      const FileGenerator::GetFormatter& getFormatter) {
    CHECK(fqName.isFullyQualified());

    AST* ast = coordinator->parse(fqName, {} /* parsed */,
                                  Coordinator::Enforce::NO_HASH /* enforcement */);

    if (ast == nullptr) {
        fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str());
        return UNKNOWN_ERROR;
    }

    const Interface* interface = ast->getInterface();
    if (interface == nullptr) {
        fprintf(stderr, "ERROR: Function count requires interface: %s.\n", fqName.string().c_str());
        return UNKNOWN_ERROR;
    }

    Formatter out = getFormatter();
    if (!out.isValid()) {
        return UNKNOWN_ERROR;
    }

    // This is wrong for android.hidl.base@1.0::IBase, but in that case, it doesn't matter.
    // This is just the number of APIs that are added.
    out << fqName.string() << " " << interface->userDefinedMethods().size() << "\n";

    return OK;
}

template <typename T>
std::vector<T> operator+(const std::vector<T>& lhs, const std::vector<T>& rhs) {
    std::vector<T> ret;
    ret.reserve(lhs.size() + rhs.size());
    ret.insert(ret.begin(), lhs.begin(), lhs.end());
    ret.insert(ret.end(), rhs.begin(), rhs.end());
    return ret;
}

// clang-format off
static const std::vector<FileGenerator> kCppHeaderFormats = {
    {
        FileGenerator::alwaysGenerate,
        [](const FQName& fqName) { return fqName.name() + ".h"; },
        astGenerationFunction(&AST::generateInterfaceHeader),
    },
    {
        FileGenerator::alwaysGenerate,
        [](const FQName& fqName) {
            return fqName.isInterfaceName() ? fqName.getInterfaceHwName() + ".h" : "hwtypes.h";
        },
        astGenerationFunction(&AST::generateHwBinderHeader),
    },
    {
        FileGenerator::generateForInterfaces,
        [](const FQName& fqName) { return fqName.getInterfaceStubName() + ".h"; },
        astGenerationFunction(&AST::generateStubHeader),
    },
    {
        FileGenerator::generateForInterfaces,
        [](const FQName& fqName) { return fqName.getInterfaceProxyName() + ".h"; },
        astGenerationFunction(&AST::generateProxyHeader),
    },
    {
        FileGenerator::generateForInterfaces,
        [](const FQName& fqName) { return fqName.getInterfacePassthroughName() + ".h"; },
        astGenerationFunction(&AST::generatePassthroughHeader),
    },
};

static const std::vector<FileGenerator> kCppSourceFormats = {
    {
        FileGenerator::alwaysGenerate,
        [](const FQName& fqName) {
            return fqName.isInterfaceName() ? fqName.getInterfaceBaseName() + "All.cpp" : "types.cpp";
        },
        astGenerationFunction(&AST::generateCppSource),
    },
};

static const std::vector<FileGenerator> kCppImplHeaderFormats = {
    {
        FileGenerator::generateForInterfaces,
        [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".h"; },
        astGenerationFunction(&AST::generateCppImplHeader),
    },
};

static const std::vector<FileGenerator> kCppImplSourceFormats = {
    {
        FileGenerator::generateForInterfaces,
        [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".cpp"; },
        astGenerationFunction(&AST::generateCppImplSource),
    },
};

static const std::vector<OutputHandler> kFormats = {
    {
        "check",
        "Parses the interface to see if valid but doesn't write any files.",
        OutputMode::NOT_NEEDED,
        Coordinator::Location::STANDARD_OUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                nullptr /* filename for fqname */,
                astGenerationFunction(),
            },
        },
    },
    {
        "c++",
        "(internal) (deprecated) Generates C++ interface files for talking to HIDL interfaces.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_OUTPUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppHeaderFormats + kCppSourceFormats,
    },
    {
        "c++-headers",
        "(internal) Generates C++ headers for interface files for talking to HIDL interfaces.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_OUTPUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppHeaderFormats,
    },
    {
        "c++-sources",
        "(internal) Generates C++ sources for interface files for talking to HIDL interfaces.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_OUTPUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppSourceFormats,
    },
    {
        "export-header",
        "Generates a header file from @export enumerations to help maintain legacy code.",
        OutputMode::NEEDS_FILE,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_PACKAGE,
        validateIsPackage,
        {singleFileGenerator("", generateExportHeaderForPackage(false /* forJava */))}
    },
    {
        "c++-impl",
        "Generates boilerplate implementation of a hidl interface in C++ (for convenience).",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppImplHeaderFormats + kCppImplSourceFormats,
    },
    {
        "c++-impl-headers",
        "c++-impl but headers only.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppImplHeaderFormats,
    },
    {
        "c++-impl-sources",
        "c++-impl but sources only.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        kCppImplSourceFormats,
    },
    {
        "java",
        "(internal) Generates Java library for talking to HIDL interfaces in Java.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_SANITIZED,
        GenerationGranularity::PER_TYPE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                [](const FQName& fqName) {
                    return StringHelper::LTrim(fqName.name(), "types.") + ".java";
                },
                generateJavaForPackage,
            },
        }
    },
    {
        "java-impl",
        "Generates boilerplate implementation of a hidl interface in Java (for convenience).",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::generateForInterfaces,
                [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".java"; },
                astGenerationFunction(&AST::generateJavaImpl),
            },
        }
    },
    {
        "java-constants",
        "(internal) Like export-header but for Java (always created by -Lmakefile if @export exists).",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_SANITIZED,
        GenerationGranularity::PER_PACKAGE,
        validateIsPackage,
        {singleFileGenerator("Constants.java", generateExportHeaderForPackage(true /* forJava */))}
    },
    {
        "vts",
        "(internal) Generates vts proto files for use in vtsd.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::GEN_OUTPUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                [](const FQName& fqName) {
                    return fqName.isInterfaceName() ? fqName.getInterfaceBaseName() + ".vts" : "types.vts";
                },
                astGenerationFunction(&AST::generateVts),
            },
        }
    },
    {
        "makefile",
        "(removed) Used to generate makefiles for -Ljava and -Ljava-constants.",
        OutputMode::NEEDS_SRC,
        Coordinator::Location::PACKAGE_ROOT,
        GenerationGranularity::PER_PACKAGE,
        [](const FQName &, const Coordinator*, const std::string &) {
           fprintf(stderr, "ERROR: makefile output is not supported. Use -Landroidbp for all build file generation.\n");
           return false;
        },
        {},
    },
    {
        "androidbp",
        "(internal) Generates Soong bp files for -Lc++-headers, -Lc++-sources, -Ljava, and -Ljava-constants",
        OutputMode::NEEDS_SRC,
        Coordinator::Location::PACKAGE_ROOT,
        GenerationGranularity::PER_PACKAGE,
        validateIsPackage,
        {singleFileGenerator("Android.bp", generateAndroidBpForPackage)},
    },
    {
        "androidbp-impl",
        "Generates boilerplate bp files for implementation created with -Lc++-impl.",
        OutputMode::NEEDS_DIR,
        Coordinator::Location::DIRECT,
        GenerationGranularity::PER_PACKAGE,
        validateIsPackage,
        {singleFileGenerator("Android.bp", generateAndroidBpImplForPackage)},
    },
    {
        "hash",
        "Prints hashes of interface in `current.txt` format to standard out.",
        OutputMode::NOT_NEEDED,
        Coordinator::Location::STANDARD_OUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                nullptr /* file name for fqName */,
                generateHashOutput,
            },
        }
    },
    {
        "function-count",
        "Prints the total number of functions added by the package or interface.",
        OutputMode::NOT_NEEDED,
        Coordinator::Location::STANDARD_OUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::generateForInterfaces,
                nullptr /* file name for fqName */,
                generateFunctionCount,
            },
        }
    },
    {
        "dependencies",
        "Prints all depended types.",
        OutputMode::NOT_NEEDED,
        Coordinator::Location::STANDARD_OUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                nullptr /* file name for fqName */,
                astGenerationFunction(&AST::generateDependencies),
            },
        },
    },
    {
        "inheritance-hierarchy",
        "Prints the hierarchy of inherited types as a JSON object.",
        OutputMode::NOT_NEEDED,
        Coordinator::Location::STANDARD_OUT,
        GenerationGranularity::PER_FILE,
        validateForSource,
        {
            {
                FileGenerator::alwaysGenerate,
                nullptr /* file name for fqName */,
                astGenerationFunction(&AST::generateInheritanceHierarchy),
            },
        },
    },
    {
        "format",
        "Reformats the .hal files",
        OutputMode::NEEDS_SRC,
        Coordinator::Location::PACKAGE_ROOT,
        GenerationGranularity::PER_FILE,
        validateForFormat,
        {
            {
                FileGenerator::alwaysGenerate,
                [](const FQName& fqName) { return fqName.name() + ".hal"; },
                astGenerationFunction(&AST::generateFormattedHidl),
            },
        }
    },
};
// clang-format on

static void usage(const char* me) {
    Formatter out(stderr);

    out << "Usage: " << me << " -o <output path> -L <language> [-O <owner>] ";
    Coordinator::emitOptionsUsageString(out);
    out << " FQNAME...\n\n";

    out << "Process FQNAME, PACKAGE(.SUBPACKAGE)*@[0-9]+.[0-9]+(::TYPE)?, to create output.\n\n";

    out.indent();
    out.indent();

    out << "-h: Prints this menu.\n";
    out << "-L <language>: The following options are available:\n";
    out.indent([&] {
        for (auto& e : kFormats) {
            std::stringstream sstream;
            sstream.fill(' ');
            sstream.width(16);
            sstream << std::left << e.name();

            out << sstream.str() << ": " << e.description() << "\n";
        }
    });
    out << "-O <owner>: The owner of the module for -Landroidbp(-impl)?.\n";
    out << "-o <output path>: Location to output files.\n";
    Coordinator::emitOptionsDetailString(out);

    out.unindent();
    out.unindent();
}

// hidl is intentionally leaky. Turn off LeakSanitizer by default.
extern "C" const char *__asan_default_options() {
    return "detect_leaks=0";
}

int main(int argc, char **argv) {
    const char *me = argv[0];
    if (argc == 1) {
        usage(me);
        exit(1);
    }

    const OutputHandler* outputFormat = nullptr;
    Coordinator coordinator;
    std::string outputPath;

    coordinator.parseOptions(argc, argv, "ho:O:L:", [&](int res, char* arg) {
        switch (res) {
            case 'o': {
                if (!outputPath.empty()) {
                    fprintf(stderr, "ERROR: -o <output path> can only be specified once.\n");
                    exit(1);
                }
                outputPath = arg;
                break;
            }

            case 'O': {
                if (!coordinator.getOwner().empty()) {
                    fprintf(stderr, "ERROR: -O <owner> can only be specified once.\n");
                    exit(1);
                }
                coordinator.setOwner(arg);
                break;
            }

            case 'L': {
                if (outputFormat != nullptr) {
                    fprintf(stderr,
                            "ERROR: only one -L option allowed. \"%s\" already specified.\n",
                            outputFormat->name().c_str());
                    exit(1);
                }
                for (auto& e : kFormats) {
                    if (e.name() == arg) {
                        outputFormat = &e;
                        break;
                    }
                }
                if (outputFormat == nullptr) {
                    fprintf(stderr, "ERROR: unrecognized -L option: \"%s\".\n", arg);
                    exit(1);
                }
                break;
            }

            case '?':
            case 'h':
            default: {
                usage(me);
                exit(1);
                break;
            }
        }
    });

    if (outputFormat == nullptr) {
        fprintf(stderr,
            "ERROR: no -L option provided.\n");
        exit(1);
    }

    argc -= optind;
    argv += optind;

    if (argc == 0) {
        fprintf(stderr, "ERROR: no fqname specified.\n");
        usage(me);
        exit(1);
    }

    // Valid options are now in argv[0] .. argv[argc - 1].

    switch (outputFormat->mOutputMode) {
        case OutputMode::NEEDS_DIR:
        case OutputMode::NEEDS_FILE: {
            if (outputPath.empty()) {
                usage(me);
                exit(1);
            }

            if (outputFormat->mOutputMode == OutputMode::NEEDS_DIR) {
                if (outputPath.back() != '/') {
                    outputPath += "/";
                }
            }
            break;
        }
        case OutputMode::NEEDS_SRC: {
            if (outputPath.empty()) {
                outputPath = coordinator.getRootPath();
            }
            if (outputPath.back() != '/') {
                outputPath += "/";
            }

            break;
        }

        default:
            outputPath.clear();  // Unused.
            break;
    }

    coordinator.setOutputPath(outputPath);

    for (int i = 0; i < argc; ++i) {
        const char* arg = argv[i];

        FQName fqName;
        if (!FQName::parse(arg, &fqName)) {
            fprintf(stderr, "ERROR: Invalid fully-qualified name as argument: %s.\n", arg);
            exit(1);
        }

        if (coordinator.getPackageInterfaceFiles(fqName, nullptr /*fileNames*/) != OK) {
            fprintf(stderr, "ERROR: Could not get sources for %s.\n", arg);
            exit(1);
        }

        // Dump extra verbose output
        if (coordinator.isVerbose()) {
            status_t err =
                dumpDefinedButUnreferencedTypeNames(fqName.getPackageAndVersion(), &coordinator);
            if (err != OK) return err;
        }

        if (!outputFormat->validate(fqName, &coordinator, outputFormat->name())) {
            fprintf(stderr, "ERROR: Validation failed.\n");
            exit(1);
        }

        status_t err = outputFormat->generate(fqName, &coordinator);
        if (err != OK) exit(1);

        err = outputFormat->writeDepFile(fqName, &coordinator);
        if (err != OK) exit(1);
    }

    return 0;
}