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

#include <CompuCell3D/Automaton/Automaton.h>
#include <CompuCell3D/Field3D/Dim3D.h>
#include <CompuCell3D/ClassRegistry.h>
#include <CompuCell3D/Simulator.h>
//#include <CompuCell3D/Diffusable.h>
#include <CompuCell3D/Field3D/Field3D.h>
#include <CompuCell3D/Field3D/Field3DIO.h>
#include <CompuCell3D/Potts3D/Cell.h>
//#include <CompuCell3D/DiffusionSolverBiofilmFE.h>
#include <CompuCell3D/DiffusableVector.h>
using namespace CompuCell3D;

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

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


#include <fstream>
#include <string>
using namespace std;

// double ChemotaxisEnergy::getConcentration(const Point3D &pt) {
//   ASSERT_OR_THROW("No chemical field has been initialized!", field);
//   return field->get(pt);
// }


double ChemotaxisEnergy::localEnergy(const Point3D &pt) {
  ASSERT_OR_THROW("No chemical field has been initialized!", fieldVec.size()>0);
  return 0;
}

void ChemotaxisEnergy::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]);
   }

}



double ChemotaxisEnergy::changeEnergy(const Point3D &pt,
                                  const CellG *newCell,
                                  const CellG *oldCell) {
  

///cells move up the concentration gradient
   

   /// new cell has to be different than medium i.e. only non-medium cells can chemotact
   ///e.g. in chemotaxis only non-medium cell can move a pixel that either belonged to other cell or to medium
   ///but situation where medium moves to a new pixel is not considered a chemotaxis
   float energy=0.0;
   if(newCell){
      for(unsigned int i = 0 ; i < fieldVec.size() ; ++i){
//          cerr<<"flexFlagVec["<<i<<"]="<<flexFlagVec[i]<<endl;
         if(flexFlagVec[i]){
         
            
//             cerr<<"(int)newCell->type="<<(int)newCell->type<<" flexChemotaxisDataVec[i].size()="<<flexChemotaxisDataVec[i].size()<<endl;
            
            if( (int)newCell->type <= flexChemotaxisDataVec[i].size() ){
//                 cerr<<" lambda chem="<<flexChemotaxisDataVec[i][(int)newCell->type]<< " (int)newCell->type="<<(int)newCell->type<<endl;
               energy+=(fieldVec[i]->get(potts->getFlipNeighbor()) - fieldVec[i]->get(pt))*flexChemotaxisDataVec[i][(int)newCell->type];
            }
         }else{

         
            if(!nonChemotactingFlagVec[i]){
               energy+=(fieldVec[i]->get(potts->getFlipNeighbor()) - fieldVec[i]->get(pt))*lambdaVec[i];
            }
            else{
               if(okToChemotact(i,newCell->type)){
                  energy+=(fieldVec[i]->get(potts->getFlipNeighbor()) - fieldVec[i]->get(pt))*lambdaVec[i];
               }
//                else
                  //return 0.0;
            }
         }
      }
/*      if(energy!=0.0){
         cerr<<"pt="<<pt<<" potts->getFlipNeighbor()="<<potts->getFlipNeighbor()<<endl;
      
         cerr<<"energy change chmotaxis="<<energy<<endl;
      }*/
//          cerr<<"energy change chmotaxis="<<energy<<endl;
      return energy;
   }
 
  return 0;
}


