#include "meca.h"
#include <pybind11/pybind11.h>
#include "python_utilities.h"
namespace py = pybind11;

class Meca;

/// a utility to enrich the cytosim python module
void load_meca_classes(py::module_ &m) {
    /// Python interface to Meca
    py::class_<Meca>(m, "Meca")
        .def("base", [](Meca & mec) { return to_numpy_raw(mec.base(), mec.nbVertices(), (int) DIM ); } , PYOWN)
        .def("points", [](Meca & mec) { return to_numpy_raw(mec.points(), mec.nbVertices(), (int) DIM ); } , PYOWN)
        .def("force", [](Meca & mec) { return to_numpy_raw(mec.force(), mec.nbVertices(), (int) DIM ); } , PYOWN)
        .def_readwrite("cycles_", &Meca::cycles_)
        .def_readwrite("doNotify", &Meca::doNotify)
        .def_readwrite("drawLinks", &Meca::drawLinks)
        .def("addMecable", &Meca::addMecable)
        .def("empty", &Meca::empty)
        .def("largestMecable", &Meca::largestMecable)
        .def("smallestMecable", &Meca::smallestMecable)
        .def("nbConstraints", &Meca::nbConstraints)
        .def("nbMecables", &Meca::nbMecables)
        .def("nbVertices", &Meca::nbVertices)
        .def("dimension", &Meca::dimension)
        .def("preconditionnerSize", &Meca::preconditionnerSize)
        .def("residualNorm", &Meca::residualNorm)
        
        .def("addForce", [](Meca & mec, const Mecapoint & pt, pyarray force) {mec.addForce(pt,to_vector(force));})
        .def("addForce", [](Meca & mec, const Mecable * able, int ix, pyarray force) {mec.addForce(able, (index_t) ix,to_vector(force));})
        .def("addForce", [](Meca & mec, const Interpolation & pt, pyarray force) {mec.addForce(pt,to_vector(force));})
        .def("addForceToAll", [](Meca & mec,  pyarray force) {mec.addForceToAll(to_vector(force));})
        .def("addTorque", [](Meca & mec, const Interpolation & pt, pyarray torque) {mec.addTorque(pt,to_torque(torque));})
        .def("addTorqueClamp", [](Meca & mec, const Interpolation & pt, pyarray dir, real w) {mec.addTorqueClamp(pt,to_vector(dir),w);})
        .def("addTorqueExplicit", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real w) 
            { mec.addTorqueExplicit(pt1,pt2,w);})
        .def("addTorqueExplicit", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, pyarray v, real w) 
            { mec.addTorqueExplicit(pt1,pt2,to_vector2(v),w);})
#if ( DIM == 2 )
        .def("addTorquePoliti", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, pyarray v, real w) 
            { mec.addTorquePoliti(pt1,pt2,to_vector2(v),w);})
