/*
 * Grid.h
 *
 *  Created on: Nov 5, 2012
 *      Author: petr
 */
#pragma once
#ifndef GRID_H_
#define GRID_H_

#include <cmath>
#include <vector>
#include <string>
#include <sstream>
#include <petscdmda.h>
#include <Eigen/Dense>
#include "BoundingBox.hpp"

/**
 * @brief GridData holds all information about grid distribution.
 *
 * The information is hold for all nodes. This waste space, however all
 * other manipulation is faster and easier
 *
 */
struct GridData {

    double minx[3]; ///< global ower left corner of the grid
    double maxx[3]; ///< global upper right corner of the grid

    double lminx[3]; ///< local lower left corner of the grid
    double lmaxx[3]; ///< local upper right corner of the grid

    double glminx[3]; ///< local ghosted lower left corner of the grid
    double glmaxx[3]; ///< local ghosted upper right corner of the grid

    int pm; ///< number of processors m
    int pn; ///< number of processors n
    int pp; ///< number of processors p

    int m; ///< number of gridcells m
    int n; ///< number of gridcells n
    int p; ///< number of gridcells p

    int ghosts; ///< number of ghost cells

    int lm; ///< local number of gridcells m
    int ln; ///< local number of gridcells n
    int lp; ///< local number of gridcells p

    int glm; ///< local number of ghosted gridcells m
    int gln; ///< local number of ghosted gridcells n
    int glp; ///< local number of ghosted gridcells p

    static void createType(MPI_Datatype* type) {
        const int    nitems = 2;
        int          blocklengths[2] = {18,
                                        13};

        MPI_Datatype types[2] = {MPI_DOUBLE,
                                 MPI_INT};

        MPI_Aint offsets[2], extent;

        offsets[0] = 0;

        MPI_Type_extent(MPI_DOUBLE, &extent);
        offsets[1] = blocklengths[0] * extent;

        MPI_Type_create_struct(nitems, blocklengths, offsets, types, type);
        MPI_Type_commit(type);
    }

    /**
     * @brief Return bounding box of the whole grid
     * @return grid bounding box
     */
    Box<double, 3> getBB() {
        return Box<double, 3>(minx, maxx);
    }
};

/**
 * @brief Grid class wrapping PETSc DMDA grid
 * @tparam type int or double
 * @tparam dim grid dimension
 */
template <typename type, PetscInt dim>
class Grid {

    MPI_Datatype mpi_grid_type;

    GridData* globalGridLayout; ///< Stores information about the global grid distribution

    // grid step size, with appropriate length
    type dx[dim]; ///< deprecated, todo remove

    // no shit Sherlock, yes it is stencil size
    type  stencilSize;

    // lower left corner of the grid
    type minX[dim]; ///< deprecated, todo remove

    // upper right corner of the grid
    type maxX[dim]; ///< deprecated, todo remove

    // store number of grid cells for each dimension - global data
    long int  numberOfGridCells[dim]; ///< deprecated, todo remove

    // store number of ghosted grid cells for each dimension - global data
    long int  numberOfGhostedGridCells[dim]; ///< deprecated, todo remove

    // store the local minimum of grid - ghosted one
    type localMinX[dim]; ///< deprecated, todo remove

    // store the local maximum of the grid - ghosted one
    type localMaxX[dim]; ///< deprecated, todo remove

    // store the local minimum of grid - ghosted one
    type localGhostedMinX[dim]; ///< deprecated, todo remove

    // store the local maximum of the grid - ghosted one
    type localGhostedMaxX[dim]; ///< deprecated, todo remove

    // holds the information about length domains
    type nodeSpan[dim]; ///< deprecated, todo remove

    // number of level sets on the grid
    int dof; ///< deprecated, todo remove

    // processor layout in each direction
    int pm, pn, pp; ///< deprecated, todo remove

    //========================
    // Petsc data structures
    //========================

    // grid associated with this object
    DM da; ///< internally stored DMDA grid

    // distribution and stuff
    DMDALocalInfo localInfo; ///< internally stored DMDA grid info, it is somehow redundant to GridData

    // duplicate info for storing type of the boundary (NONE, GHOSTED, MIRRORED, PERIODIC)
    DMDABoundaryType boundaryType;

    //==========================
    // Private helper function
    //==========================

    /** Initializes the grid class and prepare Petsc data stuctures
    *
    * \param *minX array containing lower point of the grid
    * \param *maxX array containing upper point of the grid
    * \param *M    array with number of grid cells in each direction
    * \param *NP   array with number of slices in each direction
    */
    void init(PetscReal* minX, PetscReal* maxX, PetscInt* M, PetscInt* NP);

    /**
     * \brief Friend ostream operator for debug print
     */
    template <typename T, int d>
    friend std::ostream& operator<< (std::ostream& os, const Grid<T, d>& gr);

public:

    GridData gr_layout;
    int processID, nProcs;

    /// Constructor for old interface, utilizes array pointers
    ///
    /// \param *minX array containing lower point of the grid
    /// \param *maxX array containing upper point of the grid
    /// \param *M    array with number of grid cells in each direction
    /// \param *NP   arrat with number of slices in each direction
    Grid(PetscReal* minX, PetscReal* maxX, PetscInt* M, PetscInt* NP);

    /// Constructor for new interface, utilizes Eigen classes
    ///
    /// \param minX array containing lower point of the grid
    /// \param maxX array containing upper point of the grid
    /// \param M    array with number of grid cells in each direction
    Grid(const Eigen::Array<type, dim, 1>& minX, const Eigen::Array<type, dim, 1>& maxX, const Eigen::Array<int, dim, 1>& M);

    /// Constructor used to create equidistant grids
    ///
    /// \param minX array containing lower point of the grid
    /// \param maxX array containing upper point of the grid
    /// \param M    number of grid cells in first dimension, the rest is computed accoringly to supply equidistant dx
    Grid(const Eigen::Array<type, dim, 1>& minX, const Eigen::Array<type, dim, 1>& maxX, int M);

    /// Constructor used to create equidistant grids
    ///
    /// \param gridSpan bounding box to set the grid span
    /// \param M number of grid cells in each direction
//  Grid(const Box<type, dim>& gridSpan, int M);

    /// Destructor
    ~Grid();

    const DMDALocalInfo* getLocalInfo() const;

    const DM getDA() const {return this->da;};

    /// returns minimum coordinate of the grid for asked dimension number
    PetscReal getMin(PetscInt dimNum) const;

    /// returns maximim coordinate of the grid for asked dimension number
    PetscReal getMax(PetscInt dimNum) const;

    /// return spacing between grid points for asked dimension number
    PetscReal getDx(PetscInt dimNum) const;

    /// return local minimum coordinate
    PetscReal getLocalMin(PetscInt dimNum) const;

    /// return local maximum coordinate
    PetscReal getLocalMax(PetscInt dimNum) const;

    /// returns local ghpsted minimum coordinate of the grid for asked dimension number
    PetscReal getLocalGhostedMin(PetscInt dimNum) const;

    /// returns local ghosted maximim coordinate of the grid for asked dimension number
    PetscReal getLocalGhostedMax(PetscInt dimNum)const;

    /// returns a ghosted bounding box for local region covered by this computational node
    Box<double, 3> getLocalGhostedRegion();

    PetscReal getStencilSize() const;

    /// return real world coordinate for given dimension and axis index
    PetscReal getCoordinate(PetscInt dimNum, PetscInt ind) const;

    /// return linear index from passed matrix indices (linear index is defined in row wise manner)
    long int getLinearIndex(PetscInt* ind) const;

    /// return linear index from passed matrix indices (linear index is defined in row wise manner)
    long int getLinearIndex(std::vector<PetscInt> ind) const;

    /// get coordinate from indices
    inline Eigen::Vector3d getCoord(const Eigen::Vector3i& ind) const;

    /// get coordinate from indices
    const std::vector<PetscReal> getLocalCoord(std::vector<PetscInt> ind) const;

    /// get local coordinate from indices
    Eigen::Vector3d getLocalCoord(PetscInt* ind, int procID) const;

    /// get local coordinates from indices
    const std::vector<PetscReal> getLocalCoord(PetscInt* ind) const;

    long int getNumberOfCells() const;

    long int getNumberOfLocalCells() const;

    PetscInt getM(PetscInt dimNum) const;

    PetscInt getLocalM(PetscInt dimNum) const;

    PetscReal getMaxDist() const;

    /// method that transform coordinate values to indices in the grid. Not sure if this is done in PETSc and
    Box<PetscInt, dim>& getPointSpan(const std::vector<PetscReal>& coord, Box<PetscInt, dim>& prealloc);

    /// trasfoms coordinate box into indices box
    Box<PetscInt, dim> getGlobalBoxIndices(const Box<type, dim>& bb) const;

    Box<PetscInt, 3> locateBoxIndices(const Box<PetscReal, 3>& bb);

    int getDataPosition(const type point[dim]);

    std::list<int> getProcessSpan(const Box<double, dim>& indBox) const;

    Box<double, dim> getNodeSpan(int indices[dim]) const;

