/*
 * Project: dpuser data reduction package
 * File:    dpuser_utils.cpp
 * Author:  Thomas Ott
 * History: June 14, 2001: file created
 *
 * Purpose: Utility functions (implementation)
 */

#include <stdarg.h>
#include <vector>
#include "dpuser_utils.h"
#include "fits.h"
#include "dpstring.h"
#include "dpstringlist.h"

long idum;

#ifdef WIN
#pragma warning (disable: 4297) // disable warning for assumption not to
                                // throw an exception
#endif

#ifdef HAS_PLPLOT
extern "C" {
void plsabort(void(*handler)(const char *));
void plsexit(int(*handler)(const char *));

FILE *plLibOpen(const char *fn); // to test if font files are available
extern char *plplotLibDir;
}

/* Check if the bare minimum of support files for PLPLOT can be found. If not, create them
 * in a temporary directory
 */

#include "plplot_fnt.h"
void dp_plplot_checkfontfiles() {
    FILE *f = plLibOpen("plstnd5.fnt");
    if (f == NULL) {
// Get tempDir-path. TODO: Once we switch to c++17, use std::filesystem::temp_directory_path()
        std::string tempPath, fname;
#ifdef WIN
        char tempP[MAX_PATH + 1];
        GetTempPath(MAX_PATH, tempP);
        tempPath = tempP;
        if (tempPath.at(tempPath.size() - 1) != '\\') tempPath.append("\\");
#else
        tempPath = P_tmpdir;
        if (tempPath.at(tempPath.size() - 1) != '/') tempPath.append("/");
#endif /* WIN */
        plplotLibDir = strdup(tempPath.c_str());
// try once more
        FILE *f = plLibOpen("plstnd5.fnt");
        if (f == NULL) {
            dp_output("PLPLOT font files not found. Creating them in %s.\n", tempPath.c_str());
            fname = tempPath;
            fname.append("plstnd5.fnt");
            f = fopen(fname.c_str(), "w+b");
            if (f != NULL) {
                fwrite(plstnd5_fnt, 1, plstnd5_fnt_len, f);
                fclose(f);
            }
            fname = tempPath;
            fname.append("plxtnd5.fnt");
            f = fopen(fname.c_str(), "w+b");
            if (f != NULL) {
                fwrite(plxtnd5_fnt, 1, plxtnd5_fnt_len, f);
                fclose(f);
            }
            fname = tempPath;
            fname.append("cmap0_default.pal");
            f = fopen(fname.c_str(), "w+b");
            if (f != NULL) {
                fwrite(cmap0_default_pal, 1, cmap0_default_pal_len, f);
                fclose(f);
            }
            fname = tempPath;
            fname.append("cmap1_default.pal");
            f = fopen(fname.c_str(), "w+b");
            if (f != NULL) {
                fwrite(cmap1_default_pal, 1, cmap1_default_pal_len, f);
                fclose(f);
            }
        } else fclose(f);
    } else fclose(f);
}

void dp_plplot_aborthandler(const char *msg) {
    dp_output("PLPLOT encountered an abort signal: %s\n", msg);
}

int dp_plplot_exithandler(const char *msg) {
    dp_output("PLPLOT encountered an exit signal: %s\n", msg);
    return 0;
}

void init_dpplplot() {
    dp_plplot_checkfontfiles();
    plsabort(dp_plplot_aborthandler);
    plsexit(dp_plplot_exithandler);
}
#else

void init_dpplplot() {
}

#endif /* HAS_PLPLOT */

#ifdef HAS_GDL
int GDLEventHandler();
#endif /* HAS_GDL */

//XPA definitions
#ifdef NOXPA
int XPAGet(XPA xpa, char *xtemplate, char *paramlist, char *mode,
           char **bufs, int *lens, char **names, char **errs, int n) {
    throw dpuserTypeException("DPUSER has been compiled without XPA-support!\n");
    return 0;
}

int XPASet(XPA xpa, char *xtemplate, char *paramlist, char *mode,
           char *buf, int len, char **names, char **errs, int n)
{
    throw dpuserTypeException("DPUSER has been compiled without XPA-support!\n");
    return 0;
}

int XPANSLookup(XPA xpa, char *tname, char *ttype, char ***xclasses,
                char ***names, char ***methods, char ***infs)
{
    throw dpuserTypeException("DPUSER has been compiled without XPA-support!\n");
    return 0;
}

void XPAClose(XPA xpa) {
    throw dpuserTypeException("DPUSER has been compiled without XPA-support!\n");
}

XPA XPAOpen(char *mode) {
    throw dpuserTypeException("DPUSER has been compiled without XPA-support!\n");
    return NULL;
}
#endif

XPA xpa = NULL;
char* xpaServer = "ds9";

int ds9_running() {
    char **classes;
    char **names;
    char **methods;
    char **infos;
    int n;

    n = XPANSLookup(NULL, "ds9", "g", &classes, &names, &methods, &infos);

    if (n > 0) {
        free(classes[0]);
        free(names[0]);
        free(methods[0]);
        free(infos[0]);

        if (n > 0) {
            free(classes);
            free(names);
            free(methods);
            free(infos);
        }
    } else {
        XPAClose(xpa);
        xpa = NULL;
    }

    return n;
}

int ds9_frame_loaded() {
    size_t lens[NXPA];
    char *bufs[NXPA];
    char *names[NXPA];
    char *messages[NXPA];
    int ret = 0;

    // get size of displayed fits
    ret = XPAGet(xpa, xpaServer, "fits size", NULL,
                 bufs, lens, names, messages, NXPA);
    if (messages[0] == NULL) {
        // fits loaded
        ret = 1;
    }
    else {
        // no fits loaded
        ret = 0;
    }

    if (names[0]) {
        free(names[0]);
    }

    if (bufs[0]) {
        free(bufs[0]);
    }

    if (messages[0]) {
        free(messages[0]);
    }

    return ret;
}

