/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-2012 University of Bonn. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder is free software: 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * FragmentationAutomationAction.cpp
 *
 *  Created on: May 18, 2012
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include 
// boost asio needs specific operator new
#include 
#include "CodePatterns/MemDebug.hpp"
// include headers that implement a archive in simple text format
#include 
#include 
#include 
#include 
#include 
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Info.hpp"
#include "CodePatterns/Log.hpp"
#include "JobMarket/Jobs/FragmentJob.hpp"
#include "Fragmentation/Automation/createMatrixNrLookup.hpp"
#include "Fragmentation/Automation/extractJobIds.hpp"
#include "Fragmentation/Automation/FragmentationChargeDensity.hpp"
#include "Fragmentation/Automation/FragmentationResults.hpp"
#include "Fragmentation/Automation/MPQCFragmentController.hpp"
#include "Fragmentation/Automation/parseKeySetFile.hpp"
#include "Fragmentation/Automation/VMGDebugGridFragmentController.hpp"
#include "Fragmentation/Automation/VMGFragmentController.hpp"
#include "Fragmentation/EnergyMatrix.hpp"
#include "Fragmentation/ForceMatrix.hpp"
#include "Fragmentation/Fragmentation.hpp"
#include "Fragmentation/HydrogenSaturation_enum.hpp"
#include "Fragmentation/Homology/HomologyContainer.hpp"
#include "Fragmentation/Homology/HomologyGraph.hpp"
#include "Fragmentation/KeySet.hpp"
#include "Fragmentation/KeySetsContainer.hpp"
#include "Fragmentation/SetValues/Fragment.hpp"
#include "Fragmentation/SetValues/Histogram.hpp"
#include "Fragmentation/SetValues/IndexedVectors.hpp"
#include "Fragmentation/Summation/IndexSetContainer.hpp"
#include "Fragmentation/Summation/writeTable.hpp"
#include "Graph/DepthFirstSearchAnalysis.hpp"
#include "Jobs/MPQCJob.hpp"
#include "Jobs/MPQCData.hpp"
#include "Jobs/MPQCData_printKeyNames.hpp"
#ifdef HAVE_VMG
#include "Jobs/VMGDebugGridJob.hpp"
#include "Jobs/VMGJob.hpp"
#include "Jobs/VMGData.hpp"
#include "Jobs/VMGDataFused.hpp"
#include "Jobs/VMGDataMap.hpp"
#include "Jobs/VMGData_printKeyNames.hpp"
#endif
#include "World.hpp"
#include 
#include 
#include 
#include 
#include 
#include "Actions/FragmentationAction/FragmentationAutomationAction.hpp"
using namespace MoleCuilder;
// and construct the stuff
#include "FragmentationAutomationAction.def"
#include "Action_impl_pre.hpp"
/** =========== define the function ====================== */
class controller_AddOn;
// needs to be defined for using the FragmentController
controller_AddOn *getAddOn()
{
  return NULL;
}
/** Helper function to get number of atoms somehow.
 *
 * Here, we just parse the number of lines in the adjacency file as
 * it should correspond to the number of atoms, except when some atoms
 * are not bonded, but then fragmentation makes no sense.
 *
 * @param path path to the adjacency file
 */
size_t getNoAtomsFromAdjacencyFile(const std::string &path)
{
  size_t NoAtoms = 0;
  // parse in special file to get atom count (from line count)
  std::string filename(path);
  filename += FRAGMENTPREFIX;
  filename += ADJACENCYFILE;
  std::ifstream adjacency(filename.c_str());
  if (adjacency.fail()) {
    LOG(0, endl << "getNoAtomsFromAdjacencyFile() - Unable to open " << filename << ", is the directory correct?");
    return false;
  }
  std::string buffer;
  while (getline(adjacency, buffer))
    NoAtoms++;
  LOG(1, "INFO: There are " << NoAtoms << " atoms.");
  return NoAtoms;
}
/** Place results from FragmentResult into EnergyMatrix and ForceMatrix.
 *
 * @param fragmentData MPQCData resulting from the jobs
 * @param MatrixNrLookup Lookup up-map from job id to fragment number
 * @param FragmentCounter total number of fragments
 * @param NoAtoms total number of atoms
 * @param Energy energy matrix to be filled on return
 * @param Force force matrix to be filled on return
 * @return true - everything ok, false - else
 */
