/*
 * 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_ */


/*
 * Copyright 2009-2020 The VOTCA Development Team (http://www.votca.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <libint2/initialize.h>
#define BOOST_TEST_MAIN

#define BOOST_TEST_MODULE orbitals_test

// Standard includes
#include <fstream>

// Third party includes
#include <boost/test/unit_test.hpp>

// Local VOTCA includes
#include "votca/xtp/aomatrix.h"
#include "votca/xtp/convergenceacc.h"
#include "votca/xtp/orbitals.h"

// VOTCA includes
#include <votca/tools/eigenio_matrixmarket.h>

using namespace votca::xtp;
using votca::Index;
BOOST_AUTO_TEST_SUITE(orbitals_test)
BOOST_AUTO_TEST_CASE(readxyztest) {
  std::ofstream xyzfile("molecule.xyz");
  xyzfile << " C 0.0 3 1" << std::endl;
  xyzfile << " methane" << std::endl;
  xyzfile << " C            .000000     .000000     .000000" << std::endl;
  xyzfile << " H            .629118     .629118     .629118" << std::endl;
  xyzfile << " H           -.629118    -.629118     .629118" << std::endl;
  xyzfile << " H            .629118    -.629118    -.629118" << std::endl;
  xyzfile << " H           -.629118     .629118    -.629118" << std::endl;
  xyzfile.close();

  Orbitals orb;

  BOOST_CHECK_THROW(orb.QMAtoms().LoadFromFile("molecule.xyz"),
                    std::runtime_error);
}

BOOST_AUTO_TEST_CASE(sortEnergies) {

  Orbitals orb;
  Eigen::VectorXd Energies = Eigen::VectorXd::LinSpaced(10, -5, 5);
  Eigen::VectorXd switched = Energies;
  switched(3) = Energies(5);
  switched(5) = Energies(3);
  orb.MOs().eigenvalues() = switched;
  orb.MOs().eigenvectors() = Eigen::MatrixXd::Zero(10, 10);
  orb.OrderMOsbyEnergy();
  bool issorted = Energies.isApprox(orb.MOs().eigenvalues(), 0.001);
  if (!issorted) {
    std::cout << "before" << std::endl;
    std::cout << Energies << std::endl;
    std::cout << "after" << std::endl;
    std::cout << orb.MOs().eigenvalues() << std::endl;
  }
  BOOST_CHECK_EQUAL(issorted, true);
}

BOOST_AUTO_TEST_CASE(densmat_test) {
  libint2::initialize();
  Orbitals orb;
  orb.setNumberOfOccupiedLevels(4);
  orb.setBSEindices(0, 9);
  orb.setNumberOfAlphaElectrons(5);
  orb.MOs().eigenvalues() = Eigen::VectorXd::Ones(17);
  orb.MOs().eigenvectors() = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/MOs.mm");

  orb.QMAtoms().LoadFromFile(std::string(XTP_TEST_DATA_FOLDER) +
                             "/orbitals/molecule.xyz");
  QMState s = QMState("n");
  Eigen::MatrixXd dmat_gs = orb.DensityMatrixFull(s);

  Eigen::MatrixXd dmat_gs_ref = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/dmat_gs_ref.mm");

  bool check_dmat_gs = dmat_gs.isApprox(dmat_gs_ref, 0.0001);
  if (!check_dmat_gs) {
    std::cout << "Result gs" << std::endl;
    std::cout << dmat_gs << std::endl;
    std::cout << "Ref" << std::endl;
    std::cout << dmat_gs_ref << std::endl;
  }
  BOOST_CHECK_EQUAL(check_dmat_gs, 1);
  orb.setTDAApprox(false);
  orb.BSESinglets().eigenvectors() =
      votca::tools::EigenIO_MatrixMarket::ReadMatrix(
          std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/BSE_vectors1.mm");

  orb.BSESinglets().eigenvectors2() =
      votca::tools::EigenIO_MatrixMarket::ReadMatrix(
          std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/BSE_vectors2.mm");

  QMState s1 = QMState("s1");
  Eigen::MatrixXd dmat_s1 = orb.DensityMatrixFull(s1);

  Eigen::MatrixXd dmat_s1_ref = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/dmat_s1_ref.mm");

  bool check_dmat_s1 = dmat_s1.isApprox(dmat_s1_ref, 0.0001);
  if (!check_dmat_s1) {
    std::cout << "Result s1" << std::endl;
    std::cout << dmat_s1 << std::endl;
    std::cout << "Ref" << std::endl;
    std::cout << dmat_s1_ref << std::endl;
  }
  BOOST_CHECK_EQUAL(check_dmat_s1, 1);

  QMState n2s1 = QMState("n2s1");
  Eigen::MatrixXd dmat_n2s1 = orb.DensityMatrixFull(n2s1);
  Eigen::MatrixXd dmat_n2s1_ref =
      votca::tools::EigenIO_MatrixMarket::ReadMatrix(
          std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/dmat_n2s1_ref.mm");

  bool check_dmat_n2s1 = dmat_n2s1.isApprox(dmat_n2s1_ref, 0.0001);
  if (!check_dmat_n2s1) {
    std::cout << "Result n2s1" << std::endl;
    std::cout << dmat_n2s1 << std::endl;
    std::cout << "Ref" << std::endl;
    std::cout << dmat_n2s1_ref << std::endl;
  }

  BOOST_CHECK_EQUAL(check_dmat_n2s1, 1);

  libint2::finalize();
}

BOOST_AUTO_TEST_CASE(dipole_test) {
  libint2::initialize();
  Orbitals orbitals;
  orbitals.QMAtoms().LoadFromFile(std::string(XTP_TEST_DATA_FOLDER) +
                                  "/orbitals/molecule.xyz");
  BasisSet basis;
  basis.Load(
      std::string(std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/3-21G.xml"));
  orbitals.SetupDftBasis(std::string(XTP_TEST_DATA_FOLDER) +
                         "/orbitals/3-21G.xml");
  AOBasis aobasis;
  aobasis.Fill(basis, orbitals.QMAtoms());
  orbitals.setNumberOfOccupiedLevels(4);
  Eigen::MatrixXd& MOs = orbitals.MOs().eigenvectors();
  orbitals.MOs().eigenvalues() = Eigen::VectorXd::Ones(17);
  MOs = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/MOs2.mm");

  orbitals.setBSEindices(0, 16);
  orbitals.setTDAApprox(true);

  Eigen::MatrixXd spsi_ref = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/spsi_ref.mm");

  orbitals.BSESinglets().eigenvectors() = spsi_ref;
  QMState state_trans = QMState("n2s1");

  Eigen::Vector3d res_trans = orbitals.CalcElDipole(state_trans);
  Eigen::Vector3d ref_trans = Eigen::Vector3d::Zero();
  ref_trans << 0.118565, 0.0444239, -0.0505149;

  bool check_trans = ref_trans.isApprox(res_trans, 0.0001);
  if (!check_trans) {
    std::cout << "Result transition dipole" << std::endl;
    std::cout << res_trans << std::endl;
    std::cout << "Ref transition dipole" << std::endl;
    std::cout << ref_trans << std::endl;
  }
  BOOST_CHECK_EQUAL(check_trans, 1);

  QMState state_s1 = QMState("s1");
  Eigen::Vector3d res_s1 = orbitals.CalcElDipole(state_s1);
  Eigen::Vector3d ref_s1 = Eigen::Vector3d::Zero();
  ref_s1 << -0.15153501734, -0.42406579479, 0.033954362839;
  bool check_s1 = ref_s1.isApprox(res_s1, 0.0001);
  if (!check_s1) {
    std::cout << "Result s1 dipole" << std::endl;
    std::cout << res_s1 << std::endl;
    std::cout << "Ref s1 dipole" << std::endl;
    std::cout << ref_s1 << std::endl;
  }
  BOOST_CHECK_EQUAL(check_s1, 1);

  libint2::finalize();
}

BOOST_AUTO_TEST_CASE(osc_strength) {
  libint2::initialize();
  Orbitals orb;
  orb.QMAtoms().LoadFromFile(std::string(XTP_TEST_DATA_FOLDER) +
                             "/orbitals/molecule.xyz");
  orb.SetupDftBasis(std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/3-21G.xml");

  QMState s("s1");
  orb.setNumberOfOccupiedLevels(4);
  orb.setBSEindices(0, 16);
  orb.setTDAApprox(true);

  Eigen::MatrixXd& MOs = orb.MOs().eigenvectors();
  orb.MOs().eigenvalues() = Eigen::VectorXd::Ones(17);
  MOs = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/MOs3.mm");
  Eigen::VectorXd se_ref = Eigen::VectorXd::Zero(3);
  se_ref << 0.107455, 0.107455, 0.107455;
  orb.BSESinglets().eigenvalues() = se_ref;
  // reference coefficients
  Eigen::MatrixXd spsi_ref = votca::tools::EigenIO_MatrixMarket::ReadMatrix(
      std::string(XTP_TEST_DATA_FOLDER) + "/orbitals/spsi_ref2.mm");

  orb.BSESinglets().eigenvectors() = spsi_ref;
  orb.CalcCoupledTransition_Dipoles();

  std::vector<Eigen::Vector3d> dipoles = orb.TransitionDipoles();
  std::vector<Eigen::Vector3d> dipoles_ref;
  dipoles_ref.push_back(Eigen::Vector3d(0.110512, 0.048776, -0.0515914));
  dipoles_ref.push_back(Eigen::Vector3d(-0.13408, 0.0969472, 0.0261392));
  dipoles_ref.push_back(Eigen::Vector3d(0.0586073, 0.121606, -0.0606862));

  for (Index i = 0; i < 3; i++) {
    bool check = dipoles[i].isApprox(dipoles_ref[i], 1e-5);
    BOOST_CHECK_EQUAL(check, true);
    if (!check) {
      std::cout << "ref" << i << std::endl;
      std::cout << dipoles_ref[i].transpose() << std::endl;
      std::cout << "result" << i << std::endl;
      std::cout << dipoles[i].transpose() << std::endl;
    }
  }

  Eigen::VectorXd oscs = orb.Oscillatorstrengths();
  Eigen::VectorXd oscs_ref = Eigen::VectorXd::Zero(3);
  oscs_ref << 0.001236, 0.00201008, 0.00156925;

  bool check_oscs = oscs.isApprox(oscs_ref, 1e-5);
  BOOST_CHECK_EQUAL(check_oscs, true);
  if (!check_oscs) {
    std::cout << "result" << std::endl;
    std::cout << oscs << std::endl;
    std::cout << "ref" << std::endl;
    std::cout << oscs_ref << std::endl;
  }

  libint2::finalize();
}

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================

#ifndef BOOST_SIMD_FUNCTION_SIMD_DOT_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SIMD_DOT_HPP_INCLUDED

#include <boost/simd/function/scalar/dot.hpp>
#include <boost/simd/arch/common/generic/function/autodispatcher.hpp>
#include <boost/simd/arch/common/simd/function/dot.hpp>

#if defined(BOOST_HW_SIMD_X86_OR_AMD_AVAILABLE)
#  if BOOST_HW_SIMD_X86_OR_AMD >= BOOST_HW_SIMD_X86_SSE4_1_VERSION
#    include <boost/simd/arch/x86/sse4_1/simd/function/dot.hpp>
#  endif
#  if BOOST_HW_SIMD_X86_OR_AMD >= BOOST_HW_SIMD_X86_AVX_VERSION
#    include <boost/simd/arch/x86/avx/simd/function/dot.hpp>
#  endif
#endif

#endif


// --------------------------------------------------------------------------
//                   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 (c) 2017-2019 The AMBANKCOIN developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "libzerocoin/Denominations.h"
#include "libzerocoin/CoinSpend.h"
#include "libzerocoin/Accumulator.h"
#include "zambk/zerocoin.h"
#include "zambk/deterministicmint.h"
#include "zambk/zambkwallet.h"
#include "libzerocoin/Coin.h"
#include "amount.h"
#include "chainparams.h"
#include "coincontrol.h"
#include "main.h"
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#include "txdb.h"
#include "test/test_ambankcoin.h"
#include <boost/test/unit_test.hpp>
#include <iostream>

class CDeterministicMint;

BOOST_FIXTURE_TEST_SUITE(zerocoin_coinspend_tests, TestingSetup)

/**
 * Check that wrapped serials pass and not pass using the new validation.
 */