//**********************************************
// Alex: gathered from other files for win_dll

#ifdef DPQT
#include "qtdpuser.h"
int qtinputcounter = 0;
#endif // DPQT

#ifndef DPQT
int dp_output(const char *msg, ...) {
    int rv;
    va_list args;
    va_start(args, msg);
    rv = vprintf(msg, args);
		va_end(args);
		
		return rv;
}

int dp_output_string(const char *dummy, const char *msg) {
    return printf(dummy, msg);
}
#endif // !DPQT

gsl_rng *gsl_rng_r = NULL;
void **protectedpntrs = NULL;

int ScriptInterrupt = 0;
int npg = 0;
int nunderscores = 0;
int nprotected = 0;
int silence = 0;

int dpdebuglevel = 0;
//int qftracelevel = 0;   // don't trace
//int qftracelevel = 1;   // trace function calls
int nglobals = 0;
int scriptlock = 0;

dpStringList userpronames, userprovars;
dpStringList userfncnames, userfncvars;
dpStringList script;

std::vector<FunctionDeclaration> funcs;
std::vector<FunctionDeclaration> procs;

#ifdef DPQT
int read_inp(char *response) {
    char *res;

    res = readline("");
    strcpy(response, res);
    free(res);

    return strlen(response);
}
#else
int read_inp(char *response) {
    return scanf("%s", response);
}
#endif // DPQT

#ifdef DPQT
#include "QFitsGlobal.h"
int dp_debug(const char *msg, ...) {
    if (dpdebuglevel) {
        char _tmp[1024];
        va_list args;

        if (dpuser_widget) {
            va_start(args, msg);
            vsnprintf(_tmp, 1023, msg, args);
            va_end(args);

            if (strlen(_tmp) > 0) {
                if (_tmp[strlen(_tmp)-1] == '\n') {
                    _tmp[strlen(_tmp)-1] = '\0';
                }
            }
            dpDisplayOutput(_tmp);
        }
    }
    return 1;
}
#else
int dp_debug(const char *msg, ...) {
    if (dpdebuglevel) {
        char _tmp[1024];
        va_list args;

        va_start(args, msg);
        vsnprintf(_tmp, 1023, msg, args);
        va_end(args);

        if (strlen(_tmp) > 0) {
            if (_tmp[strlen(_tmp)-1] == '\n') {
                _tmp[strlen(_tmp)-1] = '\0';
            }
        }
        printf("%s\n", _tmp);
    }
    return 1;
}
#endif // DPQT

#ifdef NO_READLINE
FILE *rl_instream = NULL;

// Stub function for history on systems without readline
void add_history(char *d) {
}

// Very simple "readline" function on systems without the real thing
char *readline(char *prompt) {
    int i, eof;
    char *response, inp;

    response = (char *)malloc(2048);
    for (i = 0; i < 2048; i++) response[i] = (char)0;
    i = 0;
    eof = 0;
    if (rl_instream != NULL) {
        scriptlock = 1;
        while (!eof) {
            eof = (fread(&response[i], 1, 1, rl_instream) != 1);
            if (response[i] == '\n') {
                response[i] = (char)0;
                if (!silence) {
                    dp_output("%s\n", response);
                }
                return response;
            }
            if (!eof) {
                i++;
            }
        }
        if (i == 0) {
            return NULL;
        }
        return response;
    } else {
#ifdef DPQT
//        dp_output(prompt);
        scriptlock = 0;
        while (qtinput.count() == qtinputcounter) {
            dpuserthread.dosleep(50);
#ifdef HAS_GDL
#ifdef WIN
            for (int i = 0; i < 10; i++) GDLEventHandler(); // we mock what GDL is doing
#endif
#endif
        }
        qtinputmutex->lock();
        strcpy(response, qtinput[qtinputcounter].toStdString().c_str());
        qtinputcounter++;
//        if (qtinputcounter > 1000) {
//            qtinput.clear();
//            qtinputcounter = 0;
//        }
        qtinputmutex->unlock();
//        if (strcmp(response, "gdlevent") == 0) {
//            for (int i = 0; i < 10; i++) GDLEventHandler();
//            goto L1;
//        }
#else
        fprintf(stdout, "%s", prompt);
        fflush(stdout);
        inp = 0;
        while (inp != '\n') {
                fscanf(stdin, "%c", &inp);
                if (inp != '\n') {
                        response[i] = inp;
                        i++;
                }
        }
#endif // DPQT
    }
    return response;
}
#endif // NO_READLINE

// end
//**********************************************

long nListOfOStrings = 0;
dpString **listOfOStrings = NULL;
dpString _qtmpstring;

// expand list of oStrings
void expandListOfOStrings(long n) {
    long i, j;

    i = nListOfOStrings;
    nListOfOStrings += n;
    listOfOStrings = (dpString **)realloc(listOfOStrings,
                                          nListOfOStrings * sizeof(dpString *));
    for (j = i; j < nListOfOStrings; j++) {
        listOfOStrings[j] = NULL;
    }
}

// add *pntr to list of strings
void addToListOfOStrings(dpString *pntr) {
    long n;
    bool found = false;

    if (pntr) {
        // first look if somewhere there is space
        for (n = 0; n < nListOfOStrings; n++) {
            if (!found) {
                if (!listOfOStrings[n]) {
                    listOfOStrings[n] = pntr;
                    found = true;
                }
            }
        }
        // nope, no space; expand list
        if (!found) {
            n = nListOfOStrings;
            expandListOfOStrings(10);
            listOfOStrings[n] = pntr;
        }
    }
}

