// Copyright 2016-2019 Envoy Project Authors
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <atomic>
#include <chrono>
#include <ctime>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "include/proxy-wasm/sdk.h"
#include "include/proxy-wasm/context_interface.h"

namespace proxy_wasm {

class PluginHandleBase;
class WasmBase;
class WasmVm;

/**
 * PluginBase is container to hold plugin information which is shared with all Context(s) created
 * for a given plugin. Embedders may extend this class with additional host-specific plugin
 * information as required.
 * @param name is the name of the plugin.
 * @param root_id is an identifier for the in VM handlers for this plugin.
 * @param vm_id is a string used to differentiate VMs with the same code and VM configuration.
 * @param plugin_configuration is configuration for this plugin.
 * @param fail_open if true the plugin will pass traffic as opposed to close all streams.
 * @param key is used to uniquely identify this plugin instance.
 */
struct PluginBase {
  PluginBase(std::string_view name, std::string_view root_id, std::string_view vm_id,
             std::string_view engine, std::string_view plugin_configuration, bool fail_open,
             std::string_view key)
      : name_(std::string(name)), root_id_(std::string(root_id)), vm_id_(std::string(vm_id)),
        engine_(std::string(engine)), plugin_configuration_(plugin_configuration),
        fail_open_(fail_open), key_(makePluginKey(root_id, plugin_configuration, key)),
        log_prefix_(makeLogPrefix()) {}

  const std::string name_;
  const std::string root_id_;
  const std::string vm_id_;
  const std::string engine_;
  const std::string plugin_configuration_;
  const bool fail_open_;

  const std::string &key() const { return key_; }
  const std::string &log_prefix() const { return log_prefix_; }

private:
  std::string makeLogPrefix() const;
  static std::string makePluginKey(std::string_view root_id, std::string_view plugin_configuration,
                                   std::string_view key);

  const std::string key_;
  const std::string log_prefix_;
};

struct BufferBase : public BufferInterface {
  BufferBase() = default;
  ~BufferBase() override = default;

  // BufferInterface
  size_t size() const override {
    if (owned_data_) {
      return owned_data_size_;
    }
    return data_.size();
  }
  WasmResult copyTo(WasmBase *wasm, size_t start, size_t length, uint64_t ptr_ptr,
                    uint64_t size_ptr) const override;
  WasmResult copyFrom(size_t /* start */, size_t /* length */,
                      std::string_view /* data */) override {
    // Setting a string buffer not supported (no use case).
    return WasmResult::BadArgument;
  }

