#pragma once

#ifndef GEODE_FWKLIB_GSRANDOM_H_
#define GEODE_FWKLIB_GSRANDOM_H_

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#include <random>
#include <string>

#include <geode/internal/geode_base.hpp>

namespace apache {
namespace geode {
namespace client {
namespace testframework {

class GsRandom {
 private:
  std::default_random_engine engine;

  std::uniform_int_distribution<int16_t> distBoolean;
  std::uniform_int_distribution<int16_t> distUint8;
  std::uniform_int_distribution<uint16_t> distUint16;
  std::uniform_int_distribution<int32_t> distInt32;
  std::uniform_int_distribution<uint32_t> distUint32;
  std::uniform_real_distribution<double> distDouble;

  GsRandom()
      : distBoolean(std::numeric_limits<bool>::min(),
                    std::numeric_limits<bool>::max()),
        distUint8(std::numeric_limits<uint8_t>::min(),
                  std::numeric_limits<uint8_t>::max()),
        distUint16(),
        distInt32(),
        distUint32(),
        distDouble() {
    std::random_device seed;
    engine = std::default_random_engine(seed());
  }
  ~GsRandom() = default;
  GsRandom(const GsRandom&) = delete;
  GsRandom& operator=(const GsRandom&) = delete;

 public:
  /**
   * Creates a new random number generator. Its seed is initialized to
   * a value based on the random device.
   *
   */
  static GsRandom& getInstance();

  /**
   * @return the next pseudorandom, uniformly distributed <code>boolean</code>
   *         value from this random number generator's sequence.
   */
  inline bool nextBoolean() { return 1 == distBoolean(engine); }

  /**
   * @return the next pseudorandom, uniformly distributed <code>uint16_t</code>
   *         value from this random number generator's sequence.
   */
  inline uint16_t nextInt16() { return distUint16(engine); }

  /**
   * @return the next pseudorandom, uniformly distributed <code>byte</code>
   *         value from this random number generator's sequence.
   */
  inline uint8_t nextByte() { return static_cast<uint8_t>(distUint8(engine)); }

  /**
   * @param   min the minimum range (inclusive) for the pseudorandom.
   * @param   max the maximum range (inclusive) for the pseudorandom.
   * @return  the next pseudorandom, uniformly distributed <code>char</code>
   *          value from this random number generator's sequence.
   *       If max < min, returns 0 .
   */
  inline uint8_t nextByte(uint8_t min, uint8_t max) {
    return static_cast<uint8_t>(
        distUint8(engine, decltype(distUint8)::param_type(min, max)));
  }

  /**
   * @param   max the maximum range (inclusive) for the pseudorandom.
   * @return  the next pseudorandom, uniformly distributed <code>double</code>
   *          value from this random number generator's sequence.
   */
  inline double nextDouble(double max) { return nextDouble(0.0, max); }

  /**
   * @param   min the minimum range (inclusive) for the pseudorandom.
   * @param   max the maximum range (inclusive) for the pseudorandom.
   * @return  the next pseudorandom, uniformly distributed <code>double</code>
   *      value from this random number generator's sequence within a range
   *      from min to max.
   */
  inline double nextDouble(double min, double max) {
    return distDouble(engine, decltype(distDouble)::param_type(min, max));
  }

  /**
   * @param   max the maximum range (inclusive) for the pseudorandom.
   * @return  the next pseudorandom, uniformly distributed <code>int32_t</code>
   *          value from this random number generator's sequence.
   */
  inline int32_t nextInt(int32_t max) { return nextInt(0, max); }

  /**
   * @param   min the minimum range (inclusive) for the pseudorandom.
   * @param   max the maximum range (inclusive) for the pseudorandom.
   * @return  the next pseudorandom, uniformly distributed <code>int32_t</code>
   *          value from this random number generator's sequence.
   *       If max < min, returns 0 .
   */
  inline int32_t nextInt(int32_t min, int32_t max) {
    return distInt32(engine, decltype(distInt32)::param_type(min, max));
  }

  /** @brief return random number where: min <= retValue < max */
  static uint32_t random(uint32_t min, uint32_t max) {
    return getInstance().nextInt(min, max - 1);
  }

  /** @brief return random number where: 0 <= retValue < max */
  static uint32_t random(uint32_t max) { return random(0, max - 1); }

  /** @brief return random double where: min <= retValue <= max */
  static double random(double min, double max) {
    return getInstance().nextDouble(min, max);
  }

  /** @brief return bounded random string,
   * Like randomString(), but returns only only alphanumeric,
   *   underscore, or space characters.
   *
   * @param size the length of the random string to generate.
   * @retval a bounded random string
   */
  static std::string getAlphanumericString(uint32_t size) {
    std::string str(size + 1, '\0');
    static const char chooseFrom[] =
        "0123456789 abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static const int32_t chooseSize = sizeof(chooseFrom) - 1;

    for (uint32_t idx = 0; idx < size; idx++) {
      str[idx] = chooseFrom[random(chooseSize)];
    }

    return str;
  }

  /** @brief return bounded random string,
   * Like randomString(), but returns only only alphanumeric,
   *   underscore, or space characters.
   *
   * @param size the length of the random string to generate.
   * @retval a bounded random string
   */
  static void getAlphanumericString(uint32_t size, char* buffer) {
    static const char chooseFrom[] =
        "0123456789 abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static const int32_t chooseSize = sizeof(chooseFrom) - 1;

    for (uint32_t idx = 0; idx < size; idx++) {
      buffer[idx] = chooseFrom[random(chooseSize)];
    }
  }

  /**
   * @param max the maximum length of the random string to generate.
   * @return a bounded random string with a length between 0 and
   * max length inclusive.
   */
  char* randomString(int32_t max, int32_t min = 0);

  /**
   * Like randomString(), but returns only readable characters.
   *
   * @param max the maximum length of the random string to generate.
   * @return a bounded random string with a length between 0 and
   * max length inclusive.
   */
  char* randomReadableString(int32_t max, int32_t min = 0);

  /**
   * Like randomString(), but returns only alphanumeric, underscore, or space
   * characters.
   *
   * @param max the maximum length of the random string to generate.
   * @return a bounded random string with a length between 0 and
   * max length inclusive.
   */
  char* randomAlphanumericString(int32_t max, int32_t min);
};

}  // namespace testframework
}  // namespace client
}  // namespace geode
}  // namespace apache

#endif  // GEODE_FWKLIB_GSRANDOM_H_
