    /*************************************************************************************

    Grid physics library, www.github.com/paboyle/Grid 

    Source file: ./tests/Test_padded_cell.cc

    Copyright (C) 2015

Author: Peter Boyle <paboyle@ph.ed.ac.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    See the full license in the file "LICENSE" in the top level distribution directory
    *************************************************************************************/
    /*  END LEGAL */
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>

using namespace std;
using namespace Grid;

// This is to optimize the SIMD
/*
template<class vobj> void gpermute(vobj & inout,int perm){
  vobj tmp=inout;
  if (perm & 0x1 ) { permute(inout,tmp,0); tmp=inout;}
  if (perm & 0x2 ) { permute(inout,tmp,1); tmp=inout;}
  if (perm & 0x4 ) { permute(inout,tmp,2); tmp=inout;}
  if (perm & 0x8 ) { permute(inout,tmp,3); tmp=inout;}
}
*/

int main (int argc, char ** argv)
{
  Grid_init(&argc,&argv);

  Coordinate latt_size  = GridDefaultLatt();
  Coordinate simd_layout= GridDefaultSimd(Nd,vComplexD::Nsimd());
  Coordinate mpi_layout = GridDefaultMpi();
  std::cout << GridLogMessage << " mpi "<<mpi_layout<<std::endl;
  std::cout << GridLogMessage << " simd "<<simd_layout<<std::endl;
  std::cout << GridLogMessage << " latt "<<latt_size<<std::endl;
  GridCartesian GRID(latt_size,simd_layout,mpi_layout);

  // Initialize configuration as hot start.
  GridParallelRNG   pRNG(&GRID);
  LatticeGaugeField Umu(&GRID);
  pRNG.SeedFixedIntegers(std::vector<int>({45,12,81,9}));
  SU<Nc>::HotConfiguration(pRNG,Umu);

  Real plaq=WilsonLoops<PeriodicGimplR>::avgPlaquette(Umu);
  LatticeComplex trplaq(&GRID);

  // Store Umu in U. Peek/Poke mean respectively getElement/setElement.
  std::vector<LatticeColourMatrix> U(Nd, Umu.Grid());
  for (int mu = 0; mu < Nd; mu++) {
    U[mu] = PeekIndex<LorentzIndex>(Umu, mu);
  }

  std::cout << GridLogMessage << " Average plaquette "<<plaq<<std::endl;

  LatticeComplex cplaq(&GRID); cplaq=Zero();

  // Create a padded cell of extra padding depth=1
  int depth = 1;
  PaddedCell Ghost(depth,&GRID);
  LatticeGaugeField Ughost = Ghost.Exchange(Umu);

  ///////////////////////////////////////////////////////////////////
  // Temporary debug Hack for single rank sim:
  // Check the contents of the cell are periodcally replicated
  // In future ONLY pad those dimensions that are not local to node
  ///////////////////////////////////////////////////////////////////
#if 0
  {
    double diff=0;
    double n=0;
  {
    autoView( Ug_v , Ughost, CpuRead);
    autoView( Ul_v , Umu   , CpuRead);
  for(int x=0;x<latt_size[0]+2;x++){
  for(int y=0;y<latt_size[1]+2;y++){
  for(int z=0;z<latt_size[2]+2;z++){
  for(int t=0;t<latt_size[3]+2;t++){
    int lx=(x-1+latt_size[0])%latt_size[0];
    int ly=(y-1+latt_size[1])%latt_size[1];
    int lz=(z-1+latt_size[2])%latt_size[2];
    int lt=(t-1+latt_size[3])%latt_size[3];
    Coordinate gcoor({x,y,z,t});
    Coordinate lcoor({lx,ly,lz,lt});
    LorentzColourMatrix g;
    LorentzColourMatrix l;
    peekLocalSite(g,Ug_v,gcoor);
    peekLocalSite(l,Ul_v,lcoor);
    g=g-l;
    GRID_ASSERT(norm2(g)==0);
    diff = diff + norm2(g);
    n = n + norm2(l);
  }}}}
  }
  std::cout << "padded field check diff "<< diff <<" / "<< n<<std::endl;
  std::cout << norm2(Ughost)<< " " << norm2(Umu)<<std::endl;
  }
#endif

  // Array for the site plaquette
  GridBase *GhostGrid = Ughost.Grid();
  LatticeComplex gplaq(GhostGrid); 

  // Now we're going to put together the "stencil" that will be useful to us when
  // calculating the plaquette. Our eventual goal is to make the product
  //    Umu(x) Unu(x+mu) Umu^dag(x+nu) Unu^dag(x),
  // which requires, in order, the sites x, x+mu, x+nu, and x. We arrive at these
  // sites relative to x through "shifts", which is represented here by a 4-d
  // vector of 0s (no movement) and 1s (shift one unit) at each site. The
  // "stencil" is the set of all these shifts.
  std::vector<Coordinate> shifts;
  for(int mu=0;mu<Nd;mu++){
    for(int nu=mu+1;nu<Nd;nu++){
      Coordinate shift_0(Nd,0);
      Coordinate shift_mu(Nd,0); shift_mu[mu]=1;
      Coordinate shift_nu(Nd,0); shift_nu[nu]=1;
      // push_back creates an element at the end of shifts and
      // assigns the data in the argument to it.
      shifts.push_back(shift_0);
      shifts.push_back(shift_mu);
      shifts.push_back(shift_nu);
      shifts.push_back(shift_0);
    }
  }
  GeneralLocalStencil gStencil(GhostGrid,shifts);

  gplaq=Zero();

  // Before doing accelerator stuff, there is an opening and closing of "Views". I guess the
  // "Views" are stored in *_v variables listed below.
  autoView( gp_v , gplaq, CpuWrite);
  autoView( t_v , trplaq, CpuRead);
  autoView( U_v , Ughost, CpuRead);

  // This is now a loop over stencil shift elements. That is, s increases as we make our
  // way through the spacetimes sites, but also as we make our way around the plaquette.
  for(int ss=0;ss<gp_v.size();ss++){
    int s=0;
    for(int mu=0;mu<Nd;mu++){
    	for(int nu=mu+1;nu<Nd;nu++){
    
    	  auto SE0 = gStencil.GetEntry(s+0,ss);
    	  auto SE1 = gStencil.GetEntry(s+1,ss);
    	  auto SE2 = gStencil.GetEntry(s+2,ss);
    	  auto SE3 = gStencil.GetEntry(s+3,ss);

        // Due to our strategy, each offset corresponds to a site.
    	  int o0 = SE0->_offset;
    	  int o1 = SE1->_offset;
    	  int o2 = SE2->_offset;
    	  int o3 = SE3->_offset;
    	  
    	  auto U0 = U_v[o0](mu);
    	  auto U1 = U_v[o1](nu);
    	  auto U2 = adj(U_v[o2](mu));
    	  auto U3 = adj(U_v[o3](nu));
    
    	  gpermute(U0,SE0->_permute);
    	  gpermute(U1,SE1->_permute);
    	  gpermute(U2,SE2->_permute);
    	  gpermute(U3,SE3->_permute);
    	  
    	  gp_v[ss]() =gp_v[ss]() + trace( U0*U1*U2*U3 );
    	  s=s+4;
    	}
    }
  }

  // Here is my understanding of this part: The padded cell has its own periodic BCs, so
  // if I take a step to the right at the right-most side of the cell, I end up on the
  // left-most side. This means that the plaquettes in the padding are wrong. Luckily
  // all we care about are the plaquettes in the cell, which we obtain from Extract.
  cplaq = Ghost.Extract(gplaq);
  RealD vol = cplaq.Grid()->gSites();
  RealD faces = (Nd * (Nd-1))/2;
  auto p = TensorRemove(sum(cplaq));
  auto result = p.real()/vol/faces/Nc;

  std::cout << GridLogMessage << " Average plaquette via padded cell "<<result<<std::endl;
  std::cout << GridLogMessage << " Diff "<<result-plaq<<std::endl;
  
  GRID_ASSERT(fabs(result-plaq)<1.0e-8);
  Grid_finalize();
}