bool putResultsintoMatrices(
    const std::map &fragmentData,
    const std::map< JobId_t, size_t > &MatrixNrLookup,
    const size_t FragmentCounter,
    const size_t NoAtoms,
    EnergyMatrix &Energy,
    ForceMatrix &Force)
{
  for (std::map::const_iterator dataiter = fragmentData.begin();
      dataiter != fragmentData.end(); ++dataiter) {
    const MPQCData &extractedData = dataiter->second;
    const JobId_t &jobid = dataiter->first;
    std::map< JobId_t, size_t >::const_iterator nriter = MatrixNrLookup.find(jobid);
    ASSERT( nriter != MatrixNrLookup.end(),
        "putResultsintoMatrices() - MatrixNrLookup does not contain id "
        +toString(jobid)+".");
    // place results into EnergyMatrix ...
    {
      MatrixContainer::MatrixArray matrix;
      matrix.resize(1);
      matrix[0].resize(1, extractedData.energies.total);
      if (!Energy.AddMatrix(
          std::string("MPQCJob ")+toString(jobid),
          matrix,
          nriter->second)) {
        ELOG(1, "Adding energy matrix failed.");
        return false;
      }
    }
    // ... and ForceMatrix (with two empty columns in front)
    {
      MatrixContainer::MatrixArray matrix;
      const size_t rows = extractedData.forces.size();
      matrix.resize(rows);
      for (size_t i=0;isecond)) {
        ELOG(1, "Adding force matrix failed.");
        return false;
      }
    }
  }
  // add one more matrix (not required for energy)
  MatrixContainer::MatrixArray matrix;
  matrix.resize(1);
  matrix[0].resize(1, 0.);
  if (!Energy.AddMatrix(std::string("MPQCJob total"), matrix, FragmentCounter))
    return false;
  // but for energy because we need to know total number of atoms
  matrix.resize(NoAtoms);
  for (size_t i = 0; i< NoAtoms; ++i)
    matrix[i].resize(2+NDIM, 0.);
  if (!Force.AddMatrix(std::string("MPQCJob total"), matrix, FragmentCounter))
    return false;
  return true;
}
/** Print MPQCData from received results.
 *
 * @param fragmentData MPQCData resulting from the jobs, associated to job id
 * @param KeySetFilename filename with keysets to associate forces correctly
 * @param NoAtoms total number of atoms
 * @param full_sample summed up charge from fragments on return
 */
