/*************************************************************************
 *    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 "Potts3D.h"

#include "Cell.h"
#include "DefaultAcceptanceFunction.h"
#include "AcceptanceFunction.h"
#include "EnergyFunction.h"
#include "CellGChangeWatcher.h"
#include "Stepper.h"
#include <CompuCell3D/Automaton/Automaton.h>

#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/plugins/Volume/VolumePlugin.h>
#include <CompuCell3D/plugins/Surface/SurfacePlugin.h>

#include <BasicUtils/BasicRandomNumberGenerator.h>
#include <BasicUtils/BasicPluginInfo.h>

#include <XMLCereal/XMLPullParser.h>
#include <XMLCereal/XMLSerializer.h>
#include "FakeGenerator.h"
#include <fstream>
using namespace CompuCell3D;
using namespace std;

Potts3D::Potts3D() :
  		    cellFieldG(0),
		    acceptanceFunction(&defaultAcceptanceFunction), 
		    energy(0) ,
          depth(1.0)
{
  neighbors.assign(100,Point3D());//statically allocated this buffer maybe will come with something better later
  frozenTypeVec.assign(0,0);
   ifstream nameFile("rand.name");
   string fileName;
   nameFile>>fileName;
   FakeGenerator *rand = FakeGenerator::getInstance();
//    string fileName("fake.rand");
   rand->setFileName(fileName);

}

Potts3D::Potts3D(const Dim3D dim) :
				 cellFieldG(0),
  				 acceptanceFunction(&defaultAcceptanceFunction), 
				 energy(0) ,
             depth(1.0)
				 {
  neighbors.assign(100,Point3D());//statically allocated this buffer maybe will come with something better later
  frozenTypeVec.assign(0,0);

  createCellField(dim);
   FakeGenerator *rand = FakeGenerator::getInstance();
   ifstream nameFile("rand.name");
   string fileName;
   nameFile>>fileName;
  
   rand->setFileName(fileName);
  

}


Potts3D::~Potts3D() {
  if (cellFieldG) delete cellFieldG;
}

void Potts3D::setDepth(double _depth){
   //this function has to be called after initializing bondary strategy and after creating cellFieldG
   depth=_depth;
   
  //will calculate necesary space for neighbor storage
  Dim3D dim=cellFieldG->getDim();
  Point3D middlePt(dim.x/2,dim.y/2,dim.z/2);
  
   unsigned int token = 0;
   int numNeighbors = 0;
   double distance;
   Point3D testPt;
   token =0;
   distance=0;

  
   
      while(true){
      testPt = cellFieldG->getNeighbor(middlePt, token, distance);
      if (distance > depth) break;
   
         numNeighbors++;
      }
   //empty previously allocated container for neighbors
   neighbors.clear();
   neighbors.assign(numNeighbors+1,Point3D());
//    cerr<<"neighbors.size()="<<neighbors.size()<<endl;

}

void Potts3D::createCellField(const Dim3D dim) {

  ASSERT_OR_THROW("createCellField() cell field G already created!", !cellFieldG);
  cellFieldG = new WatchableField3D<CellG *>(dim, 0); //added
  
  
}

void Potts3D::registerAutomaton(Automaton* autom) {automaton = autom;}
Automaton* Potts3D::getAutomaton() {return automaton;}

void Potts3D::registerEnergyFunction(EnergyFunction *function) {
  ASSERT_OR_THROW("registerEnergyFunction() function cannot be NULL!",
		  function);

  function->registerPotts3D(this);
  energyFunctions.put(function);
}

void Potts3D::registerAcceptanceFunction(AcceptanceFunction *function) {
  ASSERT_OR_THROW("registerAcceptanceFunction() function cannot be NULL!",
		  function);

  acceptanceFunction = function;
}


///BasicClassChange watcher reistration
void Potts3D::registerCellGChangeWatcher(CellGChangeWatcher *_watcher) {
  ASSERT_OR_THROW("registerBCGChangeWatcher() _watcher cannot be NULL!",_watcher);
		  
  cellFieldG->addChangeWatcher(_watcher); 
}


void Potts3D::registerClassAccessor(BasicClassAccessorBase *_accessor) {
  ASSERT_OR_THROW("registerClassAccessor() _accessor cannot be NULL!", _accessor);

  cellFactoryGroup.registerClass(_accessor);
}


void Potts3D::registerStepper(Stepper *stepper) {
  ASSERT_OR_THROW("registerStepper() stepper cannot be NULL!", stepper);

  steppers.put(stepper);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CellG * Potts3D::createCellG(const Point3D pt) {
   ASSERT_OR_THROW("createCell() cellFieldG Point out of range!", cellFieldG->isValid(pt));

   CellG * cell = new CellG();
   cell->extraAttribPtr=cellFactoryGroup.create();
   cellInventory.addToInventory(cell);
   cellFieldG->set(pt, cell);
   return cell;   
}


void Potts3D::destroyCellG(CellG *cell) {
  if(cell->extraAttribPtr){
      cellFactoryGroup.destroy(cell->extraAttribPtr);
      cell->extraAttribPtr=0;
  }
  cellInventory.removeFromInventory(cell);
  delete cell;
  cell=0;

}

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

double Potts3D::totalEnergy() {
  double energy = 0;
  Dim3D dim = cellFieldG->getDim();

  Point3D pt;
  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++)
	for (unsigned int i = 0; i < energyFunctions.getSize(); i++)
	  energy += energyFunctions[i]->localEnergy(pt);

  return energy;
}


double Potts3D::changeEnergy(Point3D pt, const CellG *newCell,
			     const CellG *oldCell) {
  double change = 0;

  for (unsigned int i = 0; i < energyFunctions.getSize(); i++)
    change += energyFunctions[i]->changeEnergy(pt, newCell, oldCell);

  return change;
}


unsigned int Potts3D::metropolis(const unsigned int steps, const double temp) {
  ASSERT_OR_THROW("Potts3D: cell field G not initialized", cellFieldG);

  unsigned int flips = 0;
//   BasicRandomNumberGenerator *rand = BasicRandomNumberGenerator::getInstance();
  FakeGenerator *rand = FakeGenerator::getInstance();

  
  ///Dim3D dim = cellField->getDim();
  Dim3D dim = cellFieldG->getDim();
  ASSERT_OR_THROW("Potts3D: You must supply an acceptance function!",
		  acceptanceFunction);

   // Find boundry neighbors - is it always 6?
//    Point3D neighbors_[6]; // No more than 6 neighbors at a distance of 1.
        
  for (unsigned int i = 0; i < steps; i++) {
    Point3D pt;

    // Pick a random point
    pt.x = rand->getInteger(0, dim.x - 1);
    pt.y = rand->getInteger(0, dim.y - 1);
    pt.z = rand->getInteger(0, dim.z - 1);
    
    ///Cell *cell = cellField->get(pt);
    CellG *cell = cellFieldG->get(pt);

    if(sizeFrozenTypeVec && cell){///must also make sure that cell ptr is different 0; Will never freeze medium
      if(checkIfFrozen(cell->type))
         continue;
    }

    unsigned int token = 0;
    int numNeighbors = 0;
    double distance;
    
/*    Point3D ptN;
    int numN=0;
    cerr<<"point="<<pt<<endl;
    while(true){
      ptN = cellFieldG->getNeighbor(pt, token, distance);
      
      if (distance > depth) break;
      cerr<<"numN="<<numN<<" distance="<<distance<<" ptN="<<ptN<<endl;
      ++numN;

    }
    cerr<<"numN="<<numN<<endl;
      */
   token =0;
   distance=0;
    while(true){

      neighbors[numNeighbors] = cellFieldG->getNeighbor(pt, token, distance);
      if (distance > depth) break;

      if (cellFieldG->get(neighbors[numNeighbors]) != cell)
        numNeighbors++;

    }