#endif
        .def("addTorque", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real w) 
            { mec.addTorque(pt1,pt2,w);})
        .def("addTorque", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, pyarray v, real w) 
            { mec.addTorque(pt1,pt2,to_vector2(v),w);})
        .def("addTorque3", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, const Mecapoint & pt3, real s, real w) 
            { mec.addTorque3(pt1,pt2,pt3,s,w);})
        .def("addTorque3Plane", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, const Mecapoint & pt3, 
                    pyarray t, pyarray v, real w) 
            { mec.addTorque3Plane(pt1,pt2,pt3,to_torque(t),to_vector2(v),w);})
        .def("addTorque4", [](Meca & mec, int A, int B, int C, int D, real w) 
            { mec.addTorque4( (index_t) A, (index_t) B, (index_t) C, (index_t) D, w);})
        .def("addTorque4", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, real w) 
            { mec.addTorque4(pt1,pt2,w);})
        .def("addPointClamp", [](Meca & mec, const Mecapoint & pt, pyarray pos, real w) {mec.addPointClamp(pt,to_vector(pos),w);})
        .def("addPointClamp", [](Meca & mec, const Interpolation & pt, pyarray pos, real w) {mec.addPointClamp(pt,to_vector(pos),w);})
        .def("addPointClampX", [](Meca & mec, const Mecapoint & pt, real x, real w) {mec.addPointClampX(pt,x,w);})
        .def("addPointClampXY", [](Meca & mec, const Mecapoint & pt, pyarray pos, real w) {mec.addPointClampXY(pt,to_vector(pos),w);})
        .def("addPointClampToAll", [](Meca & mec, pyarray pos, real w) {mec.addPointClampToAll(to_vector(pos),w);})
        .def("addSphereClamp", [](Meca & mec,  py::array pos, const Mecapoint & pt, pyarray cen, real rad, real w) 
            {mec.addSphereClamp(to_vector(pos),pt,to_vector(cen),rad,w);})
        .def("addSphereClamp", [](Meca & mec,  py::array pos, const Interpolation & pt, pyarray cen, real rad, real w) 
            {mec.addSphereClamp(to_vector(pos),pt,to_vector(cen),rad,w);})
        .def("addSphereClamp", [](Meca & mec,  const Mecapoint & pt, pyarray cen, real rad, real w) 
            {mec.addSphereClamp(pt,to_vector(cen),rad,w);})
        .def("addSphereClamp", [](Meca & mec,  const Interpolation & pt, pyarray cen, real rad, real w) 
            {mec.addSphereClamp(pt,to_vector(cen),rad,w);})
        .def("addCylinderClampX", [](Meca & mec, const Mecapoint & pt, real len, real w) {mec.addCylinderClampX(pt,len,w);})
        .def("addCylinderClampY", [](Meca & mec, const Mecapoint & pt, real len, real w) {mec.addCylinderClampY(pt,len,w);})
        .def("addCylinderClampZ", [](Meca & mec, const Mecapoint & pt, real len, real w) {mec.addCylinderClampZ(pt,len,w);})
        .def("addCylinderClamp", [](Meca & mec, const Mecapoint & pt, pyarray p1, pyarray p2, real r, real w)
            {mec.addCylinderClamp(pt,to_vector(p1), to_vector(p2),r,w);})
#if (DIM==2)
        .def("addSidePointClamp2D", [](Meca & mec, const Interpolation & pt, pyarray pos, real len, real w)
            {mec.addSidePointClamp2D(pt,to_vector(pos),len,w);})
