#include "KinetoThermostat.h"
#include "ATC_Coupling.h"
#include "ATC_Error.h"
#include "PrescribedDataManager.h"
#include "ElasticTimeIntegrator.h"
#include "ThermalTimeIntegrator.h"
#include "TransferOperator.h"

using namespace std;

namespace ATC {

  //--------------------------------------------------------
  //--------------------------------------------------------
  //  Class KinetoThermostat
  //--------------------------------------------------------
  //--------------------------------------------------------

  //--------------------------------------------------------
  //  Constructor
  //--------------------------------------------------------
  KinetoThermostat::KinetoThermostat(ATC_Coupling * atc,
                                     const string & regulatorPrefix) :
    AtomicRegulator(atc,regulatorPrefix),
    kinetostat_(atc,"Momentum"),
    thermostat_(atc,"Energy")
  {
    // nothing to do
  }

  //--------------------------------------------------------
  //  modify:
  //    parses and adjusts thermostat state based on
  //    user input, in the style of LAMMPS user input
  //--------------------------------------------------------
  bool KinetoThermostat::modify(int narg, char **arg)
  {
    bool foundMatch = false;
    return foundMatch;
  }

  //--------------------------------------------------------
  //  reset_lambda_contribution:
  //    resets the thermostat generated power to a
  //    prescribed value
  //--------------------------------------------------------
  void KinetoThermostat::reset_lambda_contribution(const DENS_MAT & target,
                                                   const FieldName field)
  {
    //TEMP_JAT
    if (field==VELOCITY) {
      kinetostat_.reset_lambda_contribution(target);
    }
    else if (field == TEMPERATURE) {
      thermostat_.reset_lambda_contribution(target);
    }
    else {
      throw ATC_Error("KinetoThermostat::reset_lambda_contribution - invalid field given");
    }
  }

  //--------------------------------------------------------
  //  construct_methods:
  //    instantiations desired regulator method(s)
  
