/*************************************************************************
 *    CompuCell - A software framework for multimodel simulations of     *
 * biocomplexity problems Copyright (C) 2003 University of Notre Dame,   *
 *                             Indiana                                   *
 *                                                                       *
 * This program is free software; IF YOU AGREE TO CITE USE OF CompuCell  *
 *  IN ALL RELATED RESEARCH PUBLICATIONS according to the terms of the   *
 *  CompuCell GNU General Public License RIDER 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., 675 Mass Ave, Cambridge, MA 02139, USA.        *
 *************************************************************************/

#include "CenterOfMassPlugin.h"

#include "CellCenterOfMass.h"
#include "CenterOfMassRenderer.h"
#include "CenterOfMassTypeRenderer.h"

#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/ClassRegistry.h>
#include <CompuCell3D/Field3D/Field3D.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/Potts3D/Cell.h>

#include <BasicUtils/BasicString.h>
#include <BasicUtils/BasicException.h>


#include <CompuCell3D/Potts3D/CellInventory.h>

using namespace CompuCell3D;


#include <XMLCereal/XMLPullParser.h>
#include <XMLCereal/XMLSerializer.h>

#include <cmath>

#include <iostream>

using namespace std;




CenterOfMassPlugin::CenterOfMassPlugin() {}

CenterOfMassPlugin::~CenterOfMassPlugin() {}

void CenterOfMassPlugin::init(Simulator *simulator) {
  potts = simulator->getPotts();

  
  potts->registerCellGChangeWatcher(this);

  simulator->getClassRegistry()
    ->registerRenderer("CenterOfMass", new BasicClassFactory<FieldRenderer,
		       CenterOfMassRenderer>);

  simulator->getClassRegistry()
    ->registerRenderer("CenterOfMassType", new BasicClassFactory<FieldRenderer,
                       CenterOfMassTypeRenderer>);

  potts->getBoundaryXName()=="Periodic" ? boundaryConditionIndicator.x=1 : boundaryConditionIndicator.x=0 ;
  potts->getBoundaryYName()=="Periodic" ? boundaryConditionIndicator.y=1 : boundaryConditionIndicator.y=0;
  potts->getBoundaryZName()=="Periodic" ? boundaryConditionIndicator.z=1 : boundaryConditionIndicator.z=0;

  fieldDim=potts->getCellFieldG()->getDim();
}



void CenterOfMassPlugin::readXML(XMLPullParser &in) {
  in.skip(TEXT);
}

void CenterOfMassPlugin::writeXML(XMLSerializer &out) {
}