BOOST_AUTO_TEST_CASE(zerocoin_wrapped_serial_spend_test)
{
    unsigned int TESTS_COINS_TO_ACCUMULATE = 5;

    SelectParams(CBaseChainParams::MAIN);
    libzerocoin::ZerocoinParams *ZCParams = Params().Zerocoin_Params(false);
    (void)ZCParams;

    // Seed + Mints
    std::string strWalletFile = "unittestwallet.dat";
    CWalletDB walletdb(strWalletFile, "cr+");
    CWallet wallet(strWalletFile);
    CzAMBKWallet *czAMBKWallet = new CzAMBKWallet(wallet.strWalletFile);

    // Get the 5 created mints.
    libzerocoin::CoinDenomination denom = libzerocoin::CoinDenomination::ZQ_FIFTY;
    std::vector<libzerocoin::PrivateCoin> vCoins;
    for (unsigned int i = 0; i < TESTS_COINS_TO_ACCUMULATE; i++) {
        libzerocoin::PrivateCoin coin(ZCParams, denom, false);
        CDeterministicMint dMint;
        czAMBKWallet->GenerateDeterministicZAMBK(denom, coin, dMint, true);
        czAMBKWallet->UpdateCount();
        vCoins.emplace_back(coin);
    }

    // Selected coin
    libzerocoin::PrivateCoin coinToSpend = vCoins[0];

    // Accumulate coins
    libzerocoin::Accumulator acc(&ZCParams->accumulatorParams, denom);
    libzerocoin::AccumulatorWitness accWitness(ZCParams, acc, coinToSpend.getPublicCoin());

    for (uint32_t i = 0; i < TESTS_COINS_TO_ACCUMULATE; i++) {
        acc += vCoins[i].getPublicCoin();
        if(i != 0) {
            accWitness += vCoins[i].getPublicCoin();
        }
    }

    // Wrapped serial
    Bignum wrappedSerial = coinToSpend.getSerialNumber() + ZCParams->coinCommitmentGroup.groupOrder * CBigNum(2).pow(256) * 2;
    coinToSpend.setSerialNumber(wrappedSerial);

    libzerocoin::CoinSpend wrappedSerialSpend(
            ZCParams,
            ZCParams,
            coinToSpend,
            acc,
            0,
            accWitness,
            0,
            libzerocoin::SpendType::SPEND
    );

    // first check that the Verify pass without do the invalid range check
    BOOST_CHECK_MESSAGE(wrappedSerialSpend.Verify(acc, false), "ERROR, Invalid coinSpend not passed without range verification");
    // Now must fail..
    BOOST_CHECK_MESSAGE(!wrappedSerialSpend.Verify(acc, true), "ERROR, Invalid coinSpend passed with range verification");

}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) 2014, OplCoin, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. 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.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// 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
// THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.