    Box<double, dim> getNodeSpan() const;

    Box<double, dim> getNodeSpan(int procID) const;

    std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > getNodeBoundaryCells(int procID, int boundaries) const;
};

///==================================
///         Implementation
///==================================

template <typename type, int dim>
void Grid<type, dim>::init(PetscReal* minX, PetscReal* maxX, PetscInt* M, PetscInt* NP) {

    GridData::createType(&mpi_grid_type);

    PetscErrorCode ierr;

    this->boundaryType = DMDA_BOUNDARY_GHOSTED;

    // values setup for dim > N are ommited
    for (int i = 0; i < dim; ++i) {
        this->numberOfGridCells[i] = M[i];
        this->dx[i]                = (maxX[i] - minX[i])/ (M[i] - 1);
        this->minX[i]              = minX[i];
        this->maxX[i]              = maxX[i];
    }

    this->stencilSize = 1;

    //  this WILL alwasy be 1 because the data is stored in other class
    //  and changing this would corrupt it all. The petsc way is interleave the data
    //  this is not exactly what we are going to do.
    this->dof = 1;

    switch (dim) {
        case 1:
            break;
        case 2:
            break;
        case 3:
            // To make petsc work correctly, we need to convert number of gridcells to number of nodes => M[.] = M[.] + 1
            ierr = DMDACreate3d(PETSC_COMM_WORLD, boundaryType, boundaryType, boundaryType, DMDA_STENCIL_BOX,
                                    M[0], M[1], M[2], NP[0], NP[1], NP[2], dof, stencilSize,
                                    PETSC_NULL, PETSC_NULL, PETSC_NULL, &da);
            ierr = DMDASetUniformCoordinates(da, minX[0], maxX[0], minX[1], maxX[1], minX[2], maxX[2]);

            // very stupid way to get back the process layout
            ierr = DMDAGetInfo(da,
                               PETSC_NULL,
                               PETSC_NULL, PETSC_NULL, PETSC_NULL,
                               &pm, &pn, &pp,
                               PETSC_NULL,
                               PETSC_NULL,
                               PETSC_NULL, PETSC_NULL, PETSC_NULL,
                               PETSC_NULL);

            nodeSpan[0] = (maxX[0] - minX[0]) / (pm);
            nodeSpan[1] = (maxX[1] - minX[1]) / (pn);
            nodeSpan[2] = (maxX[2] - minX[2]) / (pp);

            break;
        default:
            break;
    }

    if (PetscUnlikely(ierr)) {
        // some error handling add lated here :)
    }

    ierr = DMDAGetLocalInfo(da, &localInfo);

    MPI_Comm_rank(PETSC_COMM_WORLD, &processID);
    MPI_Comm_size(PETSC_COMM_WORLD, &nProcs);

    // for convenience this part defines local chunks of grid, so user do not need to recompute
    // it every time he needs it
    switch (dim) {
        case 1:
            break;
        case 2:
            break;
        case 3:
            localMinX[0]        = minX[0] +  localInfo.xs * dx[0];
            localMaxX[0]        = minX[0] + (localInfo.xs + localInfo.xm - 1) * dx[0];
            localGhostedMinX[0] = minX[0] +  localInfo.gxs * dx[0];
            localGhostedMaxX[0] = minX[0] + (localInfo.gxs + localInfo.gxm - 1) * dx[0];

            localMinX[1]        = minX[1] +  localInfo.ys * dx[1];
            localMaxX[1]        = minX[1] + (localInfo.ys + localInfo.ym - 1) * dx[1];
            localGhostedMinX[1] = minX[1] +  localInfo.gys * dx[1];
            localGhostedMaxX[1] = minX[1] + (localInfo.gys + localInfo.gym - 1) * dx[1];

            localMinX[2]        = minX[2] +  localInfo.zs * dx[2];
            localMaxX[2]        = minX[2] + (localInfo.zs + localInfo.zm - 1) * dx[2];
            localGhostedMinX[2] = minX[2] +  localInfo.gzs * dx[2];
            localGhostedMaxX[2] = minX[2] + (localInfo.gzs + localInfo.gzm -1) * dx[2];

            numberOfGhostedGridCells[0] = localInfo.gxm;
            numberOfGhostedGridCells[1] = localInfo.gym;
            numberOfGhostedGridCells[2] = localInfo.gzm;

            break;
    }

    for (int i = 0; i < 3; ++i) {

        gr_layout.lminx[i] = localMinX[i];
        gr_layout.lmaxx[i] = localMaxX[i];
        gr_layout.glminx[i] = localGhostedMinX[i];
        gr_layout.glmaxx[i] = localGhostedMaxX[i];

    }

    gr_layout.glm = localInfo.gxm;
    gr_layout.gln = localInfo.gym;
    gr_layout.glp = localInfo.gzm;

    gr_layout.lm  = localInfo.mx;
    gr_layout.ln  = localInfo.my;
    gr_layout.lp  = localInfo.mz;

    gr_layout.m   = M[0];
    gr_layout.n   = M[1];
    gr_layout.p   = M[2];

    gr_layout.minx[0] = minX[0];
    gr_layout.minx[1] = minX[1];
    gr_layout.minx[2] = minX[2];

    gr_layout.maxx[0] = maxX[0];
    gr_layout.maxx[1] = maxX[1];
    gr_layout.maxx[2] = maxX[2];

    gr_layout.ghosts = 1;

    globalGridLayout = new GridData[nProcs]; // do not forget to delete this little shit

    MPI_Allgather(&gr_layout, 1, mpi_grid_type, globalGridLayout, 1, mpi_grid_type, MPI_COMM_WORLD);

    std::stringstream ss;

    ss << "====================" << std::endl;
    ss << "procid : " << processID << std::endl;
    ss << "min: " << gr_layout.glminx[0] << ", " << gr_layout.glminx[1] << ", " << gr_layout.glminx[2] << std::endl;
    ss << "max: " << gr_layout.glmaxx[0] << ", " << gr_layout.glmaxx[1] << ", " << gr_layout.glmaxx[2] << std::endl;
    ss << "====================" << std::endl;
     for (int i = 0; i < nProcs; ++i) {
        ss << "global proc id : " << i << std::endl;
        ss << "min: " << globalGridLayout[i].glminx[0] << ", " << globalGridLayout[i].glminx[1] << ", " << globalGridLayout[i].glminx[2] << std::endl;
        ss << "max: " << globalGridLayout[i].glmaxx[0] << ", " << globalGridLayout[i].glmaxx[1] << ", " << globalGridLayout[i].glmaxx[2] << std::endl;
     }
    ss << "====================" << std::endl;

    std::string s = ss.str();
//    PetscSynchronizedPrintf(PETSC_COMM_WORLD, s.c_str());
 //   PetscSynchronizedFlush(PETSC_COMM_WORLD);
    // create indexing for the data so, because we will need fast acces for inicialization computations

}

template <typename type, int dim>
Grid<type, dim>::Grid(const Eigen::Array<type, dim, 1>& minX, const Eigen::Array<type, dim, 1>& maxX, const Eigen::Array<int, dim, 1>& M) {

    int            m[3] = {M[0], M[1], M[2]},
                  np[3] = {0, 0, 0};
    type         min[3] = {minX[0], minX[1], minX[2]},
                 max[3] = {maxX[0], maxX[1], maxX[2]};

    init(min, max, m, np);

}

template <typename type, int dim>
Grid<type, dim>::Grid(const Eigen::Array<type, dim, 1>& minX, const Eigen::Array<type, dim, 1>& maxX, int M) {

    Eigen::Array<type, dim, 1> len   = maxX - minX;
    Eigen::Array<type, dim, 1> maxX_;   // new maximum size of the grid, possibly expanded a bit
    Eigen::Array<type, dim, 1> M_float; // stores array with accoring number of cells

    M_float[0] = M;

    type dx = len[0] / (M - 1); // size of one cell on dim = 0

//  std::cout << dx << std::endl;

    M_float[1] = std::ceil( len[1] / dx + 1);
    M_float[2] = std::ceil( len[2] / dx + 1);

    Eigen::Array<type, dim, 1> newLen = dx * (M_float - 1);
    Eigen::Array<type, dim, 1> diff   = newLen - len;

    maxX_ = maxX + diff;

    Eigen::Array<int, dim, 1> M_int = M_float.template cast<int>();

//  std::cout << M_int << std::endl;

    int  m[3]   = {M_int[0], M_int[1], M_int[2]},
         np[3]  = {0, 0, 0};
    type min[3] = {minX[0], minX[1], minX[2]},
         max[3] = {maxX_[0], maxX_[1], maxX_[2]};

    init(min, max, m, np);

}

template <typename type, int dim>
Grid<type, dim>::Grid(PetscReal* minX, PetscReal* maxX, PetscInt* M, PetscInt* NP) {
    // this constructor version has one huge problem. There is now way of checking if user passed
    // the inicialized values or just some random stuff.

    init(minX, maxX, M, NP);

}

