#ifdef WIN
#pragma warning (disable: 4786) // disable warning for STL maps
#endif /* WIN */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "dpuser.h"
#include "dpuser_funcs.h"
#include "dpuser.pgplot.h"
#include "dpuser_utils.h"
#include "dpuser2c/dpuserType.h"
#include <fits.h>
#include <dpstring.h>
#include "dpuser2c/utils.h"

int nodeCount = 0;

void freeNode(nodeType *p) {
    if (!p) {
        return;
    }

    if (p->type == typeOpr) {
        for (int i = 0; i < p->opr.nops; i++) {
            freeNode(p->opr.op[i]);
        }
    } else if (p->type == typeFnc) {
        for (int i = 0; i < p->fnc.nops; i++) {
            freeNode(p->fnc.op[i]);
        }
    } else if (p->type == typeRng) {
        for (int i = 0; i < p->rng.nops; i++) {
            freeNode(p->rng.op[i]);
        }
    } else if (p->type == typeStrarr) {
        for (int i = 0; i < p->strarr.nops; i++) {
            freeNode(p->strarr.op[i]);
        }
    } else if (p->type == typePgplot) {
        for (int i = 0; i < p->pgplot.nops; i++) {
            freeNode(p->pgplot.op[i]);
        }
    }

    nodeCount--;

    free (p);
}

void yyerror(const char *s) {
    dp_output("%s\n", s);
}

//
// create variable
//
void protect(void *pntr) {
    int i, j;

    // see if symbol already exists
    for (i = 0; i < nprotected; i++) {
        if (protectedpntrs[i]) {
            if (protectedpntrs[i] == pntr) {
                return;
            }
        }
    }

    // find free node
    for (i = 0; i < nprotected; i++) {
        if (protectedpntrs[i] == NULL) {
            protectedpntrs[i] = pntr;
            return;
        }
    }

    // expand table
    nprotected += 100;
    protectedpntrs = (void **)realloc(protectedpntrs, nprotected * sizeof(void *));
    for (j = i; j < nprotected; j++) {
        protectedpntrs[j] = NULL;
    }
    protectedpntrs[i] = pntr;
}

void unprotect() {
    for (int i = 0; i < nprotected; i++) {
        protectedpntrs[i] = NULL;
    }

    for (int i = 0; i < nvariables; i++) {
        if (svariables[i] != "") {
            if (svariables[i].c_str()[svariables[i].length() - 1] == '_') {
                variables[i].dparrvalue = NULL;
                variables[i].arrvalue = NULL;
                variables[i].cvalue = NULL;
                variables[i].ffvalue = NULL;
                variables[i].fvalue = NULL;
                variables[i].svalue = NULL;
                svariables[i] = "";
            }
        }
    }
}

nodeType *con(long value) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(conNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeCon;
    p->con.type = typeCon;
    p->con.value.type = typeCon;
    p->con.value.lvalue = value;
    p->con.value.svalue = NULL;
    p->con.value.cvalue = NULL;
    p->con.value.fvalue = NULL;
    p->con.value.ffvalue = NULL;
    p->con.value.arrvalue = NULL;
    p->con.value.dparrvalue = NULL;

    return p;
}

nodeType *dcon(double value) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(conNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeCon;
    p->con.type = typeCon;
    p->con.value.type = typeDbl;
    p->con.value.dvalue = value;
    p->con.value.svalue = NULL;
    p->con.value.cvalue = NULL;
    p->con.value.fvalue = NULL;
    p->con.value.ffvalue = NULL;
    p->con.value.arrvalue = NULL;
    p->con.value.dparrvalue = NULL;

    return p;
}

nodeType *scon(char *value) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(conNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeCon;
    p->con.type = typeCon;
    p->con.value.type = typeStr;
    p->con.value.svalue = CreateString(value);
    p->con.value.cvalue = NULL;
    p->con.value.fvalue = NULL;
    p->con.value.ffvalue = NULL;
    p->con.value.arrvalue = NULL;
    p->con.value.dparrvalue = NULL;

    protect(p->con.value.svalue);
    free(value);

    return p;
}

nodeType *ccon(dpComplex *value) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(conNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeCon;
    p->con.type = typeCon;
    p->con.value.type = typeCom;
    p->con.value.cvalue = CreateComplex(*value);
    p->con.value.svalue = NULL;
    p->con.value.fvalue = NULL;
    p->con.value.ffvalue = NULL;
    p->con.value.arrvalue = NULL;
    p->con.value.dparrvalue = NULL;

    protect(p->con.value.cvalue);

    return p;
}

