//===- PathV3.inc ---------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/Support/FileSystem.h>
#include <mcld/Support/Directory.h>
#include <mcld/Support/Path.h>
#include <llvm/Support/ErrorHandling.h>

#include <cerrno>
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <stack>
#include <unistd.h>

namespace mcld{
namespace sys{
namespace fs{
namespace detail{

const char          separator = '/';
const char          preferred_separator = '/';

// return the last charactor being handled.
size_t canonicalize(std::string& pathname)
{
  // Variable Index //
  // SepTable - stack of result separators
  // LR(1) Algorithm //
  // traverse pPathName
  //   if we meet '//', '///', '////', ...
  //     -> ignore it
  //     -> push current into stack
  //     -> jump to the next not '/'
  //   if we meet '/./'
  //     -> ignore
  //     -> jump to the next not '/'
  //   if we meet '/../'
  //     -> pop previous position of '/' P
  //     -> erase P+1 to now
  //   if we meet other else
  //     -> go go go
  //   if we meet '/.../', '/..../', ... -> illegal
  if (pathname.empty())
    return 0;

  size_t handler = 0;
  std::stack<size_t> slash_stack;
  slash_stack.push(-1);
  while (handler < pathname.size()) {
    if (separator == pathname[handler]) { // handler = 1st '/'
      size_t next = handler + 1;
      if (next >= pathname.size())
        return handler;
      switch(pathname[next]) { // next = handler + 1;
        case separator: { // '//'
          while (next < pathname.size() && separator == pathname[next])
            ++next;
          // next is the last not '/'
          pathname.erase(handler, next - handler - 1);
          // handler is the first '/'
          slash_stack.push(handler);
          break;
        }
        case '.': { // '/.'
          ++next; // next = handler + 2
          if (next >= pathname.size()) // '/.'
            return handler;
          switch (pathname[next]) {
            case separator: { // '/./'
              pathname.erase(handler, 2);
              break;
            }
            case '.': { // '/..'
              ++next; // next = handler + 3;
              if (next >= pathname.size()) // '/..?'
                return handler;
              switch(pathname[next]) {
                case separator: { // '/../'
                  handler = slash_stack.top();
                  slash_stack.pop();
                  pathname.erase(handler+1, next-handler);
                  if (static_cast<size_t>(-1) == handler) {
                    slash_stack.push(-1);
                    handler = pathname.find_first_of(separator, handler);
                  }
                  break;
                }
                case '.': { // '/...', illegal
                  return handler;
                  break;
                }
                default : { // '/..a'
                  slash_stack.push(handler);
                  handler = pathname.find_first_of(separator, handler+3);
                  break;
                }
              }
              break;
            }
            default : { // '/.a'
              slash_stack.push(handler);
              handler = pathname.find_first_of(separator, handler+2);
              break;
            }
          }
          break;
        }
        default : { // '/a
          slash_stack.push(handler);
          handler = pathname.find_first_of(separator, handler+1);
          break;
        }
      }
    }
    else {
      handler = pathname.find_first_of(separator, handler);
    }
  }
  return handler;
}

bool not_found_error(int perrno)
{
  return perrno == ENOENT || perrno == ENOTDIR;
}

void status(const Path& p, FileStatus& pFileStatus)
{
  struct stat path_stat;
  if(stat(p.c_str(), &path_stat)!= 0)
  {
    if(not_found_error(errno))
    {
      pFileStatus.setType(FileNotFound);
    }
    else
      pFileStatus.setType(StatusError);
  }
  else if(S_ISDIR(path_stat.st_mode))
    pFileStatus.setType(DirectoryFile);
  else if(S_ISREG(path_stat.st_mode))
    pFileStatus.setType(RegularFile);
  else if(S_ISBLK(path_stat.st_mode))
    pFileStatus.setType(BlockFile);
  else if(S_ISCHR(path_stat.st_mode))
    pFileStatus.setType(CharacterFile);
  else if(S_ISFIFO(path_stat.st_mode))
    pFileStatus.setType(FifoFile);
  else if(S_ISSOCK(path_stat.st_mode))
    pFileStatus.setType(SocketFile);
  else
    pFileStatus.setType(TypeUnknown);
}

void symlink_status(const Path& p, FileStatus& pFileStatus)
{
  struct stat path_stat;
  if(lstat(p.c_str(), &path_stat)!= 0)
  {
    if(errno == ENOENT || errno == ENOTDIR) // these are not errors
    {
      pFileStatus.setType(FileNotFound);
    }
    else
      pFileStatus.setType(StatusError);
  }
  if(S_ISREG(path_stat.st_mode))
    pFileStatus.setType(RegularFile);
  if(S_ISDIR(path_stat.st_mode))
    pFileStatus.setType(DirectoryFile);
  if(S_ISLNK(path_stat.st_mode))
    pFileStatus.setType(SymlinkFile);
  if(S_ISBLK(path_stat.st_mode))
    pFileStatus.setType(BlockFile);
  if(S_ISCHR(path_stat.st_mode))
    pFileStatus.setType(CharacterFile);
  if(S_ISFIFO(path_stat.st_mode))
    pFileStatus.setType(FifoFile);
  if(S_ISSOCK(path_stat.st_mode))
    pFileStatus.setType(SocketFile);
  else
    pFileStatus.setType(TypeUnknown);
}

/// read_dir - return true if we read one entry
//  @return value -1: read error
//                 0: read the end
//                 1: success
static int read_dir(intptr_t& pDir, std::string& pOutFilename)
{
  errno = 0;
  dirent *cur_dir = ::readdir(reinterpret_cast<DIR*>(pDir));
  if (0 == cur_dir && 0 != errno)
    return -1;

  // idx does not stay at the end, but all elements had beed put into cache.
  if (NULL == cur_dir) {
    return 0;
  }

  llvm::StringRef name(cur_dir->d_name, strlen(cur_dir->d_name));
  if ((name.size() == 1 && name[0] == '.') ||
      (name.size() == 2 && name[0] == '.' && name[1] == '.'))
    return read_dir(pDir, pOutFilename);

  // find a new directory
  pOutFilename.append(name.data(), name.size());
  return 1;
}

/// directory_iterator_increment - increment function implementation
//
//  iterator will call this function in two situations:
//  1. All elements have been put into cache, and iterator stays at the end
//     of cache. (a real end)
//  2. Some but not all elements had beed put into cache, and we stoped.
//     An iterator now is staying at the end of cache. (a temporal end)
mcld::sys::fs::PathCache::entry_type* bring_one_into_cache(DirIterator& pIter)
{
  mcld::sys::fs::PathCache::entry_type* entry = 0;
  std::string path(pIter.m_pParent->m_Path.native());
  switch (read_dir(pIter.m_pParent->m_Handler, path)) {
  case 1: {
    // read one
    bool exist = false;
    entry = pIter.m_pParent->m_Cache.insert(path, exist);
    if (!exist)
      entry->setValue(new Path(path));
    break;
  }
  case 0:// meet real end
    pIter.m_pParent->m_CacheFull = true;
    break;
  default:
  case -1:
    llvm::report_fatal_error(std::string("Can't read directory: ")+
                             pIter.m_pParent->path().native());
    break;
  }
  return entry;
}

void open_dir(Directory& pDir)
{
  pDir.m_Handler = reinterpret_cast<intptr_t>(opendir(pDir.path().c_str()));
  if (pDir.m_Handler == 0) {
    errno = 0; // opendir() will set errno if it failed to open directory.
    pDir.m_CacheFull = true;
    return;
  }
  // read one entry for advance the end element of the cache.
  std::string path(pDir.path().native());
  switch (read_dir(pDir.m_Handler, path)) {
  case 1: {
    // find a new directory
    bool exist = false;
    mcld::sys::fs::PathCache::entry_type* entry = pDir.m_Cache.insert(path, exist);
    if (!exist)
      entry->setValue(new Path(path));
    return;
  }
  case 0:
    // FIXME: a warning function
    pDir.m_CacheFull = true;
    return;
  default:
  case -1:
    llvm::report_fatal_error(std::string("Can't read directory: ")+
                             pDir.path().native());
  }
}

void close_dir(Directory& pDir)
{
  if (pDir.m_Handler)
    closedir(reinterpret_cast<DIR *>(pDir.m_Handler));
  pDir.m_Handler = 0;
}

void get_pwd(std::string& pPWD)
{
  char* pwd = (char*)malloc(PATH_MAX);
  pPWD.assign(getcwd(pwd, PATH_MAX));
  free(pwd);
}

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