/*
   Mathieu Stefani, 15 juin 2016

   A lightweight implementation of the Reactor design-pattern.

   The main goal of this component is to provide an solid abstraction
   that can be used internally and by client code to dispatch I/O events
   to callbacks and handlers, in an efficient way.
*/

#pragma once

#include <pistache/flags.h>
#include <pistache/net.h>
#include <pistache/os.h>
#include <pistache/prototype.h>

#include <sys/resource.h>
#include <sys/time.h>

#include <memory>
#include <thread>
#include <vector>

namespace Pistache {
namespace Aio {

// A set of fds that are ready
class FdSet {
public:
  FdSet() = delete;

  explicit FdSet(std::vector<Polling::Event> &&events) : events_() {
    events_.reserve(events.size());
    events_.insert(events_.end(), std::make_move_iterator(events.begin()),
                   std::make_move_iterator(events.end()));
  }

  struct Entry : private Polling::Event {
    Entry(Polling::Event &&event) : Polling::Event(std::move(event)) {}

    bool isReadable() const { return flags.hasFlag(Polling::NotifyOn::Read); }
    bool isWritable() const { return flags.hasFlag(Polling::NotifyOn::Write); }
    bool isHangup() const { return flags.hasFlag(Polling::NotifyOn::Hangup); }

    Polling::Tag getTag() const { return this->tag; }
  };

  using iterator = std::vector<Entry>::iterator;
  using const_iterator = std::vector<Entry>::const_iterator;

  size_t size() const { return events_.size(); }

  const Entry &at(size_t index) const { return events_.at(index); }

  const Entry &operator[](size_t index) const { return events_.at(index); }

  iterator begin() { return events_.begin(); }

  iterator end() { return events_.end(); }

  const_iterator begin() const { return events_.begin(); }

  const_iterator end() const { return events_.end(); }

private:
  std::vector<Entry> events_;
};

class Handler;
class ExecutionContext;

class Reactor : public std::enable_shared_from_this<Reactor> {
public:
  class Impl;

  Reactor();
  ~Reactor();

  struct Key {

    Key();

    friend class Reactor;
    friend class Impl;
    friend class SyncImpl;
    friend class AsyncImpl;

    uint64_t data() const { return data_; }

  private:
    explicit Key(uint64_t data);
    uint64_t data_;
  };

  static std::shared_ptr<Reactor> create();

  void init();
  void init(const ExecutionContext &context);

  Key addHandler(const std::shared_ptr<Handler> &handler);

  std::vector<std::shared_ptr<Handler>> handlers(const Key &key);

  void registerFd(const Key &key, Fd fd, Polling::NotifyOn interest,
                  Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level);
  void registerFdOneShot(const Key &key, Fd fd, Polling::NotifyOn interest,
                         Polling::Tag tag,
                         Polling::Mode mode = Polling::Mode::Level);

  void registerFd(const Key &key, Fd fd, Polling::NotifyOn interest,
                  Polling::Mode mode = Polling::Mode::Level);
  void registerFdOneShot(const Key &key, Fd fd, Polling::NotifyOn interest,
                         Polling::Mode mode = Polling::Mode::Level);

  void modifyFd(const Key &key, Fd fd, Polling::NotifyOn interest,
                Polling::Mode mode = Polling::Mode::Level);

  void modifyFd(const Key &key, Fd fd, Polling::NotifyOn interest,
                Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level);

  void removeFd(const Key &key, Fd fd);

  void runOnce();
  void run();

  void shutdown();

private:
  Impl *impl() const;
  std::unique_ptr<Impl> impl_;
};

class ExecutionContext {
public:
  virtual ~ExecutionContext() {}
  virtual Reactor::Impl *makeImpl(Reactor *reactor) const = 0;
};

class SyncContext : public ExecutionContext {
public:
  virtual ~SyncContext() {}
  Reactor::Impl *makeImpl(Reactor *reactor) const override;
};

class AsyncContext : public ExecutionContext {
public:
  explicit AsyncContext(size_t threads, const std::string &threadsName = "")
      : threads_(threads), threadsName_(threadsName) {}

  virtual ~AsyncContext() {}

  Reactor::Impl *makeImpl(Reactor *reactor) const override;

  static AsyncContext singleThreaded();

private:
  size_t threads_;
  std::string threadsName_;
};

class Handler : public Prototype<Handler> {
public:
  friend class Reactor;
  friend class SyncImpl;
  friend class AsyncImpl;

  Handler() : reactor_(nullptr), context_(), key_() {}

  struct Context {
    friend class SyncImpl;

    Context() : tid() {}

    std::thread::id thread() const { return tid; }

  private:
    std::thread::id tid;
  };

  virtual void onReady(const FdSet &fds) = 0;
  virtual void registerPoller(Polling::Epoll &poller) = 0;

  Reactor *reactor() const { return reactor_; }

  Context context() const { return context_; }

  Reactor::Key key() const { return key_; };

  virtual ~Handler() {}

private:
  Reactor *reactor_;
  Context context_;
  Reactor::Key key_;
};

} // namespace Aio
} // namespace Pistache
