/** @file IsingModel.hpp
 *
 *  @author Cory Alexander Balaton (coryab)
 *  @author Janita Ovidie Sandtrøen Willumsen (janitaws)
 *
 *  @version 0.1
 *
 *  @brief The definition of the Ising model.
 *
 *  @bug No known bugs
 * */
#ifndef __ISING_MODEL__
#define __ISING_MODEL__

#include "data_type.hpp"
#include "utils.hpp"

#include <armadillo>
#include <cstdint>
#include <random>
#include <unordered_map>

#define INDEX(I, N) (I + N) % N ///< I modulo N

// Indeces for the neighbor matrix.
#define UP 0    ///< Used for the neighbor matrix in the class
#define LEFT 0  ///< Used for the neighbor matrix in the class
#define DOWN 1  ///< Used for the neighbor matrix in the class
#define RIGHT 1 ///< Used for the neighbor matrix in the class

/** @brief The Ising model in 2 dimensions.
 *
 *  @details None of the methods are parallelized, as there is very little
 *  benefit in doing so.
 * */
class IsingModel {
private:
    /** @brief Give access to private members to the test class IsingModelTest.
     * */
    friend class IsingModelTest;

    /** @brief \f$ L \times L \f$ matrix where element \f$ x \in {-1, 1}\f$.
     * */
    arma::Mat<int> lattice;

    /** @brief \f$ L \times 2 \f$ matrix with the neighbors of each element
     *  \f$ x_i \f$.
     *
     *  @details The reason why it's \f$ L \times 2 \f$ instead of
     *  \f$ L \times 2 \f$, is that we can see that we can use the same column
     *  for the left and upper neighbor, and we can use the same column for the
     *  right and lower neighbor.
     * */
    arma::Mat<int> neighbors;

    /** @brief An array containing all possible energy differences.
     * */
    double energy_diff[17];

    /** @brief The temperature of the model.
     * */
    double T;

    /** @brief Size of the lattice.
     * */
    int L;

    /** @brief The current energy state. unit: \f$ J \f$.
     * */
    int64_t E;

    /** @brief The current magnetic strength. unit: Unitless.
     * */
    int64_t M;

    /** @brief The RNG that is used for the Metropolis algorithm
     * */
    std::mt19937 engine;

    /** @brief Initialize the RNG.
     * */
    void initialize_engine();

    /** @brief Initialize the lattice with a random distribution of 1s and
     *  -1s.
     * */
    void initialize_lattice();

    /** @brief Initialize the lattice with a specific value.
     * */
    void initialize_lattice(int val);

    /** @brief initialize the neighbors matrix.
     * */
    void initialize_neighbors();

    /** @brief Initialize the energy_diff array with the correct values.
     * */
    void initialize_energy_diff();

    /** @brief Initialize the magnetization of the system.
     * */
    void initialize_magnetization();

    /** @brief Initialize the energy of the system.
     * */
    void initialize_energy();

    /** @brief Constructor used for testing.
     * */
    IsingModel();

public:
    /** @brief Constructor for the Ising model.
     *
     *  @param L The size of the lattice.
     *  @param T The temperature for the system.
     * */
    IsingModel(int L, double T);

    /** @brief Constructor for the Ising model.
     *
     *  @param L The size of the lattice.
     *  @param T The temperature for the system.
     *  @param val The value to set for all spins.
     * */
    IsingModel(int L, double T, int val);

    /** @brief The Metropolis algorithm.
     * */
    data_t Metropolis();
};

#endif
