#include <../../nrnconf.h>
#include <stdio.h>
#include <InterViews/observe.h>
#include "ocnotify.h"
#if HAVE_IV
#include "ivoc.h"
#endif
#include "classreg.h"
#include "linmod.h"
#include "nrnoc2iv.h"

extern "C" double* nrn_recalc_ptr(double*);
// hoc interface to a LinearModelAddition
// remember that the policy for equation additions to the tree matrix is
// cmat*y' + gmat*y = b and where the first nnode rows specify
// the number of equations (identfied by nodes)
// which are added to existing node equations in the tree structure
// and the first nnode columns specify the voltages (identified by nodes)
// which are coupled to the equations. i.e the number of new equations
// and states added to the tree matrix is nrow - nnode


class LinearMechanism: public Observer {
  public:
    LinearMechanism();
    virtual ~LinearMechanism();
    virtual void disconnect(Observable*);
    virtual void update(Observable*);
    void create();
    void lmfree();
    bool valid() {
        return model_ != NULL;
    }
    void update_ptrs();

    LinearModelAddition* model_;
    Matrix* c_;
    Matrix* g_;
    Vect* y_;
    Vect* y0_;
    Vect* b_;
    int nnode_;
    Object* f_callable_;
    Node** nodes_;
    Vect* elayer_;
};

extern void nrn_linmod_update_ptrs(void*);
void nrn_linmod_update_ptrs(void* p) {
    LinearMechanism* lm = (LinearMechanism*) p;
    lm->update_ptrs();
}

static double valid(void* v) {
    return double(((LinearMechanism*) v)->valid());
}

static Member_func members[] = {"valid", valid, 0, 0};

static void* cons(Object*) {
    LinearMechanism* m = new LinearMechanism();
    m->create();
    return (void*) m;
}

static void destruct(void* v) {
    LinearMechanism* m = (LinearMechanism*) v;
    delete m;
}

void LinearMechanism_reg() {
    class2oc("LinearMechanism", cons, destruct, members, NULL, NULL, NULL);
}

LinearMechanism::LinearMechanism() {
    model_ = NULL;
    c_ = NULL;
    g_ = NULL;
    y_ = NULL;
    b_ = NULL;
    nnode_ = 0;
    nodes_ = NULL;
    y0_ = NULL;
    elayer_ = NULL;
    f_callable_ = NULL;
}

LinearMechanism::~LinearMechanism() {
    // printf("~LinearMechanism\n");
    lmfree();
}

void LinearMechanism::lmfree() {
    if (f_callable_) {
        hoc_obj_unref(f_callable_);
        f_callable_ = NULL;
    }
    if (model_) {
        delete model_;
        model_ = NULL;
    }
    if (nodes_) {
        nrn_notify_pointer_disconnect(this);
        nnode_ = 0;
        delete[] nodes_;
        nodes_ = NULL;
        elayer_ = NULL;
    }
}

void LinearMechanism::update_ptrs() {
    if (nodes_) {
        nrn_notify_pointer_disconnect(this);
        for (int i = 0; i < nnode_; ++i) {
            double* pd = nrn_recalc_ptr(&(NODEV(nodes_[i])));
            if (pd != &(NODEV(nodes_[i]))) {
                nrn_notify_when_double_freed(pd, this);
            }
        }
    }
}

void LinearMechanism::disconnect(Observable*) {}
void LinearMechanism::update(Observable*) {
    lmfree();
}

void LinearMechanism::create() {
    int i;
    lmfree();
    i = 0;
    Object* o = *hoc_objgetarg(++i);

    if (strcmp(o->ctemplate->sym->name, "PythonObject") == 0) {
        f_callable_ = o;
        hoc_obj_ref(o);
        c_ = matrix_arg(++i);
    } else {
        f_callable_ = NULL;
        c_ = matrix_arg(1);
    }
    g_ = matrix_arg(++i);
    y_ = vector_arg(++i);

    if (ifarg(i + 2) && hoc_is_object_arg(i + 2) && is_vector_arg(i + 2)) {
        y0_ = vector_arg(++i);
    }
    b_ = vector_arg(++i);
    if (ifarg(++i)) {
#if HAVE_IV
        Oc oc;
#endif
        if (hoc_is_double_arg(i)) {
            nnode_ = 1;
            nodes_ = new Node*[1];
            double x = chkarg(i, 0., 1.);
            Section* sec = chk_access();
            nodes_[0] = node_exact(sec, x);
            nrn_notify_when_double_freed(&NODEV(nodes_[0]), this);
        } else {
            Object* o = *hoc_objgetarg(i);
            check_obj_type(o, "SectionList");
            SectionList* sl = new SectionList(o);
            sl->ref();
            Vect* x = vector_arg(i + 1);
            Section* sec;
            nnode_ = 0;
            nodes_ = new Node*[x->size()];
            for (sec = sl->begin(); sec; sec = sl->next()) {
                nodes_[nnode_] = node_exact(sec, x->elem(nnode_));
                nrn_notify_when_double_freed(&NODEV(nodes_[nnode_]), this);
                ++nnode_;
            }
            if (ifarg(i + 2)) {
                elayer_ = vector_arg(i + 2);
            }
            sl->unref();
        }
    }
    model_ = new LinearModelAddition(c_, g_, y_, y0_, b_, nnode_, nodes_, elayer_, f_callable_);
}
