#define __STDC_LIMIT_MACROS
#define __STDC_FORMAT_MACROS

#include <utility> // for pair
#include <vector>
#include <set>

extern "C" {
  #include <caml/mlvalues.h>
  #include <caml/alloc.h>
  #include <caml/memory.h>
  #include <caml/custom.h>
}

#include "GMLMIP-0.1/onestep.h"

//
using namespace std;

extern "C" {
  CAMLprim value gmlRules_stub(value modalities);
  CAMLprim value pmlRules_stub(value modalities);
}

template<class T>
value set2CamlList(const set<T>& vec, value (*f)(const T&)) {
    CAMLparam0();
    CAMLlocal2( cli, cons );
    cli = Val_emptylist;

    for (typename set<T>::const_reverse_iterator it = vec.rbegin();
         it != vec.rend();
         ++it)
    {
        cons = caml_alloc(2, 0);

        Store_field( cons, 0, f(*it) );  // head
        Store_field( cons, 1, cli );     // tail

        cli = cons;
    }

    return cli ;
}



static CAMLprim value pair2caml(const int& t) {
    CAMLparam0();
    CAMLlocal1( abc );
    abc = caml_alloc(2, 0);
    bool neg = t < 0;
    Store_field( abc, 0, Val_int((neg ?(-t) : t) - 1));
    Store_field( abc, 1, Val_bool(!neg));
    return abc;
}

static CAMLprim value set2caml(const set<int>& vt) {
    return set2CamlList(vt, pair2caml);
}

static CAMLprim value setset2caml(const set<set<int> >& vt) {
    return set2CamlList(vt, set2caml);
}

static void printModVec(FILE* f, const vector<pair<pair<bool,int>,int> >& modvec) {
    fprintf(f, "GML-Input: /\\ (");
    for (int i = 0; i < modvec.size(); i++) {
        bool isdia = modvec[i].first.first;
        int cnt = modvec[i].first.second;
        int underl = modvec[i].second;
        if (i > 0) fprintf(f, ",");
        if (isdia) {
            fprintf(f, " <%d> p%d", cnt, underl);
        } else {
            fprintf(f, " [%d] p%d", cnt, underl);
        }
    }
    fprintf(f, " )\n");
}

CAMLprim value gmlRules_stub(value modalities) {
    // parse caml-modalities to a C++ vector
    CAMLparam1( modalities );
    value block = modalities;
    vector<pair<pair<bool,int>,int> > modvec;
    while (Is_block(block)) {
        CAMLlocal3( vpos, vcnt, vformula );
        vpos = Field(Field(block, 0), 0);
        vcnt = Field(Field(block, 0), 1);
        vformula = Field(Field(block, 0), 2);
        modvec.push_back(make_pair(make_pair(
            Bool_val(vpos),
            Int_val(vcnt)),
            1+Int_val(vformula)));
        block = Field(block, 1);
    }
    // Do one rule step and save result in rulevec
    GMLConclusion rulevec = gmlmip_onestep_gml(modvec);
    // convert rulevec into ocaml list of pairs
    CAMLlocal1( res );
    res = set2CamlList(rulevec, setset2caml);
    CAMLreturn( res );
}

CAMLprim value pmlRules_stub(value modalities) {
    // parse caml-modalities to a C++ vector
    CAMLparam1( modalities );
    value block = modalities;
    vector<pair<pair<bool,pair<int,int> >,int> > modvec;
    while (Is_block(block)) {
        CAMLlocal4( vpos, vnom, vdenom, vformula );
        vpos = Field(Field(block, 0), 0);
        vnom = Field(Field(block, 0), 1);
        vdenom = Field(Field(block, 0), 2);
        vformula = Field(Field(block, 0), 3);
        modvec.push_back(make_pair(make_pair(
            Bool_val(vpos),
            make_pair(Int_val(vnom),Int_val(vdenom))),
            1+Int_val(vformula)));
        block = Field(block, 1);
    }
    // Do one rule step and save result in rulevec
    GMLConclusion rulevec = gmlmip_onestep_pml(modvec);
    // convert rulevec into ocaml list of pairs
    CAMLlocal1( res );
    res = set2CamlList(rulevec, setset2caml);
    CAMLreturn( res );
}