//     for (int j = 0; j < 6; j++) {
//       neighbors_[numNeighbors] = cellFieldG->getNeighbor(pt, token, distance);
//       if (distance > 1) break;
// 
//       if (cellFieldG->get(neighbors_[numNeighbors]) != cell)
// 	     numNeighbors++;
//     }

      
    // If no boundry neighbors were found start over
      if (!numNeighbors) continue;
         
    // Pick a random neighbor
    Point3D changePixel = neighbors[rand->getInteger(0, numNeighbors - 1)];
    
      
    if(sizeFrozenTypeVec && cellFieldG->get(changePixel)){///must also make sure that cell ptr is different 0; Will never freeze medium
      if(checkIfFrozen(cellFieldG->get(changePixel)->type))
         continue;
    }


    flipNeighbor=pt;/// change takes place at change pixel  and pt is a neighbor of changePixel
      
//      cerr<<"changePixel="<<changePixel<<" flipNeighbor="<<flipNeighbor<<" numNeighbors="<<numNeighbors<< " depth="<<depth<<endl;
/*     if(numNeighbors>27){
      for(int ii=0 ; ii<numNeighbors ; ++ii)
         cerr<<neighbors[ii]<<endl;
      
     }*/
    // Calculate change in energy
    double change = changeEnergy(changePixel, cell, cellFieldG->get(changePixel));
    ///cout<<"This is change: "<<change<<endl;	
    
    // Acceptance based on probability
    double prob = acceptanceFunction->accept(temp, change);
    
/*    if(changePixel.y==0 && getFlipNeighbor().y==54){
      cerr<<"change="<<change<<" prob="<<prob<<endl;
    }*/
    
    if (prob >= 1 || rand->getRatio() < prob) {
      // Accept the change
/*      if(changePixel.y==0 && getFlipNeighbor().y==54){
         cerr<<"setting pixel "<<changePixel<<"with address"<<endl;
      }*/
      energy += change;
      cellFieldG->set(changePixel, cell);
      flips++;
    }

    // Run steppers
    for (unsigned int j = 0; j < steppers.getSize(); j++)
      steppers[j]->step();
  }

  return flips;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool Potts3D::checkIfFrozen(unsigned char _type){


   
   for (unsigned int i = 0 ; i< frozenTypeVec.size(); ++i ){
      if(frozenTypeVec[i]==_type)
         return true;
   }
      return false;

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Potts3D::setFrozenTypeVector(std::vector<unsigned char> & _frozenTypeVec){
   frozenTypeVec=_frozenTypeVec;
   sizeFrozenTypeVec=frozenTypeVec.size();
}
