/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#pragma once

#include "vast/fwd.hpp"

#include "vast/bloom_filter_parameters.hpp"
#include "vast/bloom_filter_synopsis.hpp"
#include "vast/buffered_synopsis.hpp"

#include <caf/config_value.hpp>
#include <caf/settings.hpp>

#include <vast/address.hpp>
#include <vast/detail/assert.hpp>
#include <vast/error.hpp>
#include <vast/logger.hpp>

namespace vast {

template <class HashFunction>
synopsis_ptr
make_address_synopsis(vast::type type, bloom_filter_parameters params,
                      std::vector<size_t> seeds = {});

/// A synopsis for IP addresses.
template <class HashFunction>
class address_synopsis final
  : public bloom_filter_synopsis<address, HashFunction> {
public:
  using super = bloom_filter_synopsis<address, HashFunction>;

  /// Constructs an IP address synopsis from an `address_type` and a Bloom
  /// filter.
  address_synopsis(type x, typename super::bloom_filter_type bf)
    : super{std::move(x), std::move(bf)} {
    VAST_ASSERT(caf::holds_alternative<address_type>(this->type()));
  }

  bool equals(const synopsis& other) const noexcept override {
    if (typeid(other) != typeid(address_synopsis))
      return false;
    auto& rhs = static_cast<const address_synopsis&>(other);
    return this->type() == rhs.type()
           && this->bloom_filter_ == rhs.bloom_filter_;
  }
};

/// @relates buffered_synopsis_traits
template <>
struct buffered_synopsis_traits<vast::address> {
  template <typename HashFunction>
  static synopsis_ptr make(vast::type type, bloom_filter_parameters p,
                           std::vector<size_t> seeds = {}) {
    return make_address_synopsis<HashFunction>(std::move(type), std::move(p),
                                               std::move(seeds));
  }

  // Estimate the size in bytes for an unordered_set of T.
  static size_t memusage(const std::unordered_set<vast::address>& x) {
    using node_type = typename std::decay_t<decltype(x)>::node_type;
    return x.size() * sizeof(node_type);
  }
};

template <typename HashFunction>
using buffered_address_synopsis
  = buffered_synopsis<vast::address, HashFunction>;

/// Factory to construct an IP address synopsis.
/// @tparam HashFunction The hash function to use for the Bloom filter.
/// @param type A type instance carrying an `address_type`.
/// @param params The Bloom filter parameters.
/// @param seeds The seeds for the Bloom filter hasher.
/// @returns A type-erased pointer to a synopsis.
/// @pre `caf::holds_alternative<address_type>(type)`.
/// @relates address_synopsis
template <class HashFunction>
synopsis_ptr
make_address_synopsis(vast::type type, bloom_filter_parameters params,
                      std::vector<size_t> seeds) {
  VAST_ASSERT(caf::holds_alternative<address_type>(type));
  auto x = make_bloom_filter<HashFunction>(std::move(params), std::move(seeds));
  if (!x) {
    VAST_WARN("{} failed to construct Bloom filter", __func__);
    return nullptr;
  }
  using synopsis_type = address_synopsis<HashFunction>;
  return std::make_unique<synopsis_type>(std::move(type), std::move(*x));
}

/// Factory to construct a buffered IP address synopsis.
/// @tparam HashFunction The hash function to use for the Bloom filter.
/// @param type A type instance carrying an `address_type`.
/// @param params The Bloom filter parameters.
/// @param seeds The seeds for the Bloom filter hasher.
/// @returns A type-erased pointer to a synopsis.
/// @pre `caf::holds_alternative<address_type>(type)`.
/// @relates address_synopsis
template <class HashFunction>
synopsis_ptr make_buffered_address_synopsis(vast::type type,
                                            bloom_filter_parameters params) {
  VAST_ASSERT(caf::holds_alternative<address_type>(type));
  if (!params.p) {
    return nullptr;
  }
  using synopsis_type = buffered_address_synopsis<HashFunction>;
  return std::make_unique<synopsis_type>(std::move(type), *params.p);
}

/// Factory to construct an IP address synopsis. This overload looks for a type
/// attribute containing the Bloom filter parameters and hash function seeds.
/// @tparam HashFunction The hash function to use for the Bloom filter.
/// @param type A type instance carrying an `address_type`.
/// @returns A type-erased pointer to a synopsis.
/// @relates address_synopsis
template <class HashFunction>
synopsis_ptr make_address_synopsis(vast::type type, const caf::settings& opts) {
  VAST_ASSERT(caf::holds_alternative<address_type>(type));
  if (auto xs = parse_parameters(type))
    return make_address_synopsis<HashFunction>(std::move(type), std::move(*xs));
  // If no explicit Bloom filter parameters were attached to the type, we try
  // to use the maximum partition size of the index as upper bound for the
  // expected number of events.
  using int_type = caf::config_value::integer;
  auto max_part_size = caf::get_if<int_type>(&opts, "max-partition-size");
  if (!max_part_size) {
    VAST_ERROR("{} could not determine Bloom filter parameters", __func__);
    return nullptr;
  }
  bloom_filter_parameters params;
  params.n = *max_part_size;
  params.p = caf::get_or(opts, "address-synopsis-fp-rate",
                         defaults::system::address_synopsis_fp_rate);
  auto annotated_type = annotate_parameters(type, params);
  // Create either a a buffered_address_synopsis or a plain address synopsis
  // depending on the callers preference.
  auto buffered = caf::get_or(opts, "buffer-input-data", false);
  auto result
    = buffered
        ? make_buffered_address_synopsis<HashFunction>(std::move(type), params)
        : make_address_synopsis<HashFunction>(std::move(annotated_type),
                                              params);
  if (!result)
    VAST_ERROR("{} failed to evaluate Bloom filter parameters: {} {}", __func__,
               params.n, params.p);
  return result;
}

} // namespace vast
