// SPDX-License-Identifier: Apache-2.0
//
// Copyright 2008-2016 Conrad Sanderson (http://conradsanderson.id.au)
// Copyright 2008-2016 National ICT Australia (NICTA)
//
// 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.
// ------------------------------------------------------------------------

//! \addtogroup arma_rng_cxx03
//! @{

class arma_rng_cxx03 {
 public:
  typedef unsigned int seed_type;

  inline static void set_seed(const seed_type val);

  arma_inline static int randi_val();
  arma_inline static double randu_val();
  inline static double randn_val();

  template <typename eT>
  inline static void randn_dual_val(eT& out1, eT& out2);

  template <typename eT>
  inline static void randi_fill(eT* mem, const uword N, const int a, const int b);

  inline static int randi_max_val();
};

inline void arma_rng_cxx03::set_seed(const arma_rng_cxx03::seed_type val) {
  std::srand(val);
}

arma_inline int arma_rng_cxx03::randi_val() {
#if (RAND_MAX == 32767)
  {
    // NOTE: this is a better-than-nothing solution
    // NOTE: see also arma_rng_cxx03::randi_max_val()

    u32 val1 = u32(std::rand());
    u32 val2 = u32(std::rand());

    val1 <<= 15;

    return (val1 | val2);
  }
#else
  {
    return std::rand();
  }
#endif
}

arma_inline double arma_rng_cxx03::randu_val() {
  return double(double(randi_val()) * (double(1) / double(randi_max_val())));
}

inline double arma_rng_cxx03::randn_val() {
  // polar form of the Box-Muller transformation:
  // http://en.wikipedia.org/wiki/Box-Muller_transformation
  // http://en.wikipedia.org/wiki/Marsaglia_polar_method

  double tmp1 = double(0);
  double tmp2 = double(0);
  double w = double(0);

  do {
    tmp1 = double(2) * double(randi_val()) * (double(1) / double(randi_max_val())) -
           double(1);
    tmp2 = double(2) * double(randi_val()) * (double(1) / double(randi_max_val())) -
           double(1);

    w = tmp1 * tmp1 + tmp2 * tmp2;
  } while (w >= double(1));

  return double(tmp1 * std::sqrt((double(-2) * std::log(w)) / w));
}

template <typename eT>
inline void arma_rng_cxx03::randn_dual_val(eT& out1, eT& out2) {
  // make sure we are internally using at least floats
  typedef typename promote_type<eT, float>::result eTp;

  eTp tmp1 = eTp(0);
  eTp tmp2 = eTp(0);
  eTp w = eTp(0);

  do {
    tmp1 = eTp(2) * eTp(randi_val()) * (eTp(1) / eTp(randi_max_val())) - eTp(1);
    tmp2 = eTp(2) * eTp(randi_val()) * (eTp(1) / eTp(randi_max_val())) - eTp(1);

    w = tmp1 * tmp1 + tmp2 * tmp2;
  } while (w >= eTp(1));

  const eTp k = std::sqrt((eTp(-2) * std::log(w)) / w);

  out1 = eT(tmp1 * k);
  out2 = eT(tmp2 * k);
}

template <typename eT>
inline void arma_rng_cxx03::randi_fill(eT* mem, const uword N, const int a, const int b) {
  if ((a == 0) && (b == RAND_MAX)) {
    for (uword i = 0; i < N; ++i) {
      mem[i] = eT(std::rand());
    }
  } else {
    const uword length = uword(b - a + 1);

    const double scale = double(length) / double(randi_max_val());

    for (uword i = 0; i < N; ++i) {
      mem[i] = eT((std::min)(b, (int(double(randi_val()) * scale) + a)));
    }
  }
}

inline int arma_rng_cxx03::randi_max_val() {
#if (RAND_MAX == 32767)
  return ((32767 << 15) + 32767);
#else
  return RAND_MAX;
#endif
}

//! @}
