#define NATIVE_MODULE "$math"

#include <math.h>
#include "ext_math.h"
#include "list.h"

module_t ext_math_module = {NO, NULL, NO, NULL};

static int check_one_vector(cList *l1, Int *len_ret)
{
    Int i,len;

    len=list_length(l1);
    for (i=0; i<len; i++) {
        if (list_elem(l1,i)->type != FLOAT)
            THROW((type_id, "Arguments must be lists of floats."));
    }
    *len_ret=len;
    RETURN_TRUE;
}

static int check_vectors(cList *l1, cList *l2, Int *len_ret)
{
    Int i,len;

    len=list_length(l1);
    if (list_length(l2)!=len)
        THROW((range_id, "Arguments are not of the same length."));
    for (i=0; i<len; i++) {
        if (list_elem(l1,i)->type != FLOAT)
            THROW((type_id, "Arguments must be lists of floats."));
        if (list_elem(l2,i)->type != FLOAT)
            THROW((type_id, "Arguments must be lists of floats."));
    }
    *len_ret=len;
    RETURN_TRUE;
}


NATIVE_METHOD(minor) {
    Int i,len;
    cList *l,*l1,*l2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    l=list_new(len);
    l->len=len;
    for (i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        list_elem(l,i)->type=FLOAT;
        list_elem(l,i)->u.fval=p<q ? p : q;
    }
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(major) {
    Int i,len;
    cList *l,*l1,*l2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    l=list_new(len);
    l->len=len;
    for (i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        list_elem(l,i)->type=FLOAT;
        list_elem(l,i)->u.fval=p>q ? p : q;
    }
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(add) {
    Int i,len;
    cList *l,*l1,*l2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    l=list_new(len);
    l->len=len;
    for (i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        list_elem(l,i)->type=FLOAT;
        list_elem(l,i)->u.fval=p+q;
    }
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(sub) {
    Int i,len;
    cList *l,*l1,*l2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    l=list_new(len);
    l->len=len;
    for (i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        list_elem(l,i)->type=FLOAT;
        list_elem(l,i)->u.fval=p-q;
    }
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(dot) {
    Int i,len;
    cList *l1,*l2;
    Float s;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    for (s=0.0,i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        s+=p*q;
    }
    CLEAN_RETURN_FLOAT(s);
}

NATIVE_METHOD(distance) {
    Int i,len;
    cList *l1,*l2;
    Float s;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    for (s=0.0,i=0; i<len; i++) {
        Float p,q,d;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        d=p-q;
        s+=d*d;
    }
    CLEAN_RETURN_FLOAT(sqrt(s));
}

NATIVE_METHOD(cross) {
    Int len;
    cList *l,*l1,*l2;
    cData *f,*f1,*f2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    if (len!=3)
        THROW((range_id,"The vectors are not of length 3."));
    l=list_new(len);
    l->len=len;
    f=list_elem(l,0);
    f1=list_elem(l1,0);
    f2=list_elem(l2,0);
    f[0].type=f[1].type=f[2].type=FLOAT;
    f[0].u.fval=f1[1].u.fval*f2[2].u.fval-f1[2].u.fval*f2[1].u.fval;
    f[1].u.fval=f1[2].u.fval*f2[0].u.fval-f1[0].u.fval*f2[2].u.fval;
    f[2].u.fval=f1[0].u.fval*f2[1].u.fval-f1[1].u.fval*f2[0].u.fval;
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(scale) {
    Int i,len;
    cList *l,*l1;
    Float f;

    INIT_2_ARGS(FLOAT,LIST);
    l1=LIST2;
    f=FLOAT1;
    if (!check_one_vector (l1,&len))
        RETURN_FALSE;
    l=list_new(len);
    l->len=len;
    for (i=0; i<len; i++) {
        Float p;

        p=list_elem(l1,i)->u.fval;
        list_elem(l,i)->type=FLOAT;
        list_elem(l,i)->u.fval=p*f;
    }
    CLEAN_RETURN_LIST(l);
}

NATIVE_METHOD(is_lower) {
    Int i,len;
    cList *l1,*l2;

    INIT_2_ARGS(LIST,LIST);
    l1=LIST1;
    l2=LIST2;
    if (!check_vectors (l1,l2,&len))
        RETURN_FALSE;
    for (i=0; i<len; i++) {
        Float p,q;

        p=list_elem(l1,i)->u.fval;
        q=list_elem(l2,i)->u.fval;
        if (p>=q) {
            CLEAN_RETURN_INTEGER(0);
        }
    }
    CLEAN_RETURN_INTEGER(1);
}

NATIVE_METHOD(transpose) {
    Int i,len,len1;
    cList *l,*l1;
    cData *e,*o;

    INIT_1_ARG(LIST);
    l1=LIST1;
    len=list_length(l1);
    if (!len) {
        l1=list_dup(l1);
        CLEAN_RETURN_LIST(l1);
    }
    e=list_elem(l1,0);
    for (i=0; i<len; i++) {
        if (e[i].type!=LIST)
            THROW((type_id,"The argument must be a list of lists."));
    }
    len1=list_length(e[0].u.list);
    if (!len1) {
        l1=list_dup(e[0].u.list);
        CLEAN_RETURN_LIST(l1);
    }
    for (i=1; i<len; i++) {
        if (list_length(e[i].u.list)!=len1)
            THROW((range_id,"All sublists must be of the same length"));
    }
    l=list_new(len1);
    l->len=len1;
    o=list_elem(l,0);
    for (i=0; i<len1; i++) {
        cList *l2;
        cData *k;
        Int j;

        l2=list_new(len);
        l2->len=len;
        o[i].type=LIST;
        o[i].u.list=l2;
        k=list_elem(l2,0);
        for (j=0; j<len; j++)
            data_dup(&k[j],list_elem(e[j].u.list,i));
    }
    CLEAN_RETURN_LIST(l);
}
