/*
 * multi_axis_goniometer.cc
 *
 *  Copyright (C) 2016 Diamond Light Source
 *
 *  Author: Richard Gildea
 *
 *  This code is distributed under the BSD license, a copy of which is
 *  included in the root directory of this package.
 */
#include <boost/python.hpp>
#include <boost/python/def.hpp>
#include <memory>
#include <string>
#include <sstream>
#include <dxtbx/model/goniometer.h>
#include <dxtbx/model/multi_axis_goniometer.h>
#include <dxtbx/model/boost_python/to_from_dict.h>

namespace dxtbx { namespace model { namespace boost_python {
  namespace multi_axis_goniometer_detail {

    using namespace boost::python;

    std::string multi_axis_goniometer_to_string(const MultiAxisGoniometer &goniometer) {
      std::stringstream ss;
      ss << goniometer;
      return ss.str();
    }

    struct MultiAxisGoniometerPickleSuite : boost::python::pickle_suite {
      static boost::python::tuple getinitargs(const MultiAxisGoniometer &obj) {
        return boost::python::make_tuple(
          obj.get_axes(), obj.get_angles(), obj.get_names(), obj.get_scan_axis());
      }

      static boost::python::tuple getstate(boost::python::object obj) {
        const MultiAxisGoniometer &goniometer =
          boost::python::extract<const MultiAxisGoniometer &>(obj)();
        return boost::python::make_tuple(
          obj.attr("__dict__"), goniometer.get_setting_rotation_at_scan_points());
      }

      static void setstate(boost::python::object obj, boost::python::tuple state) {
        MultiAxisGoniometer &goniometer =
          boost::python::extract<MultiAxisGoniometer &>(obj)();
        DXTBX_ASSERT(boost::python::len(state) == 2);

        // restore the object's __dict__
        boost::python::dict d =
          boost::python::extract<boost::python::dict>(obj.attr("__dict__"))();
        d.update(state[0]);

        // restore the internal state of the C++ object
        scitbx::af::const_ref<mat3<double> > S_list =
          boost::python::extract<scitbx::af::const_ref<mat3<double> > >(state[1]);
        goniometer.set_setting_rotation_at_scan_points(S_list);
      }

      static bool getstate_manages_dict() {
        return true;
      }
    };
  }  // namespace multi_axis_goniometer_detail
  template <>
  boost::python::dict to_dict(const MultiAxisGoniometer &obj) {
    boost::python::dict result;
    result["axes"] = boost::python::list(obj.get_axes());
    result["angles"] = boost::python::list(obj.get_angles());
    result["names"] = boost::python::list(obj.get_names());
    result["scan_axis"] = obj.get_scan_axis();
    if (obj.get_num_scan_points() > 0) {
      boost::python::list l;
      scitbx::af::shared<mat3<double> > setting_rotation_at_scan_points =
        obj.get_setting_rotation_at_scan_points();
      for (scitbx::af::shared<mat3<double> >::iterator it =
             setting_rotation_at_scan_points.begin();
           it != setting_rotation_at_scan_points.end();
           ++it) {
        l.append(boost::python::tuple(*it));
      }
      result["setting_rotation_at_scan_points"] = l;
    }
    return result;
    return result;
  };

  template <>
  MultiAxisGoniometer *from_dict(boost::python::dict obj) {
    scitbx::af::shared<vec3<double> > axes =
      boost::python::extract<scitbx::af::shared<vec3<double> > >(obj["axes"]);
    scitbx::af::shared<double> angles =
      boost::python::extract<scitbx::af::shared<double> >(obj["angles"]);
    scitbx::af::shared<std::string> names =
      boost::python::extract<scitbx::af::shared<std::string> >(obj["names"]);
    MultiAxisGoniometer *g =
      new MultiAxisGoniometer(axes.const_ref(),
                              angles.const_ref(),
                              names.const_ref(),
                              boost::python::extract<std::size_t>(obj["scan_axis"]));
    if (obj.has_key("setting_rotation_at_scan_points")) {
      boost::python::list l = boost::python::extract<boost::python::list>(
        obj["setting_rotation_at_scan_points"]);
      scitbx::af::shared<mat3<double> > S_array;
      for (std::size_t i = 0; i < boost::python::len(l); ++i) {
        mat3<double> S = boost::python::extract<mat3<double> >(l[i]);
        S_array.push_back(S);
      }
      g->set_setting_rotation_at_scan_points(S_array.const_ref());
    }
    return g;
  };

  static std::shared_ptr<MultiAxisGoniometer> make_multi_axis_goniometer(
    const scitbx::af::const_ref<vec3<double> > &axes,
    const scitbx::af::const_ref<double> &angles,
    const scitbx::af::const_ref<std::string> &names,
    std::size_t scan_axis) {
    return std::shared_ptr<MultiAxisGoniometer>(
      new MultiAxisGoniometer(axes, angles, names, scan_axis));
  }

  void export_multi_axis_goniometer() {
    using namespace multi_axis_goniometer_detail;

    class_<MultiAxisGoniometer, bases<Goniometer> >("MultiAxisGoniometer")
      .def(
        init<const scitbx::af::const_ref<vec3<double> > &,
             const scitbx::af::const_ref<double> &,
             const scitbx::af::const_ref<std::string> &,
             std::size_t>((arg("axes"), arg("angles"), arg("names"), arg("scan_axis"))))
      .def("__init__", make_constructor(&make_multi_axis_goniometer))
      .def("get_axes", &MultiAxisGoniometer::get_axes)
      .def("set_axes", &MultiAxisGoniometer::set_axes)
      .def("get_angles", &MultiAxisGoniometer::get_angles)
      .def("set_angles", &MultiAxisGoniometer::set_angles)
      .def("get_names", &MultiAxisGoniometer::get_names)
      .def("get_scan_axis", &MultiAxisGoniometer::get_scan_axis)
      .def("__str__", &multi_axis_goniometer_to_string)
      .def("to_dict", &to_dict<MultiAxisGoniometer>)
      .def("from_dict",
           &from_dict<MultiAxisGoniometer>,
           return_value_policy<manage_new_object>())
      .staticmethod("from_dict")
      .def_pickle(MultiAxisGoniometerPickleSuite());
  }

}}}  // namespace dxtbx::model::boost_python
