#include "ExternalPotentialEnergy.h"

#include <XMLCereal/XMLPullParser.h>
#include <XMLCereal/XMLSerializer.h>
#include <CompuCell3D/Field3D/AdjacentNeighbor.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <BasicUtils/BasicClassGroup.h>
#include <vector>

using namespace std;

namespace CompuCell3D {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ExternalPotentialEnergy::ExternalPotentialEnergy():
   lambdaVec(Coordinates3D<float>(0.,0.,0.))
{
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ExternalPotentialEnergy::~ExternalPotentialEnergy()
{
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double ExternalPotentialEnergy::localEnergy(const Point3D &pt) {
  return 0.0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double ExternalPotentialEnergy::changeEnergy(const Point3D &pt,  const CellG *newCell,
                                                const CellG *oldCell)
{
   double deltaEnergyOld=0.0;
   double deltaEnergyNew=0.0;
   CellG *neighborPtr;

   Dim3D fieldDim=cellFieldG->getDim();

   //const vector<Point3D>  & neighborsOffsetVec = adjNeighbor_ptr->getAdjNeighborOffsetVec(pt);
   const vector<Point3D>  & neighborsOffsetVec = adjNeighbor_ptr->getAdjFace2FaceNeighborOffsetVec(pt);
   unsigned int neighborSize=neighborsOffsetVec.size();
   //cerr<<"neighborSize="<<neighborSize<<endl;
   Point3D ptAdj;
   

   //x, y,or z depending in which direction we want potential gradient to act
   //I add 1 to avoid zero change of energy when z is at the boundary
   short prefferedCoordinate=pt.z+1;
   //short deltaCoordinate;
   Coordinates3D<short> deltaCoordinate(0,0,0);
   
   ///COMMENT TO deltaCoordinate calculations
   // have to do fieldDim.z-2 because the neighbor of pt with max_z can be a pt with z=0 but they are spaced by delta z=1
   // thus you need to do deltaCoordinate % (fieldDim.z-2). Otherwise if you do not do fieldDim.z-2
   // then you may get the following (max_z-0) % max_z =0, whereas it should be 1 !

//    cerr<<"******************CHANGE BEGIN**************************"<<endl;
   int counter=0;
   Point3D ptFlipNeighbor=potts->getFlipNeighbor();
   Point3D deltaFlip;
   
   deltaFlip=pt;
   deltaFlip-=ptFlipNeighbor;
   
//    cerr<<"deltaFlip="<<deltaFlip<<endl;
/*   if(pt.x>0 && pt.x<80)
      return 0.0;*/
      
   for(unsigned int i = 0 ; i < neighborSize ; ++i){
      ptAdj=pt;
      ptAdj+=neighborsOffsetVec[i];
/*      cerr<<" old ="<<oldCell<<" new="<<newCell<<" neighbor="<<cellFieldG->get(ptAdj)<<endl;
      cerr<<"pt="<<pt<<" ptAdj="<<ptAdj<<" deltaEnergyOld="<<deltaEnergyOld<<" deltaEnergyNew="<<deltaEnergyNew<<endl;*/
      
      if( cellFieldG->isValid(ptAdj) ){
         ++counter;
         neighborPtr=cellFieldG->get(ptAdj);
         
         ///process old energy
         if(neighborPtr && oldCell && neighborPtr!=oldCell){

            deltaCoordinate.XRef()=(ptAdj.x-pt.x);
            deltaCoordinate.YRef()=(ptAdj.y-pt.y);
            deltaCoordinate.ZRef()=(ptAdj.z-pt.z);

            if(fabs(static_cast<double>(deltaCoordinate.X()))>1){
//                cerr<<"before manip deltaCoordinate.X()="<<deltaCoordinate.X()<<endl;
               deltaCoordinate.XRef()=
               ( deltaCoordinate.X()>0 ? -(deltaCoordinate.X()+1) % (fieldDim.x-1) :-(deltaCoordinate.X()-1) % (fieldDim.x-1) );

//                if(pt.x==0 || pt.x==120){
// 
// //                   cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-1)="<<deltaCoordinate % (fieldDim.z-1)<<endl;
//                   cerr<<"deltaCoordinate.X()="<<deltaCoordinate.X()<<endl;
//                   cerr<<"manual delta"<<(ptAdj.x-pt.x)<<endl;
//                }
             }

            if(fabs(static_cast<double>(deltaCoordinate.Y()))>1){
               deltaCoordinate.YRef()=
               ( deltaCoordinate.Y()>0 ? -(deltaCoordinate.Y()+1) % (fieldDim.y-1) :-(deltaCoordinate.Y()-1) % (fieldDim.y-1) );
               //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-2)="<<deltaCoordinate % (fieldDim.z-2)<<endl;
               //cerr<<"deltaCoordinate="<<deltaCoordinate<<endl;
            }


            if(fabs(static_cast<double>(deltaCoordinate.Z()))>1){
               deltaCoordinate.ZRef()=
               ( deltaCoordinate.Z()>0 ? -(deltaCoordinate.Z()+1) % (fieldDim.z-1) :-(deltaCoordinate.Z()-1) % (fieldDim.z-1) );
               //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-2)="<<deltaCoordinate % (fieldDim.z-2)<<endl;
               //cerr<<"deltaCoordinate="<<deltaCoordinate<<endl;
            }
            //removing contributions from "perpendicular to the force component" spin flips
//             if(deltaFlip.x==0){
//                deltaCoordinate.XRef()=0;
//             }
//             
//             if(deltaFlip.y==0){
//                deltaCoordinate.YRef()=0;
//             }
// 
//             if(deltaFlip.z==0){
//                deltaCoordinate.ZRef()=0;
//             }
//             
            //deltaEnergyOld+=fabs((double)deltaCoordinate)*lambdaCoefficient;
            deltaEnergyOld+=  deltaCoordinate.X()*lambdaVec.X()+
                              deltaCoordinate.Y()*lambdaVec.Y()+
                              deltaCoordinate.Z()*lambdaVec.Z();

         }

         ///process new energy
         if(neighborPtr && newCell && neighborPtr!=newCell){

            deltaCoordinate.XRef()=(ptAdj.x-pt.x);
            deltaCoordinate.YRef()=(ptAdj.y-pt.y);
            deltaCoordinate.ZRef()=(ptAdj.z-pt.z);
            
            if(fabs(static_cast<double>(deltaCoordinate.X()))>1){
//                cerr<<"before manip deltaCoordinate.X()="<<deltaCoordinate.X()<<endl;            
               deltaCoordinate.XRef()=
               ( deltaCoordinate.X()>0 ? -(deltaCoordinate.X()+1) % (fieldDim.x-1) :-(deltaCoordinate.X()-1) % (fieldDim.x-1) );
               
               //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-2)="<<deltaCoordinate % (fieldDim.z-2)<<endl;
               //cerr<<"deltaCoordinate="<<deltaCoordinate<<endl;
   /*            if(pt.x==0 || pt.x==80){
                  //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-1)="<<deltaCoordinate % (fieldDim.z-1)<<endl;
                  cerr<<"deltaCoordinate.X()="<<deltaCoordinate.X()<<endl;
               }
   */            
            }

            if(fabs(static_cast<double>(deltaCoordinate.Y()))>1){
               deltaCoordinate.YRef()=
               ( deltaCoordinate.Y()>0 ? -(deltaCoordinate.Y()+1) % (fieldDim.y-1) :-(deltaCoordinate.Y()-1) % (fieldDim.y-1) );
               //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-2)="<<deltaCoordinate % (fieldDim.z-2)<<endl;
               //cerr<<"deltaCoordinate="<<deltaCoordinate<<endl;
            }
                        

            if(fabs(static_cast<double>(deltaCoordinate.Z()))>1){
               deltaCoordinate.ZRef()=
               ( deltaCoordinate.Z()>0 ? -(deltaCoordinate.Z()+1) % (fieldDim.z-1) :-(deltaCoordinate.Z()-1) % (fieldDim.z-1) );
               //cerr<<"delta coordinate="<<deltaCoordinate<<" deltaCoordinate % (fieldDim.z-2)="<<deltaCoordinate % (fieldDim.z-2)<<endl;
               //cerr<<"deltaCoordinate="<<deltaCoordinate<<endl;
            }
            
            //removing contributions from "perpendicular to the force component" spin flips
//             if(deltaFlip.x==0){
//                deltaCoordinate.XRef()=0;
//             }
// 
//             if(deltaFlip.y==0){
//                deltaCoordinate.YRef()=0;
//             }
// 
//             if(deltaFlip.z==0){
//                deltaCoordinate.ZRef()=0;
//             }



            //deltaEnergyNew+=fabs((double)deltaCoordinate)*lambdaCoefficient;
//            deltaEnergyNew+=deltaCoordinate*lambdaCoefficient;
            deltaEnergyNew+=  deltaCoordinate.X()*lambdaVec.X()+
                              deltaCoordinate.Y()*lambdaVec.Y()+
                              deltaCoordinate.Z()*lambdaVec.Z();
            
         }

//           cerr<<"VALID: pt="<<pt<<" ptAdj="<<ptAdj<<" deltaEnergyOld="<<deltaEnergyOld<<" deltaEnergyNew="<<deltaEnergyNew<<endl;
      }
       
   }

   //cerr<<"pt="<<pt<<" delta E="<<deltaEnergyNew-deltaEnergyOld<<endl;
/*   if((deltaEnergyNew-deltaEnergyOld)!=0){
      cerr<<"ext energy="<<deltaEnergyNew-deltaEnergyOld<< " counter="<<counter<<endl;
   }
   cerr<<"******************CHANGE END**************************"<<endl;*/
/*   cerr<<"external potential energy="<<deltaEnergyNew-deltaEnergyOld<<endl;
   if(deltaEnergyNew-deltaEnergyOld>4){
      cerr<<"ERROR"<<endl;
      exit(0);
   }*/
   return deltaEnergyNew-deltaEnergyOld;
   
}      

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ExternalPotentialEnergy::readXML(XMLPullParser &in){
   in.skip(TEXT);
   float lX,lY,lZ;
   
   while (in.check(START_ELEMENT)) {
     if (in.getName() == "Lambda") {
        lX= BasicString::parseDouble(in.getAttribute("x").value);
        lY= BasicString::parseDouble(in.getAttribute("y").value);
        lZ= BasicString::parseDouble(in.getAttribute("z").value);
        cerr<<"lX="<<lX<<" lY="<<lY<<" lZ="<<lZ<<endl;
        in.matchSimple();
        //lambdaCoefficient = BasicString::parseDouble(in.matchSimple());
        lambdaVec=Coordinates3D<float>(lX,lY,lZ);
        
     } else {
      throw BasicException(string("Unexpected element '") + in.getName() +
            "'!", in.getLocation());
    }

    in.skip(TEXT);
  }
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ExternalPotentialEnergy::writeXML(XMLSerializer &out){
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
};