  virtual void clear() {
    data_ = "";
    owned_data_ = nullptr;
  }
  BufferBase *set(std::string_view data) {
    clear();
    data_ = data;
    return this;
  }
  BufferBase *set(std::unique_ptr<char[]> owned_data, uint32_t owned_data_size) {
    clear();
    owned_data_ = std::move(owned_data);
    owned_data_size_ = owned_data_size;
    return this;
  }

protected:
  std::string_view data_;
  std::unique_ptr<char[]> owned_data_;
  uint32_t owned_data_size_;
};

/**
 * ContextBase is the interface between the VM host and the VM. It has several uses:
 *
 * 1) To provide host-specific implementations of ABI calls out of the VM. For example, a proxy
 * which wants to provide the ability to make an HTTP call must implement the
 * ContextBase::httpCall() method.
 *
 * 2) To call into the VM. For example, when the above mentioned httpCall() completes, the host must
 * call ContextBase::onHttpCallResponse(). Similarly, when a new HTTP request arrives and the
 * headers are available, the host must create a new ContextBase object to manage the new stream and
 * call onRequestHeaders() on that object which will cause a corresponding Context to be allocated
 * in the VM which will receive the proxy_on_context_create and proxy_on_request_headers calls.
 *
 * 3) For testing and instrumentation the methods of ContextBase can be replaces or augmented.
 */
class ContextBase : public RootInterface,
                    public HttpInterface,
                    public NetworkInterface,
                    public StreamInterface,
                    public HeaderInterface,
                    public HttpCallInterface,
                    public GrpcCallInterface,
                    public GrpcStreamInterface,
                    public MetricsInterface,
                    public SharedDataInterface,
                    public SharedQueueInterface,
                    public GeneralInterface {
public:
  ContextBase();                                                          // Testing.
  ContextBase(WasmBase *wasm);                                            // Vm Context.
  ContextBase(WasmBase *wasm, const std::shared_ptr<PluginBase> &plugin); // Root Context.
  ContextBase(WasmBase *wasm, uint32_t parent_context_id,
              const std::shared_ptr<PluginHandleBase> &plugin_handle); // Stream context.
  virtual ~ContextBase();

  WasmBase *wasm() const { return wasm_; }
  uint32_t id() const { return id_; }
  // The VM Context used for calling "malloc" has an id_ == 0.
  bool isVmContext() const { return id_ == 0; }
  // Root Contexts have the VM Context as a parent.
  bool isRootContext() const { return parent_context_id_ == 0; }
  ContextBase *parent_context() const { return parent_context_; }
  ContextBase *root_context() const {
    const ContextBase *previous = this;
    ContextBase *parent = parent_context_;
    while (parent != previous) {
      previous = parent;
      parent = parent->parent_context_;
    }
    return parent;
  }
  std::string_view root_id() const { return isRootContext() ? root_id_ : plugin_->root_id_; }
  std::string_view log_prefix() const {
    return isRootContext() ? root_log_prefix_ : plugin_->log_prefix();
  }
  WasmVm *wasmVm() const;

  // Called before deleting the context.
  virtual void destroy();

  /**
   * Calls into the VM.
   * These are implemented by the proxy-independent host code. They are virtual to support some
   * types of testing.
   */

  // Context
  void onCreate() override;
  bool onDone() override;
  void onLog() override;
  void onDelete() override;
  void onForeignFunction(uint32_t foreign_function_id, uint32_t data_size) override;

  // Root
  bool onStart(std::shared_ptr<PluginBase> plugin) override;
  bool onConfigure(std::shared_ptr<PluginBase> plugin) override;
  void onTick(TimerToken token) override;
  void onQueueReady(SharedQueueDequeueToken token) override;

  // HTTP
  FilterHeadersStatus onRequestHeaders(uint32_t headers, bool end_of_stream) override;
  FilterDataStatus onRequestBody(uint32_t body_length, bool end_of_stream) override;
  FilterTrailersStatus onRequestTrailers(uint32_t trailers) override;
  FilterMetadataStatus onRequestMetadata(uint32_t elements) override;
  FilterHeadersStatus onResponseHeaders(uint32_t headers, bool end_of_stream) override;
  FilterDataStatus onResponseBody(uint32_t body_length, bool end_of_stream) override;
  FilterTrailersStatus onResponseTrailers(uint32_t trailers) override;
  FilterMetadataStatus onResponseMetadata(uint32_t elements) override;

  // Network
  FilterStatus onNetworkNewConnection() override;
  FilterStatus onDownstreamData(uint32_t data_length, bool end_of_stream) override;
  FilterStatus onUpstreamData(uint32_t data_length, bool end_of_stream) override;
  void onDownstreamConnectionClose(CloseType) override;
  void onUpstreamConnectionClose(CloseType) override;

  // Async call response.
  void onHttpCallResponse(HttpCallToken token, uint32_t headers, uint32_t body_size,
                          uint32_t trailers) override;
  // Grpc
  void onGrpcReceiveInitialMetadata(GrpcToken token, uint32_t elements) override;
  void onGrpcReceive(GrpcToken token, uint32_t response_size) override;
  void onGrpcReceiveTrailingMetadata(GrpcToken token, uint32_t trailers) override;
  void onGrpcClose(GrpcToken token, GrpcStatusCode status_code) override;

  void error(std::string_view message) override {
    std::cerr << message << "\n";
    abort();
  }
  WasmResult unimplemented() override {
    error("unimplemented proxy-wasm API");
    return WasmResult::Unimplemented;
  }
  bool isFailed();
  bool isFailOpen() { return plugin_->fail_open_; }

  //
  // General Callbacks.
  //
  WasmResult log(uint32_t /* level */, std::string_view /* message */) override {
    return unimplemented();
  }
  uint32_t getLogLevel() override {
    unimplemented();
    return 0;
  }
  uint64_t getCurrentTimeNanoseconds() override {
    unimplemented();
    return 0;
  }
  uint64_t getMonotonicTimeNanoseconds() override {
    unimplemented();
    return 0;
  }
  std::string_view getConfiguration() override {
    unimplemented();
    return "";
  }
  std::pair<uint32_t, std::string_view> getStatus() override {
    unimplemented();
    return std::make_pair(1, "unimplmemented");
  }
  WasmResult setTimerPeriod(std::chrono::milliseconds period, uint32_t *timer_token_ptr) override;

  // Buffer
  BufferInterface *getBuffer(WasmBufferType /* type */) override {
    unimplemented();
    return nullptr;
  }
  bool endOfStream(WasmStreamType /* type */) override {
    unimplemented();
    return true;
  }

  // HTTP
  WasmResult httpCall(std::string_view /* target */, const Pairs & /*request_headers */,
                      std::string_view /* request_body */, const Pairs & /* request_trailers */,
                      int /* timeout_millisconds */, uint32_t * /* token_ptr */) override {
    return unimplemented();
  }

  // gRPC
  WasmResult grpcCall(std::string_view /* grpc_service */, std::string_view /* service_name */,
                      std::string_view /* method_name */, const Pairs & /* initial_metadata */,
                      std::string_view /* request */, std::chrono::milliseconds /* timeout */,
                      GrpcToken * /* token_ptr */) override {
    return unimplemented();
  }
  WasmResult grpcStream(std::string_view /* grpc_service */, std::string_view /* service_name */,
                        std::string_view /* method_name */, const Pairs & /* initial_metadata */,
                        GrpcToken * /* token_ptr */) override {
    return unimplemented();
  }
  WasmResult grpcClose(uint32_t /* token */) override { // cancel on call, close on stream.
    return unimplemented();
  }
  WasmResult grpcCancel(uint32_t /* token */) override { // cancel on call, reset on stream.
    return unimplemented();
  }
  WasmResult grpcSend(uint32_t /* token */, std::string_view /* message */,
                      bool /* end_stream */) override { // stream only
    return unimplemented();
  }

  // Metrics
  WasmResult defineMetric(uint32_t /* type */, std::string_view /* name */,
                          uint32_t * /* metric_id_ptr */) override {
    return unimplemented();
  }
  WasmResult incrementMetric(uint32_t /* metric_id */, int64_t /* offset */) override {
    return unimplemented();
  }
  WasmResult recordMetric(uint32_t /* metric_id */, uint64_t /* value */) override {
    return unimplemented();
  }
  WasmResult getMetric(uint32_t /* metric_id */, uint64_t * /* value_ptr */) override {
    return unimplemented();
  }

  // Properties
  WasmResult getProperty(std::string_view /* path */, std::string * /* result */) override {
    return unimplemented();
  }
  WasmResult setProperty(std::string_view /* key */,
                         std::string_view /* serialized_value */) override {
    return unimplemented();
  }

  // Continue
  WasmResult continueStream(WasmStreamType /* stream_type */) override { return unimplemented(); }
  WasmResult closeStream(WasmStreamType /* stream_type */) override { return unimplemented(); }
  WasmResult sendLocalResponse(uint32_t /* response_code */, std::string_view /* body_text */,
                               Pairs /* additional_headers */, GrpcStatusCode /* grpc_status */,
                               std::string_view /* details */) override {
    return unimplemented();
  }
  void clearRouteCache() override { unimplemented(); }
  void failStream(WasmStreamType stream_type) override { closeStream(stream_type); }

  // Shared Data
  WasmResult getSharedData(std::string_view key,
                           std::pair<std::string, uint32_t /* cas */> *data) override;
  WasmResult setSharedData(std::string_view key, std::string_view value, uint32_t cas) override;
  WasmResult getSharedDataKeys(std::vector<std::string> *result) override;
  WasmResult removeSharedDataKey(std::string_view key, uint32_t cas,
                                 std::pair<std::string, uint32_t> *result) override;

  // Shared Queue
  WasmResult registerSharedQueue(std::string_view queue_name,
                                 SharedQueueDequeueToken *token_ptr) override;
  WasmResult lookupSharedQueue(std::string_view vm_id, std::string_view queue_name,
                               SharedQueueEnqueueToken *token_ptr) override;
  WasmResult dequeueSharedQueue(uint32_t token, std::string *data) override;
  WasmResult enqueueSharedQueue(uint32_t token, std::string_view value) override;

  // Header/Trailer/Metadata Maps
  WasmResult addHeaderMapValue(WasmHeaderMapType /* type */, std::string_view /* key */,
                               std::string_view /* value */) override {
    return unimplemented();
  }
  WasmResult getHeaderMapValue(WasmHeaderMapType /* type */, std::string_view /* key */,
                               std::string_view * /*result */) override {
    return unimplemented();
  }
  WasmResult getHeaderMapPairs(WasmHeaderMapType /* type */, Pairs * /* result */) override {
    return unimplemented();
  }
  WasmResult setHeaderMapPairs(WasmHeaderMapType /* type */, const Pairs & /* pairs */) override {
    return unimplemented();
  }

  WasmResult removeHeaderMapValue(WasmHeaderMapType /* type */,
                                  std::string_view /* key */) override {
    return unimplemented();
  }
  WasmResult replaceHeaderMapValue(WasmHeaderMapType /* type */, std::string_view /* key */,
                                   std::string_view /* value */) override {
    return unimplemented();
  }

  WasmResult getHeaderMapSize(WasmHeaderMapType /* type */, uint32_t * /* result */) override {
    return unimplemented();
  }

protected:
  friend class WasmBase;

  std::string makeRootLogPrefix(std::string_view vm_id) const;

  WasmBase *wasm_{nullptr};
  uint32_t id_{0};
  uint32_t parent_context_id_{0};                   // 0 for roots and the general context.
  ContextBase *parent_context_{nullptr};            // set in all contexts.
  std::string root_id_;                             // set only in root context.
  std::string root_log_prefix_;                     // set only in root context.
  std::shared_ptr<PluginBase> plugin_;              // set in root and stream contexts.
  std::shared_ptr<PluginHandleBase> plugin_handle_; // set only in stream context.
  std::shared_ptr<PluginBase> temp_plugin_;         // Remove once ABI v0.1.0 is gone.
  bool in_vm_context_created_ = false;
  bool destroyed_ = false;
  bool stream_failed_ = false; // Set true after failStream is called in case of VM failure.

private:
  // helper functions
  FilterHeadersStatus convertVmCallResultToFilterHeadersStatus(uint64_t result);
  FilterDataStatus convertVmCallResultToFilterDataStatus(uint64_t result);
  FilterTrailersStatus convertVmCallResultToFilterTrailersStatus(uint64_t result);
  FilterMetadataStatus convertVmCallResultToFilterMetadataStatus(uint64_t result);
};

class DeferAfterCallActions {
public:
  DeferAfterCallActions(ContextBase *context) : wasm_(context->wasm()) {}
  ~DeferAfterCallActions();

private:
  WasmBase *const wasm_;
};

uint32_t resolveQueueForTest(std::string_view vm_id, std::string_view queue_name);

} // namespace proxy_wasm
