// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_BIN_DIRECTORY_H_
#define RUNTIME_BIN_DIRECTORY_H_

#include "bin/builtin.h"
#include "bin/dartutils.h"
#include "bin/namespace.h"
#include "bin/reference_counting.h"
#include "bin/thread.h"
#include "platform/globals.h"

namespace dart {
namespace bin {

enum ListType {
  kListFile = 0,
  kListDirectory = 1,
  kListLink = 2,
  kListError = 3,
  kListDone = 4
};

class PathBuffer {
 public:
  PathBuffer();
  ~PathBuffer();

  bool Add(const char* name);
  bool AddW(const wchar_t* name);

  char* AsString() const;
  wchar_t* AsStringW() const;

  // Makes a scope allocated copy of the string.
  const char* AsScopedString() const;

  void Reset(intptr_t new_length);

  intptr_t length() const { return length_; }

 private:
  void* data_;
  intptr_t length_;

  DISALLOW_COPY_AND_ASSIGN(PathBuffer);
};

class DirectoryListing;

struct LinkList;

// DirectoryListingEntry is used as a stack item, when performing recursive
// directory listing. By using DirectoryListingEntry as stack elements, a
// directory listing can be paused e.g. when a buffer is full, and resumed
// later on.
//
// The stack is managed by the DirectoryListing's PathBuffer. Each
// DirectoryListingEntry stored a entry-length, that it'll reset the PathBuffer
// to on each call to Next.
class DirectoryListingEntry {
 public:
  explicit DirectoryListingEntry(DirectoryListingEntry* parent)
      : parent_(parent), fd_(-1), lister_(0), done_(false), link_(nullptr) {}

  ~DirectoryListingEntry();

  ListType Next(DirectoryListing* listing);

  DirectoryListingEntry* parent() const { return parent_; }

  LinkList* link() { return link_; }

  void set_link(LinkList* link) { link_ = link; }

  void ResetLink();

 private:
  DirectoryListingEntry* parent_;
  intptr_t fd_;
  intptr_t lister_;
  bool done_;
  int path_length_;
  LinkList* link_;

  DISALLOW_COPY_AND_ASSIGN(DirectoryListingEntry);
};

class DirectoryListing {
 public:
  DirectoryListing(Namespace* namespc,
                   const char* dir_name,
                   bool recursive,
                   bool follow_links)
      : namespc_(namespc),
        top_(nullptr),
        error_(false),
        recursive_(recursive),
        follow_links_(follow_links) {
    if (!path_buffer_.Add(dir_name)) {
      error_ = true;
    }
    Push(new DirectoryListingEntry(nullptr));
  }

  virtual ~DirectoryListing() { PopAll(); }

  virtual bool HandleDirectory(const char* dir_name) = 0;
  virtual bool HandleFile(const char* file_name) = 0;
  virtual bool HandleLink(const char* link_name) = 0;
  virtual bool HandleError() = 0;
  virtual void HandleDone() {}

  void Push(DirectoryListingEntry* directory) { top_ = directory; }

  void Pop() {
    ASSERT(!IsEmpty());
    DirectoryListingEntry* current = top_;
    top_ = top_->parent();
    delete current;
  }

  bool IsEmpty() const { return top_ == nullptr; }

  void PopAll() {
    while (!IsEmpty()) {
      Pop();
    }
  }

  Namespace* namespc() const { return namespc_; }

  DirectoryListingEntry* top() const { return top_; }

  bool recursive() const { return recursive_; }

  bool follow_links() const { return follow_links_; }

  const char* CurrentPath() { return path_buffer_.AsScopedString(); }

  PathBuffer& path_buffer() { return path_buffer_; }

  bool error() const { return error_; }

