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

#include <mcld/ADT/TypeTraits.h>
#include <mcld/Support/FileSystem.h>
#include <mcld/Support/Path.h>
#include <llvm/Support/Allocator.h>
#include <cstddef>

#include "PathCache.h"

namespace mcld {
namespace sys {
namespace fs {

class DirIterator;

/** \class Directory
 *  \brief A Directory object stores a Path object, a FileStatus object for
 *   non-symbolic link status, and a FileStatus object for symbolic link
 *   status. The FileStatus objects act as value caches.
 */
class Directory
{
friend mcld::sys::fs::PathCache::entry_type* detail::bring_one_into_cache(DirIterator& pIter);
friend void detail::open_dir(Directory& pDir);
friend void detail::close_dir(Directory& pDir);
private:
  friend class DirIterator;

public:
  typedef DirIterator iterator;

public:
  /// default constructor
  Directory();

  /// constructor - a directory whose path is pPath
  explicit Directory(const Path& pPath,
                     FileStatus st = FileStatus(),
                     FileStatus symlink_st = FileStatus());

  /// copy constructor
  /// when a copying construction happens, the cache is not copied.
  Directory(const Directory& pCopy);

  /// assignment
  /// When an assignment occurs, the cache is clear.
  Directory& operator=(const Directory& pCopy);

  /// destructor, inheritable.
  virtual ~Directory();

  /// Since we have default construtor, we must provide assign.
  void assign(const Path& pPath,
              FileStatus st = FileStatus(),
              FileStatus symlink_st = FileStatus());

  /// clear - clear the cache and close the directory handler
  void clear();

  bool isGood() const;

  /// path - the path of the directory
  const Path& path() const
  { return m_Path; }

  FileStatus status() const;
  FileStatus symlinkStatus() const;

  // -----  iterators  ----- //
  // While the iterators move, the direcotry is modified.
  // Thus, we only provide non-constant iterator.
  iterator begin();
  iterator end();

protected:
  mcld::sys::fs::Path m_Path;
  mutable FileStatus m_FileStatus;
  mutable FileStatus m_SymLinkStatus;
  intptr_t m_Handler;
  // the cache of directory
  mcld::sys::fs::PathCache m_Cache;
  bool m_CacheFull;
};

/** \class DirIterator
 *  \brief A DirIterator object can traverse all entries in a Directory
 *
 *  DirIterator will open the directory and add entry into Directory::m_Cache
 *  DirIterator() is the end of a directory.
 *  If the end of the directory elements is reached, the iterator becomes
 *  equal to the end iterator value - DirIterator().
 *
 *  @see Directory
 */
class DirIterator
{
friend mcld::sys::fs::PathCache::entry_type* detail::bring_one_into_cache(DirIterator& pIter);
friend class Directory;
public:
  typedef mcld::sys::fs::PathCache            DirCache;

public:
  typedef Directory                       value_type;
  typedef ConstTraits<Directory>          const_traits;
  typedef NonConstTraits<Directory>       non_const_traits;
  typedef std::input_iterator_tag         iterator_category;
  typedef size_t                          size_type;
  typedef ptrdiff_t                       difference_type;

private:
  explicit DirIterator(Directory* pParent,
                       const DirCache::iterator& pIter);

public:
  // Since StringMapIterator has no default constructor, we also have none.
  DirIterator(const DirIterator &X);
  ~DirIterator();
  DirIterator& operator=(const DirIterator& pCopy);

  DirIterator& operator++();
  DirIterator operator++(int);

  Path* generic_path();

  Path* path();
  const Path* path() const;

  bool operator==(const DirIterator& y) const;
  bool operator!=(const DirIterator& y) const;

private:
  Directory* m_pParent; // get handler
  DirCache::iterator m_Iter; // for full situation
  DirCache::entry_type* m_pEntry;
};

} // namespace of fs
} // namespace of sys
} // namespace of mcld

#endif