/*
 * Copyright (c) 2007 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */
#ifndef POSITION_ALLOCATOR_H
#define POSITION_ALLOCATOR_H

#include "ns3/object.h"
#include "ns3/random-variable-stream.h"
#include "ns3/vector.h"

namespace ns3
{

/**
 * @ingroup mobility
 * @brief Allocate a set of positions. The allocation strategy is implemented in subclasses.
 *
 * This is a pure abstract base class.
 */
class PositionAllocator : public Object
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    PositionAllocator();
    ~PositionAllocator() override;
    /**
     * @return the next chosen position.
     *
     * This method _must_ be implement in subclasses.
     */
    virtual Vector GetNext() const = 0;
    /**
     * Assign a fixed random variable stream number to the random variables
     * used by this model. Return the number of streams (possibly zero) that
     * have been assigned.
     *
     * This method _must_ be implement in subclasses.
     *
     * @param stream first stream index to use
     * @return the number of stream indices assigned by this model
     */
    virtual int64_t AssignStreams(int64_t stream) = 0;
};

/**
 * @ingroup mobility
 * @brief Allocate positions from a deterministic list specified by the user.
 *
 * The first call to ListPositionAllocator::GetNext  will return the
 * first element of the list, the second call, the second element, and so on.
 */
class ListPositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    ListPositionAllocator();

    /**
     * @brief Add a position to the list of positions
     * @param v the position to append at the end of the list of positions to return from GetNext.
     */
    void Add(Vector v);

    /**
     * @brief Add the positions listed in a file.
     * The file should be a simple text file, with one position per line,
     * either X and Y, or X, Y and Z, in meters.  The delimiter can
     * be any character, such as ',' or '\\t'; the default is a comma ','.
     *
     * The file is read using CsvReader, which explains how comments
     * and whitespace are handled.
     *
     * @param [in] filePath The path to the input file.
     * @param [in] defaultZ The default Z value to use when reading files
     *             with only X and Y positions.
     * @param [in] delimiter The delimiter character; see CsvReader.
     */
    void Add(const std::string filePath, double defaultZ = 0, char delimiter = ',');

    /**
     * Return the number of positions stored.  Note that this will not change
     * based on calling GetNext(), as the number of positions is not altered
     * by calling GetNext ().
     *
     * @return the number of positions stored
     */
    uint32_t GetSize() const;
    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    std::vector<Vector> m_positions;                       //!< vector of positions
    mutable std::vector<Vector>::const_iterator m_current; //!< vector iterator
};

/**
 * @ingroup mobility
 * @brief Allocate positions on a rectangular 2d grid.
 */
class GridPositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /**
     * Determine whether positions are allocated row first or column first.
     */
    enum LayoutType
    {
        /**
         * In row-first mode, positions are allocated on the first row until
         * N positions have been allocated. Then, the second row located a yMin + yDelta
         * is used to allocate positions.
         */
        ROW_FIRST,
        /**
         * In column-first mode, positions are allocated on the first column until
         * N positions have been allocated. Then, the second column located a xMin + xDelta
         * is used to allocate positions.
         */
        COLUMN_FIRST
    };

    GridPositionAllocator();

    /**
     * @param xMin the x coordinate where layout will start.
     */
    void SetMinX(double xMin);
    /**
     * @param yMin the y coordinate where layout will start
     */
    void SetMinY(double yMin);
    /**
     * @param z   the Z coordinate of all the positions allocated
     */
    void SetZ(double z);
    /**
     * @param deltaX the x interval between two x-consecutive positions.
     */
    void SetDeltaX(double deltaX);
    /**
     * @param deltaY the y interval between two y-consecutive positions.
     */
    void SetDeltaY(double deltaY);
    /**
     * @param n the number of positions allocated on each row (or each column)
     *        before switching to the next column (or row).
     */
    void SetN(uint32_t n);
    /**
     * @param layoutType the type of layout to use (row first or column first).
     */
    void SetLayoutType(LayoutType layoutType);

    /**
     * @return the x coordinate of the first allocated position.
     */
    double GetMinX() const;
    /**
     * @return the y coordinate of the first allocated position.
     */
    double GetMinY() const;
    /**
     * @return the x interval between two consecutive x-positions.
     */
    double GetDeltaX() const;
    /**
     * @return the y interval between two consecutive y-positions.
     */
    double GetDeltaY() const;
    /**
     * @return the number of positions to allocate on each row or each column.
     */
    uint32_t GetN() const;
    /**
     * @return the currently-selected layout type.
     */
    LayoutType GetLayoutType() const;

    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    mutable uint32_t m_current; //!< currently position
    LayoutType m_layoutType;    //!< currently selected layout type
    double m_xMin;              //!< minimum boundary on x positions
    double m_yMin;              //!< minimum boundary on y positions
    double m_z;                 //!< z coordinate of all the positions generated
    uint32_t m_n;               //!< number of positions to allocate on each row or column
    double m_deltaX;            //!< x interval between two consecutive x positions
    double m_deltaY;            //!< y interval between two consecutive y positions
};

/**
 * @ingroup mobility
 * @brief Allocate random positions within a rectangle according to a pair of random variables.
 */
class RandomRectanglePositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    RandomRectanglePositionAllocator();
    ~RandomRectanglePositionAllocator() override;

    /**
     * @brief Set the random variable stream object that generates x-positions
     * @param x pointer to a RandomVariableStream object
     */
    void SetX(Ptr<RandomVariableStream> x);
    /**
     * @brief Set the random variable stream object that generates y-positions
     * @param y pointer to a RandomVariableStream object
     */
    void SetY(Ptr<RandomVariableStream> y);
    /**
     * @param z   the Z coordinate of all the positions allocated
     */
    void SetZ(double z);

    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    Ptr<RandomVariableStream> m_x; //!< pointer to x's random variable stream
    Ptr<RandomVariableStream> m_y; //!< pointer to y's random variable stream
    double m_z;                    //!< z coordinate of all the positions generated
};

/**
 * @ingroup mobility
 * @brief Allocate random positions within a 3D box according to a set of three random variables.
 */
class RandomBoxPositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    RandomBoxPositionAllocator();
    ~RandomBoxPositionAllocator() override;

    /**
     * @brief Set the random variable stream object that generates x-positions
     * @param x pointer to a RandomVariableStream object
     */
    void SetX(Ptr<RandomVariableStream> x);
    /**
     * @brief Set the random variable stream object that generates y-positions
     * @param y pointer to a RandomVariableStream object
     */
    void SetY(Ptr<RandomVariableStream> y);
    /**
     * @brief Set the random variable stream object that generates z-positions
     * @param z pointer to a RandomVariableStream object
     */
    void SetZ(Ptr<RandomVariableStream> z);

    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    Ptr<RandomVariableStream> m_x; //!< pointer to x's random variable stream
    Ptr<RandomVariableStream> m_y; //!< pointer to y's random variable stream
    Ptr<RandomVariableStream> m_z; //!< pointer to z's random variable stream
};

/**
 * @ingroup mobility
 * @brief Allocate random positions within a disc according to
 * a given distribution for the polar coordinates of each node
 * with respect to the provided center of the disc.
 *
 * @note With the default uniform distribution over \f$2 \pi\f$ in \c theta and a
 * uniform distribution for \c rho this position allocator will *not*
 * uniformly populate the disc.  The radial distribution will be proportional
 * to \f$\frac{1}{r^2}\f$.
 *
 * To get a uniform distribution over a circle use the UniformDiscPositionAllocator.
 */
class RandomDiscPositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    RandomDiscPositionAllocator();
    ~RandomDiscPositionAllocator() override;

    /**
     * @brief Set the random variable that generates position angle, in radians.
     * @param theta Random variable that represents the angle in radians of a position in a random
     * disc.
     */
    void SetTheta(Ptr<RandomVariableStream> theta);
    /**
     * @brief Set the random variable that generates position radius, in meters
     * @param rho Random variable that represents the radius of a position, in meters, in a random
     * disc.
     */
    void SetRho(Ptr<RandomVariableStream> rho);
    /**
     * @param x  the X coordinate of the center of the disc
     */
    void SetX(double x);
    /**
     * @param y   the Y coordinate of the center of the disc
     */
    void SetY(double y);
    /**
     * @param z   the Z coordinate of all the positions allocated
     */
    void SetZ(double z);

    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    Ptr<RandomVariableStream> m_theta; //!< pointer to theta's random variable stream
    Ptr<RandomVariableStream> m_rho;   //!< pointer to rho's random variable stream
    double m_x;                        //!< x coordinate of center of disc
    double m_y;                        //!< y coordinate of center of disc
    double m_z;                        //!< z coordinate of the disc
};

/**
 * @ingroup mobility
 * @brief Allocate the positions uniformly (with constant density) randomly within a disc.
 *
 * UniformDiscPositionAllocator allocates the positions randomly within a disc \f$ D \f$ lying on
 * the plane \f$ z\f$ and having center at coordinates \f$ (x,y,z) \f$ and radius \f$ \rho \f$. The
 * random positions are chosen such that, for any subset \f$ S \subset D \f$, the expected value of
 * the fraction of points which fall into \f$ S \subset D \f$ corresponds to \f$ \frac{|S|}{|D|}
 * \f$, i.e., to the ratio of the area of the subset to the area of the whole disc.
 *
 * @note using UniformDiscPositionAllocator is not equivalent to using
 * a RandomDiscPositionAllocator with a uniformly-distributed radius,
 * since doing that would result in a point distribution which is
 * more dense towards the center of the disc.
 */
class UniformDiscPositionAllocator : public PositionAllocator
{
  public:
    /**
     * Register this type with the TypeId system.
     * @return the object TypeId
     */
    static TypeId GetTypeId();
    UniformDiscPositionAllocator();
    ~UniformDiscPositionAllocator() override;

    /**
     * @param rho the value of the radius of the disc
     */
    void SetRho(double rho);

    /**
     * @param x  the X coordinate of the center of the disc
     */
    void SetX(double x);

    /**
     * @param y   the Y coordinate of the center of the disc
     */
    void SetY(double y);

    /**
     * @param z   the Z coordinate of all the positions allocated
     */
    void SetZ(double z);

    Vector GetNext() const override;
    int64_t AssignStreams(int64_t stream) override;

  private:
    Ptr<UniformRandomVariable> m_rv; //!< pointer to uniform random variable
    double m_rho;                    //!< value of the radius of the disc
    double m_x;                      //!< x coordinate of center of disc
    double m_y;                      //!< y coordinate of center of disc
    double m_z;                      //!< z coordinate of the disc
};

} // namespace ns3

#endif /* RANDOM_POSITION_H */
