//===-- BinaryHolder.h - Utility class for accessing binaries -------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This program is a utility that aims to be a dropin replacement for
// Darwin's dsymutil.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TOOLS_DSYMUTIL_BINARYHOLDER_H
#define LLVM_TOOLS_DSYMUTIL_BINARYHOLDER_H

#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/Error.h"
#include "llvm/Object/MachOUniversal.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/ErrorOr.h"

#include <mutex>

namespace llvm {
namespace dsymutil {

/// The BinaryHolder class is responsible for creating and owning
/// ObjectFiles and their underlying MemoryBuffers. It differs from a simple
/// OwningBinary in that it handles accessing and caching of archives and its
/// members.
class BinaryHolder {
public:
  using TimestampTy = sys::TimePoint<std::chrono::seconds>;

  BinaryHolder(bool Verbose = false) : Verbose(Verbose) {}

  // Forward declarations for friend declaration.
  class ObjectEntry;
  class ArchiveEntry;

  /// Base class shared by cached entries, representing objects and archives.
  class EntryBase {
  protected:
    std::unique_ptr<MemoryBuffer> MemBuffer;
    std::unique_ptr<object::MachOUniversalBinary> FatBinary;
    std::string FatBinaryName;
  };

  /// Cached entry holding one or more (in case of a fat binary) object files.
  class ObjectEntry : public EntryBase {
  public:
    /// Load the given object binary in memory.
    Error load(StringRef Filename, bool Verbose = false);

    /// Access all owned ObjectFiles.
    std::vector<const object::ObjectFile *> getObjects() const;

    /// Access to a derived version of all the currently owned ObjectFiles. The
    /// conversion might be invalid, in which case an Error is returned.
    template <typename ObjectFileType>
    Expected<std::vector<const ObjectFileType *>> getObjectsAs() const {
      std::vector<const ObjectFileType *> Result;
      Result.reserve(Objects.size());
      for (auto &Object : Objects) {
        const auto *Derived = dyn_cast<ObjectFileType>(Object.get());
        if (!Derived)
          return errorCodeToError(object::object_error::invalid_file_type);
        Result.push_back(Derived);
      }
      return Result;
    }

    /// Access the owned ObjectFile with architecture \p T.
    Expected<const object::ObjectFile &> getObject(const Triple &T) const;

    /// Access to a derived version of the currently owned ObjectFile with
    /// architecture \p T. The conversion must be known to be valid.
    template <typename ObjectFileType>
    Expected<const ObjectFileType &> getObjectAs(const Triple &T) const {
      auto Object = getObject(T);
      if (!Object)
        return Object.takeError();
      return cast<ObjectFileType>(*Object);
    }

  private:
    std::vector<std::unique_ptr<object::ObjectFile>> Objects;
    friend ArchiveEntry;
  };

  /// Cached entry holding one or more (in the of a fat binary) archive files.
  class ArchiveEntry : public EntryBase {
  public:
    struct KeyTy {
      std::string Filename;
      TimestampTy Timestamp;

      KeyTy() : Filename(), Timestamp() {}
      KeyTy(StringRef Filename, TimestampTy Timestamp)
          : Filename(Filename.str()), Timestamp(Timestamp) {}
    };

    /// Load the given object binary in memory.
    Error load(StringRef Filename, TimestampTy Timestamp, bool Verbose = false);

    Expected<const ObjectEntry &> getObjectEntry(StringRef Filename,
                                                 TimestampTy Timestamp,
                                                 bool Verbose = false);

  private:
    std::vector<std::unique_ptr<object::Archive>> Archives;
    DenseMap<KeyTy, ObjectEntry> MemberCache;
    std::mutex MemberCacheMutex;
  };

  Expected<const ObjectEntry &>
  getObjectEntry(StringRef Filename, TimestampTy Timestamp = TimestampTy());

  void clear();

private:
  /// Cache of static archives. Objects that are part of a static archive are
  /// stored under this object, rather than in the map below.
  StringMap<ArchiveEntry> ArchiveCache;
  std::mutex ArchiveCacheMutex;

  /// Object entries for objects that are not in a static archive.
  StringMap<ObjectEntry> ObjectCache;
  std::mutex ObjectCacheMutex;

  bool Verbose;
};

} // namespace dsymutil

template <> struct DenseMapInfo<dsymutil::BinaryHolder::ArchiveEntry::KeyTy> {

  static inline dsymutil::BinaryHolder::ArchiveEntry::KeyTy getEmptyKey() {
    return dsymutil::BinaryHolder::ArchiveEntry::KeyTy();
  }

  static inline dsymutil::BinaryHolder::ArchiveEntry::KeyTy getTombstoneKey() {
    return dsymutil::BinaryHolder::ArchiveEntry::KeyTy("/", {});
  }

  static unsigned
  getHashValue(const dsymutil::BinaryHolder::ArchiveEntry::KeyTy &K) {
    return hash_combine(DenseMapInfo<StringRef>::getHashValue(K.Filename),
                        DenseMapInfo<unsigned>::getHashValue(
                            K.Timestamp.time_since_epoch().count()));
  }

  static bool isEqual(const dsymutil::BinaryHolder::ArchiveEntry::KeyTy &LHS,
                      const dsymutil::BinaryHolder::ArchiveEntry::KeyTy &RHS) {
    return LHS.Filename == RHS.Filename && LHS.Timestamp == RHS.Timestamp;
  }
};

} // namespace llvm
#endif
