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

#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/Potts3D/Cell.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/Field3D/Point3D.h>
#include <CompuCell3D/Field3D/Dim3D.h>
using namespace CompuCell3D;

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

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

#include <BasicUtils/BasicClassGroup.h>
#include <BasicUtils/BasicRandomNumberGenerator.h>
#include <CompuCell3D/Potts3D/CellInventory.h>

#include <string>

#include <math.h>

#include <iostream>
using namespace std;


BlobFieldInitializer::BlobFieldInitializer() :
  potts(0), gap(1), width(2), radius(-1) {}

BlobFieldInitializer::BlobFieldInitializer(int gap, 
							   int width) :
  potts(0), gap(gap), width(width) {
  
  ASSERT_OR_THROW("initializeCellField() width cannot be <= 0!", width > 0);
  ASSERT_OR_THROW("initializeCellField() gap cannot be < 0", gap >= 0);  
}

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

double BlobFieldInitializer::distance(double ax, double ay, double az, double bx, double by, double bz) {
//   printf("%f\n",sqrt((double)(ax - bx) * (ax - bx) + 
//               (double)(ay - by) * (ay - by) + 
//                      (double)(az - bz) * (az - bz)));
  return sqrt((double)(ax - bx) * (ax - bx) + 
              (double)(ay - by) * (ay - by) + 
              (double)(az - bz) * (az - bz));
}

void BlobFieldInitializer::start() {
  // TODO: Chage this code so it write the 0 spins too.  This will make it
  //       possible to re-initialize a previously used field.
  
  /// I am changing here so that now I will work with cellFieldG - the field of CellG
  /// - this way CompuCell will have more functionality

  
  int size = gap + width;

  Field3D<CellG *> *cellFieldG = potts->getCellFieldG();
  ASSERT_OR_THROW("initField() Cell field G cannot be null!", cellFieldG);

  cout<<"********************BLOB INIT***********************"<<endl;
  Dim3D dim = cellFieldG->getDim();
  Dim3D itDim=getBlobDimensions(dim,size);

   
//   itDim.x = dim.x / size;
//   if (dim.x % size) itDim.x += 1;
//   itDim.y = dim.y / size;
//   if (dim.y % size) itDim.y += 1;
//   itDim.z = dim.z / size;
//   if (dim.z % size) itDim.z += 1;

  Point3D pt;
  Point3D cellPt;
  Point3D center;
  CellG *cell;
  
   
  center.x = dim.x / 2;
  center.y = dim.y / 2;
  center.z = dim.z / 2;
  
  cout<<"********************BEFORE FOR LOOPS INIT***********************"<<endl;	  
  if(radius<0){ //in case user does not specify radius
   radius = dim.x / 5;
  }  
  for (int z = 0; z < itDim.z; z++)
    for (int y = 0; y < itDim.y; y++)
      for (int x = 0; x < itDim.x; x++) {
	       pt.x = x * size;
	       pt.y = y * size;
	       pt.z = z * size;
	
        if(distance(pt.x, pt.y, pt.z, center.x, center.y, center.z) < radius) {
	
	///cout<<"********************CREATE CELL***********************"<<endl;	  
	
          cell = potts->createCellG(pt);

	
          int counter=0;
          for (cellPt.z = pt.z; cellPt.z < pt.z + width &&
                 cellPt.z < dim.z; cellPt.z++)
            for (cellPt.y = pt.y; cellPt.y < pt.y + width &&
		             cellPt.y < dim.y; cellPt.y++)
              for (cellPt.x = pt.x; cellPt.x < pt.x + width &&
                     cellPt.x < dim.x; cellPt.x++){
                     //cellFieldG->set(cellPt, cell); //assigns cell to field
                     
		                cellFieldG->set(cellPt, cell); //assigns cell to field
                   }
                   //cout<<"COUNTER="<<counter<<endl;
        }      
      }

      if(cellSortInit){
         initializeCellTypesCellSort();
      }
}

Dim3D BlobFieldInitializer::getBlobDimensions(const Dim3D & dim,int size){
  Dim3D itDim;

  itDim.x = dim.x / size;
  if (dim.x % size) itDim.x += 1;
  itDim.y = dim.y / size;
  if (dim.y % size) itDim.y += 1;
  itDim.z = dim.z / size;
  if (dim.z % size) itDim.z += 1;

  blobDim=itDim;
  
  return itDim; 

}





void BlobFieldInitializer::initializeCellTypesCellSort(){

   BasicRandomNumberGenerator *rand = BasicRandomNumberGenerator::getInstance();
   CellInventory * cellInventoryPtr=& potts->getCellInventory();

   ///will initialize cell type here depending on the position of the cells
   CellInventory::cellInventoryIterator cInvItr;
      
   CellG * cell;
   
   ///loop over all the cells in the inventory   
   for(cInvItr=cellInventoryPtr->cellInventoryBegin() ; cInvItr !=cellInventoryPtr->cellInventoryEnd() ;++cInvItr ){

      cell=*cInvItr;
          
      if(rand->getRatio()<0.5){ /// randomly assign types for cell sort
         cell->type=1;
      }else{
         cell->type=2;
      }
            
   }
                        
}


void BlobFieldInitializer::readXML(XMLPullParser &in) {
  in.skip(TEXT);
  
   cellSortInit=false;
   string cellSortOpt;
   
  while (in.check(START_ELEMENT)) {
    if (in.getName() == "Gap") {
      gap = BasicString::parseUInteger(in.matchSimple());

    } else if (in.getName() == "Width") {
      width = BasicString::parseUInteger(in.matchSimple());

    } else if (in.getName() == "CellSortInit") {

      cellSortOpt=in.matchSimple();
      
      if(cellSortOpt=="Yes"||cellSortOpt=="yes")
         cellSortInit = true;;

    }
    else if (in.getName() == "Radius") {
      radius = BasicString::parseUInteger(in.matchSimple());
      Field3D<CellG *> *cellFieldG = potts->getCellFieldG();
      ASSERT_OR_THROW("initField() Cell field G cannot be null!", cellFieldG);
      Dim3D dim = cellFieldG->getDim();
      ASSERT_OR_THROW("Radius has to be greater than 0 and 2*radius cannot be bigger than lattice dimension x", radius>0 && 2*radius<(dim.x-2));
    }
    
     else {
      throw BasicException(string("Unexpected element '") + in.getName() + 
			   "'!", in.getLocation());
    }
    

    in.skip(TEXT);
  }
}

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