  //    dependence, but in general there is also a
  //    time integrator dependence.  In general the 
  //    precedence order is:
  //    time filter -> time integrator -> thermostat
  //    In the future this may need to be added if
  //    different types of time integrators can be
  //    specified.
  //--------------------------------------------------------
  void KinetoThermostat::construct_methods()
  {
// #ifdef WIP_JAT
//     // get data associated with stages 1 & 2 of ATC_Method::initialize
//     AtomicRegulator::construct_methods();

//     if (atc_->reset_methods()) {
//       // eliminate existing methods
//       delete_method();

//       // update time filter
//       TimeIntegrator::TimeIntegrationType myIntegrationType = (atc_->time_integrator(TEMPERATURE))->time_integration_type();
//       TimeFilterManager * timeFilterManager = atc_->time_filter_manager();
//       if (timeFilterManager->need_reset() ) {
//         if (myIntegrationType == TimeIntegrator::GEAR)
//          timeFilter_ = timeFilterManager->construct(TimeFilterManager::EXPLICIT);
//         else if (myIntegrationType == TimeIntegrator::FRACTIONAL_STEP)
//          timeFilter_ = timeFilterManager->construct(TimeFilterManager::EXPLICIT_IMPLICIT);
//       }
      
//       if (timeFilterManager->filter_dynamics()) {
//         switch (regulatorTarget_) {
//         case NONE: {
//           regulatorMethod_ = new RegulatorMethod(this);
//           break;
//         }
//         case FIELD: { // error check, rescale and filtering not supported together
//           throw ATC_Error("Cannot use rescaling thermostat with time filtering");
//           break;
//         }
//         case DYNAMICS: {
//           switch (couplingMode_) {
//           case FIXED: {
//             if (use_lumped_lambda_solve()) {
//               throw ATC_Error("Thermostat:construct_methods - lumped lambda solve cannot be used with Hoover thermostats");
//             }
//             if (myIntegrationType == TimeIntegrator::FRACTIONAL_STEP) {
//               if (md_flux_nodes(TEMPERATURE)) {
//                 if (!md_fixed_nodes(TEMPERATURE) && (boundaryIntegrationType_ == NO_QUADRATURE)) {
//                   // there are fluxes but no fixed or coupled nodes
//                   regulatorMethod_ = new ThermostatFluxFiltered(this);
//                 }
//                 else {
//                   // there are both fixed and flux nodes
//                   regulatorMethod_ = new ThermostatFluxFixedFiltered(this);
//                 }
//               }
//               else {
//                 // there are only fixed nodes
//                 regulatorMethod_ = new ThermostatFixedFiltered(this);
//               }
//             }
//             else {
//               regulatorMethod_ = new ThermostatHooverVerletFiltered(this);
//             }
//             break;
//           }
//           case FLUX: {
//             if (myIntegrationType == TimeIntegrator::FRACTIONAL_STEP) {
//               if (use_lumped_lambda_solve()) {
//                 throw ATC_Error("Thermostat:construct_methods - lumped lambda solve has been depricated for fractional step thermostats");
//               }
//               if (md_fixed_nodes(TEMPERATURE)) {
//                 if (!md_flux_nodes(TEMPERATURE) && (boundaryIntegrationType_ == NO_QUADRATURE)) {
//                 // there are fixed nodes but no fluxes
//                 regulatorMethod_ = new ThermostatFixedFiltered(this);
//                 }
//                 else {
//                   // there are both fixed and flux nodes
//                   regulatorMethod_ = new ThermostatFluxFixedFiltered(this);
//                 }
//               }
//               else {
//                 // there are only flux nodes
//                 regulatorMethod_ = new ThermostatFluxFiltered(this);
//               }
//             }
//             else {
//               if (use_localized_lambda()) {
//                 if (!((atc_->prescribed_data_manager())->no_fluxes(TEMPERATURE)) &&
//                     atc_->boundary_integration_type() != NO_QUADRATURE) {
//                   throw ATC_Error("Cannot use flux coupling with localized lambda");
//                 }
//               }
//               regulatorMethod_ = new ThermostatPowerVerletFiltered(this);
//             }
//             break;
//           }
//           default:
//             throw ATC_Error("Unknown coupling mode in Thermostat::initialize");
//           }
//           break;
//         }
//         default:
//           throw ATC_Error("Unknown thermostat type in Thermostat::initialize");
//         }
//       }
//       else {
//         switch (regulatorTarget_) {
//         case NONE: {
//           regulatorMethod_ = new RegulatorMethod(this);
//           break;
//         }
//         case FIELD: {
//           if (atc_->temperature_def()==KINETIC)
//             regulatorMethod_ = new ThermostatRescale(this);
//           else if (atc_->temperature_def()==TOTAL)
//             regulatorMethod_ = new ThermostatRescaleMixedKePe(this);
//           else
//             throw ATC_Error("Unknown temperature definition");
//           break;
//         }
//         case DYNAMICS: {
//           switch (couplingMode_) {
//           case FIXED: {
//             if (use_lumped_lambda_solve()) {
//               throw ATC_Error("Thermostat:construct_methods - lumped lambda solve cannot be used with Hoover thermostats");
//             }
//             if (myIntegrationType == TimeIntegrator::FRACTIONAL_STEP) {
//               if (md_flux_nodes(TEMPERATURE)) {
//                 if (!md_fixed_nodes(TEMPERATURE) && (boundaryIntegrationType_ == NO_QUADRATURE)) {
//                   // there are fluxes but no fixed or coupled nodes
//                   regulatorMethod_ = new ThermostatFlux(this);
//                 }
//                 else {
//                   // there are both fixed and flux nodes
//                   regulatorMethod_ = new ThermostatFluxFixed(this);
//                 }
//               }
//               else {
//                 // there are only fixed nodes
//                 regulatorMethod_ = new ThermostatFixed(this);
//               }
//             }
//             else {
//               regulatorMethod_ = new ThermostatHooverVerlet(this);
//             }
//             break;
//           }
//           case FLUX: {
//             if (myIntegrationType == TimeIntegrator::FRACTIONAL_STEP) {
//               if (use_lumped_lambda_solve()) {
//                 throw ATC_Error("Thermostat:construct_methods - lumped lambda solve has been depricated for fractional step thermostats");
//               }
//               if (md_fixed_nodes(TEMPERATURE)) {
//                 if (!md_flux_nodes(TEMPERATURE) && (boundaryIntegrationType_ == NO_QUADRATURE)) {
//                   // there are fixed nodes but no fluxes
//                   regulatorMethod_ = new ThermostatFixed(this);
//                 }
//                 else {
//                   // there are both fixed and flux nodes
//                   regulatorMethod_ = new ThermostatFluxFixed(this);
//                 }
//               }
//               else {
//                 // there are only flux nodes
//                 regulatorMethod_ = new ThermostatFlux(this);
//               }
//             }
//             else {
//               if (use_localized_lambda()) {
//                 if (!((atc_->prescribed_data_manager())->no_fluxes(TEMPERATURE)) &&
//                     atc_->boundary_integration_type() != NO_QUADRATURE) {
//                   throw ATC_Error("Cannot use flux coupling with localized lambda");
//                 }
//               }
//               regulatorMethod_ = new ThermostatPowerVerlet(this);
//             }
//             break;
//           }
//           default:
//             throw ATC_Error("Unknown coupling mode in Thermostat::initialize");
//           }
//           break;
//         }
//         default:
//           throw ATC_Error("Unknown thermostat target in Thermostat::initialize");
//         }
//       }
      
//       AtomicRegulator::reset_method();
//     }
//     else {
//       set_all_data_to_used();
//     }
// #endif
    //TEMP_JAT
    kinetostat_.construct_methods();
    thermostat_.construct_methods();
  }