/*
 * This file and its header file are for translating Electrum-style word lists
 * into their equivalent byte representations for cross-compatibility with
 * that method of "backing up" one's wallet keys.
 */

#include <string>
#include <cassert>
#include <map>
#include <cstdint>
#include <vector>
#include <boost/algorithm/string.hpp>
#include "crypto/crypto.h"  // for declaration of crypto::secret_key

#include "crypto/electrum-words.h"

namespace crypto
{
  namespace ElectrumWords
  {

    /* convert words to bytes, 3 words -> 4 bytes
     * returns:
     *    false if not a multiple of 3 words, or if a words is not in the
     *    words list
     *
     *    true otherwise
     */
    bool words_to_bytes(const std::string& words, crypto::secret_key& dst)
    {
      int n = NUMWORDS; // hardcoded because this is what electrum uses

      std::vector<std::string> wlist;

      boost::split(wlist, words, boost::is_any_of(" "));

      // error on non-compliant word list
      if (wlist.size() != 12 && wlist.size() != 24) return false;

      for (unsigned int i=0; i < wlist.size() / 3; i++)
      {
        uint32_t val;
        uint32_t w1, w2, w3;

        // verify all three words exist in the word list
        if (wordsMap.count(wlist[i*3]) == 0 ||
            wordsMap.count(wlist[i*3 + 1]) == 0 ||
            wordsMap.count(wlist[i*3 + 2]) == 0)
        {
          return false;
        }

        w1 = wordsMap.at(wlist[i*3]);
        w2 = wordsMap.at(wlist[i*3 + 1]);
        w3 = wordsMap.at(wlist[i*3 + 2]);

        val = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n);

        if (!(val % n == w1)) return false;

        memcpy(dst.data + i * 4, &val, 4);  // copy 4 bytes to position
      }

