//  Copyright (c) 2016, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

#pragma once

#if defined(LUA) && !defined(ROCKSDB_LITE)
// lua headers
extern "C" {
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
}

#include <mutex>
#include <string>
#include <vector>

#include "rocksdb/compaction_filter.h"
#include "rocksdb/env.h"
#include "rocksdb/slice.h"
#include "rocksdb/utilities/lua/rocks_lua_custom_library.h"
#include "rocksdb/utilities/lua/rocks_lua_util.h"

namespace rocksdb {
namespace lua {

struct RocksLuaCompactionFilterOptions {
  // The lua script in string that implements all necessary CompactionFilter
  // virtual functions.  The specified lua_script must implement the following
  // functions, which are Name and Filter, as described below.
  //
  // 0. The Name function simply returns a string representing the name of
  //    the lua script.  If there's any erorr in the Name function, an
  //    empty string will be used.
  //    --- Example
  //      function Name()
  //        return "DefaultLuaCompactionFilter"
  //      end
  //
  //
  // 1. The script must contains a function called Filter, which implements
  //    CompactionFilter::Filter() , takes three input arguments, and returns
  //    three values as the following API:
  //
  //   function Filter(level, key, existing_value)
  //     ...
  //     return is_filtered, is_changed, new_value
  //   end
  //
  //   Note that if ignore_value is set to true, then Filter should implement
  //   the following API:
  //
  //   function Filter(level, key)
  //     ...
  //     return is_filtered
  //   end
  //
  //   If there're any error in the Filter() function, then it will keep
  //   the input key / value pair.
  //
  //   -- Input
  //   The function must take three arguments (integer, string, string),
  //   which map to "level", "key", and "existing_value" passed from
  //   RocksDB.
  //
  //   -- Output
  //   The function must return three values (boolean, boolean, string).
  //     - is_filtered: if the first return value is true, then it indicates
  //       the input key / value pair should be filtered.
  //     - is_changed: if the second return value is true, then it indicates
  //       the existing_value needs to be changed, and the resulting value
  //       is stored in the third return value.
  //     - new_value: if the second return value is true, then this third
  //       return value stores the new value of the input key / value pair.
  //
  //   -- Examples
  //     -- a filter that keeps all key-value pairs
  //     function Filter(level, key, existing_value)
  //       return false, false, ""
  //     end
  //
  //     -- a filter that keeps all keys and change their values to "Rocks"
  //     function Filter(level, key, existing_value)
  //       return false, true, "Rocks"
  //     end

  std::string lua_script;

  // If set to true, then existing_value will not be passed to the Filter
  // function, and the Filter function only needs to return a single boolean
  // flag indicating whether to filter out this key or not.
  //
  //   function Filter(level, key)
  //     ...
  //     return is_filtered
  //   end
  bool ignore_value = false;

  // A boolean flag to determine whether to ignore snapshots.
  bool ignore_snapshots = false;

  // When specified a non-null pointer, the first "error_limit_per_filter"
  // errors of each CompactionFilter that is lua related will be included
  // in this log.
  std::shared_ptr<Logger> error_log;

  // The number of errors per CompactionFilter will be printed
  // to error_log.
  int error_limit_per_filter = 1;

  // A string to luaL_reg array map that allows the Lua CompactionFilter
  // to use custom C library.  The string will be used as the library
  // name in Lua.
  std::vector<std::shared_ptr<RocksLuaCustomLibrary>> libraries;

  ///////////////////////////////////////////////////////////////////////////
  //  NOT YET SUPPORTED
  // The name of the Lua function in "lua_script" that implements
  // CompactionFilter::FilterMergeOperand().  The function must take
  // three input arguments (integer, string, string), which map to "level",
  // "key", and "operand" passed from the RocksDB.  In addition, the
  // function must return a single boolean value, indicating whether
  // to filter the input key / operand.
  //
  // DEFAULT:  the default implementation always returns false.
  // @see CompactionFilter::FilterMergeOperand
};

class RocksLuaCompactionFilterFactory : public CompactionFilterFactory {
 public:
  explicit RocksLuaCompactionFilterFactory(
      const RocksLuaCompactionFilterOptions opt);

  virtual ~RocksLuaCompactionFilterFactory() {}

  std::unique_ptr<CompactionFilter> CreateCompactionFilter(
      const CompactionFilter::Context& context) override;

  // Change the Lua script so that the next compaction after this
  // function call will use the new Lua script.
  void SetScript(const std::string& new_script);

  // Obtain the current Lua script
  std::string GetScript();

  const char* Name() const override;

 private:
  RocksLuaCompactionFilterOptions opt_;
  std::string name_;
  // A lock to protect "opt_" to make it dynamically changeable.
  std::mutex opt_mutex_;
};

// A wrapper class that invokes Lua script to perform CompactionFilter
// functions.
class RocksLuaCompactionFilter : public rocksdb::CompactionFilter {
 public:
  explicit RocksLuaCompactionFilter(const RocksLuaCompactionFilterOptions& opt)
      : options_(opt),
        lua_state_wrapper_(opt.lua_script, opt.libraries),
        error_count_(0),
        name_("") {}

  virtual bool Filter(int level, const Slice& key, const Slice& existing_value,
                      std::string* new_value,
                      bool* value_changed) const override;
  // Not yet supported
  virtual bool FilterMergeOperand(int /*level*/, const Slice& /*key*/,
                                  const Slice& /*operand*/) const override {
    return false;
  }
  virtual bool IgnoreSnapshots() const override;
  virtual const char* Name() const override;

 protected:
  void LogLuaError(const char* format, ...) const;

  RocksLuaCompactionFilterOptions options_;
  LuaStateWrapper lua_state_wrapper_;
  mutable int error_count_;
  mutable std::string name_;
};

}  // namespace lua
}  // namespace rocksdb
#endif  // defined(LUA) && !defined(ROCKSDB_LITE)