void CompuCell3D::CenterOfMassPlugin::field3DChange(const Point3D &pt, CellG *newCell,
                   CellG * oldCell) {

   //to calculate CM for the case with periodic boundary conditions we need to do some translations rather than naively calculate centroids
   //naive calculations work in the case of no flux boundary conditions but periodic b.c. may cause troubles


   //if no boundary conditions are present
   if ( !boundaryConditionIndicator.x && !boundaryConditionIndicator.y && !boundaryConditionIndicator.z ){


      if (oldCell) {

         oldCell->xCM -= pt.x;
         oldCell->yCM -= pt.y;
         oldCell->zCM -= pt.z;
      }

      if (newCell) {
         newCell->xCM += pt.x;
         newCell->yCM += pt.y;
         newCell->zCM += pt.z;

      }

      return;
   }

   //if there are boundary conditions defined that we have to do some shifts to correctly calculate center of mass
   //This approach will work only for cells whose span is much smaller that lattice dimension in the "periodic "direction
   //e.g. cell that is very long and "wraps lattice" will have miscalculated CM using this algorithm. On the other hand, you do not real expect
   //cells to have dimensions comparable to lattice...
   

   
   Point3D shiftVec;
   Point3D shiftedPt;
   int xCM,yCM,zCM; //temporary centroids

   int x,y,z;
   int xo,yo,zo;
//     cerr<<"CM PLUGIN"<<endl;
    
  if (oldCell) {

   xo=oldCell->xCM;
   yo=oldCell->yCM;
   zo=oldCell->zCM;

        

   x=oldCell->xCM-pt.x;
   y=oldCell->yCM-pt.y;
   z=oldCell->zCM-pt.z;
      
    //calculating shiftVec - to translate CM

    //(oldCell->xCM/(float)(oldCell->volume+1) -pos of CM before th flip - note that volume is updated earlier

    //shift is defined to be zero vector for non-periodic b.c. - everything reduces to naive calculations then   
    shiftVec.x= (short)((oldCell->xCM/(float)(oldCell->volume+1)-fieldDim.x/2)*boundaryConditionIndicator.x);
    shiftVec.y= (short)((oldCell->yCM/(float)(oldCell->volume+1)-fieldDim.y/2)*boundaryConditionIndicator.y);
    shiftVec.z= (short)((oldCell->zCM/(float)(oldCell->volume+1)-fieldDim.z/2)*boundaryConditionIndicator.z);


    //shift CM to approximately center of lattice, new centroids are:
    xCM = oldCell->xCM - shiftVec.x*(oldCell->volume+1);
    yCM = oldCell->yCM - shiftVec.y*(oldCell->volume+1);
    zCM = oldCell->zCM - shiftVec.z*(oldCell->volume+1);
    //Now shift pt
    shiftedPt=pt;
    shiftedPt-=shiftVec;
    
    //making sure that shifterd point is in the lattice
    if(shiftedPt.x < 0){
      shiftedPt.x += fieldDim.x;
    }else if (shiftedPt.x > fieldDim.x-1){
      shiftedPt.x -= fieldDim.x;
    }  

    if(shiftedPt.y < 0){
      shiftedPt.y += fieldDim.y;
    }else if (shiftedPt.y > fieldDim.y-1){
      shiftedPt.y -= fieldDim.y;
    }  

    if(shiftedPt.z < 0){
      shiftedPt.z += fieldDim.z;
    }else if (shiftedPt.z > fieldDim.z-1){
      shiftedPt.z -= fieldDim.z;
    }
    //update shifted centroids
    xCM -= shiftedPt.x;
    yCM -= shiftedPt.y;
    zCM -= shiftedPt.z;

    //shift back centroids
    xCM += shiftVec.x * oldCell->volume;
    yCM += shiftVec.y * oldCell->volume;
    zCM += shiftVec.z * oldCell->volume;

    //Check if CM is in the lattice
    if( xCM/(float)oldCell->volume < 0){
      xCM += fieldDim.x*oldCell->volume;
    }else if ( xCM/(float)oldCell->volume > fieldDim.x){ //will allow to have xCM/vol slightly bigger (by 1) value than max lattice point
                                                         //to avoid rollovers for unsigned int from oldCell->xCM
                                                         
//       cerr<<"\t\t\tShifting centroid xCM="<<xCM/(float)oldCell->volume<<endl;
      xCM -= fieldDim.x*oldCell->volume;
//       cerr<<"\t\t\tShiftedxCM="<<xCM/(float)oldCell->volume<<endl;
     
    }

    if( yCM/(float)oldCell->volume < 0){
      yCM += fieldDim.y*oldCell->volume;
    }else if ( yCM/(float)oldCell->volume > fieldDim.y){
      yCM -= fieldDim.y*oldCell->volume;
    }

    if( zCM/(float)oldCell->volume < 0){
      zCM += fieldDim.z*oldCell->volume;
    }else if ( zCM/(float)oldCell->volume > fieldDim.z){
      zCM -= fieldDim.z*oldCell->volume;
    }
        
    oldCell->xCM = xCM;
    oldCell->yCM = yCM;
    oldCell->zCM = zCM;


  }

  if (newCell) {

    xo=newCell->xCM;
    yo=newCell->yCM;
    zo=newCell->zCM;


      x=newCell->xCM+pt.x;
      y=newCell->yCM+pt.y;
      z=newCell->zCM+pt.z;

  
    if(newCell->volume==1){
      shiftVec.x=0;
      shiftVec.y=0;
      shiftVec.z=0;
      
    }else{
      shiftVec.x= (short)((newCell->xCM/(float)(newCell->volume-1)-fieldDim.x/2)*boundaryConditionIndicator.x);
      shiftVec.y= (short)((newCell->yCM/(float)(newCell->volume-1)-fieldDim.y/2)*boundaryConditionIndicator.y);
      shiftVec.z= (short)((newCell->zCM/(float)(newCell->volume-1)-fieldDim.z/2)*boundaryConditionIndicator.z);
      
    }
    
    
    //shift CM to approximately center of lattice , new centroids are:
    xCM = newCell->xCM - shiftVec.x*(newCell->volume-1);
    yCM = newCell->yCM - shiftVec.y*(newCell->volume-1);
    zCM = newCell->zCM - shiftVec.z*(newCell->volume-1);
    //Now shift pt
    shiftedPt=pt;
    shiftedPt-=shiftVec;

    //making sure that shifted point is in the lattice
    if(shiftedPt.x < 0){
      shiftedPt.x += fieldDim.x;
    }else if (shiftedPt.x > fieldDim.x-1){
//       cerr<<"shifted pt="<<shiftedPt<<endl;
      shiftedPt.x -= fieldDim.x;
    }  

    if(shiftedPt.y < 0){
      shiftedPt.y += fieldDim.y;
    }else if (shiftedPt.y > fieldDim.y-1){
      shiftedPt.y -= fieldDim.y;
    }  

    if(shiftedPt.z < 0){
      shiftedPt.z += fieldDim.z;
    }else if (shiftedPt.z > fieldDim.z-1){
      shiftedPt.z -= fieldDim.z;
    }    

    //update shifted centroids
    xCM += shiftedPt.x;
    yCM += shiftedPt.y;
    zCM += shiftedPt.z;
    
    //shift back centroids
    xCM += shiftVec.x * newCell->volume;
    yCM += shiftVec.y * newCell->volume;
    zCM += shiftVec.z * newCell->volume;
    
    //Check if CM is in the lattice
    if( xCM/(float)newCell->volume < 0){
      xCM += fieldDim.x*newCell->volume;
    }else if ( xCM/(float)newCell->volume > fieldDim.x){ //will allow to have xCM/vol slightly bigger (by 1) value than max lattice point
                                                         //to avoid rollovers for unsigned int from oldCell->xCM
      xCM -= fieldDim.x*newCell->volume;
    }

    if( yCM/(float)newCell->volume < 0){
      yCM += fieldDim.y*newCell->volume;
    }else if ( yCM/(float)newCell->volume > fieldDim.y){
      yCM -= fieldDim.y*newCell->volume;
    }

    if( zCM/(float)newCell->volume < 0){
      zCM += fieldDim.z*newCell->volume;
    }else if ( zCM/(float)newCell->volume > fieldDim.z){
      zCM -= fieldDim.z*newCell->volume;
    }
        
    newCell->xCM = xCM;
    newCell->yCM = yCM;
    newCell->zCM = zCM;
    

  }
}