// delete *pntr from list of oStrings
void deleteFromListOfOStrings(dpString *pntr) {
    long n;

    if (pntr) {
        for (n = 0; n < nListOfOStrings; n++) {
            if (listOfOStrings[n]) if (listOfOStrings[n] == pntr) {
                listOfOStrings[n] = NULL;
                return;
            }
        }
    }
}

dpString *CreateString(void) {
    dpString *string = new dpString;
    addToListOfOStrings(string);

    return string;
}

dpString *CreateString(const dpString &s) {
    dpString *string = new dpString(s);
    addToListOfOStrings(string);

    return string;
}

dpString *CreateString(const double d) {
    dpString *string = new dpString;
    string->setNum(d);
    addToListOfOStrings(string);

    return string;
}

dpString *CreateString(const long l) {
    dpString *string = new dpString;
    string->setNum(l);
    addToListOfOStrings(string);

    return string;
}

dpString *CreateString(const char *s) {
    dpString *string = new dpString(s);
    addToListOfOStrings(string);

    return string;
}

dpString *CreateString(const dpComplex &c) {
    dpString *string = new dpString(FormatComplexdpString(c));
    addToListOfOStrings(string);

    return string;
}

void DeleteString(dpString *string) {
    deleteFromListOfOStrings(string);
    delete string;
}

long nListOfdpStringArrays = 0;
dpStringList **listOfdpStringArrays = NULL;

// expand list of dpStringArrays
void expandListOfdpStringArrays(long n) {
    long i, j;

    i = nListOfdpStringArrays;
    nListOfdpStringArrays += n;
    listOfdpStringArrays = (dpStringList **)realloc(listOfdpStringArrays,
                                                    nListOfdpStringArrays *
                                                        sizeof(dpStringList *));
    for (j = i; j < nListOfdpStringArrays; j++) {
        listOfdpStringArrays[j] = NULL;
    }
}

// add *pntr to list of string arrays
void addToListOfdpStringArrays(dpStringList *pntr) {
    long n;
    bool found = false;

    if (pntr) {
        // first look if somewhere there is space
        for (n = 0; n < nListOfdpStringArrays; n++) {
            if (!found) {
                if (!listOfdpStringArrays[n]) {
                    listOfdpStringArrays[n] = pntr;
                    found = true;
                }
            }
        }
        // nope, no space; expand list
        if (!found) {
            n = nListOfdpStringArrays;
            expandListOfdpStringArrays(10);
            listOfdpStringArrays[n] = pntr;
        }
    }
}

// delete *pntr from list of dpStringArrays
void deleteFromListOfdpStringArrays(dpStringList *pntr) {
    long n;

    if (pntr) {
        for (n = 0; n < nListOfdpStringArrays; n++) {
            if (listOfdpStringArrays[n]) if (listOfdpStringArrays[n] == pntr) {
                listOfdpStringArrays[n] = NULL;
                return;
            }
        }
    }
}

dpStringList *CreateStringArray(void) {
    dpStringList *stringarray = new dpStringList;
    addToListOfdpStringArrays(stringarray);

    return stringarray;
}

dpStringList *CreateStringArray(const dpint64 &u) {
    dpint64 n;
    dpStringList *stringarray = new dpStringList;
    for (n = 0; n < u; n++) stringarray->append("");
    addToListOfdpStringArrays(stringarray);

    return stringarray;
}

dpStringList *CreateStringArray(const dpStringList &s) {
    dpStringList *stringarray = new dpStringList(s);
    addToListOfdpStringArrays(stringarray);

    return stringarray;
}

void DeleteStringArray(dpStringList *stringarray) {
    deleteFromListOfdpStringArrays(stringarray);
    delete stringarray;
}



long nListOfOComplex = 0;
dpComplex **listOfOComplex = NULL;

// expand list of dpComplex
void expandListOfOComplex(long n) {
    long i, j;

    i = nListOfOComplex;
    nListOfOComplex += n;
    listOfOComplex = (dpComplex **)realloc(listOfOComplex,
                                           nListOfOComplex * sizeof(dpComplex *));
    for (j = i; j < nListOfOComplex; j++) {
        listOfOComplex[j] = NULL;
    }
}

// add *pntr to list of complex
void addToListOfOComplex(dpComplex *pntr) {
    long n;
    bool found = 0;

    if (pntr) {
        // first look if somewhere there is space
        for (n = 0; n < nListOfOComplex; n++) {
            if (!found) if (!listOfOComplex[n]) {
                listOfOComplex[n] = pntr;
                found = 1;
            }
        }
        // nope, no space; expand list
        if (!found) {
            n = nListOfOComplex;
            expandListOfOComplex(10);
            listOfOComplex[n] = pntr;
        }
    }
}

// delete *pntr from list of complex
void deleteFromListOfOComplex(dpComplex *pntr) {
    long n;

    if (pntr) {
        for (n = 0; n < nListOfOComplex; n++) {
            if (listOfOComplex[n]) if (listOfOComplex[n] == pntr) {
                listOfOComplex[n] = NULL;
                n = nListOfOComplex;
            }
        }
    }
}

dpComplex *CreateComplex(double r, double i) {
    dpComplex *c = new dpComplex(r, i);
    addToListOfOComplex(c);
    return c;
}

dpComplex *CreateComplex(const dpComplex &complex) {
    dpComplex *c = new dpComplex(complex);
    addToListOfOComplex(c);
    return c;
}

void DeleteComplex(dpComplex *c) {
    deleteFromListOfOComplex(c);
    delete c;
}

long nListOfFits = 0;
Fits **listOfFits = NULL;

// expand list of Fits
void expandListOfFits(long n) {
    long i, j;

    i = nListOfFits;
    nListOfFits += n;
    listOfFits = (Fits **)realloc(listOfFits, nListOfFits * sizeof(Fits *));
    for (j = i; j < nListOfFits; j++) {
        listOfFits[j] = NULL;
    }
}

