#include "TargetVolumeSteppableBiofilm.h"
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/Potts3D/Cell.h>
#include <CompuCell3D/Potts3D/CellInventory.h>
#include <CompuCell3D/plugins/MitosisBiofilm/MitosisPluginBiofilm.h>
#include <BasicUtils/BasicClassAccessor.h>
#include <CompuCell3D/plugins/Concentration/ConcentrationPlugin.h>

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

#include <CompuCell3D/Simulator.h>

#include <CompuCell3D/Field3D/Dim3D.h>

using namespace std;

namespace CompuCell3D {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TargetVolumeSteppableBiofilm::TargetVolumeSteppableBiofilm()
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TargetVolumeSteppableBiofilm::~TargetVolumeSteppableBiofilm()
{
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TargetVolumeSteppableBiofilm::init(Simulator *simulator){
  potts = simulator->getPotts();
  cellInventoryPtr=& potts->getCellInventory();
     
  ///initialize concentration accessor
  ConcentrationPlugin * concentrationPlugin=(ConcentrationPlugin*)(Simulator::pluginManager.get("Concentration"));
  concentrationAccessorPtr=concentrationPlugin->getConcentrationAccessorPtr();
   
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TargetVolumeSteppableBiofilm::start() {
   
   ///will initialize cell clock here depending on the position of the cells
   CellInventory::cellInventoryIterator cInvItr;
   ///loop over all the cells in the inventory
   Point3D com;
   CellG * cell;

   ASSERT_OR_THROW("divideThreshold should be greater than dieThreshold!", divideThreshold > dieThreshold);
   
   ///initializing parameters for linear concentration-targetVolume dependence V=ax+b - x- is average concentration
   surfaceFactor=4.836;
   volumeSlope=(doublingVolume)/(divideThreshold-dieThreshold);
   volumeOffset=-volumeSlope*dieThreshold;
   
   for(cInvItr=cellInventoryPtr->cellInventoryBegin() ; cInvItr !=cellInventoryPtr->cellInventoryEnd() ;++cInvItr ){
      
      cell=*cInvItr;
      cell->targetVolume=initialTargetVolume;
      cell->targetSurface=initialTargetSurface;

   }

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
float TargetVolumeSteppableBiofilm::distance(Point3D a, Point3D b) {
  
  return sqrt((float)(a.x - b.x) * (a.x - b.x) +
              (float)(a.y - b.y) * (a.y - b.y) +
              (float)(a.z - b.z) * (a.z - b.z));
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TargetVolumeSteppableBiofilm::step(const unsigned int _currentStep) {
   
   killedCells=0;
   markedCells=0;
   markedByConcentration=0;
   markedByOuterRing=0;
   
   currentStep=_currentStep;
   ///will initialize cell clock here depending on the position of the cells
   CellInventory::cellInventoryIterator cInvItr;
   ///loop over all the cells in the inventory
   Point3D com;
   CellG * cell;
   cerr<<"dieThreshold="<<dieThreshold<<" divideThreshold="<<divideThreshold<<endl;
   for(cInvItr=cellInventoryPtr->cellInventoryBegin() ; cInvItr !=cellInventoryPtr->cellInventoryEnd() ;++cInvItr ){
      
      cell=*cInvItr;
      dieOrDivide(cell); ///decide if a cell should die or divide or manipulate the target volume and target surface
      
      
   }
   
   cerr<<"Sentenced to death "<<killedCells<<" cells"<<endl;
   cerr<<"Sentenced to death by too low concentration:"<<markedByConcentration<<" cells"<<endl;
   cerr<<"Sentenced to death by outer ring "<<markedByOuterRing<<" cells"<<endl;
   cerr<<"Found  "<<markedCells<<" cells with death sentence"<<endl;
   
   
   killedCells=0;
   markedCells=0;
   markedByConcentration=0;
   markedByOuterRing=0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TargetVolumeSteppableBiofilm::cellSurfaceAndVolume(CellG *cell,float averageConcentration){
   
      
   if(averageConcentration > divideThreshold){

      cell->targetVolume = mitosisBoostFactor*doublingVolume;
      cell->targetSurface = surfaceFactor*pow((double)cell->targetVolume,(double)2.0/3.0);
//      cerr<<"Double Volume averageConcentration="<<averageConcentration<<" cell->volume="<<cell->volume<<endl;
//      cerr<<"cell->targetVolume="<<cell->targetVolume<<" cell->targetSurface="<<cell->targetSurface<<endl;
      
   }else if(averageConcentration < dieThreshold){
      //cerr<<"Capital punishment: averageConcentration="<<averageConcentration<<" cell->volume="<<cell->volume<<endl;
      cell->targetVolume=0.0;
      cell->targetSurface=0.0;
      concentrationAccessorPtr->get(cell->extraAttribPtr)->flag='a'; ///mark cell to die - any char other than 0 will do
      ++killedCells;
      ++markedByConcentration;
      cell->flag=1;
      
   }else{
   
      cell->targetVolume=volumeSlope*averageConcentration+volumeOffset;
      
      cell->targetSurface=surfaceFactor*pow((double)cell->targetVolume,(double)2.0/3.0);
      //cerr<<"cell->targetVolume="<<cell->targetVolume<<" cell->targetSurface="<<cell->targetSurface<<endl;
      
   }
   
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TargetVolumeSteppableBiofilm::dieOrDivide(CellG *cell){
///decide if a cell should die or divide or do nothing

   if(concentrationAccessorPtr->get(cell->extraAttribPtr)->flag){
      ///cell is marked to die - do not make it alive by manipulating targetVolume or targetSurface
//      cerr<<"Found dead cell "<<(int)concentrationAccessorPtr->get(cell->extraAttribPtr)->flag<<" "
//     <<concentrationAccessorPtr->get(cell->extraAttribPtr)->flag<<endl;
      ++markedCells;
      return;
   } 

   
   ///if cell is outside the ring set vol target to 0
   float xCM,yCM,zCM;
   xCM=cell->xCM/(float)cell->volume;
   yCM=cell->yCM/(float)cell->volume;
   zCM=cell->zCM/(float)cell->volume;

   Point3D ptCM((unsigned int)xCM, (unsigned int)yCM, (unsigned int)zCM);
   Dim3D fieldDim=potts->getCellFieldG()->getDim();
   Point3D ptCenter(fieldDim.x/2,fieldDim.y/2,fieldDim.z/2);

   if(distance(ptCM,ptCenter)>outerDieRadius){
      //cerr<<"cell outside the allowed ring: CM="<<ptCM<<endl;
      concentrationAccessorPtr->get(cell->extraAttribPtr)->flag='a'; ///mark cell to die - any char other than 0 will do
      cell->targetVolume=0.0;
      cell->targetSurface=0.0;
       ++killedCells;
       ++markedByOuterRing;
       cell->flag=1;
      return;
   }   
   ///if cell is outside the ring set vol target to 0 - end


   float averageConcentration = concentrationAccessorPtr->get(cell->extraAttribPtr)->concentration/cell->volume;
   cell->averageConcentration  = averageConcentration;
   
   cellSurfaceAndVolume(cell , averageConcentration);
         

}

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

void TargetVolumeSteppableBiofilm::readXML(XMLPullParser &in) {
  in.skip(TEXT);
  while (in.check(START_ELEMENT)) {
    if (in.getName() == "InitialTargetVolume") {
      initialTargetVolume = BasicString::parseDouble(in.matchSimple());
      ///getting doubleng Volume from Mitosis
      MitosisPluginBiofilm * mitosisPluginBiofilm=(MitosisPluginBiofilm*)(Simulator::pluginManager.get("MitosisBiofilm"));
      doublingVolume=mitosisPluginBiofilm->getDoublingVolume();

      mitosisPluginBiofilm->setInitialTargetVolume(initialTargetVolume);
      mitosisPluginBiofilm->setInitialTargetSurface(initialTargetSurface);
      
      

    }else if(in.getName() == "DivideThreshold"){
      divideThreshold = BasicString::parseDouble(in.matchSimple());
    }
    else if(in.getName() == "InitialTargetSurface"){
      initialTargetSurface = BasicString::parseDouble(in.matchSimple());
      MitosisPluginBiofilm * mitosisPluginBiofilm=(MitosisPluginBiofilm*)(Simulator::pluginManager.get("MitosisBiofilm"));
      mitosisPluginBiofilm->setInitialTargetSurface(initialTargetSurface);

    }
    else if(in.getName() == "DieThreshold"){
      dieThreshold = BasicString::parseDouble(in.matchSimple());
    }
    else if(in.getName() == "MitosisBoostFactor"){
      mitosisBoostFactor = BasicString::parseDouble(in.matchSimple());
    }
    else if(in.getName() == "OuterDieRadius"){
      outerDieRadius = BasicString::parseInteger(in.matchSimple());
    }

    else if(in.getName() == "MinimalTargerVolume"){
      minimalTargetVolume = BasicString::parseDouble(in.matchSimple());
    }
        
    else {
      throw BasicException(string("Unexpected element '") + in.getName() + 
            "'!", in.getLocation());
    }

    in.skip(TEXT);
  }

}


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