#include "DiffusionSolverFE.h"

#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/Automaton/Automaton.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/Potts3D/CellInventory.h>
#include <CompuCell3D/Field3D/WatchableField3D.h>
#include <CompuCell3D/Field3D/Field3DImpl.h>
#include <CompuCell3D/Field3D/Field3D.h>
#include <BasicUtils/BasicClassGroup.h>



#include <XMLCereal/XMLPullParser.h>
#include <XMLCereal/XMLSerializer.h>
#include <BasicUtils/BasicString.h>
#include <BasicUtils/BasicException.h>
#include <string>

#include <iostream>

using namespace CompuCell3D;
using namespace std;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DiffusionSolverFE::DiffusionSolverFE()
 : Diffusable<float>(),
   scratchField(0)
   
{
concentrationFieldName="Concentration";
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DiffusionSolverFE::~DiffusionSolverFE()
{

   if(scratchField) delete scratchField;
   scratchField=0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::init(Simulator *simulator) {
  simPtr=simulator;
  potts = simulator->getPotts();
   
  ///getting cell inventory
  cellInventoryPtr=& potts->getCellInventory(); 
  
  ///getting field ptr from Potts3D
  cellFieldG=potts->getCellFieldG();
  Dim3D fieldDim=cellFieldG->getDim();
  
  ///constructing concentration and scratch fields
  allocateDiffusableField(fieldDim);
  scratchField= new Field3DImpl<float>(fieldDim, 0.0);

  
  ///setting member function pointers
  //secretePtr=&DiffusionSolverFE::secrete;
  secretePtr=&DiffusionSolverFE::secreteInCM;
  diffusePtr=&DiffusionSolverFE::diffuse;
  
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::start() {
   if(diffConst> (1.0/6.0-0.05) ){ //hard coded condtion for stability of the solutions - assume dt=1 dx=dy=dz=1
                      
      cerr<<"CANNOT SOLVE DIFFUSION EQUATION: STABILITY PROBLEM - DIFFUSION CONSTANT TOO LARGE. EXITING..."<<endl;
      exit(0);

   }


   (this->*secretePtr)();
   
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::step(const unsigned int _currentStep) {
   currentStep=_currentStep;
   
   (this->*diffusePtr)();
   (this->*secretePtr)();

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::secrete() {

Point3D pt;
Dim3D dim = concentrationField->getDim();
CellG *currentCell=0;
short currentCellType=0;
float currentConcentration=0.0;



   for (pt.z = 0; pt.z < dim.z; pt.z++)
    for (pt.y = 0; pt.y < dim.y; pt.y++)
      for (pt.x = 0; pt.x < dim.x; pt.x++){
      
         currentCell=cellFieldG->get(pt);
         if(!currentCell) continue;/// go to the next iteration in case you get Medium cell - they do not secrete
         
         
         currentCellType = static_cast<short>( currentCell->type );

         if(currentCellType==2){///assume that only type 2 secretes something
            currentConcentration=concentrationField->get(pt);
            ///CHANGE HERE CELL SECTRETION
            concentrationField->set(pt,currentConcentration+secrConst);
         }
         //cerr<<"type="<<currentCellType<<" concentr="<<currentConcentration<<endl;
         
      }

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::secreteInCM() {

float currentConcentration=0.0;
CellInventory::cellInventoryIterator cInvItr;
///loop over all the cells in the inventory
Point3D com;
CellG * cell;
   
   
   for(cInvItr=cellInventoryPtr->cellInventoryBegin() ; cInvItr !=cellInventoryPtr->cellInventoryEnd() ;++cInvItr ){
      
      cell=*cInvItr;
      
      com.x=cell->xCM/ cell->volume ;
      com.y=cell->yCM/ cell->volume;
      com.z=cell->zCM/ cell->volume;
      
      currentConcentration=concentrationField->get(com);
      
      /// insert here cell type dependent decondition
      ///if(cell->type==//insert type)
      
      concentrationField->set(com,currentConcentration+secrConst); ///may change currentConcentration+secrConst to more sophisticated expression
   }



}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::diffuse() {
/// 'n' denotes neighbor

///this is the diffusion equation 
///C_{xx}+C_{yy}+C_{zz}=(1/a)*C_{t}
///a - diffusivity - diffConst

///Finite difference method:
///T_{0,\delta \tau}=F*\sum_{i=1}^N T_{i}+(1-N*F)*T_{0}
///N - number of neighbors
///will have to double check this formula


Point3D pt, n;
unsigned int token = 0;
double distance;
Dim3D dim = concentrationField->getDim();
CellG * currentCellPtr=0,*nCell=0;

short currentCellType=0;
float concentrationSum=0.0;
float updatedConcentration=0.0;
float currentConcentration=0.0;
short neighborCounter=0;


float F;/// F=a*\delta \tau / (\delta x)^2
        /// in the simplest case (\delta*\tau=1 \delta x=1) F=a;

        
   F=diffConst;
                    
   for (pt.z = 0; pt.z < dim.z; pt.z++)
    for (pt.y = 0; pt.y < dim.y; pt.y++)
      for (pt.x = 0; pt.x < dim.x; pt.x++){

         currentCellPtr=cellFieldG->get(pt);
         if(!currentCellPtr) continue; /// go to the next iteration in case you get Medium cell - medium does not participate in the diffusion
                                       ///(model dependent may be changed if necessary
               
         updatedConcentration=0.0;
         concentrationSum=0.0;
         neighborCounter=0;
         token=0; ///have to zero the token every time we access neighbors - otherwise the neighbor distance will keep growing up
         ///loop over nearest neighbors
         while (true) {
            n = cellFieldG->getNeighbor(pt, token, distance, false);
            ///cerr<<"pt="<<pt<<" n="<<n<<" distance="<<distance<<" token="<<token<<endl;
            
            if (distance > 1) break;
               
               nCell = cellFieldG->get(n); //pulls out cell from a field
               
               if(!nCell) continue; /// assume that medium does not participate in the diffusion
                                    ///(model dependent may be changed if necessary)

               
               ///cerr<<"neighborPoint="<<neighborPoint<<" nCell="<<nCell<<endl;
               concentrationSum += concentrationField->get(n);
               ++neighborCounter;
               //cerr<<"neighborCounter="<<neighborCounter<<endl;      
         }

         
         currentConcentration=concentrationField->get(pt);
         updatedConcentration = F*concentrationSum+(1.0-neighborCounter*F)*currentConcentration;
         //cerr<<" OUT neighborCounter="<<neighborCounter<<" F*cSum="<<F*concentrationSum<<" updC="<<updatedConcentration<<endl;
/*         if(updatedConcentration>2){
            cerr<<"updatedConcentration="<<updatedConcentration<<endl;
            exit(0);
         }*/
         scratchField->set(pt,updatedConcentration);
         //cerr<<"F="<<F<<" concentrationSum="<<concentrationSum<<" updated Concentr="<<updatedConcentration<<endl;

      }
      ///copy updated values from scratch to concentration field   
      scrarch2Concentration(scratchField,concentrationField);
      
/*      if(currentStep==10){
         
         outputField(cerr,concentrationField);
      }*/
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::scrarch2Concentration( Field3DImpl<float> *scratchField, Field3DImpl<float> *concentrationField){

Point3D pt;
Dim3D dim = concentrationField->getDim();
float tempValue;

   for (pt.z = 0; pt.z < dim.z; pt.z++)
    for (pt.y = 0; pt.y < dim.y; pt.y++)
      for (pt.x = 0; pt.x < dim.x; pt.x++){
         tempValue=scratchField->get(pt);
         concentrationField->set(pt,tempValue);
         scratchField->set(pt,0.0);
      }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::outputField( std::ostream & _out, Field3DImpl<float> *_concentrationField){
Point3D pt;
Dim3D dim = concentrationField->getDim();
float tempValue;

   for (pt.z = 0; pt.z < dim.z; pt.z++)
    for (pt.y = 0; pt.y < dim.y; pt.y++)
      for (pt.x = 0; pt.x < dim.x; pt.x++){
         tempValue=concentrationField->get(pt);
         _out<<pt<<" "<<tempValue<<endl;
      }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DiffusionSolverFE::readXML(XMLPullParser &in) {
  in.skip(TEXT);
  while (in.check(START_ELEMENT)) {
    if (in.getName() == "DiffusionConstant") {
      diffConst = BasicString::parseDouble(in.matchSimple());

    }
    else if (in.getName() == "SecretionConstant") {
      secrConst = BasicString::parseDouble(in.matchSimple());

    }
    else if (in.getName() == "DecayConstant") {
      decayConst = BasicString::parseDouble(in.matchSimple());

    }
    else if (in.getName() == "FieldName") {
      concentrationFieldName = in.matchSimple();
      ///register concentration field to make it accesible from external viewers etc.
      simPtr->registerConcentrationField(concentrationFieldName , concentrationField );
    }
    
     else {
      throw BasicException(string("Unexpected element '") + in.getName() + 
            "'!", in.getLocation());
    }

    in.skip(TEXT);
  }

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

