/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2012 University of Bonn. All rights reserved.
 * Copyright (C)  2013 Frederik Heber. 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 .
 */
/*
 * controller_MPQCCommandJob.cpp
 *
 *  Created on: 01.06.2012
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
// boost asio needs specific operator new
#include 
#include 
#include 
#include "CodePatterns/MemDebug.hpp"
#include "controller_MPQCCommandJob.hpp"
#include 
#include 
#include 
#include 
#include "CodePatterns/Info.hpp"
#include "CodePatterns/Log.hpp"
#include "JobMarket/Controller/ControllerCommand.hpp"
#include "JobMarket/Controller/ControllerCommandRegistry.hpp"
#include "JobMarket/Controller/FragmentController.hpp"
#include "JobMarket/JobId.hpp"
#include "JobMarket/Jobs/FragmentJob.hpp"
#include "JobMarket/Results/FragmentResult.hpp"
#include "Fragmentation/EnergyMatrix.hpp"
#include "Fragmentation/ForceMatrix.hpp"
#include "Fragmentation/KeySetsContainer.hpp"
#include "Fragmentation/defs.hpp"
#include "Helpers/defs.hpp"
#include "Jobs/MPQCCommandJob.hpp"
#include "Fragmentation/Summation/Containers/MPQCData.hpp"
#include "LinearAlgebra/defs.hpp"
#include "ControllerOptions_MPQCCommandJob.hpp"
/** Creates a MPQCCommandJob with argument \a filename.
 *
 * @param jobs created job is added to this vector
 * @param command mpqc command to execute
 * @param filename filename being argument to job
 * @param nextid id for this job
 */
void parsejob(
    std::vector &jobs,
    const std::string &command,
    const std::string &filename,
    const JobId_t nextid)
{
  std::ifstream file;
  file.open(filename.c_str());
  ASSERT( file.good(), "parsejob() - file "+filename+" does not exist.");
  std::string output((std::istreambuf_iterator(file)),
      std::istreambuf_iterator());
  FragmentJob::ptr testJob( new MPQCCommandJob(output, nextid, command) );
  jobs.push_back(testJob);
  file.close();
  LOG(1, "INFO: Added MPQCCommandJob from file "+filename+".");
}
/** Print received results.
 *
 * @param results received results to print
 */
void printReceivedResults(const std::vector &results)
{
  for (std::vector::const_iterator iter = results.begin();
      iter != results.end(); ++iter)
    LOG(1, "RESULT: job #"+toString((*iter)->getId())+": "+toString((*iter)->result));
}
/** Print MPQCData from received results.
 *
 * @param results received results to extract MPQCData from
 * @param KeySetFilename filename with keysets to associate forces correctly
 * @param NoAtoms total number of atoms
 */