void ChemotaxisEnergy::readXML(XMLPullParser &in) {
  in.skip(TEXT);

  string fieldSource;
  string fieldName;
  string nonChemotactingTypes;
  float lambdaTmp;
  vector<pair<string ,float> > lambdaTypeVecTmp;
  string typeTmp;


  
  while (in.check(START_ELEMENT)) {

    if (in.getName() == "ChemicalField") {

      
      fieldSource = in.getAttribute("Source").value;
      fieldName =in.getAttribute("Name").value;
      //have to allocate at least null vector regardless if one declares non chomotacting types or not. This is for easier bookkeeping
      nonChemotactingTypeNameVecVec.push_back(vector<string>());
      nonChemotactingFlagVec.push_back(false);
      
      if(in.findAttribute("NonChemotactingTypes")>=0){
         nonChemotactingTypes=in.getAttribute("NonChemotactingTypes").value;   
         parseStringIntoList(nonChemotactingTypes,nonChemotactingTypeNameVecVec[nonChemotactingTypeNameVecVec.size()-1],",");
      }
      
      if(in.findAttribute("Lambda")>=0){
         
         lambdaTmp=BasicString::parseDouble(in.getAttribute("Lambda").value);
         
      }
      
      if(in.findAttribute("Flex")>=0){
         flexFlagVec.push_back(true);
      }else{
         flexFlagVec.push_back(false);
      }

      
            
      //lambdaTmp=BasicString::parseDouble(in.matchSimple());
      
      chemicalFieldSourceVec.push_back(fieldSource);
      chemicalFieldNameVec.push_back(fieldName);
      lambdaVec.push_back(lambdaTmp);

      lambdaTypeVecTmp.clear();
      cerr<<flexFlagVec.size()-1<<endl;
      cerr<<"flexFlagVec[flexFlagVec.size()-1]="<<flexFlagVec[flexFlagVec.size()-1]<<endl;
      //if chemotaxis is to be flexible
      if(flexFlagVec[flexFlagVec.size()-1]){

         lambdaVec[lambdaVec.size()-1]=0.0; //if flex is ON then overall lambda has to be zero
         in.match(START_ELEMENT);
         in.skip(TEXT);
            
         while(in.check(START_ELEMENT)){
            if(in.getName() == "ChemotaxisByType"){
               typeTmp=in.getAttribute("Type").value;
               lambdaTmp=BasicString::parseDouble(in.getAttribute("Lambda").value);
               lambdaTypeVecTmp.push_back(make_pair(typeTmp,lambdaTmp));
               cerr<<"lambdaTmp="<<lambdaTmp<<endl;
               cerr<<"typaTmp="<<typeTmp<<endl;
               
               in.matchSimple();
            }else {
               throw BasicException(string("Unexpected element '") + in.getName() + 
               "'!", in.getLocation());
            }
            in.skip(TEXT);
         }
         in.skip(TEXT);
         in.match(END_ELEMENT);
         flexChemotaxisDataVecTmp.push_back(lambdaTypeVecTmp);
      }else{
         flexChemotaxisDataVecTmp.push_back(lambdaTypeVecTmp);
         in.matchSimple();
         //even if there are no flexData I have to insert empty vector to keep
         //indices in order

      }

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

    in.skip(TEXT);
  }

   
 initFlexChemotaxisData();
   
  
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ChemotaxisEnergy::writeXML(XMLSerializer &out) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ChemotaxisEnergy::initNonChemotactingTypeId(){
   Automaton *automaton=potts->getAutomaton();

   nonChemotactingTypeIdVecVec.assign(nonChemotactingTypeNameVecVec.size(),vector<unsigned char>());
   
   for(unsigned i = 0; i< nonChemotactingTypeNameVecVec.size() ; ++i ){

      nonChemotactingTypeIdVecVec[i].assign(nonChemotactingTypeNameVecVec[i].size(),0);
      if(nonChemotactingTypeIdVecVec[i].size()!=0)
         nonChemotactingFlagVec[i]=true;
         
      for(unsigned j = 0; j< nonChemotactingTypeNameVecVec[i].size() ; ++j ){
         nonChemotactingTypeIdVecVec[i][j]=automaton->getTypeId(nonChemotactingTypeNameVecVec[i][j]);
      }
   }

   

   for(unsigned i = 0; i< nonChemotactingTypeIdVecVec.size() ; ++i ){

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

   nonChemotactingTypeNameVecVec.clear();
   
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ChemotaxisEnergy::initFlexChemotaxisData(){

   Automaton *automaton=potts->getAutomaton();
   
   unsigned char maxType=0;
   //first will find max type value
   for(int i = 0 ; i < flexChemotaxisDataVecTmp.size() ; ++ i)
      for(int j = 0 ; j < flexChemotaxisDataVecTmp[i].size() ; ++j){
         if( automaton->getTypeId(flexChemotaxisDataVecTmp[i][j].first) > maxType )
            maxType = automaton->getTypeId(flexChemotaxisDataVecTmp[i][j].first);
      }
   //now will allocate vectors based on maxType - this will result in t=0 lookup time later...
   flexChemotaxisDataVec.assign(flexChemotaxisDataVecTmp.size(),vector<float>((int)maxType+1,0.0));
   
   for(int i = 0 ; i < flexChemotaxisDataVecTmp.size() ; ++ i)
      for(int j = 0 ; j < flexChemotaxisDataVecTmp[i].size() ; ++j){
      
      int cellType = automaton->getTypeId(flexChemotaxisDataVecTmp[i][j].first);
      flexChemotaxisDataVec[i][cellType]=flexChemotaxisDataVecTmp[i][j].second;
   }   
}  

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


bool ChemotaxisEnergy::okToChemotact(unsigned int fieldIdx,unsigned char cellType){

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