      std::string wlist_copy = words;
      if (wlist.size() == 12)
      {
        memcpy(dst.data, dst.data + 16, 16);  // if electrum 12-word seed, duplicate
        wlist_copy += ' ';
        wlist_copy += words;
      }

      return true;
    }

    /* convert bytes to words, 4 bytes-> 3 words
     * returns:
     *    false if wrong number of bytes (shouldn't be possible)
     *    true otherwise
     */
    bool bytes_to_words(const crypto::secret_key& src, std::string& words)
    {
      int n = NUMWORDS; // hardcoded because this is what electrum uses

      if (sizeof(src.data) % 4 != 0) return false;

      // 8 bytes -> 3 words.  8 digits base 16 -> 3 digits base 1626
      for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ')
      {
        uint32_t w1, w2, w3;

        uint32_t val;

        memcpy(&val, (src.data) + (i * 4), 4);

        w1 = val % n;
        w2 = ((val / n) + w1) % n;
        w3 = (((val / n) / n) + w2) % n;

        words += wordsArray[w1];
        words += ' ';
        words += wordsArray[w2];
        words += ' ';
        words += wordsArray[w3];
      }
      return false;
    }

  }  // namespace ElectrumWords

}  // namespace crypto


#include <unistd.h>
#include <atomic>
#include <cmath>
#include <csignal>
#include <cstdlib>