#endif            
        .def("addSidePointClamp3D", [](Meca & mec, const Interpolation & pt, pyarray pos, pyarray arm, real w)
            {mec.addSidePointClamp3D(pt,to_vector(pos),to_torque(arm),w);})
        .def("addSidePointClamp", [](Meca & mec, const Interpolation & pt, pyarray pos, real len, real w)
            {mec.addSidePointClamp(pt,to_vector(pos),len,w);})
        .def("addLineClampX", &Meca::addLineClampX)
        .def("addLineClamp", [](Meca & mec, const Interpolation & pt, pyarray g, pyarray dir, real w)
            {mec.addLineClamp(pt,to_vector(g),to_vector(dir),w);})
        .def("addLineClamp", [](Meca & mec, const Mecapoint & pt, pyarray g, pyarray dir, real w)
            {mec.addLineClamp(pt,to_vector(g),to_vector(dir),w);})
        .def("addPlaneClampXYZ", &Meca::addPlaneClampXYZ)
        .def("addPlaneClampX", &Meca::addPlaneClampX)
        .def("addPlaneClampY", &Meca::addPlaneClampY)
        .def("addPlaneClampZ", &Meca::addPlaneClampZ)
        .def("addPlaneClamp", [](Meca & mec, const Mecapoint & pt, pyarray g, pyarray dir, real w)
            {mec.addPlaneClamp(pt,to_vector(g),to_vector(dir),w);})
        .def("addPlaneClamp", [](Meca & mec, const Interpolation & pt, pyarray g, pyarray dir, real w)
            {mec.addPlaneClamp(pt,to_vector(g),to_vector(dir),w);})
        .def("addLink", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real w)
            {mec.addLink(pt1,pt2,w);})
        .def("addLink", [](Meca & mec, const Mecapoint & pt1, const Interpolation & pt2, real w)
            {mec.addLink(pt1,pt2,w);})
        .def("addLink", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, real w)
            {mec.addLink(pt1,pt2,w);})
        .def("addLink", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, real w)
            {mec.addLink(pt1,pt2,w);})
        .def("addLink2", [](Meca & mec, const Mecapoint & pt1, int ix, real a, real b, real w)
            {mec.addLink2(pt1, ix, a, b, w);})
        .def("addLink3", [](Meca & mec, const Mecapoint & pt1, int ix, real a, real b, real c, real w)
            {mec.addLink3(pt1, ix, a, b, c, w);})
        .def("addLink4", [](Meca & mec, const Mecapoint & pt1, int ix, real a, real b, real c, real d, real w)
            {mec.addLink4(pt1, ix, a, b, c, d, w);})
        .def("addLink1", [](Meca & mec, const Interpolation & pt1, int ix, real w)
            {mec.addLink1(pt1,ix,w);})
        .def("addLink2", [](Meca & mec, const Interpolation & pt1, int ix, real a, real b, real w)
            {mec.addLink2(pt1, ix, a, b, w);})
        .def("addLink3", [](Meca & mec, const Interpolation & pt1, int ix, real a, real b, real c, real w)
            {mec.addLink3(pt1, ix, a, b, c, w);})
        .def("addLink4", [](Meca & mec, const Interpolation & pt1, int ix, real a, real b, real c, real d, real w)
            {mec.addLink4(pt1, ix, a, b, c, d, w);})
        .def("addLongLink1", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, pyarray vec, real ab2, real l, real w)
            {mec.addLongLink1(pt1,pt2,to_vector(vec),ab2,l,w);})
        .def("addLongLink2", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, pyarray vec, real ab2, real l, real w)
            {mec.addLongLink2(pt1,pt2,to_vector(vec),ab2,l,w);})
        .def("addLongLink", [](Meca & mec, const Mecapoint & pt1, const Mecapoint & pt2, real l, real w)
            {mec.addLongLink(pt1,pt2,l,w);})
        .def("addLongLink", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real l, real w)
            {mec.addLongLink(pt1,pt2,l,w);})
        .def("addLongLink", [](Meca & mec, const Mecapoint & pt1, const Interpolation & pt2, real l, real w)
            {mec.addLongLink(pt1,pt2,l,w);})
        .def("addLongLink4", [](Meca & mec, const Interpolation & pt1, int pts, real a, real b, real c, real d, real l, real w)
            {mec.addLongLink4(pt1,pts,a,b,c,d,l,w);})
#if ( DIM == 2 )       
        .def("addSideLink2D", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, real arm, real w)
            {mec.addSideLink2D(pt1,pt2,arm, w);})
#endif
        .def("addSideLink3D", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, pyarray arm, real w)
            {mec.addSideLink3D(pt1,pt2,to_torque(arm), w);})
        .def("addSideLink", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, real a, real w)
            {mec.addSideLink(pt1,pt2,a,w);})
#if ( DIM == 2 )              
        .def("addSideLink2D", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real arm, real w)
            {mec.addSideLink2D(pt1,pt2,arm, w);})
#endif
        .def("addSideLink3D", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, pyarray arm, real w)
            {mec.addSideLink3D(pt1,pt2,to_torque(arm), w);})
        .def("addSideLink", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real a, real w)
            {mec.addSideLink(pt1,pt2,a,w);})
        .def("testSideLink", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, pyarray arm, real w)
            {mec.testSideLink(pt1,pt2,to_torque(arm), w);})
