/*
 *  Copyright 2008-2021 NVIDIA Corporation
 *
 *  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 <thrust/detail/config.h>

#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
#  pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
#  pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
#  pragma system_header
#endif // no system header

#include <thrust/random/detail/mod.h>
#include <thrust/random/detail/random_core_access.h>
#include <thrust/random/linear_congruential_engine.h>
#include <thrust/random/subtract_with_carry_engine.h>

THRUST_NAMESPACE_BEGIN

namespace random
{

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE subtract_with_carry_engine<UIntType, w, s, r>::subtract_with_carry_engine(result_type value)
{
  seed(value);
} // end subtract_with_carry_engine::subtract_with_carry_engine()

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE void subtract_with_carry_engine<UIntType, w, s, r>::seed(result_type value)
{
  thrust::random::linear_congruential_engine<result_type, 40014u, 0u, 2147483563u> e(
    value == 0u ? default_seed : value);

  // initialize state
  for (size_t i = 0; i < long_lag; ++i)
  {
    m_x[i] = detail::mod<UIntType, 1, 0, modulus>(e());
  } // end for i

  m_carry = (m_x[long_lag - 1] == 0);
  m_k     = 0;
} // end subtract_with_carry_engine::seed()

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE typename subtract_with_carry_engine<UIntType, w, s, r>::result_type
subtract_with_carry_engine<UIntType, w, s, r>::operator()(void)
{
  // XXX we probably need to cache these m_x[m_k] in a register
  //     maybe we need to cache the use of all member variables
  int short_index = m_k - short_lag;
  if (short_index < 0)
  {
    short_index += long_lag;
  }
  result_type xi;
  if (m_x[short_index] >= m_x[m_k] + m_carry)
  {
    // x(n) >= 0
    xi      = m_x[short_index] - m_x[m_k] - m_carry;
    m_carry = 0;
  }
  else
  {
    // x(n) < 0
    xi      = modulus - m_x[m_k] - m_carry + m_x[short_index];
    m_carry = 1;
  }
  m_x[m_k] = xi;
  ++m_k;
  if (m_k >= long_lag)
  {
    m_k = 0;
  }
  return xi;
} // end subtract_with_carry_engine::operator()()

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE void subtract_with_carry_engine<UIntType, w, s, r>::discard(unsigned long long z)
{
  for (; z > 0; --z)
  {
    this->operator()();
  } // end for
} // end subtract_with_carry_engine::discard()

template <typename UIntType, size_t w, size_t s, size_t r>
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>&
subtract_with_carry_engine<UIntType, w, s, r>::stream_out(std::basic_ostream<CharT, Traits>& os) const
{
  using ostream_type = std::basic_ostream<CharT, Traits>;
  using ios_base     = typename ostream_type::ios_base;

  const typename ios_base::fmtflags flags = os.flags();
  const CharT fill                        = os.fill();
  const CharT space                       = os.widen(' ');
  os.flags(ios_base::dec | ios_base::fixed | ios_base::left);
  os.fill(space);

  const UIntType long_lag_ = r;

  for (size_t i = 0; i < r; ++i)
  {
    os << m_x[(i + m_k) % long_lag_] << space;
  }
  os << m_carry;

  os.flags(flags);
  os.fill(fill);
  return os;
}

template <typename UIntType, size_t w, size_t s, size_t r>
template <typename CharType, typename Traits>
std::basic_istream<CharType, Traits>&
subtract_with_carry_engine<UIntType, w, s, r>::stream_in(std::basic_istream<CharType, Traits>& is)
{
  using istream_type = std::basic_istream<CharType, Traits>;
  using ios_base     = typename istream_type::ios_base;

  const typename ios_base::fmtflags flags = is.flags();
  is.flags(ios_base::dec | ios_base::skipws);

  for (size_t i = 0; i < r; ++i)
  {
    is >> m_x[i];
  }
  is >> m_carry;

  m_k = 0;

  is.flags(flags);
  return is;
}

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE bool
subtract_with_carry_engine<UIntType, w, s, r>::equal(const subtract_with_carry_engine<UIntType, w, s, r>& rhs) const
{
  const UIntType long_lag_ = r;

  bool result = true;
  for (size_t i = 0; i < r; ++i)
  {
    result &= (m_x[(i + m_k) % long_lag_] == rhs.m_x[(i + rhs.m_k) % long_lag_]);
  }

  // XXX not sure if this last check is necessary
  result &= (m_carry == rhs.m_carry);

  return result;
}

template <typename UIntType, size_t w, size_t s, size_t r, typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, const subtract_with_carry_engine<UIntType, w, s, r>& e)
{
  return thrust::random::detail::random_core_access::stream_out(os, e);
}

template <typename UIntType, size_t w, size_t s, size_t r, typename CharType, typename Traits>
std::basic_istream<CharType, Traits>&
operator>>(std::basic_istream<CharType, Traits>& is, subtract_with_carry_engine<UIntType, w, s, r>& e)
{
  return thrust::random::detail::random_core_access::stream_in(is, e);
}

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE bool operator==(const subtract_with_carry_engine<UIntType, w, s, r>& lhs,
                                  const subtract_with_carry_engine<UIntType, w, s, r>& rhs)
{
  return thrust::random::detail::random_core_access::equal(lhs, rhs);
}

template <typename UIntType, size_t w, size_t s, size_t r>
_CCCL_HOST_DEVICE bool operator!=(const subtract_with_carry_engine<UIntType, w, s, r>& lhs,
                                  const subtract_with_carry_engine<UIntType, w, s, r>& rhs)
{
  return !(lhs == rhs);
}

} // namespace random

THRUST_NAMESPACE_END
