// Copyright 2021 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

// This file defines the memory file class. This should be the only backend file
// type defined in a header since it is the default type.

#pragma once

#include "backend.h"
#include "file.h"
#include <emscripten/threading.h>

namespace wasmfs {

// This class describes a file that lives in Wasm Memory.
class MemoryDataFile : public DataFile {
  std::vector<uint8_t> buffer;

  int open(oflags_t) override { return 0; }
  int close() override { return 0; }
  ssize_t write(const uint8_t* buf, size_t len, off_t offset) override;
  ssize_t read(uint8_t* buf, size_t len, off_t offset) override;
  int flush() override { return 0; }
  off_t getSize() override { return buffer.size(); }
  int setSize(off_t size) override {
    if (size > buffer.max_size()) {
      return -EFBIG;
    }
    buffer.resize(size);
    return 0;
  }

public:
  MemoryDataFile(mode_t mode, backend_t backend) : DataFile(mode, backend) {}

  class Handle : public DataFile::Handle {

    std::shared_ptr<MemoryDataFile> getFile() {
      return file->cast<MemoryDataFile>();
    }

  public:
    Handle(std::shared_ptr<File> dataFile) : DataFile::Handle(dataFile) {}
  };

  Handle locked() { return Handle(shared_from_this()); }
};

class MemoryDirectory : public Directory {
  // Use a vector instead of a map to save code size.
  struct ChildEntry {
    std::string name;
    std::shared_ptr<File> child;
  };

  std::vector<ChildEntry> entries;

  std::vector<ChildEntry>::iterator findEntry(const std::string& name);

protected:
  void insertChild(const std::string& name, std::shared_ptr<File> child) {
    assert(findEntry(name) == entries.end());
    entries.push_back({name, child});
  }

  std::shared_ptr<File> getChild(const std::string& name) override;

  int removeChild(const std::string& name) override;

  std::shared_ptr<DataFile> insertDataFile(const std::string& name,
                                           mode_t mode) override {
    auto child = getBackend()->createFile(mode);
    insertChild(name, child);
    return child;
  }

  std::shared_ptr<Directory> insertDirectory(const std::string& name,
                                             mode_t mode) override {
    auto child = getBackend()->createDirectory(mode);
    insertChild(name, child);
    return child;
  }

  std::shared_ptr<Symlink> insertSymlink(const std::string& name,
                                         const std::string& target) override {
    auto child = getBackend()->createSymlink(target);
    insertChild(name, child);
    return child;
  }

  int insertMove(const std::string& name, std::shared_ptr<File> file) override;

  ssize_t getNumEntries() override { return entries.size(); }
  Directory::MaybeEntries getEntries() override;

  std::string getName(std::shared_ptr<File> file) override;

  // Since we internally track files with `File` objects, we don't need the
  // dcache as well.
  bool maintainsFileIdentity() override { return true; }

public:
  MemoryDirectory(mode_t mode, backend_t backend) : Directory(mode, backend) {}
};

class MemorySymlink : public Symlink {
  std::string target;

  std::string getTarget() const override { return target; }

public:
  MemorySymlink(const std::string& target, backend_t backend)
    : Symlink(backend), target(target) {}
};

backend_t createMemoryBackend();

} // namespace wasmfs