// add *pntr to list of Fits
void addToListOfFits(Fits *pntr) {
    bool found = false;

    if (pntr) {
        // look if it is already in the list
        for (long n = 0; n < nListOfFits; n++) {
            if (pntr == listOfFits[n]) {
                return;
            }
        }

        // first look if somewhere there is space
        for (long n = 0; n < nListOfFits; n++) {
            if (!found) if (!listOfFits[n]) {
                listOfFits[n] = pntr;
                found = true;
            }
        }
        // nope, no space; expand list
        if (!found) {
            long n = nListOfFits;
            expandListOfFits(10);
            listOfFits[n] = pntr;
        }
    }
}

// delete *pntr from list of Fits
void deleteFromListOfFits(Fits *pntr) {
    if (pntr) {
        for (long n = 0; n < nListOfFits; n++) {
                if (listOfFits[n]) {
                    if (listOfFits[n] == pntr) {
                        listOfFits[n] = NULL;
//                        return;
                }
            }
        }
    }
}

Fits *CreateFits(void) {
    Fits *f = new Fits;
    addToListOfFits(f);
    return f;
}

Fits *CreateFits(int n1, int n2, int n3, int bitpix) {
    Fits *f = new Fits;
    f->create(n1, n2, n3, (FitsBitpix)bitpix);
    addToListOfFits(f);
    return f;
}

void DeleteFits(Fits *f) {
    deleteFromListOfFits(f);
    delete f;
}

long nListOfDpLists = 0;
dpuserTypeList **listOfDpLists = NULL;

// expand list of dpuserTypeLists
void expandListOfDpLists(long n) {
    long i = nListOfDpLists;
    nListOfDpLists += n;
    listOfDpLists = (dpuserTypeList **)realloc(listOfDpLists,
                                               nListOfDpLists * sizeof(dpuserTypeList *));
    for (long j = i; j < nListOfDpLists; j++) {
        listOfDpLists[j] = NULL;
    }
}

// add *pntr to list of dpuserTypeLists
void addToListOfDpLists(dpuserTypeList *pntr) {
    bool found = false;

    if (pntr) {
        // look if it is already in the list
        for (long n = 0; n < nListOfDpLists; n++) {
            if (pntr == listOfDpLists[n]) {
                return;
            }
        }

        // first look if somewhere there is space
        for (long n = 0; n < nListOfDpLists; n++) {
            if (!found) if (!listOfDpLists[n]) {
                listOfDpLists[n] = pntr;
                found = true;
            }
        }
        // nope, no space; expand list
        if (!found) {
            long n = nListOfDpLists;
            expandListOfDpLists(10);
            listOfDpLists[n] = pntr;
        }
    }
}

// delete *pntr from list of dpuserTypeLists
void deleteFromListOfDpLists(dpuserTypeList *pntr) {
    if (pntr) {
        for (long n = 0; n < nListOfDpLists; n++) {
            if (listOfDpLists[n]) {
                if (listOfDpLists[n] == pntr) {
                    listOfDpLists[n] = NULL;
//                    return;
                }
            }
        }
    }
}

dpuserTypeList *CreateDpList(void) {
    dpuserTypeList *f = new dpuserTypeList;
    addToListOfDpLists(f);
    return f;
}

dpuserTypeList *CreateDpList(const dpuserTypeList &l) {
    dpuserTypeList *f = new dpuserTypeList(l);
    addToListOfDpLists(f);
    return f;
}

void DeleteDpList(dpuserTypeList *f) {
    deleteFromListOfDpLists(f);
    for (int i = 0; i < f->size(); i++) {
        switch (f->at(i)->type) {
        case typeFits:
            delete f->at(i)->fvalue;
            break;
        case typeCom:
            delete f->at(i)->cvalue;
            break;
        case typeStr:
            delete f->at(i)->svalue;
            break;
        case typeDpArr:
            DeleteDpList(f->at(i)->dparrvalue);
//            delete f->at(i)->dparrvalue;
            break;
        case typeStrarr:
            delete f->at(i)->arrvalue;
            break;
        default:
            break;
        }
    }
    delete f;
    f = NULL;
}

// return a dpString from a dpComplex. format defaults to "*.16g", other use could be "%.2f"

const dpString &FormatComplexdpString(const dpComplex &z, const char *format) {
    dpString re, im;

    if (isReal(z)) {
        re.sprintf(format, real(z));
    } else if (isImag(z)) {
        im.sprintf(format, fabs(imag(z)));
    } else {
        re.sprintf(format, real(z));
        im.sprintf(format, fabs(imag(z)));
    }

    // Strip off trailing 0's
    if (re.length() > 0) {
        while (re.right(1) == "0") {
            re.truncate(re.length() - 1);
        }
        if (re.right(1) == ".") {
            re.truncate(re.length() - 1);
        }
    }
    if (im.length() > 0) {
        while (im.right(1) == "0") {
            im.truncate(im.length() - 1);
        }
        if (im.right(1) == ".") {
            im.truncate(im.length() - 1);
        }
        if (im == "1") {
            im = "i";
        } else {
            im += "i";
        }
    }
    _qtmpstring = re;
    if (im.length() > 0) {
        if (imag(z) < 0.0) {
            _qtmpstring += "-";
        } else if (re.length() > 0) {
            _qtmpstring += "+";
        }
        _qtmpstring += im;
    }
    return _qtmpstring;
}

// Comparison function for createStringIndex;
// This function uses the global variable char *_compareString
char *_compareString;

int charCompare(const void *a, const void *b)
{
    return _compareString[*(int *)a] - _compareString[*(int *)b];
}