 private:
  PathBuffer path_buffer_;
  Namespace* namespc_;
  DirectoryListingEntry* top_;
  bool error_;
  bool recursive_;
  bool follow_links_;
};

class AsyncDirectoryListing : public ReferenceCounted<AsyncDirectoryListing>,
                              public DirectoryListing {
 public:
  enum Response {
    kListFile = 0,
    kListDirectory = 1,
    kListLink = 2,
    kListError = 3,
    kListDone = 4
  };

  AsyncDirectoryListing(Namespace* namespc,
                        const char* dir_name,
                        bool recursive,
                        bool follow_links)
      : ReferenceCounted(),
        DirectoryListing(namespc, dir_name, recursive, follow_links),
        array_(nullptr),
        index_(0),
        length_(0) {}

  virtual bool HandleDirectory(const char* dir_name);
  virtual bool HandleFile(const char* file_name);
  virtual bool HandleLink(const char* file_name);
  virtual bool HandleError();
  virtual void HandleDone();

  void SetArray(CObjectArray* array, intptr_t length) {
    ASSERT(length % 2 == 0);
    array_ = array;
    index_ = 0;
    length_ = length;
  }

  intptr_t index() const { return index_; }

 private:
  virtual ~AsyncDirectoryListing() {}
  bool AddFileSystemEntityToResponse(Response response, const char* arg);
  CObjectArray* array_;
  intptr_t index_;
  intptr_t length_;

  friend class ReferenceCounted<AsyncDirectoryListing>;
  DISALLOW_IMPLICIT_CONSTRUCTORS(AsyncDirectoryListing);
};

class SyncDirectoryListing : public DirectoryListing {
 public:
  SyncDirectoryListing(Dart_Handle results,
                       Namespace* namespc,
                       const char* dir_name,
                       bool recursive,
                       bool follow_links)
      : DirectoryListing(namespc, dir_name, recursive, follow_links),
        results_(results),
        dart_error_(Dart_Null()) {
    add_string_ = DartUtils::NewString("add");
    from_raw_path_string_ = DartUtils::NewString("fromRawPath");
    directory_type_ = DartUtils::GetDartType(DartUtils::kIOLibURL, "Directory");
    file_type_ = DartUtils::GetDartType(DartUtils::kIOLibURL, "File");
    link_type_ = DartUtils::GetDartType(DartUtils::kIOLibURL, "Link");
  }
  virtual ~SyncDirectoryListing() {}
  virtual bool HandleDirectory(const char* dir_name);
  virtual bool HandleFile(const char* file_name);
  virtual bool HandleLink(const char* file_name);
  virtual bool HandleError();

  Dart_Handle dart_error() { return dart_error_; }

 private:
  Dart_Handle results_;
  Dart_Handle add_string_;
  Dart_Handle from_raw_path_string_;
  Dart_Handle directory_type_;
  Dart_Handle file_type_;
  Dart_Handle link_type_;
  Dart_Handle dart_error_;

  DISALLOW_ALLOCATION()
  DISALLOW_IMPLICIT_CONSTRUCTORS(SyncDirectoryListing);
};

class Directory {
 public:
  enum ExistsResult { UNKNOWN, EXISTS, DOES_NOT_EXIST };

  static void List(DirectoryListing* listing);

#if defined(DART_HOST_OS_WINDOWS)
  static ExistsResult Exists(const wchar_t* path);
#endif

  static ExistsResult Exists(Namespace* namespc, const char* path);

  // Returns the current working directory. The caller must call
  // free() on the result.
  static char* CurrentNoScope();

  // Returns the current working directory. The returned string is allocated
  // with Dart_ScopeAllocate(). It lasts only as long as the current API scope.
  static const char* Current(Namespace* namespc);
  static const char* SystemTemp(Namespace* namespc);
  static const char* CreateTemp(Namespace* namespc, const char* path);
  // Set the system temporary directory.
  static void SetSystemTemp(const char* path);
  static bool SetCurrent(Namespace* namespc, const char* path);
  static bool Create(Namespace* namespc, const char* path);
  static bool Delete(Namespace* namespc, const char* path, bool recursive);
  static bool Rename(Namespace* namespc,
                     const char* path,
                     const char* new_path);

  static CObject* CreateRequest(const CObjectArray& request);
  static CObject* DeleteRequest(const CObjectArray& request);
  static CObject* ExistsRequest(const CObjectArray& request);
  static CObject* CreateTempRequest(const CObjectArray& request);
  static CObject* CreateSystemTempRequest(const CObjectArray& request);
  static CObject* ListStartRequest(const CObjectArray& request);
  static CObject* ListNextRequest(const CObjectArray& request);
  static CObject* ListStopRequest(const CObjectArray& request);
  static CObject* RenameRequest(const CObjectArray& request);

 private:
  static char* system_temp_path_override_;
  DISALLOW_ALLOCATION();
  DISALLOW_IMPLICIT_CONSTRUCTORS(Directory);
};

}  // namespace bin
}  // namespace dart

#endif  // RUNTIME_BIN_DIRECTORY_H_
