#pragma once

#include <uv.h>

#include <stdexcept>

#include "common.hpp"
#include "components/logger.hpp"
#include "utils/mixins.hpp"

POLYBAR_NS

namespace eventloop {
/**
 * Runs any libuv function with an integer error code return value and throws an
 * exception on error.
 */
#define UV(fun, ...)                                                                                    \
  do {                                                                                                  \
    int res = fun(__VA_ARGS__);                                                                         \
    if (res < 0) {                                                                                      \
      throw std::runtime_error(__FILE__ ":"s + std::to_string(__LINE__) +                               \
                               ": libuv error for '" #fun "(" #__VA_ARGS__ ")': "s + uv_strerror(res)); \
    }                                                                                                   \
  } while (0);

  using cb_void = function<void(void)>;

  template <typename Event>
  using cb_event = std::function<void(const Event&)>;

  template <typename Self, typename H>
  class Handle : public non_copyable_mixin, public non_movable_mixin {
   public:
    Handle(uv_loop_t* l) : uv_loop(l) {
      get()->data = this;
    }

    Self& leak(std::unique_ptr<Self> h) {
      lifetime_extender = std::move(h);
      return *lifetime_extender;
    }

    void unleak() {
      lifetime_extender.reset();
    }

    H* raw() {
      return get();
    }

    const H* raw() const {
      return get();
    }

    /**
     * Close this handle and free associated memory.
     *
     * After this function returns, any reference to this object should be considered invalid.
     */
    void close() {
      if (!is_closing()) {
        uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast<Self*>(handle->data)); });
      }
    }

    bool is_closing() const {
      return uv_is_closing(this->template get<uv_handle_t>());
    }

    bool is_active() {
      return uv_is_active(this->template get<uv_handle_t>()) != 0;
    }

   protected:
    /**
     * Generic callback function that can be used for all uv handle callbacks.
     *
     * @tparam Event Event class/struct. Must have a constructor that takes all arguments passed to the uv callback,
     * except for the handle argument.
     * @tparam Member Pointer to class member where callback function is stored
     * @tparam Args Additional arguments in the uv callback. Inferred by the compiler
     */
    template <typename Event, cb_event<Event> Self::*Member, typename... Args>
    static void event_cb(H* handle, Args... args) {
      Self& This = *static_cast<Self*>(handle->data);
      (This.*Member)(Event{std::forward<Args>(args)...});
    }

    /**
     * Same as event_cb except that no event is constructed.
     */
    template <cb_void Self::*Member>
    static void void_event_cb(H* handle) {
      Self& This = *static_cast<Self*>(handle->data);
      (This.*Member)();
    }

    static Self& cast(H* handle) {
      return *static_cast<Self*>(handle->data);
    }

    template <typename T = H>
    T* get() {
      return reinterpret_cast<T*>(&uv_handle);
    }

    template <typename T = H>
    const T* get() const {
      return reinterpret_cast<const T*>(&uv_handle);
    }

    uv_loop_t* loop() const {
      return uv_loop;
    }

    static void close_callback(Self& self) {
      self.unleak();
    }

    static void alloc_callback(uv_handle_t*, size_t, uv_buf_t* buf) {
      buf->base = new char[BUFSIZ];
      buf->len = BUFSIZ;
    }

   private:
    H uv_handle;
    uv_loop_t* uv_loop;

    /**
     * The handle stores the unique_ptr to itself so that it effectively leaks memory.
     *
     * This saves us from having to guarantee that the handle's lifetime extends to at least after it is closed.
     *
     * Once the handle is closed, either explicitly or by walking all handles when the loop shuts down, this reference
     * is reset and the object is explicitly destroyed.
     */
    std::unique_ptr<Self> lifetime_extender;
  };

  struct ErrorEvent {
    int status;
  };

  using cb_error = cb_event<ErrorEvent>;

  class WriteRequest : public non_copyable_mixin {
   public:
    using cb_write = cb_void;

    WriteRequest(cb_write user_cb, cb_error err_cb) : write_callback(user_cb), write_err_cb(err_cb) {
      get()->data = this;
    };

    static WriteRequest& create(cb_write user_cb, cb_error err_cb) {
      auto r = std::make_unique<WriteRequest>(user_cb, err_cb);
      return r->leak(std::move(r));
    };

    uv_write_t* get() {
      return &req;
    }

    /**
     * Trigger the write callback.
     *
     * After that, this object is destroyed.
     */
    void trigger(int status) {
      if (status < 0) {
        if (write_err_cb) {
          write_err_cb(ErrorEvent{status});
        }
      } else {
        if (write_callback) {
          write_callback();
        }
      }

      unleak();
    }

   protected:
    WriteRequest& leak(std::unique_ptr<WriteRequest> h) {
      lifetime_extender = std::move(h);
      return *lifetime_extender;
    }

    void unleak() {
      lifetime_extender.reset();
    }

   private:
    uv_write_t req;

    cb_write write_callback;
    cb_error write_err_cb;

    /**
     * The handle stores the unique_ptr to itself so that it effectively leaks memory.
     *
     * This means that each instance manages its own lifetime.
     */
    std::unique_ptr<WriteRequest> lifetime_extender;
  };

  struct SignalEvent {
    int signum;
  };

  class SignalHandle : public Handle<SignalHandle, uv_signal_t> {
   public:
    using Handle::Handle;
    using cb = cb_event<SignalEvent>;

    void init();
    void start(int signum, cb user_cb);

   private:
    cb callback;
  };

  struct PollEvent {
    uv_poll_event event;
  };

  class PollHandle : public Handle<PollHandle, uv_poll_t> {
   public:
    using Handle::Handle;
    using cb = cb_event<PollEvent>;

    void init(int fd);
    void start(int events, cb user_cb, cb_error err_cb);
    static void poll_callback(uv_poll_t*, int status, int events);

   private:
    cb callback;
    cb_error err_cb;
  };

  struct FSEvent {
    const char* path;
    uv_fs_event event;
  };

  class FSEventHandle : public Handle<FSEventHandle, uv_fs_event_t> {
   public:
    using Handle::Handle;
    using cb = cb_event<FSEvent>;

    void init();
    void start(const string& path, int flags, cb user_cb, cb_error err_cb);
    static void fs_event_callback(uv_fs_event_t*, const char* path, int events, int status);

   private:
    cb callback;
    cb_error err_cb;
  };

  class TimerHandle : public Handle<TimerHandle, uv_timer_t> {
   public:
    using Handle::Handle;
    using cb = cb_void;

    void init();
    void start(uint64_t timeout, uint64_t repeat, cb user_cb);
    void stop();

   private:
    cb callback;
  };

  class AsyncHandle : public Handle<AsyncHandle, uv_async_t> {
   public:
    using Handle::Handle;
    using cb = cb_void;

    void init(cb user_cb);
    void send();

   private:
    cb callback;
  };

  struct ReadEvent {
    const char* data;
    size_t len;
  };

  template <typename Self, typename H>
  class StreamHandle : public Handle<Self, H> {
   public:
    using Handle<Self, H>::Handle;
    using cb_read = cb_event<ReadEvent>;
    using cb_read_eof = cb_void;
    using cb_connection = cb_void;

    void listen(int backlog, cb_connection user_cb, cb_error err_cb) {
      this->connection_callback = user_cb;
      this->connection_err_cb = err_cb;
      UV(uv_listen, this->template get<uv_stream_t>(), backlog, connection_cb);
    };

    static void connection_cb(uv_stream_t* server, int status) {
      auto& self = Self::cast((H*)server);

      if (status == 0) {
        self.connection_callback();
      } else {
        self.connection_err_cb(ErrorEvent{status});
      }
    }

    template <typename ClientSelf, typename ClientH>
    void accept(StreamHandle<ClientSelf, ClientH>& client) {
      UV(uv_accept, this->template get<uv_stream_t>(), client.template get<uv_stream_t>());
    }

    void read_start(cb_read fun, cb_void eof_cb, cb_error err_cb) {
      this->read_callback = fun;
      this->read_eof_cb = eof_cb;
      this->read_err_cb = err_cb;
      UV(uv_read_start, this->template get<uv_stream_t>(), &this->alloc_callback, read_cb);
    };

    static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
      auto& self = Self::cast((H*)handle);
      /*
       * Wrap pointer so that it gets automatically freed once the function returns (even with exceptions)
       */
      auto buf_ptr = unique_ptr<char[]>(buf->base);
      if (nread > 0) {
        self.read_callback(ReadEvent{buf->base, (size_t)nread});
      } else if (nread < 0) {
        if (nread != UV_EOF) {
          self.read_err_cb(ErrorEvent{(int)nread});
        } else {
          self.read_eof_cb();
        }
      }
    };

    void write(const vector<uint8_t>& data, WriteRequest::cb_write user_cb = {}, cb_error err_cb = {}) {
      WriteRequest& req = WriteRequest::create(user_cb, err_cb);

      uv_buf_t buf{(char*)data.data(), data.size()};

      UV(uv_write, req.get(), this->template get<uv_stream_t>(), &buf, 1,
          [](uv_write_t* r, int status) { static_cast<WriteRequest*>(r->data)->trigger(status); });
    }

   private:
    /**
     * Callback for receiving data
     */
    cb_read read_callback;

    /**
     * Callback for receiving EOF.
     *
     * Called after the associated handle has been closed.
     */
    cb_read_eof read_eof_cb;

    /**
     * Called if an error occurs.
     */
    cb_error read_err_cb;

    cb_connection connection_callback;
    cb_error connection_err_cb;
  };

  class PipeHandle : public StreamHandle<PipeHandle, uv_pipe_t> {
   public:
    using StreamHandle::StreamHandle;
    using cb_connect = cb_void;

    void init(bool ipc = false);
    void open(int fd);

    void bind(const string& path);

    void connect(const string& name, cb_connect user_cb, cb_error err_cb);

   private:
    static void connect_cb(uv_connect_t* req, int status);

    cb_error connect_err_cb;
    cb_connect connect_callback;
  };

  class PrepareHandle : public Handle<PrepareHandle, uv_prepare_t> {
   public:
    using Handle::Handle;
    using cb = cb_void;

    void init();
    void start(cb user_cb);

   private:
    static void connect_cb(uv_connect_t* req, int status);

    cb callback;
  };

  class loop : public non_copyable_mixin, public non_movable_mixin {
   public:
    loop();
    ~loop();
    void run();
    void stop();
    uint64_t now() const;

    template <typename H, typename... Args>
    H& handle(Args... args) {
      auto ptr = make_unique<H>(get());
      ptr->init(std::forward<Args>(args)...);
      return ptr->leak(std::move(ptr));
    }

    uv_loop_t* get() const;

   private:
    std::unique_ptr<uv_loop_t> m_loop{nullptr};
  };

} // namespace eventloop

POLYBAR_NS_END