// Create an index table of the string in ascending or descending order.
bool createStringIndex(Fits &result, const dpString &string, bool reverse) {
    dpint64 i;

    if (!result.create(string.length(), 1, I4)) {
        return false;
    }
    for (i = 0; i < string.length(); i++) {
        result.i4data[i] = i;
    }
    _compareString = (char *)string.c_str();
    qsort(result.i4data, string.length(), sizeof(int), charCompare);
    for (i = 0; i < string.length(); i++) {
        result.i4data[i]++;
    }
    if (reverse) {
        result.flip(1);
    }

    return true;
}

// Comparison function for createStringArrayIndex;
// This function uses the global variable dpStringList *_compareStringArray
dpStringList *_compareStringArray;

int stringCompare(const void *a, const void *b)
{
    return strcmp((*_compareStringArray)[*(int *)a].c_str(),
                  (*_compareStringArray)[*(int *)b].c_str());
}

//Create an index table of the string array in ascending or descending order.
bool createStringArrayIndex(Fits &result, const dpStringList &stringarray,
                            bool reverse)
{
    if (!result.create(stringarray.count(), 1, I4)) {
        return false;
    }
    for (dpint64 i = 0; i < stringarray.count(); i++) {
        result.i4data[i] = i;
    }
    _compareStringArray = (dpStringList *)&stringarray;
    qsort(result.i4data, stringarray.count(), sizeof(int), stringCompare);
    for (dpint64 i = 0; i < stringarray.count(); i++) {
        result.i4data[i]++;
    }
    if (reverse) {
        result.flip(1);
    }

    return true;
}

bool reindexString(dpString &string, const Fits &indices) {
    char *_tmpstr;

    if (indices.Nelements() > string.length()) {
        dp_output("reindexString: too many indices.\n");
        return false;
    }
    if ((_tmpstr = (char *)malloc((indices.Nelements() + 1) * sizeof(char))) == NULL) {
        dp_output("reindexString: memory allocation error.\n");
        return false;
    }

    for (dpint64 n = 0; n < indices.Nelements(); n++) {
        _tmpstr[n] = string.c_str()[nint(indices.ValueAt(n)) - 1];
    }
    _tmpstr[indices.Nelements()] = 0;

    string = _tmpstr;
    free(_tmpstr);

    return true;
}

bool reindexStringArray(dpStringList &stringarray, const Fits &indices) {
    dpStringList _tmpstringarray;

    if (indices.Nelements() > stringarray.count()) {
        dp_output("reindexStringArray: too many indices.\n");
        return false;
    }

    for (dpint64 n = 0; n < indices.Nelements(); n++) {
        _tmpstringarray.append(stringarray[nint(indices.ValueAt(n)) - 1]);
    }

    stringarray = _tmpstringarray;

    return true;
}

// Make a mosaic of the images specified.
bool mosaic(Fits &result, dpStringList files,
            float *xshift, float *yshift, float *scale)
{
    float xmin, xmax, ymin, ymax;
    Fits current, mask, sc;

    // Find out how many lines there are in the file
    int nlines = files.count();

    // find greatest shift vectors
    xmin = xmax = xshift[0];
    ymin = ymax = yshift[0];
    for (int i = 0; i < nlines; i++) {
        if (xshift[i] < xmin) {
            xmin = xshift[i];
        } else if (xshift[i] > xmax) {
            xmax = xshift[i];
        }
        if (yshift[i] < ymin) {
            ymin = yshift[i];
        } else if (yshift[i] > ymax) {
            ymax = yshift[i];
        }
    }
    if (xmin > 0.0) {
        xmin = 0.0;
    }
    if (ymin > 0.0) {
        ymin = 0.0;
    }

    // Read in the first fits file to see how big we are
    current.ReadFITS(files[0].c_str());
    result.create((int)fabs(xmax) + (int)fabs(xmin) + current.Naxis(1),
                  (int)fabs(ymax) + (int)fabs(ymin) + current.Naxis(2),
                  current.Naxis(3));
//	result.CopyHeader(current);

    sc.create(result.Naxis(1), result.Naxis(2));
    for (int i = 0; i < nlines; i++) {
        current.ReadFITS(files[i].c_str());
        current.setType(R4);
        mask.create(current.Naxis(1), current.Naxis(2));
        mask = 1.0;

        // dead with special 3D data
        if ((current.Naxis(1) == 16 ) && (current.Naxis(2) == 16)) {
            dp_output("Dealing with 3D data\n");
            for (int x = 1; x <= 16; x++) {
                mask.r4data[mask.F_I(16, x)] = 0.0;
            }
            if (current.Naxis(0) == 2) {
                for (int x = 1; x <= 16; x++) {
                    current.r4data[mask.F_I(16, x)] = 0.0;
                }
            } else {
                for (int x = 1; x <= 16; x++) {
                    for (int z = 1; z <= 600; z++) {
                        current.r4data[mask.F_I(16, x, z)] = 0.0;
                    }
                }
            }
        }
        if (!current.resize(result.Naxis(1), result.Naxis(2))) {
            return false;
        }
        if (!mask.resize(result.Naxis(1), result.Naxis(2))) {
            return false;
        }
        current.fshift(xshift[i] - xmin, yshift[i] - ymin, 0);
        mask.fshift(xshift[i] - xmin, yshift[i] - ymin, 0);
        if (scale != NULL) {
            current *= scale[i];
        }
        result.add(current);
        sc += mask;
        if (i == 0) {
            result.CopyHeader(current);
        }
    }
    sc.czero(1.0);
    result /= sc;
    return true;
}

