#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>

#include <boost/python/return_value_policy.hpp>
#include <boost/python/return_by_value.hpp>

#include <cctbx/geometry_restraints/angle.h>
#include <cctbx/geometry_restraints/bond.h>
#include <cctbx/geometry_restraints/dihedral.h>
#include <cctbx/geometry_restraints/bond_similarity.h>
#include <cctbx/geometry_restraints/chirality.h>
#include <cctbx/adp_restraints/adp_similarity.h>
#include <cctbx/adp_restraints/rigid_bond.h>
#include <cctbx/adp_restraints/rigu.h>
#include <cctbx/adp_restraints/isotropic_adp.h>
#include <cctbx/adp_restraints/fixed_u_eq_adp.h>
#include <cctbx/restraints.h>

#include <smtbx/import_scitbx_af.h>

namespace uctbx = cctbx::uctbx;

namespace smtbx { namespace refinement { namespace restraints {

namespace boost_python {

  template <typename FloatType>
  struct linearised_eqns_of_restraint_wrapper
  {
    typedef cctbx::restraints::linearised_eqns_of_restraint<FloatType> wt;

    static void wrap(char const *name) {
      using namespace boost::python;
      typedef return_value_policy<return_by_value> rbv;
      class_<wt>(name, no_init)
        .def(init<std::size_t, std::size_t>
             ((arg("n_restraints"),
               arg("n_crystallographic_params"))))
        .def_readwrite("design_matrix", &wt::design_matrix)
        .add_property("deltas", make_getter(&wt::deltas, rbv()))
        .add_property("weights", make_getter(&wt::weights, rbv()))
        .def("n_crystallographic_params", &wt::n_crystallographic_params)
        .def("n_restraints", &wt::n_restraints)
        .def("add_equation", &wt::add_equation)
        ;
    }
  };

  template <typename FloatType, typename ProxyType, typename RestraintType>
  struct linearise_restraints_with_parameter_map_wrapper
  {
    static void linearise_restraints(
      uctbx::unit_cell const &unit_cell,
      af::const_ref<scitbx::vec3<FloatType> > const &sites_cart,
      cctbx::xray::parameter_map<cctbx::xray::scatterer<FloatType> > const
        &parameter_map,
      af::const_ref<ProxyType> const &proxies,
      cctbx::restraints::linearised_eqns_of_restraint<FloatType>
        &linearised_eqns)
    {
      for(std::size_t i=0;i<proxies.size();i++) {
        ProxyType const& proxy = proxies[i];
        RestraintType restraint(unit_cell, sites_cart, proxy);
        restraint.linearise(
          unit_cell, linearised_eqns, parameter_map, proxy);
      }
    }

    static void wrap() {
      using namespace boost::python;
      def("linearise_restraints",
          linearise_restraints, (
            arg("unit_cell"),
            arg("sites_cart"),
            arg("parameter_map"),
            arg("proxies"),
            arg("restraints_matrix")));
    }
  };

  template <typename FloatType,
            template<typename> class ParameterTypeTemplate,
            typename ProxyType,
            typename RestraintType>
  struct linearise_restraints_with_parameter_map_and_extra_parameters {

    typedef ParameterTypeTemplate<FloatType> ParameterType;

    static void linearise_restraints(
      uctbx::unit_cell const &unit_cell,
      ParameterType const &params,
      cctbx::xray::parameter_map<cctbx::xray::scatterer<FloatType> > const
        &parameter_map,
      af::const_ref<ProxyType> const &proxies,
      cctbx::restraints::linearised_eqns_of_restraint<FloatType>
        &linearised_eqns)
    {
      for(std::size_t i=0;i<proxies.size();i++) {
        ProxyType const& proxy = proxies[i];
        RestraintType restraint(params, proxy);
        restraint.linearise(
          unit_cell, linearised_eqns, parameter_map, proxy.i_seqs);
      }
    }

    static void wrap() {
      using namespace boost::python;
      def("linearise_restraints",
          linearise_restraints, (
            arg("unit_cell"),
            arg("params"),
            arg("parameter_map"),
            arg("proxies"),
            arg("linearised_eqns")));
    }
  };

  namespace geom_res = cctbx::geometry_restraints;
  namespace adp_res = cctbx::adp_restraints;

  void wrap_least_squares_restraints() {
    using namespace boost::python;

    linearised_eqns_of_restraint_wrapper<
      double>::wrap("linearised_eqns_of_restraint");

    // geometrical restraints
    linearise_restraints_with_parameter_map_wrapper<
      double, geom_res::angle_proxy, geom_res::angle>::wrap();

    linearise_restraints_with_parameter_map_wrapper<
      double, geom_res::bond_simple_proxy, geom_res::bond>::wrap();

    linearise_restraints_with_parameter_map_wrapper<
      double, geom_res::dihedral_proxy, geom_res::dihedral>::wrap();

    linearise_restraints_with_parameter_map_wrapper<
      double, geom_res::chirality_proxy, geom_res::chirality>::wrap();

    //linearise_restraints_with_parameter_map_wrapper<
    //  double, double, geom_res::planarity_proxy, geom_res::planarity>::wrap();

    linearise_restraints_with_parameter_map_wrapper<
      double, geom_res::bond_similarity_proxy, geom_res::bond_similarity>::wrap();

    // ADP restraints
    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::isotropic_adp_proxy, adp_res::isotropic_adp>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::fixed_u_eq_adp_proxy, adp_res::fixed_u_eq_adp>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::adp_similarity_proxy, adp_res::adp_similarity>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::adp_u_eq_similarity_proxy, adp_res::adp_u_eq_similarity>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::rigid_bond_proxy, adp_res::rigid_bond>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::rigu_proxy, adp_res::rigu>::wrap();

    linearise_restraints_with_parameter_map_and_extra_parameters<
      double, cctbx::adp_restraints::adp_restraint_params,
      adp_res::adp_volume_similarity_proxy, adp_res::adp_volume_similarity>::wrap();

  }

  void wrap_origin_fixing_restraints();

  namespace {
    void init_module() {
      wrap_least_squares_restraints();
      wrap_origin_fixing_restraints();
    }
  }

}}}} // namespace smtbx::refinement::restraints::boost_python

BOOST_PYTHON_MODULE(smtbx_refinement_restraints_ext)
{
  smtbx::refinement::restraints::boost_python::init_module();
}