nodeType *ffcon(char *value) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(conNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeCon;
    p->con.type = typeCon;
    p->con.value.type = typeFitsFile;
    p->con.value.ffvalue = CreateString(value);
    p->con.value.cvalue = NULL;
    p->con.value.svalue = NULL;
    p->con.value.fvalue = NULL;
    p->con.value.arrvalue = NULL;
    p->con.value.dparrvalue = NULL;

    protect(p->con.value.ffvalue);
    free(value);

    return p;
}

nodeType *id(int i) {
    nodeType *p;

    // allocate node
    if ((p = (nodeType *)malloc(sizeof(idNodeType))) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeId;
    p->id.i = i;
    p->id.range = NULL;

    return p;
}

nodeType *opr(int oper, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(oprNodeType) + (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeOpr;
    p->opr.oper = oper;
    p->opr.nops = nops;
    va_start(ap, nops);
    for (int i = 0; i < nops; i++) {
        p->opr.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    return p;
}

nodeType *iddereference(int oper, int i, nodeType *range) {
    nodeType *p, *ii;

    if ((ii = (nodeType *)malloc(sizeof(idNodeType))) == NULL) {
        yyerror("out of memory");
    }
    // copy information
    ii->type = typeId;
    ii->id.i = i;
    ii->id.range = NULL;

    nodeCount++;

    p = opr(oper, 2, ii, range);

    return p;
}

nodeType *fnc(int oper, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(fncNodeType) + (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeFnc;
    p->fnc.func = oper;
    p->fnc.nops = nops;
    p->fnc.options = 0;
    va_start(ap, nops);
    for (int i = 0; i < nops; i++) {
        p->fnc.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    return p;
}

nodeType *addfncarg(nodeType *src, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(fncNodeType) + (nops + src->fnc.nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    // copy information
    p->type = typeFnc;
    p->fnc.func = src->fnc.func;
    p->fnc.options = src->fnc.options;
    p->fnc.nops = nops + src->fnc.nops;
    for (int i = 0; i < src->fnc.nops; i++) {
        p->fnc.op[i] = src->fnc.op[i];
    }
    va_start(ap, nops);
    for (int i = src->fnc.nops; i < p->fnc.nops; i++) {
        p->fnc.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    free(src);

    return p;
}

nodeType *addfncopt(nodeType *src, char *option) {
    if (funcs[src->fnc.func - 1].noptions == 0) {
        dp_output("WARNING - Function %s does not support options [/%s].\n",
                  funcs[src->fnc.func - 1].name.c_str(), option);
    } else {
        int j = 1,
            oldoptions = 1;

        for (int i = 0; i < funcs[src->fnc.func - 1].noptions; i++) {
            if (funcs[src->fnc.func - 1].options[i] == option) {
                src->fnc.options |= j;
                oldoptions = 0;
            }
            j *= 2;
        }
        if (oldoptions) {
            dp_output("WARNING - Ignoring invalid option /%s for function %s.\n",
                      option, funcs[src->fnc.func - 1].name.c_str());
        }
    }
    free(option);

    return src;
}

nodeType *pgp(int oper, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(pgplotNodeType) + (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typePgplot;
    p->pgplot.proc = oper;
    p->pgplot.nops = nops;
    p->pgplot.options = 0;
    va_start(ap, nops);
    for (int i = 0; i < nops; i++) {
        p->pgplot.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);

    return p;
}

nodeType *addpgplotarg(nodeType *src, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(pgplotNodeType) + (nops + src->pgplot.nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    // copy information
    p->type = typePgplot;
    p->pgplot.proc = src->pgplot.proc;
    p->pgplot.options = src->pgplot.options;
    p->pgplot.nops = nops + src->pgplot.nops;
    for (int i = 0; i < src->pgplot.nops; i++) {
        p->pgplot.op[i] = src->pgplot.op[i];
    }
    va_start(ap, nops);
    for (int i = src->pgplot.nops; i < p->pgplot.nops; i++) {
        p->pgplot.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    free(src);

    return p;
}

nodeType *addprocopt(nodeType *src, char *option) {
    if (procs[src->pgplot.proc].noptions == 0) {
        dp_output("WARNING - Procedure %s does not support options [/%s].\n",
                  procs[src->pgplot.proc].name.c_str(), option);
    } else {
        int j = 1, oldoptions = 1;
        for (int i = 0; i < procs[src->pgplot.proc].noptions; i++) {
            if (procs[src->pgplot.proc].options[i] == option) {
                src->pgplot.options |= j;
                oldoptions = 0;
            }
            j *= 2;
        }
        if (oldoptions) {
            dp_output("WARNING - Ignoring invalid option /%s for procedure %s.\n",
                      option, procs[src->pgplot.proc].name.c_str());
        }
    }
    free(option);

    return src;
}

nodeType *rng(int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    nodeCount++;

    // allocate node
    size = sizeof(rngNodeType) + (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    // copy information
    p->type = typeRng;
    p->rng.nops = nops;
    va_start(ap, nops);
    for (int i = 0; i < nops; i++) {
        p->rng.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);

    return p;
}

nodeType *addrngarg(nodeType *src, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    if (nops == 0) {
    va_start(ap, nops);
        src->rng.op[src->rng.nops - 1] = va_arg(ap, nodeType*);
        va_end(ap);
        return src;
    }

    // allocate node
    size = sizeof(rngNodeType) + (nops + src->rng.nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL)
        yyerror("out of memory");

    // copy information
    p->type = typeRng;
    p->rng.nops = nops + src->rng.nops;
    for (int i = 0; i < src->rng.nops; i++) {
        p->rng.op[i] = src->rng.op[i];
    }
    va_start(ap, nops);
    for (int i = src->rng.nops; i < p->rng.nops; i++) {
        p->rng.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    free(src);

    return p;
}

nodeType *strdef(int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    // allocate node
    size = sizeof(strarrNodeType) + (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL) {
        yyerror("out of memory");
    }

    nodeCount++;

    // copy information
    p->type = typeStrarr;
    p->strarr.nops = nops;
    va_start(ap, nops);
    for (int i = 0; i < nops; i++) {
        p->strarr.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);

    return p;
}

nodeType *addstrdef(nodeType *src, int nops, ...) {
    va_list ap;
    nodeType *p;
    dpint64 size;

    if (nops == 0) {
        va_start(ap, nops);
        src->rng.op[src->strarr.nops - 1] = va_arg(ap, nodeType*);
        va_end(ap);
        return src;
    }

    // allocate node
    size = sizeof(strarrNodeType) + (nops + src->rng.nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType *)malloc(size)) == NULL)
        yyerror("out of memory");

    // copy information
    p->type = typeStrarr;
    p->strarr.nops = nops + src->strarr.nops;
    for (int i = 0; i < src->rng.nops; i++) {
        p->strarr.op[i] = src->strarr.op[i];
    }
    va_start(ap, nops);
    for (int i = src->strarr.nops; i < p->strarr.nops; i++) {
        p->strarr.op[i] = va_arg(ap, nodeType*);
    }
    va_end(ap);
    free(src);

    return p;
}

//
// Garbage Collector:
//
// Set all pointers to NULL on variables with not matching type
//
void consolidateVariables() {
    for (int i = 0; i < nvariables; i++) {
        if (variables[i].svalue) {
            if (variables[i].type != typeStr) {
                variables[i].svalue = NULL;
            }
        } else if (variables[i].type == typeStr) {
            variables[i].type = typeUnknown;
        }

        if (variables[i].arrvalue) {
            if (variables[i].type != typeStrarr) {
                variables[i].arrvalue = NULL;
            }
        } else if (variables[i].type == typeStrarr) {
            variables[i].type = typeUnknown;
        }

        if (variables[i].cvalue) {
            if (variables[i].type != typeCom) {
                variables[i].cvalue = NULL;
            }
        } else if (variables[i].type == typeCom) {
            variables[i].type = typeUnknown;
        }

        if (variables[i].fvalue) {
            if (variables[i].type != typeFits) {
                variables[i].fvalue = NULL;
            }
        } else if (variables[i].type == typeFits) {
            variables[i].type = typeUnknown;
        }

        if (variables[i].dparrvalue) {
            if (variables[i].type != typeDpArr) {
                variables[i].dparrvalue = NULL;
            }
        } else if (variables[i].type == typeDpArr) {
            variables[i].type = typeUnknown;
        }
    }
}

//
// Deletes all allocated objects which are not stored in local variables
//
void freePointerlist() {
    consolidateVariables();

    for (long i = 0; i < nListOfOStrings; i++) {
        if (listOfOStrings[i]) {
            if (isVariable(listOfOStrings[i]) == 0) {
                DeleteString(listOfOStrings[i]);
            }
        }
    }
    for (long i = 0; i < nListOfdpStringArrays; i++) {
        if (listOfdpStringArrays[i]) {
            if (isVariable(listOfdpStringArrays[i]) == 0) {
                DeleteStringArray(listOfdpStringArrays[i]);
            }
        }
    }
    for (long i = 0; i < nListOfOComplex; i++) {
        if (listOfOComplex[i]) {
            if (isVariable(listOfOComplex[i]) == 0) {
                DeleteComplex(listOfOComplex[i]);
            }
        }
    }
    for (long i = 0; i < nListOfFits; i++) {
        if (listOfFits[i]) {
            if (isVariable(listOfFits[i]) == 0) {
                DeleteFits(listOfFits[i]);
            }
        }
    }
    for (long i = 0; i < nListOfDpLists; i++) {
        if (listOfDpLists[i]) {
            if (isVariable(listOfDpLists[i]) == 0) {
                DeleteDpList(listOfDpLists[i]);
            }
        }
    }
}
