//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11

#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <random>
#include <type_traits>
#include <vector>

#include "fuzz.h"

template <class IntT>
std::vector<IntT> GetValues(const std::uint8_t *data, std::size_t size) {
  std::vector<IntT> result;
  while (size >= sizeof(IntT)) {
    IntT tmp;
    std::memcpy(&tmp, data, sizeof(IntT));
    size -= sizeof(IntT);
    data += sizeof(IntT);
    result.push_back(tmp);
  }
  return result;
}

template <class Dist>
struct ParamTypeHelper {
  using ParamT = typename Dist::param_type;
  using ResultT = typename Dist::result_type;
  static_assert(std::is_same<ResultT, typename ParamT::distribution_type::result_type>::value, "");

  static ParamT Create(const uint8_t* data, std::size_t size, bool &OK) {
    constexpr bool select_vector_result = std::is_constructible<ParamT, ResultT*, ResultT*, ResultT*>::value;
    constexpr bool select_vector_double = std::is_constructible<ParamT, double*, double*>::value;
    constexpr int selector = select_vector_result ? 0 : (select_vector_double ? 1 : 2);
    return DispatchAndCreate(std::integral_constant<int, selector>{}, data, size, OK);
  }

  // Vector result
  static ParamT DispatchAndCreate(std::integral_constant<int, 0>, const std::uint8_t *data, std::size_t size, bool &OK) {
    auto Input = GetValues<ResultT>(data, size);
    OK = false;
    if (Input.size() < 10)
      return ParamT{};
    OK = true;
    auto Beg = Input.begin();
    auto End = Input.end();
    auto Mid = Beg + ((End - Beg) / 2);

    assert(Mid - Beg <= (End  -  Mid));
    ParamT p(Beg, Mid, Mid);
    return p;
  }

  // Vector double
  static ParamT DispatchAndCreate(std::integral_constant<int, 1>, const std::uint8_t *data, std::size_t size, bool &OK) {
    auto Input = GetValues<double>(data, size);

    OK = true;
    auto Beg = Input.begin();
    auto End = Input.end();

    ParamT p(Beg, End);
    return p;
  }

  // Default
  static ParamT DispatchAndCreate(std::integral_constant<int, 2>, const std::uint8_t *data, std::size_t size, bool &OK) {
    OK = false;
    if (size < sizeof(ParamT))
      return ParamT{};
    OK = true;
    ParamT input;
    std::memcpy(&input, data, sizeof(ParamT));
    return input;
  }
};

template <class IntT>
struct ParamTypeHelper<std::poisson_distribution<IntT>> {
  using Dist = std::poisson_distribution<IntT>;
  using ParamT = typename Dist::param_type;
  using ResultT = typename Dist::result_type;

  static ParamT Create(const std::uint8_t *data, std::size_t size, bool& OK) {
    OK = false;
    auto vals = GetValues<double>(data, size);
    if (vals.empty() || std::isnan(vals[0]) || std::isnan(std::abs(vals[0])) || vals[0] < 0)
      return ParamT{};
    OK = true;
    return ParamT{vals[0]};
  }
};

template <class IntT>
struct ParamTypeHelper<std::geometric_distribution<IntT>> {
  using Dist = std::geometric_distribution<IntT>;
  using ParamT = typename Dist::param_type;
  using ResultT = typename Dist::result_type;

  static ParamT Create(const std::uint8_t *data, std::size_t size, bool& OK) {
    OK = false;
    auto vals = GetValues<double>(data, size);
    if (vals.empty() || std::isnan(vals[0]) || vals[0] < 0 )
      return ParamT{};
    OK = true;
    return ParamT{vals[0]};
  }
};

template <class IntT>
struct ParamTypeHelper<std::lognormal_distribution<IntT>> {
  using Dist = std::lognormal_distribution<IntT>;
  using ParamT = typename Dist::param_type;
  using ResultT = typename Dist::result_type;

  static ParamT Create(const std::uint8_t *data, std::size_t size, bool& OK) {
    OK = false;
    auto vals = GetValues<ResultT>(data, size);
    if (vals.size() < 2 )
      return ParamT{};
    OK = true;
    return ParamT{vals[0], vals[1]};
  }
};

template <>
struct ParamTypeHelper<std::bernoulli_distribution> {
  using Dist = std::bernoulli_distribution;
  using ParamT = Dist::param_type;
  using ResultT = Dist::result_type;

  static ParamT Create(const std::uint8_t *data, std::size_t size, bool& OK) {
    OK = false;
    auto vals = GetValues<double>(data, size);
    if (vals.empty())
      return ParamT{};
    OK = true;
    return ParamT{vals[0]};
  }
};

template <class Distribution>
int helper(const std::uint8_t *data, std::size_t size) {
  std::mt19937 engine;
  using ParamT = typename Distribution::param_type;
  bool OK;
  ParamT p = ParamTypeHelper<Distribution>::Create(data, size, OK);
  if (!OK)
    return 0;
  Distribution d(p);
  volatile auto res = d(engine);
  if (std::isnan(res)) {
    // FIXME(llvm.org/PR44289):
    // Investigate why these distributions are returning NaN and decide
    // if that's what we want them to be doing.
    //
    // Make this assert false (or return non-zero).
    return 0;
  }
  return 0;
}

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
  return helper<std::uniform_int_distribution<std::int16_t>>(data, size)       ||
         helper<std::uniform_real_distribution<float>>(data, size)             ||
         helper<std::bernoulli_distribution>(data, size)                       ||
         helper<std::poisson_distribution<std::int16_t>>(data, size)           ||
         helper<std::geometric_distribution<std::int16_t>>(data, size)         ||
         helper<std::binomial_distribution<std::int16_t>>(data, size)          ||
         helper<std::negative_binomial_distribution<std::int16_t>>(data, size) ||
         helper<std::exponential_distribution<float>>(data, size)              ||
         helper<std::gamma_distribution<float>>(data, size)                    ||
         helper<std::weibull_distribution<float>>(data, size)                  ||
         helper<std::extreme_value_distribution<float>>(data, size)            ||
         helper<std::normal_distribution<float>>(data, size)                   ||
         helper<std::lognormal_distribution<float>>(data, size)                ||
         helper<std::chi_squared_distribution<float>>(data, size)              ||
         helper<std::cauchy_distribution<float>>(data, size)                   ||
         helper<std::fisher_f_distribution<float>>(data, size)                 ||
         helper<std::student_t_distribution<float>>(data, size)                ||
         helper<std::discrete_distribution<std::int16_t>>(data, size)          ||
         helper<std::piecewise_constant_distribution<float>>(data, size)       ||
         helper<std::piecewise_linear_distribution<float>>(data, size);
}
