/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-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 .
 */
/*
 * 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"
#ifdef HAVE_JOBMARKET
#include "JobMarket/Jobs/FragmentJob.hpp"
#else
#include "Jobs/JobMarket/FragmentJob.hpp"
#endif
#include "Fragmentation/Automation/FragmentJobQueue.hpp"
#ifdef HAVE_JOBMARKET
#include "Fragmentation/Automation/MPQCFragmentController.hpp"
#else
#include "Fragmentation/Automation/MPQCCommandFragmentController.hpp"
#endif
#include "Fragmentation/Summation/Containers/FragmentationChargeDensity.hpp"
#include "Fragmentation/Summation/Containers/FragmentationLongRangeResults.hpp"
#include "Fragmentation/Summation/Containers/FragmentationResultContainer.hpp"
#include "Fragmentation/Summation/Containers/FragmentationShortRangeResults.hpp"
#include "Fragmentation/Summation/Containers/MPQCData.hpp"
#include "Fragmentation/KeySetsContainer.hpp"
#if defined(HAVE_JOBMARKET) && defined(HAVE_VMG)
#include "Fragmentation/Automation/VMGDebugGridFragmentController.hpp"
#include "Fragmentation/Automation/VMGFragmentController.hpp"
#include "Fragmentation/Summation/Containers/VMGData.hpp"
#include "Fragmentation/Summation/Containers/VMGDataFused.hpp"
#include "Fragmentation/Summation/Containers/VMGDataMap.hpp"
#include "Fragmentation/Summation/Containers/VMGData_printKeyNames.hpp"
#endif
#include "World.hpp"
#include 
#include 
#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;
}
static void updateSteps(Process &p, const size_t step, const size_t total)
{
  LOG(1, "There are " << step << " steps out of " << total << " done.");
  p.setCurrStep(step);
}
ActionState::ptr FragmentationFragmentationAutomationAction::performCall() {
  boost::asio::io_service io_service;
  // TODO: Have io_service run in second thread and merge with current again eventually
  FragmentationResultContainer &container =
      FragmentationResultContainer::getInstance();
  const KeySetsContainer& keysets = FragmentJobQueue::getInstance().getKeySets();
  const KeySetsContainer& forcekeysets = FragmentJobQueue::getInstance().getFullKeySets();
  size_t Exitflag = 0;
  std::map shortrangedata;
#ifdef HAVE_JOBMARKET
  {
    const size_t NumberJobs = FragmentJobQueue::getInstance().size();
    MPQCFragmentController mpqccontroller(io_service);
    mpqccontroller.setHost(params.host.get());
    mpqccontroller.setPort(params.port.get());
    // Phase One: obtain ids
    mpqccontroller.requestIds(NumberJobs);
    if (mpqccontroller.getExitflag() != 0)
      return Action::failure;
    // Phase Two: add MPQCJobs and send
    const bool AddJobsStatus = mpqccontroller.addJobsFromQueue(
        params.DoLongrange.get() ? MPQCData::DoSampleDensity : MPQCData::DontSampleDensity,
        params.DoValenceOnly.get() ? MPQCData::DoSampleValenceOnly : MPQCData::DontSampleValenceOnly
        );
    if (AddJobsStatus)
      LOG(1, "INFO: Added jobs from FragmentJobsQueue.");
    else {
      ELOG(1, "Adding jobs failed.");
      return Action::failure;
    }
    mpqccontroller.run();
    // Phase Three: calculate result
    setMaxSteps(NumberJobs);
    mpqccontroller.setUpdateHandler(
        boost::bind(&updateSteps, boost::ref(*this), _1, _2)
        );
    start();
    boost::thread wait_thread(
        boost::bind(&MPQCFragmentController::waitforResults, boost::ref(mpqccontroller), boost::cref(NumberJobs))
    );
    wait_thread.join();
    stop();
    if (mpqccontroller.getExitflag() != 0)
      return Action::failure;
    mpqccontroller.getResults(shortrangedata);
    if (mpqccontroller.getExitflag() != 0)
      return Action::failure;
    Exitflag += mpqccontroller.getExitflag();
  }
#else
  {
    const size_t NumberJobs = FragmentJobQueue::getInstance().size();
    MPQCCommandFragmentController mpqccontroller;
    // Phase One: obtain ids: not needed, we have infinite pool
    // Phase Two: add MPQCJobs and send
    const size_t NoJobs = mpqccontroller.addJobsFromQueue(
        params.DoLongrange.get() ? MPQCData::DoSampleDensity : MPQCData::DontSampleDensity,
        params.DoValenceOnly.get() ? MPQCData::DoSampleValenceOnly : MPQCData::DontSampleValenceOnly,
        params.executable.get().string()
        );
    if (NoJobs != NumberJobs) {
      ELOG(1, "Not all jobs were added succesfully.");
      return Action::failure;
    }
    LOG(1, "INFO: Added " << NoJobs << " from FragmentJobsQueue.");
    // prepare process
    setMaxSteps(NumberJobs);
    mpqccontroller.setUpdateHandler(
        boost::bind(&updateSteps, boost::ref(*this), _1, _2)
        );
    start();
    mpqccontroller.run();
    stop();
    if (mpqccontroller.getExitflag() != 0)
      return Action::failure;
    // get back the results and place them in shortrangedata
    mpqccontroller.getResults(shortrangedata);
    ASSERT( shortrangedata.size() == NumberJobs,
        "FragmentationFragmentationAutomationAction::performCall() - number of converted results "
        +toString(shortrangedata.size())+" and number of jobs "+toString(NumberJobs)+ " differ.");
    if (mpqccontroller.getExitflag() != 0)
      return Action::failure;
    Exitflag += mpqccontroller.getExitflag();
  }
#endif
#if defined(HAVE_JOBMARKET) && defined(HAVE_VMG)
  if (params.DoLongrange.get()) {
  if ( const_cast(World::getInstance()).getAllAtoms().size() == 0) {
    STATUS("Please load the full molecule into the world before starting this action.");
    return Action::failure;
  }
  // obtain combined charge density
  FragmentationChargeDensity summedChargeDensity(
      shortrangedata,
      FragmentJobQueue::getInstance().getKeySets());
  const std::vector full_sample = summedChargeDensity.getFullSampledGrid();
  LOG(1, "INFO: There are " << shortrangedata.size() << " short-range and "
      << full_sample.size() << " level-wise long-range jobs.");
  // check boundary conditions
  const BoundaryConditions::Conditions_t &conditions =
      World::getInstance().getDomain().getConditions();
  const bool OpenBoundaryConditions =
      !((conditions[0] == BoundaryConditions::Wrap) &&
      (conditions[1] == BoundaryConditions::Wrap) &&
      (conditions[2] == BoundaryConditions::Wrap));
  LOG(1, std::string("INFO: Using ") 
      << (OpenBoundaryConditions ? "open" : "periodic")
      << " boundary conditions.");
  // 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 = shortrangedata.size()+full_sample.size();
    vmgcontroller.requestIds(2*NoJobs);
    if (vmgcontroller.getExitflag() != 0)
      return Action::failure;
    // Phase Five a: create VMGJobs for electronic charge distribution
    const size_t near_field_cells = params.near_field_cells.get();
    const size_t interpolation_degree = params.interpolation_degree.get();
    if (!vmgcontroller.createLongRangeJobs(
        shortrangedata,
        full_sample,
        near_field_cells,
        interpolation_degree,
        VMGFragmentController::DontSampleParticles,
        VMGFragmentController::DoTreatGrid,
        params.DoValenceOnly.get() ? MPQCData::DoSampleValenceOnly : MPQCData::DontSampleValenceOnly,
        params.DoPrintDebug.get(),
        OpenBoundaryConditions,
        params.DoSmearCharges.get())) {
      STATUS("Could not create long-range jobs for electronic charge distribution.");
      return Action::failure;
    }
    // Phase Six a: calculate result
    vmgcontroller.waitforResults(NoJobs);
    if (vmgcontroller.getExitflag() != 0)
      return Action::failure;
    vmgcontroller.getResults(longrangedata);
    ASSERT( NoJobs == longrangedata.size(),
        "FragmentationFragmentationAutomationAction::performCall() - number of MPQCresults+"
        +toString(full_sample.size())+"="+toString(NoJobs)
        +" and first VMGresults "+toString(longrangedata.size())+" don't match.");
    Exitflag += vmgcontroller.getExitflag();
    {
      std::map longrangedata_both;
      // Phase Five b: create VMGJobs for nuclei charge distributions
      const size_t near_field_cells = params.near_field_cells.get();
      const size_t interpolation_degree = params.interpolation_degree.get();
      if (!vmgcontroller.createLongRangeJobs(
          shortrangedata,
          full_sample,
          near_field_cells,
          interpolation_degree,
          VMGFragmentController::DoSampleParticles,
          VMGFragmentController::DoTreatGrid,
          params.DoValenceOnly.get() ? MPQCData::DoSampleValenceOnly : MPQCData::DontSampleValenceOnly,
          params.DoPrintDebug.get(),
          OpenBoundaryConditions,
          params.DoSmearCharges.get())) {
        STATUS("Could not create long-range jobs for nuclei charge distribution.");
        return Action::failure;
      }
      // Phase Six b: calculate result
      vmgcontroller.waitforResults(NoJobs);
      if (vmgcontroller.getExitflag() != 0)
        return Action::failure;
      vmgcontroller.getResults(longrangedata_both);
      ASSERT( NoJobs == longrangedata_both.size(),
          "FragmentationFragmentationAutomationAction::performCall() - number of MPQCresults+"
          +toString(full_sample.size())+"="+toString(NoJobs)
          +" and second VMGresults "+toString(longrangedata_both.size())+" don't match.");
      if (vmgcontroller.getExitflag() != 0)
        return Action::failure;
      Exitflag += vmgcontroller.getExitflag();
      // go through either data and replace nuclei_long with contribution from both
      ASSERT( longrangedata.size() == longrangedata_both.size(),
          "FragmentationFragmentationAutomationAction::performCall() - longrange results have different sizes.");
      std::map::iterator destiter = longrangedata.begin();
      std::map::const_iterator srciter = longrangedata_both.begin();
      for (;destiter != longrangedata.end(); ++srciter, ++destiter) {
        destiter->second.both_sampled_potential = srciter->second.sampled_potential;
        destiter->second.nuclei_long = srciter->second.nuclei_long;
        destiter->second.forces = srciter->second.forces;
        destiter->second.hasForces = srciter->second.hasForces;
      }
    }
  }
  if (params.DoPrintDebug.get()) {
    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, OpenBoundaryConditions)) {
          STATUS("Could not create debug jobs.");
          return Action::failure;
        }
        debugcontroller.waitforResults(full_sample.size());
        debugcontroller.getResults(debugData);
        Exitflag += debugcontroller.getExitflag();
      }
    }
  }
  container.addFullResults(keysets, forcekeysets, shortrangedata, longrangedata);
  } else {
    container.addShortRangeResults(keysets, forcekeysets, shortrangedata);
  }
#else
  container.addShortRangeResults(keysets, forcekeysets, shortrangedata);
#endif
  // now clear all present jobs as we are done
  FragmentJobQueue::getInstance().clear();
  // if file is given, advise results container to store to file
  if (!params.resultsfile.get().empty()) {
    boost::filesystem::path resultsfile = params.resultsfile.get();
    std::ofstream returnstream(resultsfile.string().c_str());
    if (returnstream.good()) {
      boost::archive::text_oarchive oa(returnstream);
      oa << container;
    }
    Exitflag += (int)(!returnstream.good());
    returnstream.close();
  }
  if (Exitflag != 0)
    STATUS("Controller has returned failure.");
  return (Exitflag == 0) ? Action::success : Action::failure;
}
ActionState::ptr FragmentationFragmentationAutomationAction::performUndo(ActionState::ptr _state) {
  return Action::success;
}
ActionState::ptr FragmentationFragmentationAutomationAction::performRedo(ActionState::ptr _state){
  return Action::success;
}
bool FragmentationFragmentationAutomationAction::canUndo() {
  return false;
}
bool FragmentationFragmentationAutomationAction::shouldUndo() {
  return false;
}
/** =========== end of function ====================== */