#include <Eigen/Eigen>
#include <fstream>
#include <istream>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include <vector>

#include "ouster/os1.h"
#include "ouster/os1_packet.h"
#include "ouster/os1_util.h"
#include "ouster/viz.h"

#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>

namespace OS1 = ouster::OS1;
namespace viz = ouster::viz;

int W = 1024;
int H = OS1::pixels_per_column;
int count = 0;

pcl::PointCloud<pcl::PointXYZ> cloud;

std::string getMilliTime(){
    auto time = std::chrono::system_clock::now();
    auto mill = std::chrono::duration_cast<std::chrono::milliseconds>
    (time.time_since_epoch());
    long long currentTimeMillis = mill.count();
    int msc = currentTimeMillis % 1000;
    long nowTime = currentTimeMillis/1000;
    tm *ts = localtime(&nowTime);
    char buffer[80];
    strftime(buffer, 80, "%Y%m%d%H%M%S", ts);
    std::string s(buffer);
    std::string str;
    if(stoi(std::to_string(msc)) < 10){
        str = s+"00"+std::to_string(msc);
    }
    else if(stoi(std::to_string(msc)) < 100){
        str = s+"0"+std::to_string(msc);
    }else{
        str = s+std::to_string(msc);
    }
    return str;
}

std::string path = "/home/kanakim/Documents/LiDAR/i30_LiDAR_ts_" + getMilliTime() + ".txt";
std::ofstream writeFile(path.data());

/*save pcd*/
void WritePCD(std::vector<double> xyz_lut, uint8_t* buf){

    for (int icol = 0; icol < OS1::columns_per_buffer; icol++) {
        const uint8_t* col_buf = OS1::nth_col(icol, buf);
        const uint16_t m_id = OS1::col_measurement_id(col_buf);
        const int idx = H * m_id;

        for (uint8_t ipx = 0; ipx < H; ipx++) {
            //const uint8_t* px_buf = OS1::nth_px(ipx, col_buf);
            uint32_t r = OS1::px_range(OS1::nth_px(ipx, col_buf));
            int ind = 3 * (idx + ipx);
            pcl::PointXYZ LiDAR;

            LiDAR.x = r * 0.001f * xyz_lut[ind + 0];
            LiDAR.y = r * 0.001f * xyz_lut[ind + 1];
            LiDAR.z = r * 0.001f * xyz_lut[ind + 2];

            if(count > W*H) break;

            cloud.push_back(LiDAR);
            count++;
        }
    }

}

/**
 * Print usage
 */
void print_help() {
    std::cout
        << "Usage: viz [options] [hostname] [udp_destination]\n"
        << "Options:\n"
        << "  -m <512x10 | 512x20 | 1024x10 | 1024x20 | 2048x10> : lidar mode, "
           "default 1024x10\n"
        << "  -l <port> : use specified port for lidar data\n"
        << "  -i <port> : use specified port for imu data \n"
        << "  -f <path> : use provided metadata file; do not configure via TCP"
        << std::endl;
}

