#include <cctbx/boost_python/flex_fwd.h>

#include <scitbx/array_family/boost_python/flex_wrapper.h>
#include <scitbx/array_family/boost_python/flex_pickle_double_buffered.h>
#include <scitbx/array_family/boost_python/ref_pickle_double_buffered.h>
#include <boost/python/args.hpp>
#include <boost/python/return_internal_reference.hpp>

#include <spotfinder/core_toolbox/distl.h>

namespace scitbx { namespace af { namespace boost_python {

namespace {

  struct to_string_ir : pickle_double_buffered::to_string
  {
    using pickle_double_buffered::to_string::operator<<;

    to_string_ir()
    {
      int version = 1;
      *this << version;
    }

    to_string_ir& operator<<(Distl::icering const& val)
    {
      *this << val.lowerr2
            << val.upperr2
            << val.lowerresol
            << val.upperresol
            << val.strength;
      return *this;
    }
  };

  struct from_string_ir : pickle_double_buffered::from_string
  {
    from_string_ir(const char* str_ptr)
    : pickle_double_buffered::from_string(str_ptr)
    {
          *this >> version;
    }

    using pickle_double_buffered::from_string::operator>>;

    from_string_ir& operator>>(Distl::icering& val)
    {
      Distl::point lpeak;

      *this >> val.lowerr2
            >> val.upperr2
            >> val.lowerresol
            >> val.upperresol
            >> val.strength;
      return *this;
    }

    int version;
  };

  struct to_string : pickle_double_buffered::to_string
  {
    using pickle_double_buffered::to_string::operator<<;

    to_string()
    {
      int version = 2; //Current version for flex_distl_spot
      //printf("Pickle flex_distl_spot; version: %d\n", version);
      *this << version;
    }

    to_string& operator<<(scitbx::vec2<double> const& vec)
    {
      //special time saver for 2D profiles:  ignore z-direction
      *this << (float)vec[0] << (float)vec[1];
      return *this;
    }

    to_string& operator<<(spotfinder::distltbx::w_spot const& val)
    {
      *this << val.max_pxl_x()
            << val.max_pxl_y()
            << val.peakintensity
            << val.bodypixels.size();

      for (int i=0; i<val.bodypixels.size(); ++i){
      *this << val.bodypixels[i].x
            << val.bodypixels[i].y;
      }

      *this << val.total_mass;
      to_string::operator<<(val.model_m->center_of_mass());
      *this << (float)val.model_m->eigenvalue(0)
            << (float)val.model_m->eigenvalue(1);

      to_string::operator<<(val.model_m->eigenvector(0));
      to_string::operator<<(val.model_m->eigenvector(1));

      return *this;
    }
  };

  struct from_string : pickle_double_buffered::from_string
  {
    from_string(const char* str_ptr)
    : pickle_double_buffered::from_string(str_ptr)
    {
      *this >> version;
      //printf("Unpickle flex_distl_spot; version: %d\n", version);
    }

    from_string& operator>>(scitbx::vec2<double>& vec)
    {
      //special time saver for 2D profiles:  ignore z-direction
      float a,b;
      *this >> a >> b;
      vec[0] = (double)a; vec[1] = (double)b;
      return *this;
    }

    using pickle_double_buffered::from_string::operator>>;

    from_string& operator>>(spotfinder::distltbx::w_spot& val)
    {
      int iptx,ipty,bodylen;
      Distl::point lpeak;

      *this >> iptx
            >> ipty;
      lpeak = Distl::point(iptx,ipty);
      *this >> val.peakintensity
            >> bodylen;

      val.bodypixels = scitbx::af::shared<Distl::point>();
      for (int i=0; i<bodylen; ++i){
      *this >> iptx
            >> ipty;
        val.bodypixels.push_back(Distl::point(iptx,ipty));
      }

      val.setstate(lpeak);
      if (version == 2) {
        double mass;
        *this >> mass;
        scitbx::vec2<double> com,value,axis0,axis1;
        float a,b;
        from_string::operator>>(com);
        *this >> a >> b;
        value[0] = (double)a; value[1] = (double)b;
        from_string::operator>>(axis0);
        from_string::operator>>(axis1);
        val.ss_setstate(com,value,axis0,axis1,mass);
      }
      return *this;
    }

    int version;
  };

}}}} // namespace scitbx::af::boost_python::<anonymous>


namespace scitbx { namespace af { namespace boost_python {

  void wrap_flex_icering()
  {
    flex_wrapper<Distl::icering,
                 boost::python::return_internal_reference<>
                 >::plain("distl_icering")
    .def_pickle(flex_pickle_double_buffered<
                 Distl::icering, to_string_ir, from_string_ir>())
    ;
  }

  af::shared<double>
  ctr_mass_distances_from_direct_beam(
    af::const_ref<spotfinder::distltbx::w_spot> const& spots,
    scitbx::vec2<double> detector_size,
    scitbx::vec2<int> detector_pixels,
    scitbx::vec2<double> xy_beam)
  {
    af::shared<double> result(
      spots.size(),
      af::init_functor_null<double>());
    scitbx::vec2<double> sop;
    for(std::size_t i=0;i<2;i++) {
      sop[i] = detector_size[i] / detector_pixels[i];
    }
    double const* b = xy_beam.begin();
    for(std::size_t i=0;i<spots.size();i++) {
      spotfinder::distltbx::w_spot const& spot = spots[i];
      double dx = spot.ctr_mass_x() * sop[0] - b[0];
      double dy = spot.ctr_mass_y() * sop[1] - b[1];
      result[i] = std::sqrt(dx*dx + dy*dy);
    }
    return result;
  }

  void wrap_flex_w_spot()
  {
    using boost::python::arg;
    flex_wrapper<spotfinder::distltbx::w_spot,
                 boost::python::return_internal_reference<>
                 >::plain("distl_spot")
    .def_pickle(flex_pickle_double_buffered<
                 spotfinder::distltbx::w_spot, to_string, from_string>())
    .def("ctr_mass_distances_from_direct_beam",
      ctr_mass_distances_from_direct_beam, (
        arg("detector_size"),
        arg("detector_pixels"),
        arg("xy_beam")))
    ;
  }

  void wrap_flex_point()
  {
    flex_wrapper<Distl::point>::plain("distl_point");
  }

}}} // namespace scitbx::af::boost_python