//Take a fits cube and do an average of the images therein.
bool CubeAverage(Fits &result, const dpStringList & files)
{
    if (files.count() < 1) {
        return false;
    }
    if (!cube_avg(files[0].c_str(), result)) {
        return false;
    }

    dpint64 numfiles = 1;
    if (files.count() > 1) {
        Fits current;
        for (dpint64 z = 1; z < files.count(); z++) {
            if (!cube_avg(files[z].c_str(), current)) {
                return false;
            }
            if ((current.Naxis(1) != result.Naxis(1)) ||
                (current.Naxis(2) != result.Naxis(2))) {
                return false;
            }
            result.add(current);
            numfiles++;
            if (FitsInterrupt) {
                break;
            }
        }
    }
    if (numfiles > 1) {
        result.div((float)numfiles);
    }
    return true;
}

//Take a number of FITS cubes and write them to disk as a single FITS cube.
bool CubeMerge(const char *outname, dpStringList &innames) {
    long n1 = 0, n2 = 0;
    FitsBitpix bi;
    Fits work;

    // First check: if outname equals to one of the innames, bail out
    for (dpint64 i = 0; i < innames.count(); i++) {
        if (innames[i] == outname) {
            dp_output("Fits::CubeMerge: One of the input names equals "
                      "the output name.\n");
            return false;
        }
    }

    // Read in the headers, check for consistency of the arrays and
    // count the number of images
    long n = 0;
    for (dpint64 i = 0; i < innames.count(); i++) {
        if (!work.OpenFITS(innames[i].c_str())) {
            return false;
        }
        work.ReadFitsHeader();
        work.getHeaderInformation();
        work.CloseFITS();
        if (i == 0) {
            n1 = work.Naxis(1);
            n2 = work.Naxis(2);
            n = work.Naxis(3);
            bi = work.membits;
        } else {
            if ((work.Naxis(1) != n1) ||
                (work.Naxis(2) != n2) ||
                (bi != work.membits))
            {
                dp_output("Fits::CubeMerge: The input arrays do not match "
                          "in size or type.\n");
                return false;
            }
            n += work.Naxis(3);
        }
    }

    // go through the arrays and write a new file
    Fits out;
    void *ptr;
    dpint64 memsize = (dpint64)n1 * n2 * (abs(bi) / 8);
    if ((ptr = malloc(memsize)) == NULL) {
        dp_output("Fits::CubeMerge: Could not allocate memory.\n");
        return false;
    }
    for (dpint64 i = 0; i < innames.count(); i++) {
        dp_output("adding %s to %s...\n", innames[i].c_str(), outname);
        if (!work.OpenFITS(innames[i].c_str())) {
            return false;
        }
        work.ReadFitsHeader();
        work.getHeaderInformation();
        if (i == 0) {
            out.CopyHeader(work);
            if (!out.CreateFITS(outname)) {
                return false;
            }
            if (!out.SetIntKey("NAXIS", 3))  {
                return false;
            }
            if (!out.SetIntKey("NAXIS1", n1))  {
                return false;
            }
            if (!out.SetIntKey("NAXIS2", n2))  {
                return false;
            }
            if (!out.SetIntKey("NAXIS3", n))  {
                return false;
            }
            if (!out.WriteFitsHeader(out.fd))  {
                return false;
            }
        }
        for (n = 0; n < work.Naxis(3); n++) {
            if (!work.dpread(ptr, memsize)) {
                dp_output("Fits::CubeMerge: Could not read data.\n");
                work.CloseFITS();
                out.CloseFITS();
                return false;
            }
            if (fwrite(ptr, 1, memsize, out.fd) != memsize) {
                dp_output("Fits::CubeMerge: Could not write data.\n");
                work.CloseFITS();
                out.CloseFITS();
                return false;
            }
        }
        work.CloseFITS();
    }
    out.CloseFITS();
    free(ptr);
    return true;
}

