// -*- C++ -*-
//
// asap_emt_driver.cpp: OpenKIM Model Driver interface for EMT.
//
// Copyright (C) 2012-2013 Jakob Schiotz and the Department of Physics,
// Technical University of Denmark.  Email: schiotz@fysik.dtu.dk
//
// This file is part of Asap version 3.
// Asap is released under the GNU Lesser Public License (LGPL) version 3.
// However, the parts of Asap distributed within the OpenKIM project
// (including this file) are also released under the Common Development
// and Distribution License (CDDL) version 1.0.
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// version 3 as published by the Free Software Foundation.  Permission
// to use other versions of the GNU Lesser General Public License may
// granted by Jakob Schiotz or the head of department of the
// Department of Physics, Technical University of Denmark, as
// described in section 14 of the GNU General Public License.
//
// This program 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
// and the GNU Lesser Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.

#include "KIM_API_C.h"
#include "KIM_API_status.h"

#include "asap_kim_api.h"
#include "asap_emt_driver.h"
#include "KimParameterProvider.h"

#include <stdlib.h>

static int asap_emt_driver_initmodel(AsapKimPotential *model);

KimEMT::KimEMT(AsapKimPotential *owner, EMTParameterProvider *provider) : EMT(NULL)
{
  this->owner = owner;
  nblist = NULL;
  nblist_obj = NULL;
  provider_obj = NULL;  // Bypass EMT's Python-based memory management.
  this->provider = provider;
}

KimEMT::~KimEMT()
{
  assert(provider_obj == NULL);
  delete provider;
}

void KimEMT::CreateNeighborList()
{
  PyAsap_NeighborLocatorObject *nbl = owner->CreateNeighborList(atoms, rNbCut, driftfactor);
  nblist = nbl->cobj;
  nblist_obj = (PyObject *) nbl;
  nblist->UpdateNeighborList();
}

/* Destruction function */

static int asap_emt_destroy(void *km)
{
  intptr_t* pkim = *((intptr_t**) km);
  int ier;
  AsapKimPotential *model = (AsapKimPotential *) KIM_API_get_model_buffer(pkim, &ier);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_get_model_buffer", ier);
      return ier;
    }
  delete model;

  // Remove the model pointer from the model buffer (if left, the API will
  // release it with free instead of delete).
  KIM_API_set_model_buffer(pkim, NULL, &ier);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_set_model_buffer", ier);
      return ier;
    }
  return KIM_STATUS_OK;
}

/* Reinit function */

static int asap_emt_reinit(void *km)
{
  intptr_t* pkim = *((intptr_t**) km);
  int ier;
  // Remove the old model
  AsapKimPotential *model = (AsapKimPotential *) KIM_API_get_model_buffer(pkim, &ier);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_get_model_buffer", ier);
      return ier;
    }

  // Add the new model
  AsapKimPotential *newmodel = new AsapKimPotential(pkim, model->paramfile_names, model->nmstrlen,
      model->numparamfiles, true);
  delete model;
  return asap_emt_driver_initmodel(newmodel);
}

/* Initialization function */
extern "C" int model_driver_init(void *km, char* paramfile_names, int* nmstrlen, int* numparamfiles)
{
  intptr_t* pkim = *((intptr_t**) km);
  int ier;

  if(*numparamfiles !=1)
  {
     ier = KIM_STATUS_FAIL;
     KIM_API_report_error(__LINE__, __FILE__, "Wrong number of parameter files", ier);
     return ier;
  }

  /* store pointer to compute function in KIM object */
  ier = KIM_API_set_method(pkim, "compute", 1, (func_ptr) &AsapKimPotential::compute_static);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_set_method", ier);
      return ier;
    }

  /* store pointer to destroy function in KIM object */
  ier = KIM_API_set_method(pkim, "destroy", 1, (func_ptr) &asap_emt_destroy);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_set_method", ier);
      return ier;
    }

  /* store pointer to reinit function in KIM object */
  ier = KIM_API_set_method(pkim, "reinit", 1, (func_ptr) &asap_emt_reinit);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_set_method", ier);
      return ier;
    }

  // Create the model
  AsapKimPotential *model = new AsapKimPotential(pkim, paramfile_names,
      *nmstrlen, *numparamfiles, true);
  return asap_emt_driver_initmodel(model);
}

static int asap_emt_driver_initmodel(AsapKimPotential *model)
{
  intptr_t *pkim = model->pkim;
  const char *paramfile_names = model->paramfile_names;
  double *model_cutoff;
  int ier;

  KimParameterProvider *provider = NULL;
  try
  {
      provider = new KimParameterProvider(paramfile_names, pkim);
  }
  catch (AsapError &e)
  {
      std::cerr << e.GetMessage() << std::endl;
      return KIM_STATUS_FAIL;
  }
  model->potential = new KimEMT(model, provider);

  // Store the model in the model buffer
  KIM_API_set_model_buffer(pkim, (void*) model, &ier);
  if (KIM_STATUS_OK > ier)
    {
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_set_model_buffer", ier);
      return ier;
    }

  /* store maximal model cutoff in KIM object */
  model_cutoff = (double*) KIM_API_get_data(pkim, "cutoff", &ier);
  if (KIM_STATUS_OK > ier){
      KIM_API_report_error(__LINE__, __FILE__, "KIM_API_get_data", ier);
      return ier;
  }
  provider->CalcGammaEtc();
  *model_cutoff = provider->GetCutoffDistance();
  //std::cerr << "Setting cutoff to " << *model_cutoff << std::endl;

  return KIM_STATUS_OK;
}