/*
 *    This file is part of CasADi.
 *
 *    CasADi -- A symbolic framework for dynamic optimization.
 *    Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
 *                            KU Leuven. All rights reserved.
 *    Copyright (C) 2011-2014 Greg Horn
 *
 *    CasADi is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 *
 *    CasADi 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with CasADi; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "callback_internal.hpp"

namespace casadi {

#define TRY_CALL(FCN, OBJ, ...) \
try { \
  casadi_assert((OBJ)!=0, "Callback object has been deleted"); \
  return (OBJ)->FCN(__VA_ARGS__);\
} catch (std::exception& ex) { \
  casadi_error("Error calling \"" CASADI_STR(FCN) "\" for object " \
                + name_ + ":\n" + std::string(ex.what())); \
}

CallbackInternal::
CallbackInternal(const std::string& name, Callback *self)
  : FunctionInternal(name), self_(self), has_eval_buffer_(false) {
}

CallbackInternal::~CallbackInternal() {
  clear_mem();
}

size_t CallbackInternal::get_n_in() {
  TRY_CALL(get_n_in, self_);
}

size_t CallbackInternal::get_n_out() {
  TRY_CALL(get_n_out, self_);
}

Sparsity CallbackInternal::get_sparsity_in(casadi_int i) {
  TRY_CALL(get_sparsity_in, self_, i);
}

Sparsity CallbackInternal::get_sparsity_out(casadi_int i) {
  TRY_CALL(get_sparsity_out, self_, i);
}

std::string CallbackInternal::get_name_in(casadi_int i) {
  TRY_CALL(get_name_in, self_, i);
}

std::string CallbackInternal::get_name_out(casadi_int i) {
  TRY_CALL(get_name_out, self_, i);
}

bool CallbackInternal::has_eval_buffer() const {
  TRY_CALL(has_eval_buffer, self_);
}

bool CallbackInternal::has_jac_sparsity(casadi_int oind, casadi_int iind) const {
  TRY_CALL(has_jac_sparsity, self_, oind, iind);
}

Sparsity CallbackInternal::get_jac_sparsity(casadi_int oind, casadi_int iind,
    bool symmetric) const {
  TRY_CALL(get_jac_sparsity, self_, oind, iind, symmetric);
}

void CallbackInternal::init(const Dict& opts) {
  // Initialize the base classes
  FunctionInternal::init(opts);

  // Initialize this
  casadi_assert(self_!=nullptr, "Callback object has been deleted");
  self_->init();
}

void CallbackInternal::finalize() {
  // Finalize this
  casadi_assert(self_!=nullptr, "Callback object has been deleted");
  self_->finalize();

  // Finalize the base classes
  FunctionInternal::finalize();

  has_eval_buffer_ = has_eval_buffer();

  if (has_eval_buffer_) {
    sizes_arg_.resize(n_in_);
    for (casadi_int i=0;i<n_in_;++i) {
      sizes_arg_[i] = nnz_in(i);
    }
    sizes_res_.resize(n_out_);
    for (casadi_int i=0;i<n_out_;++i) {
      sizes_res_[i] = nnz_out(i);
    }
  }
}

std::vector<DM> CallbackInternal::eval_dm(const std::vector<DM>& arg) const {
  TRY_CALL(eval, self_, arg);
}

/** \brief  Evaluate numerically */
int CallbackInternal::eval(const double** arg, double** res,
    casadi_int* iw, double* w, void* mem) const {
  setup(mem, arg, res, iw, w);
  if (has_eval_dm()) {
    return FunctionInternal::eval(arg, res, iw, w, mem);
  } else {
    TRY_CALL(eval_buffer, self_, arg, sizes_arg_, res, sizes_res_);
  }
}

bool CallbackInternal::uses_output() const {
  TRY_CALL(uses_output, self_);
}

Function CallbackInternal::factory(const std::string& name,
    const std::vector<std::string>& s_in,
    const std::vector<std::string>& s_out,
    const Function::AuxOut& aux,
    const Dict& opts) const {
  TRY_CALL(get_factory, self_, name, s_in, s_out, aux, opts);
}

bool CallbackInternal::has_jacobian() const {
  TRY_CALL(has_jacobian, self_);
}

Function CallbackInternal::
get_jacobian(const std::string& name,
                const std::vector<std::string>& inames,
                const std::vector<std::string>& onames,
                const Dict& opts) const {
  TRY_CALL(get_jacobian, self_, name, inames, onames, opts);
}

Function CallbackInternal::
get_forward(casadi_int nfwd, const std::string& name,
            const std::vector<std::string>& inames,
            const std::vector<std::string>& onames, const Dict& opts) const {
  TRY_CALL(get_forward, self_, nfwd, name, inames, onames, opts);
}

bool CallbackInternal::has_forward(casadi_int nfwd) const {
  TRY_CALL(has_forward, self_, nfwd);
}

Function CallbackInternal::
get_reverse(casadi_int nadj, const std::string& name,
            const std::vector<std::string>& inames,
            const std::vector<std::string>& onames, const Dict& opts) const {
  TRY_CALL(get_reverse, self_, nadj, name, inames, onames, opts);
}

bool CallbackInternal::has_reverse(casadi_int nadj) const {
  TRY_CALL(has_reverse, self_, nadj);
}

} // namespace casadi