bool qssa(Fits &result, dpStringList &fnames, int x, int y, int method,
          Fits *sky, Fits *flat, Fits *dpl, Fits *mask, int resizing)
{
    Fits current, findmax, factor, sky1, flat1, dpl1, mask1;
    int xmax = 0, ymax = 0, c = 0, count = 0;
    int has_sky = 0, has_flat = 0, has_dpl = 0, has_mask = 0;
    double fxmax = 0., fymax = 0.;
    char *fname = NULL;

    if (sky != NULL) {
        has_sky = 1;
        if (sky->Naxis(0) == 3) {
            if (sky->Naxis(3) != (long)fnames.count()) {
                dp_output("Fits::qssa: sky must have either one or the "
                          "exact number of images.\n");
                return false;
            }
        }
    }
    if (flat != NULL) {
        has_flat = 1;
        if (flat->Naxis(0) == 3) {
            if (flat->Naxis(3) != (long)fnames.count()) {
                dp_output("Fits::qssa: flat must have either one or the "
                          "exact number of images.\n");
                return false;
            }
        }
    }
    if (dpl != NULL) {
        has_dpl = 1;
        if (dpl->Naxis(0) == 3) {
            if (dpl->Naxis(3) != (long)fnames.count()) {
                dp_output("Fits::qssa: dpl must have either one or the "
                          "exact number of images.\n");
                return false;
            }
        }
    }
    if (mask != NULL) {
        has_mask = 1;
        if (mask->Naxis(0) == 3) {
            if (mask->Naxis(3) != (long)fnames.count()) {
                dp_output("Fits::qssa: mask must have either one or the "
                          "exact number of images.\n");
                return false;
            }
        }
    }

    count = 0;
    for (int n = 0; n < (long)fnames.count(); n++) {
        fname = (char *)fnames[n].c_str();
        if (!current.OpenFITS(fname)) {
            return false;
        }
        if ((current.hdr = current.ReadFitsHeader()) < 0) {
            return false;
        }
        current.getHeaderInformation();
        c = current.Naxis(3);
        if (has_sky) {
            if (sky->Naxis(0) == 3) {
                sky->extractRange(sky1, -1, -1, -1, -1, n+1, n+1);
            } else {
                sky1.copy(*sky);
            }
        }
        if (has_flat) {
            if (flat->Naxis(0) == 3) {
                flat->extractRange(flat1, -1, -1, -1, -1, n+1, n+1);
            } else {
                flat1.copy(*flat);
            }
        }
        if (has_dpl) {
            if (dpl->Naxis(0) == 3) {
                dpl->extractRange(dpl1, -1, -1, -1, -1, n+1, n+1);
            } else {
                dpl1.copy(*dpl);
            }
        }
        if (has_mask) {
            if (mask->Naxis(0) == 3) {
                mask->extractRange(mask1, -1, -1, -1, -1, n+1, n+1);
            } else {
                mask1.copy(*mask);
            }
        }
        for (int i = 1; i <= c; i++) {
            count++;
            if (!current.ReadFITSCubeImage(i)) {
                return false;
            }
            if (has_sky == 1) {
                current.sub(sky1);
            }
            if (has_flat == 1) {
                current.mul(flat1);
            }
            if (has_dpl == 1) {
                DeadpixApply(current, dpl1);
            }
            if ((i == 1) && (n == 0)) {
                factor.create(current.Naxis(1), current.Naxis(2));
                if (resizing) {
                    factor.resize(factor.Naxis(1) * 2, factor.Naxis(2) * 2);
                }
                factor = 0.0;
                result.create(current.Naxis(1), current.Naxis(2));
                if (resizing) {
                    result.resize(result.Naxis(1) * 2, result.Naxis(2) * 2);
                }
                result = 0.0;
            }
            findmax.copy(current);
            if (method < 0) {
                findmax.smooth(-(float)method);
            }
            if (method <= 0) {
                if (has_mask == 1) {
                    findmax.max_pos_in_mask(&xmax, &ymax, mask1);
                } else {
                    findmax.max_pos(&xmax, &ymax);
                }
                dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n",
                          count, x-xmax, y-ymax);
                findmax = 1.0;
                if (resizing) {
                    result.ShiftAddIntoBigger(current, x-xmax, y-ymax);
                    factor.ShiftAddIntoBigger(findmax, x-xmax, y-ymax);
                } else {
                    current.shift(x - xmax, y - ymax, 2);
                    findmax.shift(x - xmax, y - ymax, 2);
                    result.add(current);
                    factor.add(findmax);
                }
            } else {
                if (has_mask == 1) {
                    findmax.mul(mask1);
                }
                findmax.centroid(&fxmax, &fymax);
                findmax = 1.0;
                if (method == 1) {
                    xmax = (int)(fxmax + 0.5);
                    ymax = (int)(fymax + 0.5);
                    dp_output("Reading image no. %5i...    dx = %4i, dy = %4i\n",
                              count, x-xmax, y-ymax);
                    if (resizing) {
                        result.ShiftAddIntoBigger(current, x-xmax, y-ymax);
                        factor.ShiftAddIntoBigger(findmax, x-xmax, y-ymax);
                    } else {
                        current.shift(x - xmax, y - ymax, 2);
                        findmax.shift(x - xmax, y - ymax, 2);
                        result.add(current);
                        factor.add(findmax);
                    }
                } else {
                    dp_output("Reading image no. %5i...    dx = %6.2f, dy = %6.2f\n",
                              count, x-fxmax, y-fymax);
                    if (resizing) {
                        current.resize(current.Naxis(1) * 2, current.Naxis(2) * 2);
                    }
                    current.fshift(x - fxmax, y - fymax, 2);
                    if (resizing) {
                        findmax.resize(findmax.Naxis(1) * 2, findmax.Naxis(2) * 2);
                    }
                    findmax.fshift(x - fxmax, y - fymax, 2);
                    result.add(current);
                    factor.add(findmax);
                }
            }
            if (FitsInterrupt) {
                dp_output("SSA interrupted after %i images\n", i);
                break;
            }
        }
    }
    factor.czero(1.0);
    result.div(factor);

    return true;
}

// modulo operator / function
dpuserType modulo(const dpuserType &arg1, const dpuserType &arg2) {
    dpuserType result;

    switch (arg1.type) {
        case typeCon: switch (arg2.type) {
            case typeCon:
                result.type = typeCon;
                if (arg2.lvalue != 0) result.lvalue = arg1.lvalue % arg2.lvalue;
                else result.lvalue = 0;
                break;
            case typeDbl:
                result.type = typeDbl;
                if (arg2.dvalue != 0.0) {
                    result.dvalue = (double)(int)(arg1.lvalue / arg2.dvalue);
                    result.dvalue = arg1.lvalue - arg2.dvalue * result.dvalue;
                } else {
                    result.dvalue = 0.0;
                }
                break;
            default:
                dp_output("Wrong arguments to modulo operator\n");
                break;
        }
            break;
        case typeDbl: switch (arg2.type) {
            case typeCon:
                result.type = typeDbl;
                if (arg2.lvalue != 0) {
                    result.dvalue = (double)(int)(arg1.dvalue / ((double)arg2.lvalue));
                    result.dvalue = arg1.dvalue - arg2.lvalue * result.dvalue;
                } else {
                    result.dvalue = 0.0;
                }
                break;
            case typeDbl:
                result.type = typeDbl;
                if (arg2.dvalue != 0.0) {
                    result.dvalue = (double)(int)(arg1.dvalue / arg2.dvalue);
                    result.dvalue = arg1.dvalue - arg2.dvalue * result.dvalue;
                } else {
                    result.dvalue = 0.0;
                }
                break;
            default:
                dp_output("Wrong arguments to modulo operator\n");
                break;
            }
            break;
        case typeFits:
            result.type = typeFits;
            result.clone(arg1);
            result.fvalue->setType(R4);
            switch (arg2.type) {
            case typeCon:
                if (arg2.lvalue != 0) {
                    double value;
                    for (unsigned long n = 0; n < result.fvalue->Nelements(); n++) {
                        value = (double)(int)(arg1.fvalue->ValueAt(n) / ((double)arg2.lvalue));
                        result.fvalue->r4data[n] = arg1.fvalue->ValueAt(n) - arg2.lvalue * value;
                    }
                } else {
                    *result.fvalue = 0.0;
                }
                break;
            case typeDbl:
                if (arg2.dvalue != 0.0) {
                    double value;
                    for (unsigned long n = 0; n < result.fvalue->Nelements(); n++) {
                        value = (double)(int)(arg1.fvalue->ValueAt(n) / arg2.dvalue);
                        result.fvalue->r4data[n] = arg1.fvalue->ValueAt(n) - arg2.dvalue * value;
                    }
                } else {
                    *result.fvalue = 0.0;
                }
                break;
            default:
                dp_output("Wrong arguments to modulo operator\n");
                break;
            }
            break;
        default:
            dp_output("Wrong arguments to modulo operator\n");
            break;
    }
    return result;
}

