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

#include "ClassRegistry.h"
using namespace CompuCell3D;

#include <XMLCereal/XMLPullParser.h>
#include <XMLCereal/XMLSerializer.h>
#include <src/CompuCell3D/Boundary/BoundaryStrategy.h>
#include <BasicUtils/BasicString.h>
#include <BasicUtils/BasicException.h>

#include <string>

#define DEBUG_IN "("<<in.getLineNumber()<<" , "<<in.getColumnNumber()<<")"<<endl

using namespace std;

PluginManager Simulator::pluginManager;

Simulator::Simulator() : numSteps(0), flip2DimRatio(1),
			 temperature(1), anneal(0) {
  pluginManager.setSimulator(this);

  classRegistry = new ClassRegistry(this);
  simulatorIsStepping=false;

}

Simulator::~Simulator() {
  delete classRegistry;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Simulator::registerConcentrationField(std::string _name,Field3DImpl<float>* _fieldPtr){
   concentrationFieldNameMap.insert(std::make_pair(_name,_fieldPtr));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Simulator::start() {
  // Print the names of loaded plugins
  cout << "Plugins:";
  BasicPluginManager<Plugin>::infos_t *infos = &pluginManager.getPluginInfos();
  BasicPluginManager<Plugin>::infos_t::iterator it; 
  for (it = infos->begin(); it != infos->end(); it++)
    if (pluginManager.isLoaded((*it)->getName())) {
      if (it != infos->begin()) cout << ",";
      cout << " " << (*it)->getName();
    }
  cout << endl;

  
  classRegistry->start();
  currstep = 0;
  // Output statisitcs
  cout << "Step " << 0 << " "
       << "Energy " << potts.getEnergy() << " "
       << "Cells " << potts.getNumCells()
       << endl;
       
  simulatorIsStepping=true; //initialize flag that simulator is stepping
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Simulator::extraInit(){
  BasicPluginManager<Plugin>::infos_t *infos = &pluginManager.getPluginInfos();
  BasicPluginManager<Plugin>::infos_t::iterator it; 
  for (it = infos->begin(); it != infos->end(); it++)
    if (pluginManager.isLoaded((*it)->getName())) {
      pluginManager.get((*it)->getName())->extraInit(this);
      if (it != infos->begin()) cout << ",";
      cerr << " extraInit for: " << (*it)->getName()<<endl;
      
    }

    classRegistry->extraInit(this);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Simulator::step(const unsigned int currentStep) {
   //potts is initialized in readXML - so is most of other Steppables etc.
   
  // Run potts metropolis
  Dim3D dim = potts.getCellFieldG()->getDim();
  int flipAttempts = (int)(dim.x * dim.y * dim.z * flip2DimRatio); //may be a member 
  int flips = potts.metropolis(flipAttempts, temperature);

  currstep = currentStep;
  
  // Run steppables -after metropilis function is finished you sweep over classRegistry - mainly for outputing purposes
  classRegistry->step(currentStep);
  
  // Output statisitcs
  cout << "Step " << currentStep << " "
       << "Flips " << flips << "/" << flipAttempts << " "
       << "Energy " << potts.getEnergy() << " "
       << "Cells " << potts.getNumCells()<<" Inventory="<<potts.getCellInventory().getCellInventorySize()
       << endl;
}


void Simulator::finish() {
   temperature = 0.0;
   for (unsigned int i = 1; i <= anneal; i++)
       step(numSteps+i);
   classRegistry->finish();
}


void Simulator::readXML(XMLPullParser &in) {
  

  in.skip(TEXT);  //equivalent to in.next(-TEXT);

  string algorithm = "Default";
  int index = 0;
  int size = 0;
  string inputfile = "none";
  bool regular = true; 
  string boundary_x = ""; // boundary condition for x axiz
  string boundary_y = ""; // boundary condition for y axis
  string boundary_z = ""; // boundary condition for z axis
  Dim3D dim; // Initial dimension
  // Potts
  double depth=1.0;    
  in.assertName("Potts");

//  in.match(START_ELEMENT, -TEXT);
  	in.assertEvent(START_ELEMENT);
	in.next(-TEXT);

  
  while (in.check(START_ELEMENT)) {
    if (in.getName() == "Steps") {
      numSteps = BasicString::parseUInteger(in.matchSimple());
    } 
    else if (in.getName() == "Anneal") {
        anneal = BasicString::parseUInteger(in.matchSimple());
    }
    else if (in.getName() == "Flip2DimRatio") {
      flip2DimRatio = BasicString::parseDouble(in.matchSimple());
    } else if (in.getName() == "Dimensions") {
       string found_str;	
      dim.readXML(in);
      found_str=in.matchSimple();
      potts.createCellField(dim);
    } else if (in.getName() == "Temperature") {
      temperature = BasicString::parseDouble(in.matchSimple());
    }
    else if (in.getName() == "Offset") {
         double offset = BasicString::parseDouble(in.matchSimple());
         potts.getAcceptanceFunction()->setOffset(offset);
    }
    else if (in.getName() == "KBoltzman") {
         double k = BasicString::parseDouble(in.matchSimple());
         potts.getAcceptanceFunction()->setK(k);
    }

    else if (in.getName() == "FlipNeighborMaxDistance") {
         depth = BasicString::parseDouble(in.matchSimple());
         //potts.setDepth(depth);
    }
    
        
     else if (in.getName() == "Boundary_x") {
        boundary_x = in.matchSimple();
        potts.setBoundaryXName(boundary_x);

    } else if (in.getName() == "Boundary_y") {
        boundary_y = in.matchSimple();
        potts.setBoundaryYName(boundary_y);
        
    } else if (in.getName() == "Boundary_z") {
        boundary_z = in.matchSimple();
        potts.setBoundaryZName(boundary_z);

        
    }  
    else if (in.getName() == "Shape") {
        algorithm = in.getAttribute("Algorithm").value;
        index = atoi(in.getAttribute("Index").value.c_str());
        size = atoi(in.getAttribute("Size").value.c_str());
        inputfile = in.getAttribute("File").value;
        string reg = in.matchSimple();
        if (reg == "irregular") {
           regular = false;
           boundary_x = "noflux";
           boundary_y = "noflux";
           boundary_z = "noflux";
        }
    }
    else {
      THROW(string("Unexpected element '") + in.getName() + "'!");
    }

    in.skip(TEXT);

  }

  ASSERT_OR_THROW("You must set Dimensions!", potts.getCellFieldG());

  in.assertName("Potts");
  in.match(END_ELEMENT, -TEXT);

  // Instantiate the boundary conditions and set their initial dimensions
  BoundaryStrategy::instantiate(boundary_x, boundary_y, boundary_z, algorithm, index, size, inputfile);
  BoundaryStrategy::getInstance()->setDim(dim);
  //Once Boundary Strategy has been intialized will initialize neighbors array in potts and set depth
  potts.setDepth(depth);

  // Plugins
  while (in.check(START_ELEMENT) && in.getName() == "Plugin") {
    string name = in.getAttribute("Name").value;
    Plugin *plugin = pluginManager.get(name);

    in.match(START_ELEMENT);
    plugin->readXML(in);
    in.match(END_ELEMENT, -TEXT);
  }

  // Configure Classes
  classRegistry->readXML(in);

  in.skip(TEXT);

}

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