std::string read_metadata(const std::string& meta_file) {
    std::stringstream buf{};
    std::ifstream ifs{};
    ifs.open(meta_file);
    buf << ifs.rdbuf();
    ifs.close();

    if (!ifs) {
        std::cerr << "Failed to read " << meta_file
                  << "; check that the path is valid" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    return buf.str();
}

int main() {

    OS1::lidar_mode mode = OS1::MODE_1024x10;
    bool do_config = true;  // send tcp commands to configure sensor
    std::string metadata{};
    int lidar_port = 0;
    int imu_port = 0;
/*
    try {
        int c = 0;
        while ((c = getopt(argc, argv, "hm:l:i:f:")) != -1) {
            switch (c) {
                case 'h':
                    print_help();
                    return 1;
                    break;
                case 'm':
                    mode = OS1::lidar_mode_of_string(optarg);
                    if (mode) {
                        W = OS1::n_cols_of_lidar_mode(mode);
                    } else {
                        std::cout << "Lidar Mode must be 512x10, 512x20, "
                                     "1024x10, 1024x20, or 2048x10"
                                  << std::endl;
                        print_help();
                        std::exit(EXIT_FAILURE);
                    }
                    break;
                case 'l':
                    lidar_port = std::stoi(optarg);
                    break;
                case 'i':
                    imu_port = std::stoi(optarg);
                    break;
                case 'f':
                    do_config = false;
                    metadata = read_metadata(optarg);
                    break;
                case '?':
                    std::cout << "Invalid Argument Format" << std::endl;
                    print_help();
                    std::exit(EXIT_FAILURE);
                    break;
            }
        }
    } catch (const std::exception& ex) {
        std::cout << "Invalid Argument Format: " << ex.what() << std::endl;
        print_help();
        std::exit(EXIT_FAILURE);
    }

    if (do_config && argc != optind + 2) {
        std::cerr << "Expected 2 arguments after options" << std::endl;
        print_help();
        std::exit(EXIT_FAILURE);
    }
*/
    std::shared_ptr<OS1::client> cli;
    if (do_config) {
        std::string os1_host = "os1-991904000944.local";
//std::string os1_host = "169.254.181.100";
        std::string os1_udp_dest = "10.5.5.1";
        std::cout << "Configuring sensor: " << os1_host
                  << " UDP Destination:" << os1_udp_dest << std::endl;
        cli =
            OS1::init_client(os1_host, os1_udp_dest, mode,
                             OS1::TIME_FROM_INTERNAL_OSC, lidar_port, imu_port);
    } else {
        if (lidar_port == 0) lidar_port = 7502;
        if (imu_port == 0) imu_port = 7503;
        std::cout << "Listening for sensor data on udp ports " << lidar_port
                  << " and " << imu_port << std::endl;
        cli = OS1::init_client(lidar_port, imu_port);
    }

    if (!cli) {
        std::cerr << "Failed to initialize client" << std::endl;
        //print_help();
        std::exit(EXIT_FAILURE);
    }

    uint8_t lidar_buf[OS1::lidar_packet_bytes + 1];
    uint8_t imu_buf[OS1::imu_packet_bytes + 1];

    //lidar data init
    auto ls = std::unique_ptr<ouster::LidarScan>(new ouster::LidarScan(W, H));
    //initiaulize dispaly view of lidar
    auto vh = viz::init_viz(W, H);

    //get metadata from cli ( yaml compativility, precision, indentation )
    if (do_config) metadata = OS1::get_metadata(*cli);
    //parsing from metadata
    auto info = OS1::parse_metadata(metadata);

    //enerate a matrix of unit vectors pointing radially outwards, useful for
    //efficiently computing cartesian coordinates from ranges.  The result is a n x
    //3 array of doubles stored in row-major order where each row is the unit
    //vector corresponding to the nth point in a lidar scan, with 0 <= n < H*W.
    auto xyz_lut = OS1::make_xyz_lut(W, H, info.beam_azimuth_angles, info.beam_altitude_angles);

    // Use to signal termination
    std::atomic_bool end_program{false};

    // auto it = std::back_inserter(*ls);
    auto it = ls->begin();

    // callback that calls update with filled lidar scan
    /*
    데이터의 단일 스캔 (회전)을 임의 액세스 반복기로 일괄 처리하는 함수를 만듭니다.
    콜백 f ()는 새 스캔에서 데이터를 추가하기 전에
    스캔에서 첫 번째 열의 타임 스탬프와 함께 호출됩니다.
    각 열의 타임 스탬프는 스캔 타임 스탬프에 상대적인 ns입니다.
    미터 단위의 XYZ 좌표는 제공된 조회 테이블을 사용하여 계산됩니다.
    */
    auto batch_and_update = OS1::batch_to_iter<ouster::LidarScan::iterator>(
        xyz_lut, W, H, ouster::LidarScan::Point::Zero(),
        &ouster::LidarScan::make_val, [&](uint64_t) {
            // swap lidar scan and point it to new buffer
            //std::cout<<"batch_and_update"<<std::endl;
            viz::update(*vh, ls);
            it = ls->begin();
        });

    // Start poll thread
    std::thread poll([&] {
        while (!end_program) {
            // Poll the client for data and add to our lidar scan
            OS1::client_state st = OS1::poll_client(*cli);
            if (st & OS1::client_state::LIDAR_DATA) {
                if (OS1::read_lidar_packet(*cli, lidar_buf)){
                    batch_and_update(lidar_buf, it);
                    WritePCD(xyz_lut, lidar_buf);
                }
            }

            if(count == W*H){

                std::string ch_ts = getMilliTime();
                std::string name = "/home/kanakim/Documents/LiDAR/PCD/i30_LiDAR_" + ch_ts + ".pcd";
                //pcl::io::savePCDFileASCII (name, cloud);
                pcl::io::savePCDFile(name, cloud, true); //save by binary
                cloud = {};
                count = 0;

                if(writeFile.is_open()){
                    writeFile << ch_ts << "\n";
                }
            }

        }
    });

    // Start render loop
    viz::run_viz(*vh);
    end_program = true;

    // clean up
    poll.join();
    return EXIT_SUCCESS;
}


//==================================================================================================
/*!
    @file

    @Copyright 2016 Numscale SAS
    @copyright 2016 J.T.Lapreste

    Distributed under the Boost Software License, Version 1.0.
    (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_COMMON_SIMD_FUNCTION_TOINT_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_COMMON_SIMD_FUNCTION_TOINT_HPP_INCLUDED

#include <boost/simd/meta/hierarchy/simd.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
//#include <boost/simd/function/simd/split_low.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd =  boost::dispatch;
  namespace bs =  boost::simd;
  BOOST_DISPATCH_OVERLOAD ( toint_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::double_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE  bd::as_integer_t<A0>  operator() ( const A0 & a) const BOOST_NOEXCEPT
    {
      return {int64_t(a[0]), int64_t(a[1])};
    }
  };

  BOOST_DISPATCH_OVERLOAD ( toint_
                          , (typename A0)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE  bd::as_integer_t<A0>  operator() ( const A0 & a) const BOOST_NOEXCEPT
    {
      return {int64_t(a[0]), int64_t(a[1]), int64_t(a[2]), int64_t(a[3])};
    }
  };

} } }

#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_FIXED_BLOCK_STREAM_PROCESSOR_HPP
#define CRYPTO3_FIXED_BLOCK_STREAM_PROCESSOR_HPP

#include <array>
#include <iterator>

#include <boost/crypto3/detail/pack.hpp>
#include <boost/crypto3/detail/digest.hpp>

#include <boost/crypto3/codec/accumulators/codec.hpp>

#include <boost/integer.hpp>
#include <boost/static_assert.hpp>
#include <boost/utility/enable_if.hpp>

#include <boost/range/algorithm/copy.hpp>

namespace boost {
    namespace crypto3 {
        namespace codec {
            template<typename Mode, typename StateAccumulator, typename Params>
            struct fixed_block_stream_processor {
            public:
                typedef Mode mode_type;
                typedef StateAccumulator accumulator_type;
                typedef Params params_type;

                constexpr static const std::size_t input_block_bits = mode_type::input_block_bits;
                typedef typename mode_type::input_block_type input_block_type;

                constexpr static const std::size_t input_value_bits = mode_type::input_value_bits;
                typedef typename input_block_type::value_type input_value_type;

            public:
                typedef typename params_type::endian_type endian_type;

                constexpr static const std::size_t value_bits = params_type::value_bits;
                typedef typename boost::uint_t<value_bits>::least value_type;
                BOOST_STATIC_ASSERT(input_block_bits % value_bits == 0);
                constexpr static const std::size_t block_values = input_block_bits / value_bits;
                typedef std::array<value_type, block_values> cache_type;

            private:
                constexpr static const std::size_t length_bits = params_type::length_bits;
                // FIXME: do something more intelligent than capping at 64
                constexpr static const std::size_t length_type_bits =
                    length_bits < input_block_bits ? input_block_bits : length_bits > 64 ? 64 : length_bits;
                typedef typename boost::uint_t<length_type_bits>::least length_type;

                BOOST_STATIC_ASSERT(!length_bits || length_bits % input_block_bits == 0);
                BOOST_STATIC_ASSERT(input_block_bits % value_bits == 0);

                BOOST_STATIC_ASSERT(!length_bits || value_bits <= length_bits);

                void update_one(value_type value) {
                    std::size_t i = seen % input_block_bits;
                    cache[i / value_bits] = value;
                    seen += value_bits;
                    if (i == input_block_bits - value_bits) {
                        // Convert the input into words
                        input_block_type block = {0};

                        ::boost::crypto3::detail::pack_to<endian_type, value_bits, input_value_bits>(cache.begin(), cache.end(), block.begin());

                        // Process the block
                        std::size_t ibb = input_block_bits;
                        state(block, accumulators::bits = ibb);

                        // Reset seen if we don't need to track the length
                        if (!length_bits) {
                            seen = 0;
                        }
                    }
                }

                template<typename InputIterator>
                void update_n(InputIterator first, InputIterator last) {
                    std::size_t n = std::distance(first, last);
#ifndef CRYPTO3_CODEC_NO_OPTIMIZATION
#pragma clang loop unroll(full)
                    for (; n && (seen % input_block_bits); --n, ++first) {
                        update_one(*first);
                    }
#pragma clang loop unroll(full)
                    for (; n >= block_values; n -= block_values, first += block_values) {
                        // Convert the input into words
                        input_block_type block = {0};

                        ::boost::crypto3::detail::pack_to<endian_type, value_bits, input_value_bits>(first, first + block_values, block.begin());

                        seen += value_bits * block_values;

                        std::size_t ibb = input_block_bits;
                        state(block, accumulators::bits = ibb);

                        // Reset seen if we don't need to track the length
                        if (!length_bits) {
                            seen = 0;
                        }
                    }
#endif

#pragma clang loop unroll(full)
                    for (; n; --n, ++first) {
                        update_one(*first);
                    }
                }

            public:
                fixed_block_stream_processor(accumulator_type &s) : state(s), seen(0), cache(cache_type()) {
                }

                virtual ~fixed_block_stream_processor() {
                    if (!cache.empty()) {
                        input_block_type block = {0};
                        typename input_block_type::const_iterator v = block.cbegin();

                        ::boost::crypto3::detail::pack_to<endian_type, value_bits, input_value_bits>(cache.begin(), cache.begin() + cache.size(), block.begin());

                        for (length_type itr = seen - (seen % input_block_bits); itr < seen; itr += value_bits) {
                            state(*v++);
                        }
                    }
                }

                template<typename InputIterator>
                inline void operator()(InputIterator b, InputIterator e, std::random_access_iterator_tag) {
                    return update_n(b, e);
                }

                template<typename InputIterator, typename Category>
                inline void operator()(InputIterator first, InputIterator last, Category) {
#pragma clang loop unroll(full)
                    while (first != last) {
                        update_one(*first++);
                    }
                }

                template<typename ValueType>
                inline void operator()(const ValueType &value) {
                    return update_one(value);
                }

                template<typename InputIterator>
                inline void operator()(InputIterator b, InputIterator e) {
                    typedef typename std::iterator_traits<InputIterator>::iterator_category cat;
                    return operator()(b, e, cat());
                }

                template<typename ValueType>
                inline void operator()(const std::initializer_list<ValueType> &il) {
                    return operator()(il.begin(), il.end());
                }

                inline void reset() {
                    seen = 0;
                }

                accumulator_type &state;

                length_type seen;
                cache_type cache;
            };
        }    // namespace codec
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_FIXED_BLOCK_STREAM_PROCESSOR_HPP