#include "mainCC3D.h"
#include <iostream>
#include <typeinfo>


#include <Simulator.h>
using namespace CompuCell3D;

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

#include <CompuCell3D/Automaton/Automaton.h>
#include <CompuCell3D/Potts3D/Potts3D.h>
#include <CompuCell3D/plugins/Volume/VolumeEnergy.h>
#include <CompuCell3D/plugins/VolumeFlex/VolumeFlexEnergy.h>
#include <CompuCell3D/plugins/CellVelocity/CellVelocityData.h>
#include <CompuCell3D/plugins/CellVelocity/CellVelocityPlugin.h>
#include <CompuCell3D/plugins/PlayerSettings/PlayerSettingsPlugin.h>
#include <CompuCell3D/plugins/PlayerSettings/PlayerSettings.h>


#include <BasicUtils/BasicClassAccessor.h>

#include <XMLCereal/XMLPullParser.h>

#include <XercesUtils/XercesStr.h>

#include <xercesc/util/PlatformUtils.hpp>
XERCES_CPP_NAMESPACE_USE;

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

#include <stdlib.h>

//#include <config.h>
#include <qmutex.h>
#include <qsemaphore.h>

//#include <transactionthread.h>

void Syntax(const string name) {
  cerr << "Syntax: " << name << " <config>" << endl;
  exit(1);
}