template <typename type, int dim>
Grid<type, dim>::~Grid() {
    // definitely is nice to clean
//  std::cout << "call destructor in grid" << std::endl;
    PetscBool finalized;
    PetscFinalized(&finalized);

    delete [] globalGridLayout;

    if (!finalized) {
        DMDestroy(&da);
    } // else I am not really sure what happen, will the object stay hanging?
    else {
        std::cout << "already finalized" << std::endl;
    }

}

// this should be removed !!!!!!!!!!!!!
template <typename type, int dim>
const DMDALocalInfo* Grid<type, dim>::getLocalInfo() const{

    return &localInfo;

}

//=====================//
//      Getters        //
//=====================//

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getMin(PetscInt dimNum) const {
    return minX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getMax(PetscInt dimNum) const {
    return maxX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getDx(PetscInt dimNum) const {
    return dx[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getLocalMin(PetscInt dimNum) const {
    return localMinX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getLocalMax(PetscInt dimNum) const {
    return localMaxX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getLocalGhostedMin(PetscInt dimNum) const {
    return localGhostedMinX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getLocalGhostedMax(PetscInt dimNum) const {
    return localGhostedMaxX[dimNum];
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getStencilSize() const {
    return stencilSize;
}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getCoordinate(PetscInt dimNum, PetscInt ind) const {
    return localMinX[dimNum] + dx[dimNum]*ind;
}

template <typename type, int dim>
inline long int Grid<type, dim>::getLinearIndex(PetscInt* ind) const {
    if (dim == 2) {
        return ind[1]*localInfo.gxm + ind[0];
    } else if (dim == 3) {
        return ind[2]*localInfo.gxm*localInfo.gym + ind[1]*localInfo.gxm + ind[0];
    }
    return -1;
}

template <typename type, int dim>
inline long int Grid<type, dim>::getLinearIndex(std::vector<PetscInt> ind) const {
    if (dim == 2) {
        return ind[1]*localInfo.gxm + ind[0];
    } else if (dim == 3) {
        return ind[2]*localInfo.gxm*localInfo.gym + ind[1]*localInfo.gxm + ind[0];
    }
    return -1;
}

template <typename type, int dim>
inline Eigen::Vector3d Grid<type, dim>::getCoord(const Eigen::Vector3i& ind) const {
    Eigen::Vector3d x_lo(minX), x_dx(dx);

    return x_lo + x_dx.cwiseProduct( ind.cast<double>() );
}

template <typename type, int dim>
inline const std::vector<PetscReal> Grid<type, dim>::getLocalCoord(PetscInt* ind) const {
    std::vector<PetscReal> coord;
    for (int i = 0; i < dim; ++i) {
        coord.push_back( localGhostedMinX[i] + ind[i]*dx[i] );
    }

    return coord;
}

template <typename type, int dim>
inline Eigen::Vector3d Grid<type, dim>::getLocalCoord(PetscInt* ind, int procID) const {
    Eigen::Vector3d coord;
    for (int i = 0; i < dim; ++i) {
        coord[i] = globalGridLayout[procID].glminx[i] + ind[i]*dx[i];
    }

    return coord;
}

template <typename type, int dim>
inline const std::vector<PetscReal> Grid<type, dim>::getLocalCoord(std::vector<PetscInt> ind) const {
    std::vector<PetscReal> coord;
    for (int i = 0; i < ind.size(); ++i) {
        coord.push_back( localGhostedMinX[i] + ind[i]*dx[i] );
    }

    return coord;
}

template <typename type, int dim>
inline long int Grid<type, dim>::getNumberOfCells() const {
    long int numberOfCells = 1;
    for (int i = 0; i < dim; ++i) {
        numberOfCells *= numberOfGridCells[i];
    }
    return numberOfCells;
}

template <typename type, int dim>
inline long int Grid<type, dim>::getNumberOfLocalCells() const {
    long int numberOfCells = 1;
    if (dim >= 1) {
        numberOfCells *= localInfo.gxm;
    }
    if (dim >= 2) {
        numberOfCells *= localInfo.gym;
    }
    if (dim == 3) {
        numberOfCells *= localInfo.gzm;
    }

    return numberOfCells;
}

template <typename type, int dim>
inline PetscInt Grid<type, dim>::getM(PetscInt dimNum) const {
    return numberOfGridCells[dimNum];
}

template <typename type, int dim>
inline PetscInt Grid<type, dim>::getLocalM(PetscInt dimNum) const {
    switch (dimNum) {
        case 0:
            return localInfo.gxm;
            break;
        case 1:
            return localInfo.gym;
            break;
        case 2:
            return localInfo.gzm;
            break;
        default:
            return -1;
            break;
    }
}

template <typename type, int dim>
inline Box<double, 3> Grid<type, dim>::getLocalGhostedRegion() {

    return Box<double, 3>(localGhostedMinX, localGhostedMaxX);

}

template <typename type, int dim>
inline PetscReal Grid<type, dim>::getMaxDist() const {
//  return sqrt( (localGhostedMaxX[0] - localGhostedMinX[0])*(localGhostedMaxX[0] - localGhostedMinX[0]) +
//               (localGhostedMaxX[1] - localGhostedMinX[1])*(localGhostedMaxX[1] - localGhostedMinX[1]) +
//               (localGhostedMaxX[2] - localGhostedMinX[2])*(localGhostedMaxX[2] - localGhostedMinX[2])   );
    return     ( (maxX[0] - minX[0])*(maxX[0] - minX[0]) +
                 (maxX[1] - minX[1])*(maxX[1] - minX[1]) +
                 (maxX[2] - minX[2])*(maxX[2] - minX[2])   );

}

template <typename type, int dim>
Box<PetscInt, dim>& Grid<type, dim>::getPointSpan(const std::vector<PetscReal>& coord, Box<PetscInt, dim>& prealloc) {
// method that transform coordinate values to indices in the grid. Not sure if this is done in PETSc and
// I have no time to go through the doucmentation.
// TODO: resolve this in future :)

    int       bandSize = 3;
    int       indRow[dim];

    for (PetscInt i = 0; i < dim; ++i) {

        PetscInt ind = round( (coord[i] - localGhostedMinX[i]) / dx[i] );
        indRow[i] = ind;

    }

    if (dim == 1) {
// pass
    } if (dim == 2) {
// pass
    } if (dim == 3) {

        int k_low  = (indRow[2] - bandSize) <  0         ? 0                       : (indRow[2] - bandSize);
        int k_high = (indRow[2] + bandSize) >= localInfo.gzm ? (localInfo.gzm - 1) : (indRow[2] + bandSize);

        int j_low  = (indRow[1] - bandSize) <  0         ? 0                       : (indRow[1] - bandSize);
        int j_high = (indRow[1] + bandSize) >= localInfo.gym ? (localInfo.gym - 1) : (indRow[1] + bandSize);

        int i_low  = (indRow[0] - bandSize) <  0         ? 0                       : (indRow[0] - bandSize);
        int i_high = (indRow[0] + bandSize) >= localInfo.gxm ? (localInfo.gxm - 1) : (indRow[0] + bandSize);

        int minx[3] = {i_low, j_low, k_low};
        int maxx[3] = {i_high, j_high, k_high};

        prealloc.updateFromMinMax(minx, maxx);

    }

    return prealloc;

}

template <typename type, int dim>
Box<PetscInt, dim> Grid<type, dim>::getGlobalBoxIndices(const Box<type, dim>& bb) const {

    PetscInt padding = 2;
    PetscInt gMin[dim], gMax[dim];

    for (int i = 0; i < dim; ++i) {

        gMin[i] = floor( (bb.minX(i) - this->minX[i]) / dx[i] ) - padding;
        gMax[i] = ceil(  (bb.maxX(i) - this->minX[i]) / dx[i] ) + padding;

        gMin[i] = (gMin[i] < 0)                     ? 0                        : gMin[i];
        gMax[i] = (gMax[i] >= numberOfGridCells[i]) ? (numberOfGridCells[i]-1) : gMax[i];

    }

//  std::cout << "gmin : " << gMin[0] << ", " << gMin[1] << ", " << gMin[2] << std::endl;
//  std::cout << "gmax : " << gMax[0] << ", " << gMax[1] << ", " << gMax[2] << std::endl;

    return Box<PetscInt, dim>(gMin, gMax);

}

template <typename type, int dim>
Box<PetscInt, 3> Grid<type, dim>::locateBoxIndices(const Box<PetscReal, 3>& bb) {
    // method that transform coordinate values to indices in the grid. Not sure if this is done in PETSc and
    // I have no time to go through the documentation.
    // TODO: resolve this in future :)

    PetscInt padding = 2;
    PetscInt minx[dim], maxx[dim];

    for (int i = 0; i < dim; ++i) {

        minx[i] = floor( (bb.minX(i) - localGhostedMinX[i]) / dx[i] ) - padding;
        maxx[i] = ceil(  (bb.maxX(i) - localGhostedMinX[i]) / dx[i] ) + padding;

        minx[i] = (minx[i] < 0)                            ? 0                               : minx[i];
        maxx[i] = (maxx[i] >= numberOfGhostedGridCells[i]) ? (numberOfGhostedGridCells[i]-1) : maxx[i];

    }

    return Box<PetscInt, dim>(minx, maxx);

}

// Returns physical position of the data in question
template <typename type, int dim>
int Grid<type, dim>::getDataPosition(const type point[dim]) {
    Eigen::Array<type, dim, 1> x_lo(minX), p(point), span(nodeSpan), x_hi(maxX);
    Eigen::Array<int, dim, 1> procs;

    procs << 1, pm, pm*pn;

    auto pos_cor = ((p - x_lo) / span).template cast<int>();

    return (pos_cor*procs).sum();

}

template <typename type, int dim>
std::list<int> Grid<type, dim>::getProcessSpan(const Box<double, dim>& indBox) const {
    Eigen::Array<type, dim, 1> x_lo(minX), span(nodeSpan), x_hi(maxX);
    Eigen::Array<int, dim, 1> procs;
    std::list<int> processes;
    double eps = 1E-10; // this is purely local epsilon need to be machine eps

    procs << 1, pm, pm*pn;

    auto pos_cor_bl = ((indBox.bl() - x_lo - eps) / span).template cast<int>();
    auto pos_cor_tr = ((indBox.tr() - x_lo - eps) / span).template cast<int>();

    for (int k = pos_cor_bl[2]; k <= pos_cor_tr[2]; ++k) {
        for (int j = pos_cor_bl[1]; j <= pos_cor_tr[1]; ++j) {
            for (int i = pos_cor_bl[0]; i <= pos_cor_tr[0]; ++i) {
                processes.push_back( (Eigen::Array3i(i,j,k)*procs).sum() );
            }
        }
    }

    return processes;

}

template <typename type, int dim>
Box<double, dim> Grid<type, dim>::getNodeSpan(int indices[dim]) const {

    Eigen::Array<type, dim, 1> x_lo(minX), span(nodeSpan);
    Eigen::Array<int, dim, 1> inds(indices);

    Eigen::Array<double, dim, 1> c = x_lo + (0.5*span)*(inds+1).template cast<double>();
    Eigen::Array<double, dim, 1> e = 0.5*span;

    return Box<double, 3>( c.data(), e.data() );

}

template <typename type, int dim>
Box<double, dim> Grid<type, dim>::getNodeSpan() const {

    return Box<double, 3>(globalGridLayout[processID].glminx, globalGridLayout[processID].glmaxx);

}

template <typename type, int dim>
Box<double, dim> Grid<type, dim>::getNodeSpan(int procID) const {
    std::stringstream ss;
    ss << "ProcID = " << procID << std::endl;
    ss << globalGridLayout[procID].glminx[0] << ", " << globalGridLayout[procID].glminx[1] << ", " << globalGridLayout[procID].glminx[2] << std::endl;
    ss << globalGridLayout[procID].glmaxx[0] << ", " << globalGridLayout[procID].glmaxx[1] << ", " << globalGridLayout[procID].glmaxx[2] << std::endl;
    std::string s = ss.str();
    PetscSynchronizedPrintf(PETSC_COMM_WORLD, s.c_str());
    PetscSynchronizedFlush(PETSC_COMM_WORLD);
    return Box<double, 3>(globalGridLayout[procID].glminx, globalGridLayout[procID].glmaxx);

}

template <typename type, int dim>
std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > Grid<type, dim>::getNodeBoundaryCells(int procID, int boundaries) const {

    // std::cout << "proc: " << procID << " , boundaries : " << boundaries << std::endl;

    std::vector<Eigen::Vector3d, Eigen::aligned_allocator<Eigen::Vector3d> > cellList;
    long int reserveSize = (globalGridLayout[procID].glm + globalGridLayout[procID].gln + globalGridLayout[procID].glp) / 3;
    reserveSize          = reserveSize*reserveSize*4;

    cellList.reserve( reserveSize );

    if ( boundaries & 1 ) {
        // this means lets initialize left side
        for (int i = 0; i < globalGridLayout[procID].ghosts; ++i) {
            for (int j = 0; j < globalGridLayout[procID].gln; ++j) {
                for (int k = 0; k < globalGridLayout[procID].glp; ++k) {

                    PetscInt inds[dim]    = {i, j, k};
                    Eigen::Vector3d coord  = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }
    if ( boundaries & 2 ) {
        // this means lets initialize right side
        for (int i = 0; i < globalGridLayout[procID].ghosts; ++i) {
            for (int j = 0; j < globalGridLayout[procID].gln; ++j) {
                for (int k = 0; k < globalGridLayout[procID].glp; ++k) {

                    PetscInt inds[dim]    = {( globalGridLayout[procID].glm - 1) - i, j, k};
                    Eigen::Vector3d coord  = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }
    if ( boundaries & 4 ) {
        // this means initialize the bottom side
        for (int j = 0; j < globalGridLayout[procID].ghosts; ++j) {
            for (int i = 0; i < globalGridLayout[procID].glm; ++i) {
                for (int k = 0; k < globalGridLayout[procID].glp; ++k) {

                    PetscInt inds[dim]           = {i, j, k};
                    Eigen::Vector3d coord = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }
    if ( boundaries & 8 ) {
        // this means initialize the top side
        for (int j = 0; j < globalGridLayout[procID].ghosts; ++j) {
            for (int i = 0; i < globalGridLayout[procID].glm; ++i) {
                for (int k = 0; k < globalGridLayout[procID].glp; ++k) {

                    PetscInt inds[dim]           = {i, (globalGridLayout[procID].gln-1) - j, k};
                    Eigen::Vector3d coord = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }
    if ( boundaries & 16 ) {
        // this means initialize the front side
        for (int k = 0; k < globalGridLayout[procID].ghosts; ++k) {
            for (int i = 0; i < globalGridLayout[procID].glm; ++i) {
                for (int j = 0; j < globalGridLayout[procID].gln; ++j) {

                    PetscInt inds[dim]   = {i, j, k};
                    Eigen::Vector3d coord = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }
    if ( boundaries & 32 ) {
        // this means initialize the far side
        for (int k = 0; k < globalGridLayout[procID].ghosts; ++k) {
            for (int i = 0; i < globalGridLayout[procID].glm; ++i) {
                for (int j = 0; j < globalGridLayout[procID].gln; ++j) {

                    PetscInt inds[dim]           = {i, j, (globalGridLayout[procID].glp-1) - k};
                    Eigen::Vector3d coord = getLocalCoord(inds, procID);
                    cellList.push_back(coord);

                }
            }
        }
    }

    return cellList;

}

///////////////////////////////////////
/// Ostream operator for debug print
///////////////////////////////////////

template <typename T, int d>
std::ostream& operator<< (std::ostream& os, const Grid<T, d>& gr) {

    os << "Grid info for process: " << gr.processID << std::endl;
    os << "-------------------------------------------------------------------------------------" << std::endl;
    if (gr.processID == 0) {
        os << "Global grid size: " << "X: " << gr.minX[0] << " -- " << gr.maxX[0] << std::endl <<
              "                  " << "Y: " << gr.minX[1] << " -- " << gr.maxX[1] << std::endl <<
              "                  " << "Z: " << gr.minX[2] << " -- " << gr.maxX[2] << std::endl <<
              "Global number of grid points: " << "[X, Y, Z] : [" << gr.numberOfGridCells[0] << ", " << gr.numberOfGridCells[1] << ", " << gr.numberOfGridCells[2] << "]" << std::endl <<
              "Resolution for the grid is: " << "[dx, dy, dz] : [" << gr.dx[0] << ", " << gr.dx[1] << ", " << gr.dx[2] << "]" << std::endl;
        os << "-------------------------------------------------------------------------------------" << std::endl;
    }
    os <<  "Local grid size: " << "X: " << gr.localMinX[0] << " -- " << gr.localMaxX[0] << std::endl <<
           "                 " << "Y: " << gr.localMinX[1] << " -- " << gr.localMaxX[1] << std::endl <<
           "                 " << "Z: " << gr.localMinX[2] << " -- " << gr.localMaxX[2] << std::endl <<
           "Local ghosted grid size: " << "X: " << gr.localGhostedMinX[0] << " -- " << gr.localGhostedMaxX[0] << std::endl <<
           "                         " << "Y: " << gr.localGhostedMinX[1] << " -- " << gr.localGhostedMaxX[1] << std::endl <<
           "                         " << "Z: " << gr.localGhostedMinX[2] << " -- " << gr.localGhostedMaxX[2] << std::endl <<
           "Local grid numbering: " << "X: " << gr.localInfo.xs << " -- " << gr.localInfo.xs + gr.localInfo.xm - 1 << std::endl <<
           "                      " << "Y: " << gr.localInfo.ys << " -- " << gr.localInfo.ys + gr.localInfo.ym - 1 << std::endl <<
           "                      " << "Z: " << gr.localInfo.zs << " -- " << gr.localInfo.zs + gr.localInfo.zm - 1 << std::endl <<
           "Local ghosted grid numbering: " << "X: " << gr.localInfo.gxs << " -- " << gr.localInfo.gxs + gr.localInfo.gxm - 1 << std::endl <<
           "                              " << "Y: " << gr.localInfo.gys << " -- " << gr.localInfo.gys + gr.localInfo.gym - 1 << std::endl <<
           "                              " << "Z: " << gr.localInfo.gzs << " -- " << gr.localInfo.gzs + gr.localInfo.gzm - 1 << std::endl;
    os << "-------------------------------------------------------------------------------------" << std::endl;

    return os;

}

#endif /* GRID_H_ */


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2015.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: George Rosenberger $
// $Authors: George Rosenberger, Hannes Roest $
// --------------------------------------------------------------------------

#include <OpenMS/ANALYSIS/OPENSWATH/MRMRTNormalizer.h>
#include <OpenMS/MATH/STATISTICS/LinearRegression.h>
#include <OpenMS/CONCEPT/LogStream.h> // LOG_DEBUG
#include <OpenMS/MATH/MISC/RANSAC.h> // RANSAC algorithm

#include <numeric>
#include <boost/math/special_functions/erf.hpp>
#include <algorithm>

namespace OpenMS
{

  std::vector<std::pair<double, double> > MRMRTNormalizer::removeOutliersRANSAC(
      std::vector<std::pair<double, double> >& pairs, double rsq_limit,
      double coverage_limit, size_t max_iterations, double max_rt_threshold, size_t sampling_size)
  {
    size_t n = sampling_size;
    size_t k = (size_t)max_iterations;
    double t = max_rt_threshold*max_rt_threshold;
    size_t d = (size_t)(coverage_limit*pairs.size());

    if (n < 5)
    {
      throw Exception::UnableToFit(__FILE__, __LINE__, __PRETTY_FUNCTION__,
          "UnableToFit-LinearRegression-RTNormalizer", "WARNING: RANSAC: " +
          boost::lexical_cast<std::string>(n) + " sampled RT peptides is below limit of 5 peptides required for the RANSAC outlier detection algorithm.");
    }

    if (pairs.size() < 30)
    {
      throw Exception::UnableToFit(__FILE__, __LINE__, __PRETTY_FUNCTION__,
          "UnableToFit-LinearRegression-RTNormalizer", "WARNING: RANSAC: " +
          boost::lexical_cast<std::string>(pairs.size()) + " input RT peptides is below limit of 30 peptides required for the RANSAC outlier detection algorithm.");
    }

    std::vector<std::pair<double, double> > new_pairs = Math::RANSAC::ransac(pairs, n, k, t, d);
    double bestrsq = Math::RANSAC::llsm_rsq(new_pairs);

    if (bestrsq < rsq_limit)
    {
      throw Exception::UnableToFit(__FILE__, __LINE__, __PRETTY_FUNCTION__,
          "UnableToFit-LinearRegression-RTNormalizer", "WARNING: rsq: " +
          boost::lexical_cast<std::string>(bestrsq) + " is below limit of " +
          boost::lexical_cast<std::string>(rsq_limit) +
          ". Validate assays for RT-peptides and adjust the limit for rsq or coverage.");
    }

    if (new_pairs.size() < d)
    {
      throw Exception::UnableToFit(__FILE__, __LINE__, __PRETTY_FUNCTION__,
          "UnableToFit-LinearRegression-RTNormalizer", "WARNING: number of data points: " +
          boost::lexical_cast<std::string>(new_pairs.size()) +
          " is below limit of " + boost::lexical_cast<std::string>(d) +
          ". Validate assays for RT-peptides and adjust the limit for rsq or coverage.");
    }

    return new_pairs;
  }

  int MRMRTNormalizer::jackknifeOutlierCandidate_(std::vector<double>& x, std::vector<double>& y)
  {
    // Returns candidate outlier: A linear regression and rsq is calculated for
    // the data points with one removed pair. The combination resulting in
    // highest rsq is considered corresponding to the outlier candidate. The
    // corresponding iterator position is then returned.
    std::vector<double> x_tmp, y_tmp, rsq_tmp;

    for (Size i = 0; i < x.size(); i++)
    {
      x_tmp = x;
      y_tmp = y;
      x_tmp.erase(x_tmp.begin() + i);
      y_tmp.erase(y_tmp.begin() + i);

      Math::LinearRegression lin_reg;
      lin_reg.computeRegression(0.95, x_tmp.begin(), x_tmp.end(), y_tmp.begin());

      rsq_tmp.push_back(lin_reg.getRSquared());
    }
    return max_element(rsq_tmp.begin(), rsq_tmp.end()) - rsq_tmp.begin();
  }

  int MRMRTNormalizer::residualOutlierCandidate_(std::vector<double>& x, std::vector<double>& y)
  {
    // Returns candidate outlier: A linear regression and residuals are calculated for
    // the data points. The one with highest residual error is selected as the outlier candidate. The
    // corresponding iterator position is then returned.
    Math::LinearRegression lin_reg;
    lin_reg.computeRegression(0.95, x.begin(), x.end(), y.begin());

    std::vector<double> residuals;

    for (Size i = 0; i < x.size(); i++)
    {
      double residual = fabs(y[i] - (lin_reg.getIntercept() + (lin_reg.getSlope() * x[i])));
      residuals.push_back(residual);
    }

    return max_element(residuals.begin(), residuals.end()) - residuals.begin();
  }

  std::vector<std::pair<double, double> > MRMRTNormalizer::removeOutliersIterative(
      std::vector<std::pair<double, double> >& pairs, double rsq_limit,
      double coverage_limit, bool use_chauvenet, std::string method)
  {
    if (pairs.size() < 2)
    {
      throw Exception::IllegalArgument(__FILE__, __LINE__, __PRETTY_FUNCTION__,
        "Need at least 2 points for the regression.");
    }

    // Removes outliers from vector of pairs until upper rsq and lower coverage limits are reached.
    std::vector<double> x, y;
    double confidence_interval = 0.95;

    std::vector<std::pair<double, double> > pairs_corrected;

    for (std::vector<std::pair<double, double> >::iterator it = pairs.begin(); it != pairs.end(); ++it)
    {
      x.push_back(it->first);
      y.push_back(it->second);
      LOG_DEBUG << "RT Normalization pairs: " << it->first << " : " << it->second << std::endl;
    }

    double rsq;
    rsq = 0;

    while (x.size() >= coverage_limit * pairs.size() && rsq < rsq_limit)
    {
      Math::LinearRegression lin_reg;
      lin_reg.computeRegression(confidence_interval, x.begin(), x.end(), y.begin());

      rsq = lin_reg.getRSquared();

      std::cout << "rsq: " << rsq << " points: " << x.size() << std::endl;

      if (rsq < rsq_limit)
      {
        std::vector<double> residuals;

        // calculate residuals
        for (std::vector<std::pair<double, double> >::iterator it = pairs.begin(); it != pairs.end(); ++it)
        {
          double intercept = lin_reg.getIntercept();
          double slope = (double)lin_reg.getSlope();
          residuals.push_back(abs(it->second - (intercept + it->first * slope)));
          LOG_DEBUG << " RT Normalization residual is " << residuals.back() << std::endl;
        }

        int pos;

        if (method == "iter_jackknife")
        {
          // get candidate outlier: removal of which datapoint results in best rsq?
          pos = jackknifeOutlierCandidate_(x, y);
        }
        else if (method == "iter_residual")
        {
          // get candidate outlier: removal of datapoint with largest residual?
          pos = residualOutlierCandidate_(x, y);
        }
        else
        {
          throw Exception::IllegalArgument(__FILE__, __LINE__, __PRETTY_FUNCTION__,
            String("Method ") + method + " is not a valid method for removeOutliersIterative");
        }

        // remove if residual is an outlier according to Chauvenet's criterion
        // or if testing is turned off
        LOG_DEBUG << " Got outlier candidate " << pos << "(" << x[pos] << " / " << y[pos] << std::endl;
        if (!use_chauvenet || chauvenet(residuals, pos))
        {
          x.erase(x.begin() + pos);
          y.erase(y.begin() + pos);
        }
        else
        {
          break;
        }
      }
      else
      {
        break;
      }
    }

    if (rsq < rsq_limit)
    {
      // If the rsq is below the limit, this is an indication that something went wrong!
      throw Exception::UnableToFit(__FILE__, __LINE__, __PRETTY_FUNCTION__,
          "UnableToFit-LinearRegression-RTNormalizer", "WARNING: rsq: " +
          boost::lexical_cast<std::string>(rsq) + " is below limit of " +
          boost::lexical_cast<std::string>(rsq_limit) +
          ". Validate assays for RT-peptides and adjust the limit for rsq or coverage.");
    }

    for (Size i = 0; i < x.size(); i++)
    {
      pairs_corrected.push_back(std::make_pair(x[i], y[i]));
    }

#ifdef DEBUG_MRMRTNORMALIZER
    std::cout << "=======STARTPOINTS=======" << std::endl;
    for (std::vector<std::pair<double, double> >::iterator it = pairs_corrected.begin(); it != pairs_corrected.end(); ++it)
    {
      std::cout << it->first << "\t" << it->second << std::endl;
    }
    std::cout << "=======ENDPOINTS=======" << std::endl;
#endif

    return pairs_corrected;
  }

  bool MRMRTNormalizer::chauvenet(std::vector<double>& residuals, int pos)
  {
    double criterion = 1.0 / (2 * residuals.size());
    double prob = MRMRTNormalizer::chauvenet_probability(residuals, pos);

    LOG_DEBUG << " Chauvinet testing " << prob << " < " << criterion << std::endl;
    if (prob < criterion)
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  double MRMRTNormalizer::chauvenet_probability(std::vector<double>& residuals, int pos)
  {
    double mean = std::accumulate(residuals.begin(), residuals.end(), 0.0) / residuals.size();
    double stdev = std::sqrt(
        std::inner_product(residuals.begin(), residuals.end(), residuals.begin(), 0.0
          ) / residuals.size() - mean * mean);

    double d = fabs(residuals[pos] - mean) / stdev;
    d /= pow(2.0, 0.5);
    double prob = boost::math::erfc(d);

    return prob;
  }

}


//---------------------------------------------------------------------------//
//  MIT License
//
//  Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
//  Copyright (c) 2020-2021 Nikita Kaskov <nemo@nil.foundation>

//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef FILECOIN_STORAGE_PROOFS_CORE_DRGRAPH_HPP
#define FILECOIN_STORAGE_PROOFS_CORE_DRGRAPH_HPP

#include <boost/graph/directed_graph.hpp>

#include <nil/crypto3/random/chacha.hpp>

#include <nil/crypto3/hash/sha2.hpp>
#include <nil/crypto3/hash/algorithm/hash.hpp>

#include <nil/filecoin/storage/proofs/core/utilities.hpp>
#include <nil/filecoin/storage/proofs/core/parameter_cache.hpp>

#include <nil/filecoin/storage/proofs/core/crypto/domain_seed.hpp>

namespace nil {
    namespace filecoin {
        constexpr static const bool PARALLEL_MERKLE = true;

        /// The base degree used for all DRG graphs. One degree from this value is used to ensure that a
        /// given node always has its immediate predecessor as a parent, thus ensuring unique topological
        /// ordering of the graph nodes.
        constexpr static const std::size_t BASE_DEGREE = 6;

        std::array<std::uint8_t, 28> derive_drg_seed(const std::array<std::uint8_t, 32> &porep_id) {
            std::array<std::uint8_t, 28> drg_seed {};
            std::array<std::uint8_t, 32> raw_seed = derive_porep_domain_seed(DRSAMPLE_DST, porep_id);
            std::copy(raw_seed.begin(), raw_seed.begin() + 28, drg_seed.begin());
            return drg_seed;
        }

        template<std::size_t Arity>
        std::size_t graph_height(std::size_t number_of_leafs) {
            return merkletree::merkle::get_merkle_tree_row_count(number_of_leafs, Arity);
        }

        /// A depth robust graph.
        template<typename Hash, typename KeyType>
        struct Graph {
            typedef KeyType key_type;
            typedef Hash hash_type;

            /// Returns the expected size of all nodes in the graph.
            virtual std::size_t expected_size() const {
                return size() * NODE_SIZE;
            }

            /// Returns the merkle tree depth.
            virtual std::uint64_t merkle_tree_depth() const {
                return graph_height<PoseidonArity>(size());
            }

            /// Returns a sorted list of all parents of this node. The parents may be repeated.
            ///
            /// If a node doesn't have any parents, then this vector needs to return a vector where
            /// the first element is the requested node. This will be used as indicator for nodes
            /// without parents.
            ///
            /// The `parents` parameter is used to store the result. This is done fore performance
            /// reasons, so that the vector can be allocated outside this call.
            virtual void parents(std::size_t node, std::vector<std::uint32_t> &parents) const = 0;

            /// Returns the size of the graph (number of nodes).
            virtual std::size_t size() const = 0;

            /// Returns the number of parents of each node in the graph.
            virtual std::size_t degree() const = 0;

            virtual std::array<std::uint8_t, 28> seed() const = 0;

            /// Creates the encoding key.
            /// The algorithm for that is `Sha256(id | encodedParentNode1 | encodedParentNode1 | ...)`.
            virtual key_type create_key(const typename hash_type::digest_type &id, std::size_t node,
                                        const std::vector<std::uint32_t> &parents,
                                        const std::vector<std::uint8_t> &parents_data,
                                        const std::vector<std::uint8_t> &exp_parents_data) = 0;
        };

        template<typename Hash, typename ParentsHash = crypto3::hashes::sha2<256>>
        struct BucketGraph : public parameter_set_metadata, public Graph<Hash, typename Hash::digest_type> {
            typedef typename Graph<Hash, typename Hash::digest_type>::hash_type hash_type;
            typedef typename Graph<Hash, typename Hash::digest_type>::key_type key_type;

            BucketGraph(size_t nodes, size_t base_degree, size_t expansion_degree,
                        const std::array<uint8_t, 32> &porep_id) :
                nodes(nodes),
                base_degree(base_degree) {
                BOOST_ASSERT_MSG(expansion_degree == 0, "Expansion degree must be zero.");

                // The number of metagraph nodes must be less than `2u64^54` as to not incur rounding errors
                // when casting metagraph node indexes from `std::uint64_t` to `double` during parent generation.
                std::size_t m_prime = base_degree - 1;
                std::size_t n_metagraph_nodes = nodes * m_prime;
                BOOST_ASSERT_MSG(n_metagraph_nodes <= 1ULL << 54,
                                 "The number of metagraph nodes must be precisely castable to `double`");

                seed = derive_drg_seed(porep_id);
            }

            virtual size_t expected_size() const override {
                return Graph<Hash, typename Hash::digest_type>::expected_size();
            }
            virtual uint64_t merkle_tree_depth() const override {
                return Graph<Hash, typename Hash::digest_type>::merkle_tree_depth();
            }
            inline virtual void parents(std::size_t node, std::vector<uint32_t> &parents) const override {
                std::size_t m = degree();

                if (node == 0 || node == 1) {
                    // There are special cases for the first and second node: the first node self
                    // references, the second node only references the first node.
                    // Use the degree of the current graph (`m`) as `parents.len()` might be bigger than
                    // that (that's the case for Stacked Graph).
                    for (auto parent = parents.begin(); parent < parents.begin() + m; ++parent) {
                        *parent = 0;
                    }
                } else {
                    // DRG node indexes are guaranteed to fit within a `u32`.
                    std::array<std::uint8_t, 32> s32;
                    boost::copy(seed, s32);
                    crypto3::detail::pack_to<crypto3::stream_endian::little_octet_big_bit>({node}, seed);
                    crypto3::random::chacha rng(seed);

                    std::size_t m_prime = m - 1;
                    // Large sector sizes require that metagraph node indexes are `u64`.
                    std::size_t metagraph_node = node * m_prime;
                    std::size_t n_buckets = std::ceil(std::log2(static_cast<double>(metagraph_node)));

                    for (typename std::vector<uint32_t>::iterator parent = parents.begin();
                         parent < parents.begin() + m_prime;
                         ++parent) {
                        std::uint64_t bucket_index = (rng() % n_buckets) + 1;
                        std::size_t largest_distance_in_bucket = std::min(metagraph_node, 1UL << bucket_index);
                        std::size_t smallest_distance_in_bucket = std::max(2UL, largest_distance_in_bucket >> 1UL);

                        // Add 1 becuase the number of distances in the bucket is inclusive.
                        std::size_t n_distances_in_bucket =
                            largest_distance_in_bucket - smallest_distance_in_bucket + 1;

                        std::size_t distance = smallest_distance_in_bucket + (rng() % n_distances_in_bucket);

                        std::uint32_t metagraph_parent = metagraph_node - distance;

                        // Any metagraph node mapped onto the DRG can be safely cast back to `u32`.
                        std::uint32_t mapped_parent = (metagraph_parent / m_prime);

                        if (mapped_parent == node) {
                            *parent = node - 1;
                        } else {
                            *parent = mapped_parent;
                        }
                    }

                    parents[m_prime] = node - 1;
                }
            }
            virtual size_t size() const override {
                return 0;
            }
            virtual size_t degree() const override {
                return 0;
            }
            virtual key_type create_key(const typename hash_type::digest_type &id, std::size_t node,
                                        const std::vector<uint32_t> &parents, const std::vector<uint8_t> &parents_data,
                                        const std::vector<uint8_t> &exp_parents_data) override {
                using namespace nil::crypto3;
                accumulator_set<ParentsHash> acc;
                hash<ParentsHash>(id, acc);

                // The hash is about the parents, hence skip if a node doesn't have any parents
                if (node != parents[0]) {
                    for (std::uint32_t parent : parents) {
                        std::size_t offset = data_at_node_offset(parent);
                        hash<ParentsHash>(parents_data.begin(), parents_data.begin() + NODE_SIZE, acc);
                    }
                }

                typename ParentsHash::digest_type hash = accumulators::extract::hash<ParentsHash>(acc);
                return bytes_into_fr_repr_safe(hash);
            }

            virtual std::string identifier() const override {
                return std::string();
            }
            virtual size_t sector_size() const override {
                return nodes * NODE_SIZE;
            }

            std::size_t nodes;
            std::size_t base_degree;
            std::array<std::uint8_t, 28> seed;
        };
    }    // namespace filecoin
}    // namespace nil

#endif


/*
Copyright 2009-2021 Nicolas Colombe

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

#include <gen/pattern.hpp>
#include <core/randomsetwalk.hpp>
#include <boost/optional.hpp>

namespace eXl
{
  class Random;

  template <unsigned int N>
  struct NearestPow2;

  template <>
  struct NearestPow2<1>
  {
    static const unsigned int Pow = 1;
  };

  template <unsigned int N>
  struct NearestPow2
  {
    static const unsigned int Pow = NearestPow2<N / 2>::Pow + 1;
  };

  template <unsigned int N, unsigned int Size>
  struct CompactStorage
  {
    static const unsigned int ArraySize = (NearestPow2<N>::Pow * Size) / 32 + ((NearestPow2<N>::Pow * Size) % 32 == 0 ? 0 : 1);
    unsigned int Data[ArraySize];

    void Build(Pattern<unsigned int> const& iSample);
    void Extract(Pattern<unsigned int>& oSample) const;
  };

  template <unsigned int N, unsigned int FieldSize, bool UseDictionnary = false>
  class ConvChains
  {
  public:

    static const unsigned int GridSize = FieldSize * FieldSize;
    typedef CompactStorage<N - 1, GridSize> Storage;

    struct iequal_to : std::binary_function<Storage,Storage, bool>
    {
      bool operator()(Storage const& x, Storage const& y) const;
    };

    struct ihash : std::unary_function<Storage, std::size_t>
    {
      std::size_t operator()(Storage const& x) const;
    };

    ConvChains(bool iToroidal = false) : m_Toroidal(iToroidal)
    {
      if(UseDictionnary)
      {
        m_Dict.push_back(0xFFFFFFFF);
        m_RevDict[0xFFFFFFFF] = 0;
      }
    }

    ConvChains(Pattern<unsigned int> const& iSample, bool iToroidal = false) : m_Toroidal(iToroidal)
    {
      if(UseDictionnary)
      {
        m_Dict.push_back(0xFFFFFFFF);
        m_RevDict[0xFFFFFFFF] = 0;
      }
      AddPattern(iSample);
    }

    void AddPattern(Pattern<unsigned int> const& iSample, bool iRotateSym = true);

    void Generate(Pattern<unsigned int>& ioResult, Random& iRand, float iTemperature, int iIterations, bool iInitRand = false, Pattern<float> const* iTemp = nullptr);

    void Evaluate(Pattern<unsigned int> const& iPattern, Vector<double>& oRef, Vector<double>& oScore, bool iRotateSym = true) const;

    struct PatternInfo
    {
      uint32_t weight = 0;
      bool isBorder = false;
    };

    typedef UnorderedMap<Storage, PatternInfo, ihash, iequal_to> WeightsMap;

    struct WaveState
    {
      struct CellState
      {
        Vector<uint32_t> comp[4];
        Vector<bool> possible;

        int sumOfOnes;
        float sumOfWeights;
        float sumOfWeightsLogWeight;
        float entropy;
      };

      bool Outside(Vector2i& ioPos)
      {
        auto const& wkAreaSize = m_State.GetSize();

        if(m_Toroidal)
        {
          Vector2i coord((ioPos.X() + wkAreaSize.X()) % wkAreaSize.X(),
            (ioPos.Y() + wkAreaSize.Y()) % wkAreaSize.Y());
          ioPos = coord;
          return false;
        }
        else
        {
          if(ioPos.X() < 0 || ioPos.X() >= (wkAreaSize.X() /*- FieldSize*/)
          || ioPos.Y() < 0 || ioPos.Y() >= (wkAreaSize.Y() /*- FieldSize*/))
          {
            return true;
          }
          return false;
        }
      }

      void InitPropagator()
      {
        auto agrees = [](Pattern<uint32_t> const& iP1, Pattern<uint32_t> const& iP2, int dx, int dy)
        {
          int xmin = dx < 0 ? 0 : dx;
          int xmax = dx < 0 ? dx + FieldSize : FieldSize;
          int ymin = dy < 0 ? 0 : dy;
          int ymax = dy < 0 ? dy + FieldSize : FieldSize;
          for (int y = ymin; y < ymax; y++)
          {
            for (int x = xmin; x < xmax; x++)
            {
              if (iP1[Vector2i(x, y)] != iP2[Vector2i(x - dx, (y - dy))])
              {
                return false;
              }
            }
          }
          return true;
        };

        int DX[4] = {1, -1, 0, 0};
        int DY[4] = {0, 0, 1, -1};

        Vector<Pattern<uint32_t>> extracted(numSymbols, Pattern<uint32_t> (Vector2i(FieldSize, FieldSize)));

        for (int sym = 0; sym < numSymbols; ++sym)
        {
          m_PatternDict[sym]->first.Extract(extracted[sym]);
        }

        for (int d = 0; d < 4; d++)
        {
          for (int sym = 0; sym < numSymbols; ++sym)
          {
            Pattern<uint32_t> const& p1 = extracted[sym];
            //List<int> list = new List<int>();
            Vector<uint32_t> list;
            uint32_t count = 0;
            for (int sym2 = 0; sym2 < numSymbols; ++sym2)
            {
              Pattern<uint32_t> const& p2 = extracted[sym2];

              if (agrees(p1, p2, DX[d], DY[d]))
              {
                ++count;
                list.push_back(sym2);
              }
            }
            //propagator[d][t] = new int[list.Count];
            //for (int c = 0; c < list.Count; c++)
            //{
            //  propagator[d][t][c] = list[c];
            //}
            m_Propagator[d][sym] = std::move(list);
          }
        }
      }

      void Init(ConvChains const& iMem, Vector2i const& iSize)
      {
        m_Toroidal = iMem.m_Toroidal;

        numSymbols = iMem.m_WeightsMap.size();

        CellState defaultCell;
        defaultCell.possible.resize(numSymbols, true);
        for(uint32_t i = 0; i<4; ++i)
        {
          defaultCell.comp[i].resize(numSymbols, 0);
          m_Propagator[i].resize(numSymbols);
        }

        m_State.SetSize(iSize, defaultCell);
        m_CollapsedState.SetSize(iSize, numSymbols);

        for(auto iter = iMem.m_WeightsMap.begin(); iter != iMem.m_WeightsMap.end(); ++iter)
        {
          m_PatternDict.push_back(iter);

          m_SumOfWeights += iter->second.weight;
          m_WeightLogWeights.push_back(iter->second.weight * log(iter->second.weight));
          m_SumOfWeightLogWeights += m_WeightLogWeights.back();
        }

        InitPropagator();

        m_StartingEntropy = log(m_SumOfWeights) - m_SumOfWeightLogWeights / m_SumOfWeights;

      }

      boost::optional<bool> Observe(Random& iRand)
      {
        uint32_t const stateSize = m_State.GetSize().X() * m_State.GetSize().Y();

        float min = 1.0e3;
        int argmin = -1;
        Vector2i selectedPos;
        uint32_t offset = 0;
        for (int y = 0; y < m_State.GetSize().Y(); ++y)
        {
          for (int x = 0; x < m_State.GetSize().X(); ++x)
          {
            if (Outside(Vector2i(x, y)))
            {
              ++offset;
              continue;
            }

            auto const& cellState = m_State[offset];

            int amount = cellState.sumOfOnes;

            if (amount == 0)
            {
              return false;
            }

            float entropy = cellState.entropy;
            if (amount > 1 && entropy <= min)
            {
              double noise = 1.0e-11 * (iRand() % 10000);
              if (entropy + noise < min)
              {
                min = entropy + noise;
                selectedPos = Vector2i(x, y);
                argmin = offset;
              }
            }
            ++offset;
          }
        }

        //if (argmin == -1)
        {
          for (int i = 0; i < stateSize; ++i)
          {
            for (int sym = 0; sym < numSymbols; ++sym)
            {
              if (m_State.GetBitmap()[i].possible[sym])
              {
                m_CollapsedState.GetBitmap()[i] = sym;
                break;
              }
            }
          }
          if (argmin == -1)
          {
            return true;
          }
        }

        float distTotSum = 0;
        Multimap<float, uint32_t> distribution;
        for (int sym = 0; sym < numSymbols; sym++)
        {
          if(m_State.GetBitmap()[argmin].possible[sym])
          {
            distribution.insert(std::make_pair(m_PatternDict[sym]->second.weight, sym));
            distTotSum += m_PatternDict[sym]->second.weight;
          }
        }

        eXl_ASSERT(!distribution.empty());

        uint32_t selectedSym = distribution.begin()->second;
        //int r = distribution.Random(random.NextDouble());
        float val = (float(iRand() % 1000) / 1000.0) * distTotSum;
        for(auto revIter = distribution.rbegin(); revIter != distribution.rend(); ++revIter)
        {
          if(revIter->first > val)
          {
            selectedSym = revIter->second;
            break;
          }
          else
          {
            val -= revIter->first;
          }
        }

        auto const& possibilities = m_State.GetBitmap()[argmin].possible;
        for (int sym = 0; sym < numSymbols; sym++)
        {
          if (possibilities[sym] != (sym == selectedSym))
          {
            Ban(selectedPos, sym);
          }
        }

        return boost::none;
      }

      void Ban(Vector2i pos, uint32_t sym)
      {
        auto& cellState = m_State[pos];
        cellState.possible[sym] = false;

        for (int dir = 0; dir < 4; dir++)
        {
          cellState.comp[dir][sym] = 0;
        }

        m_Stack.push_back(std::make_pair(pos, sym));

        cellState.sumOfOnes -= 1;
        if(cellState.sumOfOnes == 0)
        {
          printf("Termination reached at (%i,%i)", pos.X(), pos.Y());
        }
        cellState.sumOfWeights -= m_PatternDict[sym]->second.weight;
        cellState.sumOfWeightsLogWeight -= m_WeightLogWeights[sym];

        float sum = cellState.sumOfWeights;
        cellState.entropy = log(sum) - cellState.sumOfWeightsLogWeight / sum;
      }

      void Propagate()
      {
        while (!m_Stack.empty())
        {
          auto cell = m_Stack.back();
          m_Stack.pop_back();

          Vector2i const& pos = cell.first;

          uint32_t const& sym = cell.second;

          for (int dim = 0; dim < 2; ++dim)
          {
            for (int signIdx = 0; signIdx < 2; ++signIdx)
            {
              Vector2i otherPos = pos;
              otherPos.m_Data[dim] += 1 - 2*signIdx;

              if(Outside(otherPos))
              {
                continue;
              }
              else
              {
                //int i2 = x2 + y2 * FMX;

                uint32_t dirIdx = dim * 2 + signIdx;

                auto& p = m_Propagator[dirIdx][sym];

                CellState& otherCell = m_State[otherPos];

                for (uint32_t otherSym : p)
                {
                  uint32_t& comp = otherCell.comp[dirIdx][otherSym];

                  comp--;

                  if (comp == 0)
                  {
                    Ban(otherPos, otherSym);
                  }
                }
              }
            }
          }
        }
      }

      void Clear()
      {
        uint32_t localOffset = 0;
        for (int y = 0; y < m_State.GetSize().Y(); ++y)
        {
          for (int x = 0; x < m_State.GetSize().X(); ++x)
          {
            CellState& state = m_State.GetBitmap()[localOffset];

            for (int sym = 0; sym < numSymbols; ++sym)
            {
              state.possible[sym] = true;

              for (int d = 0; d < 4; d++)
              {
                state.comp[d][sym] = state.possible[sym] ? m_Propagator[s_Opposite[d]][sym].size() : 0;
              }

            }

            state.sumOfOnes = numSymbols;
            state.sumOfWeights = m_SumOfWeights;
            state.sumOfWeightsLogWeight = m_SumOfWeightLogWeights;
            state.entropy = m_StartingEntropy;

            ++localOffset;
          }
        }
        if(!m_Toroidal)
        {
          for (int y = 0; y < (int)m_State.GetSize().Y() - FieldSize; ++y)
          {
            for (int x = 0; x < (int)m_State.GetSize().X() - FieldSize; ++x)
            {
              for (int sym = 0; sym < numSymbols; ++sym)
              {
                if(m_PatternDict[sym]->second.isBorder)
                {
                  Ban(Vector2i(x,y), sym);
                }
              }
            }
          }
          Propagate();
        }
      }

      uint32_t s_Opposite[4] = {1, 0, 3, 2};

      Vector<Vector<uint32_t>> m_Propagator[4];

      Pattern<CellState> m_State;
      Pattern<uint32_t> m_CollapsedState;

      Vector<double> m_WeightLogWeights;

      uint32_t numSymbols;
      bool m_Toroidal;

      float m_SumOfWeights = 0;
      float m_SumOfWeightLogWeights = 0;
      float m_StartingEntropy = 0;

      uint32_t m_RestartCounter = 0;

      Vector<typename WeightsMap::const_iterator> m_PatternDict;
      Vector<std::pair<Vector2i, uint32_t>> m_Stack;
    };

    void Wave(Pattern<unsigned int>& ioResult, Random& iRand, uint32_t numIter, WaveState& ioState, bool iInit = false)
    {
      if(iInit)
      {
        ioState.Clear();
      }

      eXl_ASSERT(ioResult.GetSize() == ioState.m_State.GetSize());

      for (uint32_t l = 0; l < numIter; l++)
      {
        auto result = ioState.Observe(iRand);
        if (!result)
        {
          ioState.Propagate();
        }
        else if(!result.get())
        {
          ioState.Clear();
          ioState.m_RestartCounter++;
          printf("Restarted %i \n", ioState.m_RestartCounter);
        }
      }

      uint32_t globOffset = 0;
      for (int y = 0; y < ioState.m_State.GetSize().Y(); ++y)
      {
        for (int x = 0; x < ioState.m_State.GetSize().X(); ++x)
        {
          Vector2i curPos(x,y);

          uint32_t sym = ioState.m_CollapsedState.GetBitmap()[globOffset];
          if(sym != ioState.numSymbols)
          {
            Pattern<uint32_t> pattern(Vector2i(FieldSize, FieldSize));
            ioState.m_PatternDict[sym]->first.Extract(pattern);
            //ioResult[curPos] = m_Dict[pattern[Vector2i::ZERO]];
            for (int ly = 0; ly < FieldSize; ++ly)
            {
              for (int lx = 0; lx < FieldSize; ++lx)
              {
                Vector2i localPos(lx, ly);
                Vector2i otherPos = curPos;
                otherPos += Vector2i (lx, ly);
                if(otherPos.X() >=0 && otherPos.X() < ioState.m_State.GetSize().X()
                && otherPos.Y() >=0 && otherPos.Y() < ioState.m_State.GetSize().Y())
                {
                  ioResult[otherPos] = m_Dict[pattern[localPos]];
                }
              }
            }
          }

          ++globOffset;
        }
      }

      //return true;
    }

    Image GetPatternImage()
    {
      uint32_t dimX = Mathf::Round(sqrt(m_WeightsMap.size()));
      uint32_t dimY = m_WeightsMap.size() / dimX;
      if(m_WeightsMap.size() % dimX != 0)
      {
        ++dimX;
      }

      Image::Size imageSize(dimX * (FieldSize + 1) - 1, dimY * (FieldSize + 1) - 1);

      Vector<WeightsMap::iterator> patternDict;
      Multimap<uint32_t, uint32_t> patternSort;

      for(auto iter = m_WeightsMap.begin(); iter != m_WeightsMap.end(); ++iter)
      {
        patternDict.push_back(iter);
        patternSort.insert(std::make_pair(iter->second.weight, patternDict.size() - 1));
      }

      Image outImg(nullptr, imageSize, Image::RGBA, Image::Char, 1);
      memset(outImg.GetImageData(), 0, outImg.GetByteSize());

      Vector2i curPos;

      Pattern<uint32_t> pattern(Vector2i::ONE * FieldSize);
      for(auto rIter = patternSort.rbegin(); rIter != patternSort.rend(); ++rIter)
      {
        patternDict[rIter->second]->first.Extract(pattern);

        for(uint32_t y = 0; y<FieldSize; ++y)
        {
          for(uint32_t x = 0; x<FieldSize; ++x)
          {
            Vector2i locPos(x,y);
            Vector2i globPos = curPos + locPos;
            *((uint32_t*)outImg.GetPixel(globPos.Y(), globPos.X())) = pattern[locPos] == 1 ? 0 : 0xFFFFFFFF;
          }
        }

        if(curPos.X() + FieldSize < imageSize.X())
        {
          curPos.X() += FieldSize + 1;
        }
        else
        {
          curPos.X() = 0;
          curPos.Y() += FieldSize + 1;
        }
      }

      return outImg;
    }

  protected:

    Vector<unsigned int> m_TempBuffer;

    WeightsMap m_WeightsMap;

    void _AddPattern(Pattern<unsigned int> const& iSample, bool iRotateSym);

    unsigned int GetValue(unsigned int iOldValue, Random& iRand);

    void ComputePatternReceptor(Pattern<unsigned int>& oPattern, Pattern<unsigned int> const& iSample, Vector2i const& iCoord) const;

    typename WeightsMap::iterator ComputeReceptorIndex(Pattern<unsigned int> const& iReceptor, bool iCreate);

    typename WeightsMap::iterator ComputeReceptorIndex(Pattern<unsigned int> const& iReceptor) const;

    double ComputeEnergy(Pattern<unsigned int>& temp, Pattern<unsigned int> const& iSample, Vector2i const& iCoord);

    Vector<unsigned int> m_Dict;
    Map<unsigned int, unsigned int> m_RevDict;
    bool m_Toroidal;

  };

#include <gen/convchains.inl>

}