/** @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 "constants.hpp"
#include "data_type.hpp"
#include "typedefs.hpp"
#include "utils.hpp"

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

#define INDEX(I, N) (I + N) % N

// Indeces for the neighbor matrix.
#define UP 0
#define LEFT 0
#define DOWN 1
#define RIGHT 1

/** @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:
    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 A hash map containing all possible energy changes.
     * */
    std::unordered_map<int, double> energy_diff;

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

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

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

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

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

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

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

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

    /** @brief Initialize the energy.
     * */
    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();

    /** @brief Get the current energy.
     *
     *  @return double
     * */
    int get_E();

    /** @brief Get the current magnetization.
     *
     *  @return double
     * */
    int get_M();
};

#endif
