//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/actor/actor.h"

#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"

#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/port/Fd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"

namespace td {

class FdInterface {
 public:
  FdInterface() = default;
  FdInterface(const FdInterface &) = delete;
  FdInterface &operator=(const FdInterface &) = delete;
  FdInterface(FdInterface &&) = default;
  FdInterface &operator=(FdInterface &&) = default;
  virtual ~FdInterface() = default;
  virtual const Fd &get_fd() const = 0;
  virtual Fd &get_fd() = 0;
  virtual int32 get_flags() const = 0;
  virtual Status get_pending_error() TD_WARN_UNUSED_RESULT = 0;

  virtual Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT = 0;
  virtual Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT = 0;

  virtual void close() = 0;
  virtual bool empty() const = 0;
};

template <class FdT>
class FdToInterface : public FdInterface {
 public:
  FdToInterface() = default;
  explicit FdToInterface(FdT fd) : fd_(std::move(fd)) {
  }

  const Fd &get_fd() const final {
    return fd_.get_fd();
  }
  Fd &get_fd() final {
    return fd_.get_fd();
  }
  int32 get_flags() const final {
    return fd_.get_flags();
  }
  Status get_pending_error() final TD_WARN_UNUSED_RESULT {
    return fd_.get_pending_error();
  }

  Result<size_t> write(Slice slice) final TD_WARN_UNUSED_RESULT {
    return fd_.write(slice);
  }
  Result<size_t> read(MutableSlice slice) final TD_WARN_UNUSED_RESULT {
    return fd_.read(slice);
  }

  void close() final {
    fd_.close();
  }
  bool empty() const final {
    return fd_.empty();
  }

 private:
  FdT fd_;
};

template <class FdT>
std::unique_ptr<FdInterface> make_fd_interface(FdT fd) {
  return make_unique<FdToInterface<FdT>>(std::move(fd));
}

class FdProxy {
 public:
  FdProxy() = default;
  explicit FdProxy(std::unique_ptr<FdInterface> fd) : fd_(std::move(fd)) {
  }

  const Fd &get_fd() const {
    return fd_->get_fd();
  }
  Fd &get_fd() {
    return fd_->get_fd();
  }
  int32 get_flags() const {
    return fd_->get_flags();
  }
  Status get_pending_error() TD_WARN_UNUSED_RESULT {
    return fd_->get_pending_error();
  }

  Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT {
    return fd_->write(slice);
  }
  Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT {
    return fd_->read(slice);
  }

  void close() {
    fd_->close();
  }
  bool empty() const {
    return fd_->empty();
  }

 private:
  std::unique_ptr<FdInterface> fd_;
};

template <class FdT>
FdProxy make_fd_proxy(FdT fd) {
  return FdProxy(make_fd_interface(std::move(fd)));
}

namespace detail {
class HttpConnectionBase : public Actor {
 public:
  void write_next(BufferSlice buffer);
  void write_ok();
  void write_error(Status error);

 protected:
  enum class State { Read, Write, Close };
  template <class FdT>
  HttpConnectionBase(State state, FdT fd, size_t max_post_size, size_t max_files, int32 idle_timeout)
      : HttpConnectionBase(state, make_fd_proxy(std::move(fd)), max_post_size, max_files, idle_timeout) {
  }
  HttpConnectionBase(State state, FdProxy fd, size_t max_post_size, size_t max_files, int32 idle_timeout);

 private:
  using StreamConnection = BufferedFd<FdProxy>;
  State state_;
  StreamConnection stream_connection_;
  size_t max_post_size_;
  size_t max_files_;
  int32 idle_timeout_;
  HttpReader reader_;
  HttpQueryPtr current_query_;
  bool close_after_write_ = false;

  void live_event();

  void start_up() override;
  void tear_down() override;
  void timeout_expired() override;
  void loop() override;

  virtual void on_query(HttpQueryPtr) = 0;
  virtual void on_error(Status error) = 0;
};
}  // namespace detail
}  // namespace td