#if ( DIM == 2 )
        .def("addSideSideLink2D", &Meca::addSideSideLink2D)
#endif
         .def("addSideSideLink", [](Meca & mec, const Interpolation & pt1, pyarray arm1, const Interpolation & pt2, 
                pyarray arm2, real w)
            {mec.addSideSideLink(pt1,to_torque(arm1),pt2,to_torque(arm2),w);})
        .def("addSideSideLink", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real a, real w)
            {mec.addSideSideLink(pt1,pt2,a,w);})
        .def("addSlidingLink", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, real w)
            {mec.addSlidingLink(pt1,pt2,w);})
        .def("addSlidingLink", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, real w)
            {mec.addSlidingLink(pt1,pt2,w);})
#if ( DIM == 2 )    
         .def("addSideSlidingLink2D", [](Meca & mec, const Interpolation & pt1, real leg, const Mecapoint & pt2, pyarray dir, real w)
            {mec.addSideSlidingLink2D(pt1,leg,pt2,to_vector(dir), w);})
#endif
        .def("addSideSlidingLinkS", [](Meca & mec, const Interpolation & pt1, const Mecapoint & pt2, pyarray arm, real w)
            {mec.addSideSlidingLinkS(pt1,pt2,to_torque(arm), w);})
        .def("addSideSlidingLink3D", [](Meca & mec, const Interpolation & pt1, pyarray leg, const Mecapoint & pt2, pyarray dir, real w)
            {mec.addSideSlidingLink3D(pt1,to_torque(leg),pt2,to_vector(dir), w);})
        .def("addSideSlidingLink", [](Meca & mec, const FiberSegment & seg, real a, const Mecapoint & pt, real len, real w)
            {mec.addSideSlidingLink(seg,a,pt,len,w);})

#if ( DIM == 2 )    
         .def("addSideSlidingLink2D", [](Meca & mec, const Interpolation & pt1, real leg, const Interpolation & pt2, pyarray dir, real w)
            {mec.addSideSlidingLink2D(pt1,leg,pt2,to_vector(dir), w);})
#endif
        .def("addSideSlidingLinkS", [](Meca & mec, const Interpolation & pt1, const Interpolation & pt2, pyarray arm, real w)
            {mec.addSideSlidingLinkS(pt1,pt2,to_torque(arm), w);})
        .def("addSideSlidingLink3D", [](Meca & mec, const Interpolation & pt1, pyarray leg, const Interpolation & pt2, pyarray dir, real w)
            {mec.addSideSlidingLink3D(pt1,to_torque(leg),pt2,to_vector(dir), w);})
        .def("addSideSlidingLink", [](Meca & mec, const FiberSegment & seg, real a, const Interpolation & pt, real len, real w)
            {mec.addSideSlidingLink(seg,a,pt,len,w);})
            
        .def("addTriLink", [](Meca & mec, const Interpolation & pt1, real w1, const Interpolation & pt2, real w2, const Interpolation & pt3, real w3)
            {mec.addTriLink(pt1,w1,pt2,w2,pt3,w3);})
            
        .def("selectStericEngine", &Meca::selectStericEngine)
        .def("readyMecables", &Meca::readyMecables)
        .def("importParameters", &Meca::importParameters)
        .def("prepare", &Meca::getReady)
        .def("getReady", &Meca::getReady)
        .def("solve", &Meca::solve)
        .def("apply", &Meca::apply)
        .def("calculateForces", [](Meca & mec) {return mec.calculateForces();})
        
        .def("saveSystem", &Meca::saveSystem)
        .def("exportSystem", &Meca::exportSystem)
        .def("flagClusters", &Meca::flagClusters)
        .def("saveMatrixBitmaps", &Meca::saveMatrixBitmaps)
        .def("saveConnectivityBitmap", &Meca::saveConnectivityBitmap);
            
}