using namespace std;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CC3DTransaction::CC3DTransaction(std::string  _filename){
   
   ///Filename
   filename=_filename;

   
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CC3DTransaction::~CC3DTransaction(){

}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void  CC3DTransaction::createConcentrationFields(GraphicsDataFields  &graphFields ,
      std::map<std::string,CompuCell3D::Field3DImpl<float>*> & _fieldMap
      )
{

   std::map<std::string,Field3DImpl<float>*>::iterator mitr;

   for( mitr = _fieldMap.begin(); mitr != _fieldMap.end() ; ++mitr){
      cerr<<"GOT FIELD: "<<mitr->first<<endl;

      CompuCell3D::Dim3D fieldDim=mitr->second->getDim();
      cerr<<"Dimension:"<<fieldDim<<endl;
      
      graphFields.allocateFloatField3D(fieldDim.x, fieldDim.y, fieldDim.z , mitr->first);
   }
   
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::fillConcentrationFields(GraphicsDataFields  &graphFields, std::map<std::string,CompuCell3D::Field3DImpl<float>*> & _fieldMap){

   ///set mutextes
   fieldDrawMutexPtr->lock();
//    cerr<<"MAIN: GOT fieldDrawMutexPtr "<<endl;

   std::map<std::string,GraphicsDataFields::floatField3D_t*> & floatField3DNameMap=graphFields.getFloatField3DNameMap();
   
   std::map<std::string,GraphicsDataFields::floatField3D_t*>::iterator floatFieldMitr;
   std::map<std::string,CompuCell3D::Field3DImpl<float>*>::iterator mitr;
   
   for( mitr = _fieldMap.begin(); mitr != _fieldMap.end() ; ++mitr){
      floatFieldMitr = floatField3DNameMap.find(mitr->first);
      
      if(floatFieldMitr == floatField3DNameMap.end()){
         cerr<<"Could not find element "<<mitr->first<<" in the floatField3DNameMap."<<endl;
         continue;
      }

      GraphicsDataFields::floatField3D_t & floatField3D = *floatFieldMitr->second;
      CompuCell3D::Field3DImpl<float> & concentrationField = *mitr->second;

      

      CompuCell3D::Point3D pt;
      CompuCell3D::Point3D fieldDim=mitr->second->getDim();
      
      for(unsigned int x = 0 ; x < fieldDim.x ; ++x)
         for(unsigned int y = 0 ; y < fieldDim.y ; ++y)
            for(unsigned int z = 0 ; z < fieldDim.z ; ++z){
            pt.x=x;
            pt.y=y;
            pt.z=z;      
            
            floatField3D[x][y][z]=concentrationField.get(pt);
                      
         }
      
      

   }

    ///unset
   fieldDrawMutexPtr->unlock();
//    cerr<<"MAIN: RELEASE fieldDrawMutexPtr "<<endl;
   
}



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::createPreasureFields(  GraphicsDataFields  &graphFields,
                                             CompuCell3D::Dim3D fieldDim,
                                             BasicArray<CompuCell3D::EnergyFunction *>  & energyFunctions
)
{
   string energyFunctionName;
   string::size_type N_POS=string::npos;
   
   for(int i  = 0 ; i < energyFunctions.getSize() ; ++i ){

      energyFunctionName=energyFunctions[i]->toString();

      if(energyFunctionName.find("Volume")!=N_POS){
      
         pressureNameEnergyMap.insert(make_pair(energyFunctionName,energyFunctions[i]));
         string fieldName("Pressure ");
         fieldName+=energyFunctionName;
         graphFields.allocateFloatField3D(fieldDim.x, fieldDim.y, fieldDim.z , fieldName);
      }
      
   }


}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::fillPressureFields(GraphicsDataFields  &graphFields){

   ///set mutextes
   fieldDrawMutexPtr->lock();
//    cerr<<"MAIN: GOT fieldDrawMutexPtr "<<endl;

   std::map<std::string,GraphicsDataFields::floatField3D_t*> & floatField3DNameMap=graphFields.getFloatField3DNameMap();

   std::map<std::string,GraphicsDataFields::floatField3D_t*>::iterator floatFieldMitr;
   map<string, CompuCell3D::EnergyFunction *>::iterator mitr;



   for( mitr = pressureNameEnergyMap.begin(); mitr != pressureNameEnergyMap.end() ; ++mitr){

      string fieldName("Pressure ");
      fieldName+=mitr->first;

      floatFieldMitr = floatField3DNameMap.find(fieldName);//getting field ptr for appropriate pressure field
      if(floatFieldMitr == floatField3DNameMap.end()){
         cerr<<"Could not find element "<<mitr->first<<" in the floatField3DNameMap."<<endl;
         continue;
      }

      GraphicsDataFields::floatField3D_t & floatField3D = *floatFieldMitr->second;


      if(mitr->first=="Volume"){

         fillPressureVolume(graphFields , floatField3D , mitr->second);

      }
      else if(mitr->first=="VolumeFlex"){
         fillPressureVolumeFlex(graphFields , floatField3D , mitr->second);
      }



   }

    ///unset
   fieldDrawMutexPtr->unlock();
//    cerr<<"MAIN: RELEASE fieldDrawMutexPtr "<<endl;
   
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::fillPressureVolume( GraphicsDataFields  &graphFields,
                                          GraphicsDataFields::floatField3D_t & _floatField3D,
                                          CompuCell3D::EnergyFunction * energyFunctionPtr                                          
){

VolumeEnergy * volEnergy = (VolumeEnergy*)energyFunctionPtr;

double targetVolume = volEnergy->getTargetVolume();

Point3D pt;
CompuCell3D::CellG * cell=0;

   for(unsigned int x = 0 ; x < graphFields.getSizeL() ; ++x)
      for(unsigned int y = 0 ; y < graphFields.getSizeM() ; ++y)
         for(unsigned int z = 0 ; z < graphFields.getSizeN() ; ++z){
         pt.x=x;
         pt.y=y;
         pt.z=z;
         
         cell=cellFieldG->get(pt);

         if(!cell){
            _floatField3D[x][y][z]=0.0;
            continue;
         }

         
         
         _floatField3D[x][y][z]=targetVolume - cell->volume;
      }


}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::fillPressureVolumeFlex( GraphicsDataFields  &graphFields,
                                          GraphicsDataFields::floatField3D_t & _floatField3D,
                                          CompuCell3D::EnergyFunction * energyFunctionPtr                                          
){

VolumeFlexEnergy * volFlexEnergy = (VolumeFlexEnergy*)energyFunctionPtr;

const std::vector<VolumeEnergyParam> & volumeEnergyParamVector= volFlexEnergy->getVolumeEnergyParamVector();


Point3D pt;
CompuCell3D::CellG * cell=0;

   for(unsigned int x = 0 ; x < graphFields.getSizeL() ; ++x)
      for(unsigned int y = 0 ; y < graphFields.getSizeM() ; ++y)
         for(unsigned int z = 0 ; z < graphFields.getSizeN() ; ++z){
         pt.x=x;
         pt.y=y;
         pt.z=z;
         
         cell=cellFieldG->get(pt);

         if(!cell){
            continue;
         }

         _floatField3D[x][y][z]=volumeEnergyParamVector[cell->type].targetVolume - cell->volume;
          
      }


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


void CC3DTransaction::createVectorCellFields(  GraphicsDataFields  &graphFields,
                                             CompuCell3D::Dim3D fieldDim,
                                             BasicArray<CompuCell3D::EnergyFunction *>  & energyFunctions,
                                             CompuCell3D::PluginManager::plugins_t & pluginMap
)
{
   
   string fieldName;
      
      for(PluginManager::pluginMapItr_t itr = pluginMap.begin() ; itr != pluginMap.end() ; ++itr){
         if(itr->second && itr->second->toString()=="CellVelocity"){

            fieldName="CellVelocity";
            graphFields.allocateVectorCellFloatField3D(fieldDim.x, fieldDim.y, fieldDim.z , fieldName);
            pluginNameMap.insert(make_pair(fieldName,itr->second));

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

void CC3DTransaction::fillVectorCellFields(GraphicsDataFields  &graphFields){
   ///set mutextes
   fieldDrawMutexPtr->lock();
//    cerr<<"MAIN: GOT fieldDrawMutexPtr "<<endl;

   std::map<std::string,GraphicsDataFields::vectorCellFloatField3D_t*> &
      vectorCellFloatField3DNameMap=graphFields.getVectorCellFloatField3DNameMap();

   
   std::map<std::string,GraphicsDataFields::vectorCellFloatField3D_t*>::iterator mitr;
   string fieldName;
   
   for( mitr = vectorCellFloatField3DNameMap.begin(); mitr != vectorCellFloatField3DNameMap.end() ; ++mitr){

      
      fieldName=mitr->first;
      GraphicsDataFields::vectorCellFloatField3D_t & vectorCellFloatField3D = *mitr->second;
      


      if(fieldName=="CellVelocity"){
         std::map<std::string, CompuCell3D::Plugin *>::iterator pitr = pluginNameMap.find(fieldName);
         if(pitr != pluginNameMap.end() ){
            fillVelocity(graphFields , vectorCellFloatField3D , pitr->second);
         }else{
            cerr<<"Could not find pointer to "<<fieldName<<" related plugin"<<endl;
         }
      }
   }
         
   
   

    ///unset
   fieldDrawMutexPtr->unlock();
//    cerr<<"MAIN: RELEASE fieldDrawMutexPtr "<<endl;


}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::fillVelocity( GraphicsDataFields  &graphFields,
                                          GraphicsDataFields::vectorCellFloatField3D_t & _vectorCellFloatField3D,
                                          CompuCell3D::Plugin * plugin
                                          
){

Point3D pt;
CompuCell3D::CellG * cell=0;

CompuCell3D::CellVelocityPlugin * cellVelocityPlugin;
if(plugin->toString() == "CellVelocity"){
   cellVelocityPlugin = (CompuCell3D::CellVelocityPlugin*)(plugin);
}else{
   cerr<<"Wrong Plugin - "<<plugin->toString()<<endl;
   exit(0);
}



BasicClassAccessor<CellVelocityData> * accessorPtr= cellVelocityPlugin -> getCellVelocityDataAccessorPtr();


   for(unsigned int x = 0 ; x < graphFields.getSizeL() ; ++x)
      for(unsigned int y = 0 ; y < graphFields.getSizeM() ; ++y)
         for(unsigned int z = 0 ; z < graphFields.getSizeN() ; ++z){
         pt.x=x;
         pt.y=y;
         pt.z=z;

         cell=cellFieldG->get(pt);

         if(!cell){
            _vectorCellFloatField3D[x][y][z]=make_pair(Coordinates3D<float>(0.,0.,0.),cell);
            continue;
         }

         
         
         //_vectorCellFloatField3D[x][y][z]=make_pair(accessorPtr->get(cell->extraAttribPtr)->getInstantenousVelocity(),cell);
//          cerr<<"vel="<<accessorPtr->get(cell->extraAttribPtr)->getAverageVelocity().X()<<endl;
         _vectorCellFloatField3D[x][y][z]=make_pair(accessorPtr->get(cell->extraAttribPtr)->getAverageVelocity(),cell);
      }

}

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


void CC3DTransaction::fillField3D(GraphicsDataFields  &graphFields){

Point3D pt;
CompuCell3D::CellG * cell=0;
   ///set mutextes
   fieldDrawMutexPtr->lock();
   
   for(unsigned int x = 0 ; x < graphFields.getSizeL() ; ++x)
      for(unsigned int y = 0 ; y < graphFields.getSizeM() ; ++y)
         for(unsigned int z = 0 ; z < graphFields.getSizeN() ; ++z){
         pt.x=x;
         pt.y=y;
         pt.z=z;
         
         cell=cellFieldG->get(pt);

         if(!cell){
            graphFields.field3DGraphicsData[x][y][z].type = 0;
            graphFields.field3DGraphicsData[x][y][z].id = 0;
            graphFields.field3DGraphicsData[x][y][z].flag = 0;
            graphFields.field3DGraphicsData[x][y][z].averageConcentration = 0;
            continue;
         }

         
         graphFields.field3DGraphicsData[x][y][z].type=cell->type;
         graphFields.field3DGraphicsData[x][y][z].id = (int)cell;
         graphFields.field3DGraphicsData[x][y][z].flag = cell->flag;
         graphFields.field3DGraphicsData[x][y][z].averageConcentration = cell->averageConcentration;
                   
      }
   ///unset   
   fieldDrawMutexPtr->unlock();
   
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::markBorder(const CompuCell3D::Point3D & _pt , CellG *_currentCell, GraphicsData * _pixel){

   Point3D pt;
   CellG *cell;
   vector<CompuCell3D::Point3D> offset(4);
   
   offset[0]=Point3D(0,0,-1);
   offset[1]=Point3D(0,0,1);
   offset[2]=Point3D(-1,0,0);
   offset[3]=Point3D(1,0,0);


   
   for( unsigned int i = 0 ; i < offset.size() ; ++i){
      pt=_pt;
      pt+=offset[i];
      
      cell=cellFieldG->get(pt);
      if(!cell)

      if(cell != _currentCell){
         _pixel->type  = 4;
         return;
      }
   }
   

   
   

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QImage CC3DTransaction::apply(const QImage &image){
   return image;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CC3DTransaction::applySimulation(){



      
  try {


    
  
    // Load Plugin Libaries
    // Libaries in COMPUCELL3D_PLUGIN_PATH can override the
    // DEFAULT_PLUGIN_PATH
    char *pluginPath = getenv("COMPUCELL3D_PLUGIN_PATH");
    cerr<<"THIS IS COMPUCELL3D_PLUGIN_PATH"<<pluginPath<<endl;
    if (pluginPath) Simulator::pluginManager.loadLibraries(pluginPath);


    

        
    
#ifdef DEFAULT_PLUGIN_PATH
    Simulator::pluginManager.loadLibraries(DEFAULT_PLUGIN_PATH);
#endif

    BasicPluginManager<Plugin>::infos_t *infos = 
      &Simulator::pluginManager.getPluginInfos();

    if (!infos->empty()) {
      cout << "Found the following plugins:" << endl;
      BasicPluginManager<Plugin>::infos_t::iterator it; 
      for (it = infos->begin(); it != infos->end(); it++)
   cout << "  " << *(*it) << endl;
      cout << endl;
    }


    // Create Simulator
    Simulator sim;
   

    // Initialize Xerces
    XMLPlatformUtils::Initialize();
    
    // Create parser
    BasicSmartPointer<XMLPullParser> parser = XMLPullParser::createInstance();

    // Parse config
    try {

      parser->initParse(filename.c_str());
      parser->match(XMLEventTypes::START_DOCUMENT, -XMLEventTypes::TEXT);

      // Parse
      parser->assertName("CompuCell3D");
      parser->match(XMLEventTypes::START_ELEMENT);
      sim.readXML(*parser);
      parser->match(XMLEventTypes::END_ELEMENT, -XMLEventTypes::TEXT);

      // End
      parser->match(XMLEventTypes::END_DOCUMENT);

    } catch (BasicException e) {
      throw BasicException("While parsing configuration!",
            parser->getLocation(), e);
    }

   sim.extraInit();///additional initialization after all plugins and steppables have been loaded and preinitialized
   ///get dimension of the cell field
   Dim3D fieldDim=sim.getPotts()->getCellFieldG()->getDim();

    ///initialization of the drawing utils
      
   ///allocate fields 
   graphFieldsPtr->allocateField3D(fieldDim.x,fieldDim.y,fieldDim.z);
   
   createConcentrationFields(*graphFieldsPtr ,sim.getConcentrationFieldNameMap());
   createPreasureFields( *graphFieldsPtr , fieldDim , sim.getPotts()->getEnergyFunctionsArray());
   
   createVectorCellFields( *graphFieldsPtr , fieldDim , sim.getPotts()->getEnergyFunctionsArray(), sim.getPluginMap());
                                           
   ///assign field ptr
   cellFieldG=sim.getPotts()->getCellFieldG();

   
   ///3 rd coordinate value
   zPosition=50;
   
   /// Now will set up drawing canvas sizes and configure the player 
   TransactionStartEvent *eventStart=new TransactionStartEvent();
   CompuCell3D::PlayerSettingsPlugin *playerSettingsPlugin=0;
   playerSettingsPlugin=(PlayerSettingsPlugin*) (Simulator::pluginManager.get("PlayerSettings"));
   
   if(playerSettingsPlugin){
      eventStart->playerSettings=playerSettingsPlugin->getPlayerSettings();
   }
   eventStart->xSize=fieldDim.x;
   eventStart->ySize=fieldDim.y;
   eventStart->zSize=fieldDim.z;

   
   QApplication::postEvent(getTargetWidget(),eventStart);
   
    // Run simulation
    sim.start();
    
      fillField3D(*graphFieldsPtr); ///fills field3D with values of the cell type
      fillConcentrationFields(*graphFieldsPtr, sim.getConcentrationFieldNameMap());

      (*bufferFillFreeSemPtr)++;//acquire semaphore
    
      TransactionRefreshEvent *eventRefresh=new TransactionRefreshEvent();

      eventRefresh->mcStep=sim.getStep();
    
      QApplication::postEvent(getTargetWidget(),eventRefresh);
      (*bufferFillUsedSemPtr)--;//release semaphore
      
   

     
    for (unsigned int i = 1; i <= sim.getNumSteps(); i++){
      sim.step(i);
      fillField3D(*graphFieldsPtr);
      fillConcentrationFields(*graphFieldsPtr, sim.getConcentrationFieldNameMap());
      fillPressureFields(*graphFieldsPtr);
      
      fillVectorCellFields(*graphFieldsPtr);
      
      
      //mutexTransactionPtr->lock(); ///lock mutex - imagewindow.cpp, customEvent(QCustomEvent *event) will unlock it
      (*bufferFillFreeSemPtr)++; //acquire semaphore
      TransactionRefreshEvent *eventRefresh=new TransactionRefreshEvent(); ///once a pointer is owned by Qt object
                                                                           ///it will not create memory leak

      eventRefresh->mcStep=sim.getStep();
      QApplication::postEvent(getTargetWidget(),eventRefresh);
      (*bufferFillUsedSemPtr)--;//release semaphore
      ///Play -  Pause mutex
      pauseMutexPtr->lock();
      pauseMutexPtr->unlock();
     
    }
    
    cerr<<"final: "<<sim.getNumSteps()<<endl;
    sim.finish();

    TransactionFinishEvent *finishEvent = new TransactionFinishEvent();//TransactionFinishEvent();
    finishEvent->message=QString("DONE!!!");
    QApplication::postEvent(getTargetWidget(),finishEvent);
    

    cerr<<"Done:"<<endl;
    return;

  } catch (const XMLException &e) {
    cerr << "ERROR: " << XercesStr(e.getMessage()) << endl;

  } catch (const BasicException &e) {
    cerr << "ERROR: " << e << endl;
  }

}
