//===- Archive.h ----------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef MCLD_ARCHIVE_H
#define MCLD_ARCHIVE_H
#ifdef ENABLE_UNITTEST
#include <gtest.h>
#endif

#include <mcld/ADT/HashEntry.h>
#include <mcld/ADT/HashTable.h>
#include <mcld/ADT/StringHash.h>
#include <mcld/Support/GCFactory.h>
#include <mcld/MC/InputTree.h>

#include <vector>
#include <string>

namespace mcld
{
class InputTree;
class Input;

/** \class Archive
 *  \brief This class define the interfacee to Archive files
 */
class Archive
{
public:
  static const char   MAGIC[];             ///< magic string
  static const char   THIN_MAGIC[];        ///< magic of thin archive
  static const size_t MAGIC_LEN;           ///< length of magic string
  static const char   SVR4_SYMTAB_NAME[];  ///< SVR4 symtab entry name
  static const char   STRTAB_NAME[];       ///< Name of string table
  static const char   PAD[];               ///< inter-file align padding
  static const char   MEMBER_MAGIC[];      ///< fmag field magic #

  struct MemberHeader
  {
    char name[16];  ///< Name of the file member.
    char date[12];  ///< File date, decimal seconds since Epoch
    char uid[6];    ///< user id in ASCII decimal
    char gid[6];    ///< group id in ASCII decimal
    char mode[8];   ///< file mode in ASCII octal
    char size[10];  ///< file size in ASCII decimal
    char fmag[2];   ///< Always contains ARFILE_MAGIC_TERMINATOR
  };

private:
  template<typename OFFSET_TYPE>
  struct OffsetCompare
  {
    bool operator()(OFFSET_TYPE X, OFFSET_TYPE Y) const
    { return (X == Y); }
  };

  struct MurmurHash3
  {
    size_t operator()(uint32_t pKey) const
    {
      size_t h;
      h ^= h >> 16;
      h *= 0x85ebca6b;
      h ^= h >> 13;
      h *= 0xc2b2ae35;
      h ^= h >> 16;
      return h;
    }
  };

  typedef HashEntry<uint32_t,
                    InputTree::iterator,
                    OffsetCompare<uint32_t> > ObjectMemberEntryType;
public:
  typedef HashTable<ObjectMemberEntryType,
                    MurmurHash3,
                    EntryFactory<ObjectMemberEntryType> > ObjectMemberMapType;

  struct ArchiveMember
  {
    Input* file;
    InputTree::iterator lastPos;
    InputTree::Mover* move;
  };

private:
  typedef HashEntry<const llvm::StringRef,
                    ArchiveMember,
                    StringCompare<llvm::StringRef> > ArchiveMemberEntryType;

public:
  typedef HashTable<ArchiveMemberEntryType,
                    StringHash<ELF>,
                    EntryFactory<ArchiveMemberEntryType> > ArchiveMemberMapType;

  struct Symbol
  {
  public:
    enum Status
    {
      Include,
      Exclude,
      Unknown
    };

    Symbol(const char* pName,
           uint32_t pOffset,
           enum Status pStatus)
     : name(pName), fileOffset(pOffset), status(pStatus)
    {}

    ~Symbol()
    {}

  public:
    std::string name;
    uint32_t fileOffset;
    enum Status status;
  };

  typedef std::vector<Symbol*> SymTabType;

public:
  Archive(Input& pInputFile, InputFactory& pInputFactory);

  ~Archive();

  /// getARFile - get the Input& of the archive file
  Input& getARFile();

  /// getARFile - get the Input& of the archive file
  const Input& getARFile() const;

  /// inputs - get the input tree built from this archive
  InputTree& inputs();

  /// inputs - get the input tree built from this archive
  const InputTree& inputs() const;

  /// getObjectMemberMap - get the map that contains the included object files
  ObjectMemberMapType& getObjectMemberMap();

  /// getObjectMemberMap - get the map that contains the included object files
  const ObjectMemberMapType& getObjectMemberMap() const;

  /// numOfObjectMember - return the number of included object files
  size_t numOfObjectMember() const;

  /// addObjectMember - add a object in the object member map
  /// @param pFileOffset - file offset in symtab represents a object file
  /// @param pIter - the iterator in the input tree built from this archive
  bool addObjectMember(uint32_t pFileOffset, InputTree::iterator pIter);

  /// hasObjectMember - check if a object file is included or not
  /// @param pFileOffset - file offset in symtab represents a object file
  bool hasObjectMember(uint32_t pFileOffset) const;

  /// getArchiveMemberMap - get the map that contains the included archive files
  ArchiveMemberMapType& getArchiveMemberMap();

  /// getArchiveMemberMap - get the map that contains the included archive files
  const ArchiveMemberMapType& getArchiveMemberMap() const;

  /// addArchiveMember - add an archive in the archive member map
  /// @param pName    - the name of the new archive member
  /// @param pLastPos - this records the point to insert the next node in the
  ///                   subtree of this archive member
  /// @param pMove    - this records the direction to insert the next node in
  ///                   the subtree of this archive member
  bool addArchiveMember(const llvm::StringRef& pName,
                        InputTree::iterator pLastPos,
                        InputTree::Mover* pMove);

  /// hasArchiveMember - check if an archive file is included or not
  bool hasArchiveMember(const llvm::StringRef& pName) const;

  /// getArchiveMember - get a archive member
  ArchiveMember* getArchiveMember(const llvm::StringRef& pName);

  /// getSymbolTable - get the symtab
  SymTabType& getSymbolTable();

  /// getSymbolTable - get the symtab
  const SymTabType& getSymbolTable() const;

  /// setSymTabSize - set the memory size of symtab
  void setSymTabSize(size_t pSize);

  /// getSymTabSize - get the memory size of symtab
  size_t getSymTabSize() const;

  /// numOfSymbols - return the number of symbols in symtab
  size_t numOfSymbols() const;

  /// addSymbol - add a symtab entry to symtab
  /// @param pName - symbol name
  /// @param pFileOffset - file offset in symtab represents a object file
  void
  addSymbol(const char* pName,
            uint32_t pFileOffset,
            enum Symbol::Status pStatus = Archive::Symbol::Unknown);

  /// getSymbolName - get the symbol name with the given index
  const std::string& getSymbolName(size_t pSymIdx) const;

  /// getObjFileOffset - get the file offset that represent a object file
  uint32_t getObjFileOffset(size_t pSymIdx) const;

  /// getSymbolStatus - get the status of a symbol
  enum Symbol::Status getSymbolStatus(size_t pSymIdx) const;

  /// setSymbolStatus - set the status of a symbol
  void setSymbolStatus(size_t pSymIdx, enum Symbol::Status pStatus);

  /// getStrTable - get the extended name table
  std::string& getStrTable();

  /// getStrTable - get the extended name table
  const std::string& getStrTable() const;

private:
  typedef GCFactory<Symbol, 0> SymbolFactory;

private:
  Input& m_ArchiveFile;
  InputTree *m_pInputTree;
  ObjectMemberMapType m_ObjectMemberMap;
  ArchiveMemberMapType m_ArchiveMemberMap;
  SymbolFactory m_SymbolFactory;
  SymTabType m_SymTab;
  size_t m_SymTabSize;
  std::string m_StrTab;
};

} // namespace of mcld

#endif