bool printReceivedMPQCResults(
    const std::vector &results,
    const std::string &KeySetFilename,
    size_t NoAtoms)
{
  EnergyMatrix Energy;
  EnergyMatrix EnergyFragments;
  ForceMatrix Force;
  ForceMatrix ForceFragments;
  KeySetsContainer KeySet;
  // align fragments
  std::map< JobId_t, size_t > MatrixNrLookup;
  size_t FragmentCounter = 0;
  {
    // bring ids in order ...
    typedef std::map< JobId_t, FragmentResult::ptr> IdResultMap_t;
    IdResultMap_t IdResultMap;
    for (std::vector::const_iterator iter = results.begin();
        iter != results.end(); ++iter) {
  #ifndef NDEBUG
      std::pair< IdResultMap_t::iterator, bool> inserter =
  #endif
      IdResultMap.insert( make_pair((*iter)->getId(), *iter) );
      ASSERT( inserter.second,
          "printReceivedMPQCResults() - two results have same id "
          +toString((*iter)->getId())+".");
    }
    // ... and fill lookup
    for(IdResultMap_t::const_iterator iter = IdResultMap.begin();
        iter != IdResultMap.end(); ++iter)
      MatrixNrLookup.insert( make_pair(iter->first, FragmentCounter++) );
  }
  LOG(1, "INFO: There are " << FragmentCounter << " fragments.");
  // extract results
  std::vector fragmentData(results.size());
  MPQCData combinedData;
  LOG(2, "DEBUG: Parsing now through " << results.size() << " results.");
  for (std::vector::const_iterator iter = results.begin();
      iter != results.end(); ++iter) {
    LOG(1, "RESULT: job #"+toString((*iter)->getId())+": "+toString((*iter)->result));
    MPQCData extractedData;
    std::stringstream inputstream((*iter)->result);
    LOG(2, "DEBUG: First 50 characters FragmentResult's string: "+(*iter)->result.substr(0, 50));
    boost::archive::text_iarchive ia(inputstream);
    ia >> extractedData;
    LOG(1, "INFO: extracted data of #" << (*iter)->getId() << " is " << extractedData << ".");
    // place results into EnergyMatrix ...
    {
      MatrixContainer::MatrixArray matrix;
      matrix.resize(1);
      matrix[0].resize(1, extractedData.energies.total);
      if (!Energy.AddMatrix(
          std::string("MPQCJob ")+toString((*iter)->getId()),
          matrix,
          MatrixNrLookup[(*iter)->getId()])) {
        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;igetId()),
          matrix,
          MatrixNrLookup[(*iter)->getId()])) {
        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;
  // combine all found data
  if (!Energy.InitialiseIndices()) return false;
  if (!Force.ParseIndices(KeySetFilename.c_str())) return false;
  {
    std::stringstream filename;
    filename << FRAGMENTPREFIX << KEYSETFILE;
    if (!KeySet.ParseKeySets(KeySetFilename.c_str(), filename.str(), Force.MatrixCounter)) return 1;
  }
  if (!KeySet.ParseManyBodyTerms()) return false;
  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 jobs;
  for (std::vector< std::string >::const_iterator iter = ControllerInfo.jobfiles.begin();
      iter != ControllerInfo.jobfiles.end(); ++iter) {
    const JobId_t next_id = controller.getAvailableId();
    const std::string &filename = *iter;
    LOG(1, "INFO: Creating MPQCCommandJob with filename '"
        +filename+"', and id "+toString(next_id)+".");
    parsejob(jobs, ControllerInfo.executable, filename, next_id);
  }
  controller.addJobs(jobs);
  controller.sendJobs(ControllerInfo.server, ControllerInfo.serverport);
}
inline std::vector getListOfCommands(const ControllerCommandRegistry &ControllerCommands)
{
  std::vector Commands;
  for (ControllerCommandRegistry::const_iterator iter = ControllerCommands.getBeginIter();
      iter != ControllerCommands.getEndIter(); ++iter)
    Commands.push_back(iter->first);
  return Commands;
}
ControllerOptions *controller_MPQCCommandJob::allocateControllerInfo()
{
  return new ControllerOptions_MPQCCommandJob();
}
void controller_MPQCCommandJob::addSpecificCommands(
    boost::function ®istrator,
    FragmentController &controller,
    ControllerOptions &ControllerInfo)
{
  ControllerOptions_MPQCCommandJob &CI =
      reinterpret_cast(ControllerInfo);
  registrator(new ControllerCommand("addjobs",
      boost::assign::list_of< ControllerCommand::commands_t >
        (boost::bind(&FragmentController::requestIds,
            boost::ref(controller), boost::cref(ControllerInfo.server), boost::cref(ControllerInfo.serverport),
            boost::bind(&std::vector::size, boost::cref(CI.jobfiles))))
        (boost::bind(&AddJobs, boost::ref(controller), boost::cref(CI)))
      ));
  registrator(new ControllerCommand("receiveresults",
      boost::assign::list_of< ControllerCommand::commands_t >
        (boost::bind(&FragmentController::receiveResults,
            boost::ref(controller), boost::cref(ControllerInfo.server), boost::cref(ControllerInfo.serverport)))
        (boost::bind(&printReceivedResults,
            boost::bind(&FragmentController::getReceivedResults, boost::ref(controller))))
      ));
  registrator(new ControllerCommand("receivempqc",
      boost::assign::list_of< ControllerCommand::commands_t >
        (boost::bind(&FragmentController_JobIdProxy::setJobids,
            boost::ref(controller), boost::cref(CI.ids)))
        (boost::bind(&FragmentController::receiveResults,
            boost::ref(controller), boost::cref(ControllerInfo.server), boost::cref(ControllerInfo.serverport)))
        (boost::bind(&printReceivedMPQCResults,
            boost::bind(&FragmentController::getReceivedResults, boost::ref(controller)),
            boost::cref(CI.fragmentpath),
            boost::bind(&getNoAtomsFromAdjacencyFile, boost::cref(CI.fragmentpath))))
  ));
}
void controller_MPQCCommandJob::addSpecificOptions(
    boost::program_options::options_description_easy_init option)
{
  option
    ("executable", boost::program_options::value< std::string >(), "executable for commands 'createjobs'")
    ("fragment-path", boost::program_options::value< std::string >(), "path to fragment files for 'receivempqc'")
    ("jobfiles", boost::program_options::value< std::vector< std::string > >()->multitoken(), "list of files as single argument toexecutable for 'addjobs'")
    ("ids", boost::program_options::value< std::vector< JobId_t > >()->multitoken(), "list of jobids to request from server for 'receivempqc'")
    ;
}
int controller_MPQCCommandJob::addOtherParsings(
    ControllerOptions &ControllerInfo,
    boost::program_options::variables_map &vm)
{
  ControllerOptions_MPQCCommandJob &CI =
      reinterpret_cast(ControllerInfo);
  int status = 0;
  status = CI.parseExecutable(vm);
  if (status) return status;
  status = CI.parseFragmentpath(vm);
  if (status) return status;
  status = CI.parseJobfiles(vm);
  if (status) return status;
  status = CI.parseIds(vm);
  return status;
}