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

#include <CompuCell3D/Field3D/Field3D.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/Potts3D/Cell.h>
#include <CompuCell3D/Automaton/Automaton.h>
#include <CompuCell3D/Simulator.h>
#include <CompuCell3D/ClassRegistry.h>
#include <CompuCell3D/DiffusableVector.h>
#include <PublicUtilities/StringUtils.h>



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

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

#include <CompuCell3D/Potts3D/Cell.h>
#include <string>

using namespace CompuCell3D;
using namespace std;
ContactFlexEnergy::ContactFlexEnergy(Simulator *_sim) :
   sim(_sim), depth(1)
{
   potts=sim->getPotts();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double ContactFlexEnergy::localEnergy(const Point3D &pt) {
  return 0;
}

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

// void ContactFlexEnergy::initializeField(){
// 
//    ClassRegistry *classRegistry=sim->getClassRegistry();
//    Steppable * steppable;
//    fieldVec.assign(chemicalFieldSourceVec.size(),0);//allocate fieldVec
//    
//       
//    for(unsigned int i=0; i < chemicalFieldSourceVec.size() ; ++i ){
//       steppable=classRegistry->getStepper(chemicalFieldSourceVec[i]);
//       fieldVec[i]=((DiffusableVector<float> *) steppable)->getConcentrationField(chemicalFieldNameVec[i]);
//       ASSERT_OR_THROW("No chemical field has been loaded!", fieldVec[i]);
//    }
// 
// }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ContactFlexEnergy::initHaptotactingTypeMap(){

   ClassRegistry *classRegistry=sim->getClassRegistry();
   Steppable * steppable;


   Automaton *automaton=potts->getAutomaton();

   
   unsigned char type1,type2;
   //haptotactingTypeIdVecVec.assign(haptotactingTypeNameVecVec.size(),vector<unsigned char>());
   
   for(unsigned i = 0; i< haptotactingTypeNamePairVec.size() ; ++i ){
      type1=automaton->getTypeId(haptotactingTypeNamePairVec[0].first);
      type2=automaton->getTypeId(haptotactingTypeNamePairVec[0].second);
      
      OrderedTypePair typePair(type1,type2);
       
      HaptotaxisData haptotaxisData;

      haptotaxisData.lambda=lambdaVec[i];
      steppable=classRegistry->getStepper(chemicalFieldSourceVec[i]);
      haptotaxisData.field=((DiffusableVector<float> *) steppable)->getConcentrationField(chemicalFieldNameVec[i]);;
      ASSERT_OR_THROW("No chemical field has been loaded!", haptotaxisData.field);

      haptotacingTypesMap.insert( make_pair(typePair,haptotaxisData) );
      //haptotactingTypeIdVecVec[i].assign(haptotactingTypeNameVecVec[i].size(),0);
      
         
/*      for(unsigned j = 0; j< haptotactingTypeNameVecVec[i].size() ; ++j ){
         haptotactingTypeIdVecVec[i][j]=automaton->getTypeId(haptotactingTypeNameVecVec[i][j]);
      }*/
   }

   

/*   for(unsigned i = 0; i< haptotactingTypeIdVecVec.size() ; ++i ){

      
      
      for(unsigned j = 0; j< haptotactingTypeIdVecVec[i].size() ; ++j ){
         cerr<<"type["<<i<<"]["<<j<<"]="<<haptotactingTypeNameVecVec[i][j]<<" id="<<(int)haptotactingTypeIdVecVec[i][j]<<endl;
      }
   }

   haptotactingTypeNameVecVec.clear();*/
   
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double ContactFlexEnergy::changeEnergy(const Point3D &pt,
                                  const CellG *newCell,
                                  const CellG *oldCell) {
   //cerr<<"ChangeEnergy"<<endl;
   
   
  double energy = 0;
  unsigned int token = 0;
  double distance = 0;
  Point3D n;
  
  CellG *nCell=0;
  Field3D<CellG *> *fieldG = potts->getCellFieldG();
  //set changePt so that it is accesible from other methods
  changePt=pt;
     
  while (true) {
    n = fieldG->getNeighbor(pt, token, distance, false);
    if (distance > depth) break;

    nCell = fieldG->get(n);
    
    changeN=n;
    
/*    energy -= contactEnergy(oldCell, nCell) / distance;
    energy += contactEnergy(newCell, nCell) / distance;*/
    
    energy -= (this->*contactEnergyFcnPtr)(oldCell, nCell) / distance;
    energy += (this->*contactEnergyFcnPtr)(newCell, nCell) / distance;

        
    
  }
  
  //cerr<<"ChangeEnergy before return"<<endl;
  return energy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
double ContactFlexEnergy::contactEnergy(const CellG *cell1, const CellG *cell2) {

   return contactEnergyArray[cell1 ? cell1->type : 0][cell2? cell2->type : 0];


}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

double ContactFlexEnergy::contactHaptotaxisEnergy(const CellG *cell1, const CellG *cell2){

  static int type1 = automaton->getCellType(cell1);
  static int type2 = automaton->getCellType(cell2);
  static double energy=0.0;
  static float lambda=0.0;
  static OrderedTypePair typePair;
  static HaptotaxisData haptotaxisData;
  static haptotactingTypesMap_t::iterator mitr;
  
  type1 = automaton->getCellType(cell1);
  type2 = automaton->getCellType(cell2);

  typePair.assign(type1,type2);

  energy=contactEnergy(cell1, cell2);
  
  
  
  mitr=haptotacingTypesMap.find(typePair);
  if(mitr != haptotacingTypesMap.end() ){
  
     //cerr<<"energy="<<energy;
     
     energy -= mitr->second.lambda*mitr->second.field->get(changePt)*(
      delta((cell1?cell1->type:0),type1) * delta((cell2?cell2->type:0),type2)+
      delta((cell1?cell1->type:0),type2) * delta((cell2?cell2->type:0),type1)  
               );
               
     //cerr<<" "<<energy<<endl;
     
     return energy; 
  }else{
      return energy;
  }   
  
  
  
//   energy=
//   contactEnergy(cell1, cell2)-
//   lambdaVec[0]*
//   (
//     delta((cell1?cell1->type:0),(int)haptotactingTypeIdVecVec[0][0]) * delta((cell2?cell2->type:0),(int)haptotactingTypeIdVecVec[0][1]) *
//     fieldVec[0]->get(changePt)
//     +
//     delta((cell1?cell1->type:0),(int)haptotactingTypeIdVecVec[0][1]) * delta((cell2?cell2->type:0),(int)haptotactingTypeIdVecVec[0][0]) *
//     fieldVec[0]->get(changeN)
//   )
//   ;
//   
//   return energy;

}

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

void ContactFlexEnergy::setContactEnergy(const string typeName1,
				     const string typeName2,
				     const double energy) {
                    
  char type1 = automaton->getTypeId(typeName1);
  char type2 = automaton->getTypeId(typeName2);
    
  int index = getIndex(type1, type2);

  contactEnergies_t::iterator it = contactEnergies.find(index);
  ASSERT_OR_THROW(string("Contact energy for ") + typeName1 + " " + typeName2 +
		  " already set!", it == contactEnergies.end());

  contactEnergies[index] = energy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int ContactFlexEnergy::getIndex(const int type1, const int type2) const {
  if (type1 < type2) return ((type1 + 1) | ((type2 + 1) << 16));
  else return ((type2 + 1) | ((type1 + 1) << 16));
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ContactFlexEnergy::readXML(XMLPullParser &in) {
  in.skip(TEXT);
  
  string fieldSource;
  string fieldName;
  string haptotactingTypes;
  string contactEnergyFcnName;
  float lambdaTmp;
  set<unsigned char> cellTypesSet;
  
  automaton = potts->getAutomaton();
  while (in.check(START_ELEMENT)) {
    if (in.getName() == "Energy") {
      string type1 = in.getAttribute("Type1").value;
      string type2 = in.getAttribute("Type2").value;
      double energy = BasicString::parseDouble(in.matchSimple());

      setContactEnergy(type1, type2, energy);
      
      //inserting all the types to the set (duplicate are automatically eleminated) to figure out max value of type Id
      cellTypesSet.insert(automaton->getTypeId(type1));
      cellTypesSet.insert(automaton->getTypeId(type2));
      

    } 
    else if (in.getName() == "Depth") {
      depth = BasicString::parseDouble(in.matchSimple());
      
    }
    else if(in.getName() == "Haptotaxis"){
      contactEnergyFcnName="contactHaptotaxis";
      
      fieldSource = in.getAttribute("Source").value;
      
      chemicalFieldSourceVec.push_back(fieldSource);
      
      fieldName =in.getAttribute("Name").value;
      
      chemicalFieldNameVec.push_back(fieldName);

      //have to allocate at least null vector regardless if one declares non chomotacting types or not. This is for easier bookkeeping
      //haptotactingTypeNameVecVec.push_back(vector<string>());
      //haptotactingFlagVec.push_back(false);
      
      vector<string> typeNameVec;
      if(in.findAttribute("HaptotactingTypes")>=0){
         haptotactingTypes=in.getAttribute("HaptotactingTypes").value;   
         parseStringIntoList(haptotactingTypes,typeNameVec,",");
      }
      ASSERT_OR_THROW(
         "There has to be pair of types listed as HaptotypingTypes attribute" , typeNameVec.size() == 2
      );

      haptotactingTypeNamePairVec.push_back( pair<string,string>( typeNameVec[0] , typeNameVec[1] ) );
      
      lambdaTmp=BasicString::parseDouble(in.matchSimple());
      lambdaVec.push_back(lambdaTmp);
      
      

    }
    else {
      throw BasicException(string("Unexpected element '") + in.getName() +
                           "'!", in.getLocation());
    }

    in.skip(TEXT);
  }
   //initialize contact energy fcn ptr
   if(contactEnergyFcnName=="contactHaptotaxis"){
      contactEnergyFcnPtr=&ContactFlexEnergy::contactHaptotaxisEnergy;
   }else{
      contactEnergyFcnPtr=&ContactFlexEnergy::contactEnergy;
   }


  //Now that we know all the types used in the simulation we will find size of the contactEnergyArray
  vector<unsigned char> cellTypesVector(cellTypesSet.begin(),cellTypesSet.end());//coping set to the vector

  int size= * max_element(cellTypesVector.begin(),cellTypesVector.end());
  size+=1;//if max element is e.g. 5 then size has to be 6 for an array to be properly allocated
  
  int index ;
  contactEnergyArray.assign(size,vector<double>(size,0.0));

  for(int i = 0 ; i < size ; ++i)
   for(int j = 0 ; j < size ; ++j){
   
      index = getIndex(cellTypesVector[i],cellTypesVector[j]);
      
      contactEnergyArray[i][j] = contactEnergies[index];
      
   }
   cerr<<"size="<<size<<endl;
   
  for(int i = 0 ; i < size ; ++i)
   for(int j = 0 ; j < size ; ++j){
   
      cerr<<"contact["<<i<<"]["<<j<<"]="<<contactEnergyArray[i][j]<<endl;
      
   }

   
   
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ContactFlexEnergy::writeXML(XMLSerializer &out) {
}