// return number which describes a buffer name of the form buffer%i which
// is not already in use
int freeBufferNumber(void) {
    bool done = false;

    int number = 0;
    while (!done) {
        number++;
        done = true;
        for (auto var:dpuser_vars) {
            if (var.first == "buffer" + dpString::number(number)) done = false;
        }
    }

    return number;
}

dpString freeBufferName(void) {
    return "buffer" + dpString::number(freeBufferNumber());
}

//TODO: replace protectedpntrs by std::set
//TODO: Think about how to deal with self-written functions (currently: looplock being used, but this makes it super slow)
int isVariable(void *pntr) {
//    if (looplock > 0) return 1;
    for (int i = 0; i < nprotected; i++) {
        if (protectedpntrs[i]) {
            if (pntr == protectedpntrs[i]) {
                return 1;
            }
        }
    }
    for (auto var:dpuser_vars) {
        if (var.second.svalue == pntr) return 1;
        if (var.second.cvalue == pntr) return 1;
        if (var.second.fvalue == pntr) return 1;
        if (var.second.arrvalue == pntr) return 1;
        if (var.second.dparrvalue == pntr) return 1;
    }
    return 0;
}

void createGlobalVariables() {
    /* speed of light, m/s */
    dpuser_vars["c"] = 299792458.0;

    /* pi */
    dpuser_vars["pi"] = 2.0 * acos(0.0);

    /* e */
    dpuser_vars["e"] = exp(1.0);

    /* naxis1, naxis2 */
    dpuser_vars["naxis1"] = 256;
    dpuser_vars["naxis2"] = 256;

    /* plot device */
    dpuser_vars["plotdevice"].type = typeStr;
    #if (defined(Q_OS_MACX) && defined(DPQT))
        dpuser_vars["plotdevice"].svalue = CreateString("/QT");
    #elif (defined(WIN32))
        dpuser_vars["plotdevice"].svalue = CreateString("/WINDOWS");
    #else
        dpuser_vars["plotdevice"].svalue = CreateString("/XSERVE");
    #endif /* WIN32 */

    /* method for some functions */
    dpuser_vars["method"] = 0;

    /* amount of temporary memory for some libfits functions */
    dpuser_vars["tmpmem"] = MEDIAN_MEM;
}

int nalternate = 4;

/* alternate names for functions - mainly for IDL compatibility */
char *alternateFunctionNames[][4] = {
    { "fltarr", "floatarray" },
    { "intarr", "intarray" },
    { "dblarr", "doublearray" },
    { "double", "float" }
};

char *alternateFunctionName(char *s) {
    int i;

    for (i = 0; i < nalternate; i++) {
        if (strcmp(s, alternateFunctionNames[i][0]) == 0) return alternateFunctionNames[i][1];
    }
    return s;
}

int lookupUserFunction(char *s) {
    dpint64 i;

    for (i = 0; i < userfncnames.count(); i++) {
        if (userfncnames[i] == s) return i;
    }
    return -1;
}

// resolve function-names
int funcWord(char *s) {
    int i;
    char *name = alternateFunctionName(s);

   // built-in
    for (i = 0; i < funcs.size(); i++) {
        if (funcs[i].name == name) return i + 1;
    }
   // compiled
//    for (i = 0; i < compiledfunctions.size(); i++) {
//        if (compiledfunctions[i].name == name) return nfunctions + i + 1;
//    }
   // stored
    if ((i = lookupUserFunction(s)) != -1) return -i-1;

    return 0;
}

int lookupUserProcedure(char *s) {
    dpint64 i;

    for (i = 0; i < userpronames.count(); i++) {
        if (userpronames[i] == s) return i;
    }
    return -1;
}

// resolve procedure-names
int pgWord(char *s) {
    int i;

   // built-in
    for (i = 0; i < npg; i++)
        if (procs[i].name == s) return i;

   // compiled
//    for (i = 0; i < compiledprocedures.size(); i++) {
//        if (compiledprocedures[i].name == s) return npg + i + 1;
//    }

   // stored
    if ((i = lookupUserProcedure(s)) != -1) return -i-1;

    return -10000;
}

void updateQFitsViewTitle(dpuserType *ptr) {
#ifdef DPQT
    if (ptr == NULL) return;
    for (auto var:dpuser_vars) {
        if (((var.second.type == typeFits) && (var.second.fvalue == ptr->fvalue)) ||
            ((var.second.type == typeDpArr)  && (var.second.dparrvalue == ptr->dparrvalue))) {
            dpUpdateVar(var.first);
            break;
        }
    }
#endif /* DPQT */
}