bool printReceivedMPQCResults(
    const std::map &fragmentData,
    const std::string &KeySetFilename,
    size_t NoAtoms)
{
  // create a vector of all job ids
  std::vector jobids;
  std::transform(fragmentData.begin(),fragmentData.end(),
      std::back_inserter(jobids),
      boost::bind( &std::map::value_type::first, boost::lambda::_1 )
  );
  // create lookup from job nr to fragment number
  size_t FragmentCounter = 0;
  const std::map< JobId_t, size_t > MatrixNrLookup=
      createMatrixNrLookup(jobids, FragmentCounter);
  // place results into maps
  EnergyMatrix Energy;
  ForceMatrix Force;
  if (!putResultsintoMatrices(fragmentData, MatrixNrLookup, FragmentCounter, NoAtoms, Energy, Force))
    return false;
  if (!Energy.InitialiseIndices()) return false;
  if (!Force.ParseIndices(KeySetFilename.c_str())) return false;
  // initialise keysets
  KeySetsContainer KeySet;
  parseKeySetFile(KeySet, KeySetFilename, FragmentCounter, NonHydrogenKeySets);
  KeySetsContainer ForceKeySet;
  parseKeySetFile(ForceKeySet, KeySetFilename, FragmentCounter, HydrogenKeySets);
  // combine all found data
  if (!KeySet.ParseManyBodyTerms()) return false;
  EnergyMatrix EnergyFragments;
  ForceMatrix ForceFragments;
  if (!EnergyFragments.AllocateMatrix(Energy.Header, Energy.MatrixCounter, Energy.RowCounter, Energy.ColumnCounter)) return false;
  if (!ForceFragments.AllocateMatrix(Force.Header, Force.MatrixCounter, Force.RowCounter, Force.ColumnCounter)) return false;
  if(!Energy.SetLastMatrix(0., 0)) return false;
  if(!Force.SetLastMatrix(0., 2)) return false;
  for (int BondOrder=0;BondOrder::type
      MPQCDataEnergyVector_noeigenvalues_t;
    const std::string energyresult =
        writeTable()(
            results.Result_Energy_fused, results.getMaxLevel());
    LOG(0, "Energy table is \n" << energyresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_Energy.dat");
    writeToFile(filename, energyresult);
  }
  if (results.Result_LongRange_fused.size() >= (results.getMaxLevel()-2))
  {
    const std::string gridresult =
        writeTable()(
            results.Result_LongRange_fused, results.getMaxLevel(), 2);
    LOG(0, "VMG table is \n" << gridresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_VMGEnergy.dat");
    writeToFile(filename, gridresult);
  }
  if (results.Result_LongRangeIntegrated_fused.size() >= (results.getMaxLevel()-2))
  {
    const std::string gridresult =
        writeTable()(
            results.Result_LongRangeIntegrated_fused, results.getMaxLevel(), 2);
    LOG(0, "LongRange table is \n" << gridresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_LongRangeEnergy.dat");
    writeToFile(filename, gridresult);
  }
  {
    const std::string eigenvalueresult;
    LOG(0, "Eigenvalue table is \n" << eigenvalueresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_Eigenvalues.dat");
    writeToFile(filename, eigenvalueresult);
  }
  {
    const std::string forceresult =
        writeTable()(
            results.Result_Force_fused, results.getMaxLevel());
    LOG(0, "Force table is \n" << forceresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_Forces.dat");
    writeToFile(filename, forceresult);
  }
  // we don't want to print grid to a table
  {
    // print times (without flops for now)
    typedef boost::mpl::remove<
        boost::mpl::remove::type,
        MPQCDataFused::times_gather_flops>::type
        MPQCDataTimeVector_noflops_t;
    const std::string timesresult =
        writeTable()(
            results.Result_Time_fused, results.getMaxLevel());
    LOG(0, "Times table is \n" << timesresult);
    std::string filename;
    filename += FRAGMENTPREFIX + std::string("_Times.dat");
    writeToFile(filename, timesresult);
  }
}
bool appendToHomologyFile(
    const boost::filesystem::path &homology_file,
    const FragmentationResults &results,
    const std::string &KeySetFilename)
{
  /// read homology container (if present)
  HomologyContainer homology_container;
  if (boost::filesystem::exists(homology_file)) {
    std::ifstream returnstream(homology_file.string().c_str());
    if (returnstream.good()) {
      boost::archive::text_iarchive ia(returnstream);
      ia >> homology_container;
    } else {
      ELOG(2, "Failed to parse from " << homology_file.string() << ".");
    }
    returnstream.close();
  } else {
    LOG(2, "Could not open " << homology_file.string()
        << ", creating empty container.");
  }
  /// append all fragments to a HomologyContainer
  HomologyContainer::container_t values;
  const size_t FragmentCounter = results.Result_perIndexSet_Energy.size();
  // convert KeySetContainer to IndexSetContainer
  IndexSetContainer::ptr ForceContainer(new IndexSetContainer(results.getForceKeySet()));
  const IndexSetContainer::Container_t &Indices = results.getContainer();
  const IndexSetContainer::Container_t &ForceIndices = ForceContainer->getContainer();
  IndexSetContainer::Container_t::const_iterator iter = Indices.begin();
  IndexSetContainer::Container_t::const_iterator forceiter = ForceIndices.begin();
  /// go through all fragments
  for (;iter != Indices.end(); ++iter, ++forceiter) // go through each IndexSet
  {
    /// create new graph entry in HomologyContainer which is (key,value) type
    LOG(1, "INFO: Creating new graph with " << **forceiter << ".");
    HomologyGraph graph(**forceiter);
    LOG(2, "DEBUG: Created graph " << graph << ".");
    const IndexSet::ptr &index = *iter;
    HomologyContainer::value_t value;
    // obtain fragment as key
    std::map >::const_iterator fragmentiter
        = results.Result_perIndexSet_Fragment.find(index);
    ASSERT( fragmentiter != results.Result_perIndexSet_Fragment.end(),
        "appendToHomologyFile() - cannot find index "+toString(*index)
        +" in FragmentResults.");
    value.first = boost::fusion::at_key(fragmentiter->second.first);
    // obtain energy as value
    std::map >::const_iterator energyiter
        = results.Result_perIndexSet_Energy.find(index);
    ASSERT( energyiter != results.Result_perIndexSet_Energy.end(),
        "appendToHomologyFile() - cannot find index "+toString(*index)
        +" in FragmentResults.");
    // value.second = boost::fusion::at_key(energyiter->second.first); // values
    value.second = boost::fusion::at_key(energyiter->second.second); // contributions
    values.insert( std::make_pair( graph, value) );
  }
  homology_container.insert(values);
  LOG(1, "INFO: Listing all present atomic ids ...");
  std::stringstream output;
  for (World::AtomIterator iter = World::getInstance().getAtomIter();
      iter != World::getInstance().atomEnd(); ++iter)
    output << (*iter)->getId() << " ";
  LOG(1, "INFO: { " << output.str() << "} .");
  // for debugging: print container
  LOG(1, "INFO: Listing all present homologies ...");
  for (HomologyContainer::container_t::const_iterator iter =
      homology_container.begin(); iter != homology_container.end(); ++iter) {
    LOG(1, "INFO: graph " << iter->first << " has Fragment "
        << iter->second.first << " and associated energy " << iter->second.second << ".");
  }
  /// store homology container again
  std::ofstream outputstream(homology_file.string().c_str());
  if (outputstream.good()) { // check if opened
    boost::archive::text_oarchive oa(outputstream);
    oa << homology_container;
    if (outputstream.fail()) { // check if correctly written
      LOG(1, "Failed to write to file " << homology_file.string() << ".");
      return false;
    } else
      outputstream.close();
  } else {
    LOG(1, "Failed to open file " << homology_file.string()
        << " for writing.");
    return false;
  }
  return true;
}
Action::state_ptr FragmentationFragmentationAutomationAction::performCall() {
  boost::asio::io_service io_service;
  // TODO: Have io_service run in second thread and merge with current again eventually
  size_t Exitflag = 0;
  std::map fragmentData;
  {
    MPQCFragmentController mpqccontroller(io_service);
    mpqccontroller.setHost(params.host.get());
    mpqccontroller.setPort(params.port.get());
    mpqccontroller.setLevel(params.level.get());
    // Phase One: obtain ids
    std::vector< boost::filesystem::path > jobfiles = params.jobfiles.get();
    mpqccontroller.requestIds(jobfiles.size());
    // Phase Two: create and add MPQCJobs
    if (!mpqccontroller.addJobsFromFiles(params.executable.get().string(), jobfiles))
      return Action::failure;
    // Phase Three: calculate result
    mpqccontroller.waitforResults(jobfiles.size());
    mpqccontroller.getResults(fragmentData);
    Exitflag += mpqccontroller.getExitflag();
  }
#ifdef HAVE_VMG
  if (params.DoLongrange.get()) {
  if ( World::getInstance().getAllAtoms().size() == 0) {
    ELOG(1, "Please load the full molecule into the world before starting this action.");
    return Action::failure;
  }
  // obtain combined charge density
  FragmentationChargeDensity summedChargeDensity(
      fragmentData,
      params.path.get());
  const std::vector full_sample = summedChargeDensity.getFullSampledGrid();
  LOG(1, "INFO: There are " << fragmentData.size() << " short-range and "
      << full_sample.size() << " level-wise long-range jobs.");
  // Phase Four: obtain more ids
  std::map longrangeData;
  {
    VMGFragmentController vmgcontroller(io_service);
    vmgcontroller.setHost(params.host.get());
    vmgcontroller.setPort(params.port.get());
    const size_t NoJobs = fragmentData.size()+full_sample.size();
    vmgcontroller.requestIds(NoJobs);
    // Phase Five: create VMGJobs
    const size_t near_field_cells = params.near_field_cells.get();
    const size_t interpolation_degree = params.interpolation_degree.get();
    if (!vmgcontroller.createLongRangeJobs(
        fragmentData,
        full_sample,
        summedChargeDensity.getFragment(),
        near_field_cells,
        interpolation_degree))
      return Action::failure;
    // Phase Six: calculate result
    vmgcontroller.waitforResults(NoJobs);
    vmgcontroller.getResults(longrangeData);
    ASSERT( NoJobs == longrangeData.size(),
        "FragmentationFragmentationAutomationAction::performCall() - number of MPQCresults+"
        +toString(full_sample.size())+"="+toString(NoJobs)
        +" and VMGresults "+toString(longrangeData.size())+" don't match.");
    Exitflag += vmgcontroller.getExitflag();
  }
  // remove full solution corresponding to full_sample from map (must be highest ids), has to be treated extra
  std::map::iterator iter = longrangeData.end();
  for (size_t i=0;i::iterator remove_iter = iter;
  std::vector fullsolutionData;
  for (; iter != longrangeData.end(); ++iter)
    fullsolutionData.push_back(iter->second);
  longrangeData.erase(remove_iter, longrangeData.end());
  // Final phase: sum up and print result
  FragmentationResults results(
      fragmentData,
      longrangeData,
      params.path.get());
  results(
      fragmentData,
      longrangeData,
      fullsolutionData,
      full_sample);
  {
    LOG(1, "INFO: Parsing fragment files from " << params.path.get() << ".");
    printReceivedFullResults(results);
  }
  // append all keysets to homology file
  if (Exitflag == 0) {
    const boost::filesystem::path &homology_file = params.homology_file.get();
    if (homology_file.string() != "") {
      LOG(1, "INFO: Appending HomologyGraphs to file " << homology_file.string() << ".");
      if (!appendToHomologyFile(homology_file, results, params.path.get()))
        Exitflag = 1;
    }
  }
  std::map debugData;
  {
    if (!full_sample.empty()) {
      // create debug jobs for each level to print the summed-up potential to vtk files
      VMGDebugGridFragmentController debugcontroller(io_service);
      debugcontroller.setHost(params.host.get());
      debugcontroller.setPort(params.port.get());
      debugcontroller.requestIds(full_sample.size());
      if (!debugcontroller.createDebugJobs(full_sample))
        return Action::failure;
      debugcontroller.waitforResults(full_sample.size());
      debugcontroller.getResults(debugData);
      Exitflag += debugcontroller.getExitflag();
    }
  }
  }
#else
  // Final phase: print result
  {
    LOG(1, "INFO: Parsing fragment files from " << params.path.get() << ".");
    printReceivedMPQCResults(
        fragmentData,
        params.path.get(),
        getNoAtomsFromAdjacencyFile(params.path.get()));
  }
#endif
  return (Exitflag == 0) ? Action::success : Action::failure;
}
Action::state_ptr FragmentationFragmentationAutomationAction::performUndo(Action::state_ptr _state) {
  return Action::success;
}
Action::state_ptr FragmentationFragmentationAutomationAction::performRedo(Action::state_ptr _state){
  return Action::success;
}
bool FragmentationFragmentationAutomationAction::canUndo() {
  return false;
}
bool FragmentationFragmentationAutomationAction::shouldUndo() {
  return false;
}
/** =========== end of function ====================== */