  //TEMP_JAT all functions which have explicit thermo and kinetostats need to be removed
    //--------------------------------------------------------
  //  reset_nlocal:
  //    resizes lambda force if necessary
  //--------------------------------------------------------
  void KinetoThermostat::reset_nlocal()
  {
    kinetostat_.reset_nlocal();
    thermostat_.reset_nlocal();
  }
  //--------------------------------------------------------
  //  reset_atom_materials:
  //    resets the localized atom to material map
  //--------------------------------------------------------
  void KinetoThermostat::reset_atom_materials(const Array<int> & elementToMaterialMap,
                                             const MatrixDependencyManager<DenseMatrix, int> * atomElement)
  {
    kinetostat_.reset_atom_materials(elementToMaterialMap,
                                     atomElement);
    thermostat_.reset_atom_materials(elementToMaterialMap,
                                     atomElement);
  }
  //--------------------------------------------------------
  //  construct_transfers:
  //    pass through to appropriate transfer constuctors
  //--------------------------------------------------------
  void KinetoThermostat::construct_transfers()
  {
    kinetostat_.construct_transfers();
    thermostat_.construct_transfers();
  }
  //--------------------------------------------------------
  //  initialize:
  //    sets up methods before a run
  //--------------------------------------------------------
  void KinetoThermostat::initialize()
  {
    kinetostat_.initialize();
    thermostat_.initialize();
    needReset_ = false;
  }
  //--------------------------------------------------------
  //  output:
  //    pass through to appropriate output methods
  //--------------------------------------------------------
  void KinetoThermostat::output(OUTPUT_LIST & outputData) const
  {
    kinetostat_.output(outputData);
    thermostat_.output(outputData);
  }
  //--------------------------------------------------------
  //  finish:
  //    pass through to appropriate end-of-run methods
  //--------------------------------------------------------
  void KinetoThermostat::finish()
  {
    kinetostat_.finish();
    thermostat_.finish();
  }
    //--------------------------------------------------
  // pack_fields
  //   bundle all allocated field matrices into a list
  //   for output needs
  //--------------------------------------------------
  void KinetoThermostat::pack_fields(RESTART_LIST & data)
  {
    kinetostat_.pack_fields(data);
    thermostat_.pack_fields(data);
  }
  //--------------------------------------------------------
  //  compute_boundary_flux:
  //    computes the boundary flux to be consistent with
  //    the controller
  //--------------------------------------------------------
  void KinetoThermostat::compute_boundary_flux(FIELDS & fields)
  {
    kinetostat_.compute_boundary_flux(fields);
    thermostat_.compute_boundary_flux(fields);
  }
  //--------------------------------------------------------
  //  add_to_rhs:
  //    adds any controller contributions to the FE rhs 
  //--------------------------------------------------------
  void KinetoThermostat::add_to_rhs(FIELDS & rhs)
  {
    thermostat_.add_to_rhs(rhs);
    kinetostat_.add_to_rhs(rhs);
  }
  //--------------------------------------------------------
  //  apply_pre_predictor:
  //    applies the controller in the pre-predictor
  //    phase of the time integrator  
  //--------------------------------------------------------
  void KinetoThermostat::apply_pre_predictor(double dt, int timeStep)
  {
    thermostat_.apply_pre_predictor(dt,timeStep);
    kinetostat_.apply_pre_predictor(dt,timeStep);
  }
  //--------------------------------------------------------
  //  apply_mid_predictor:
  //    applies the controller in the mid-predictor
  //    phase of the time integrator  
  //--------------------------------------------------------
  void KinetoThermostat::apply_mid_predictor(double dt, int timeStep)
  {
    thermostat_.apply_mid_predictor(dt,timeStep);
    kinetostat_.apply_mid_predictor(dt,timeStep);
  }
  //--------------------------------------------------------
  //  apply_post_predictor:
  //    applies the controller in the post-predictor
  //    phase of the time integrator  
  //--------------------------------------------------------
  void KinetoThermostat::apply_post_predictor(double dt, int timeStep)
  {
    thermostat_.apply_post_predictor(dt,timeStep);
    kinetostat_.apply_post_predictor(dt,timeStep);
  }
  //--------------------------------------------------------
  //  apply_pre_corrector:
  //    applies the controller in the pre-corrector phase
  //    of the time integrator
  //--------------------------------------------------------
  void KinetoThermostat::apply_pre_corrector(double dt, int timeStep)
  {
    thermostat_.apply_pre_corrector(dt,timeStep);
    kinetostat_.apply_pre_corrector(dt,timeStep);
  }
  //--------------------------------------------------------
  //  apply_post_corrector:
  //    applies the controller in the post-corrector phase
  //    of the time integrator
  //--------------------------------------------------------
  void KinetoThermostat::apply_post_corrector(double dt, int timeStep)
  {
    thermostat_.apply_post_corrector(dt,timeStep);
    kinetostat_.apply_post_corrector(dt,timeStep);
  }
  //--------------------------------------------------------
  //  pre_exchange
  //--------------------------------------------------------
  void KinetoThermostat::pre_exchange()
  {
    thermostat_.pre_exchange();
    kinetostat_.pre_exchange();
  }
  //--------------------------------------------------------
  //  pre_force
  //--------------------------------------------------------
  void KinetoThermostat::pre_force()
  {
    thermostat_.pre_force();
    kinetostat_.pre_force();
  }
  //--------------------------------------------------------
  // coupling_mode
  //-------------------------------------------------------
  AtomicRegulator::RegulatorCouplingType KinetoThermostat::coupling_mode(const FieldName field) const
  {
    //TEMP_JAT
    if (field==VELOCITY) {
      return kinetostat_.coupling_mode();
    }
    else if (field == TEMPERATURE) {
      return thermostat_.coupling_mode();
    }
    else {
      throw ATC_Error("KinetoThermostat::coupling_mode - invalid field given");
    }
  }

};
