/* PSIPRED 2 - Neural Network Prediction of Secondary Structure */

/* Copyright (C) 2000 David T. Jones - Created : January 2000 */
/* Original Neural Network code Copyright (C) 1990 David T. Jones */

/* Average Prediction Module */

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <QDir>
#include <QFile>
#include <QString>
#include <QTemporaryFile>
#include <QTextStream>

#include <U2Core/AppContext.h>
#include <U2Core/AppSettings.h>
#include <U2Core/UserApplicationsSettings.h>

#ifdef Q_OS_WIN
#    pragma warning(disable : 4996)
#endif

#include "sspred_avpred.h"
#include "sspred_net.h"
#include "sspred_utils.h"

//void           *calloc(), *malloc();

// char           *wtfnm;
//
// int             nwtsum, fwt_to[TOTAL], lwt_to[TOTAL];
// float           activation[TOTAL], bias[TOTAL], *weight[TOTAL];
//
// int             profile[MAXSEQLEN][20];
//
// int             seqlen;
//
// char seq[MAXSEQLEN];

enum aacodes {
    ALA,
    ARG,
    ASN,
    ASP,
    CYS,
    GLN,
    GLU,
    GLY,
    HIS,
    ILE,
    LEU,
    LYS,
    MET,
    PHE,
    PRO,
    SER,
    THR,
    TRP,
    TYR,
    VAL,
    UNK
};

PsiPassOne::PsiPassOne(QTemporaryFile *matFile, const QStringList &weightFiles)
    : matrixFile(matFile), weightFileNames(weightFiles) {
    fwt_to = (int *)malloc(TOTAL * sizeof(int));
    lwt_to = (int *)malloc(TOTAL * sizeof(int));
    activation = (float *)malloc(TOTAL * sizeof(float));
    bias = (float *)malloc(TOTAL * sizeof(float));
    weight = (float **)malloc(TOTAL * sizeof(float *));

    profile = (int **)malloc(MAXSEQLEN * sizeof(int *));
    for (int i = 0; i < MAXSEQLEN; i++) {
        profile[i] = (int *)malloc(20 * sizeof(int));
    }
}

PsiPassOne::~PsiPassOne() {
    free(fwt_to);
    free(lwt_to);
    free(activation);
    free(bias);
    free(weight);

    for (int i = 0; i < MAXSEQLEN; i++) {
        delete profile[i];
    }
    delete profile;
}

void PsiPassOne::compute_output(void) {
    int i, j;
    float netinp;

    for (i = NUM_IN; i < TOTAL; i++) {
        netinp = bias[i];

        for (j = fwt_to[i]; j < lwt_to[i]; j++)
            netinp += activation[j] * weight[i][j];

        /* Trigger neuron */
        activation[i] = logistic(netinp);
    }
}

/*
* load weights - load all link weights from a disk file
*/
void PsiPassOne::load_wts(const char *fname) {
    int i, j;
    double t;

    QFile weightFile(fname);
    if (!weightFile.open(QIODevice::ReadOnly)) {
        return;
    }

    QTextStream stream(&weightFile);

    /* Load input units to hidden layer weights */
    for (i = NUM_IN; i < NUM_IN + NUM_HID; i++) {
        for (j = fwt_to[i]; j < lwt_to[i]; j++) {
            stream >> t;
            weight[i][j] = t;
        }
    }

    /* Load hidden layer to output units weights */
    for (i = NUM_IN + NUM_HID; i < TOTAL; i++) {
        for (j = fwt_to[i]; j < lwt_to[i]; j++) {
            stream >> t;
            weight[i][j] = t;
        }
    }

    /* Load bias weights */
    for (j = NUM_IN; j < TOTAL; j++) {
        stream >> t;
        bias[j] = t;
    }
}

/* Initialize network */
void PsiPassOne::init(void) {
    int i;

    for (i = NUM_IN; i < TOTAL; i++)
        if (!(weight[i] = (float *)calloc(TOTAL - NUM_OUT, sizeof(float))))
            fail("init: Out of Memory!");

    /* Connect input units to hidden layer */
    for (i = NUM_IN; i < NUM_IN + NUM_HID; i++) {
        fwt_to[i] = 0;
        lwt_to[i] = NUM_IN;
    }

    /* Connect hidden units to output layer */
    for (i = NUM_IN + NUM_HID; i < TOTAL; i++) {
        fwt_to[i] = NUM_IN;
        lwt_to[i] = NUM_IN + NUM_HID;
    }
}

/* Make 1st level prediction averaged over specified weight sets */
void PsiPassOne::predict() {
    //int             aa, i, j, k, n, winpos,ws;
    int aa, j, winpos;
    //char fname[80], predsst[MAXSEQLEN];
    char *predsst;
    //float   avout[MAXSEQLEN][3], conf, confsum[MAXSEQLEN];
    float **avout, conf, *confsum;

    // Allocate buffers
    // TODO: not good, memory is allocated in small chunks for avout
    predsst = (char *)malloc(seqlen * sizeof(char));
    avout = (float **)malloc(seqlen * sizeof(float *));
    for (int i = 0; i < seqlen; ++i) {
        avout[i] = (float *)malloc(3 * sizeof(float));
    }
    confsum = (float *)malloc(seqlen * sizeof(float));

    for (winpos = 0; winpos < seqlen; winpos++)
        avout[winpos][0] = avout[winpos][1] = avout[winpos][2] = confsum[winpos] = 0.0F;

    foreach (const QString &wfName, weightFileNames) {
        load_wts(qPrintable(wfName));

        for (winpos = 0; winpos < seqlen; winpos++) {
            for (j = 0; j < NUM_IN; j++)
                activation[j] = 0.0;
            for (j = WINL; j <= WINR; j++) {
                if (j + winpos >= 0 && j + winpos < seqlen)
                    for (aa = 0; aa < 20; aa++)
                        activation[(j - WINL) * 21 + aa] = profile[j + winpos][aa] / 1000.0;
                else
                    activation[(j - WINL) * 21 + 20] = 1.0;
            }

            compute_output();

            conf = (2 * MAX(MAX(activation[TOTAL - NUM_OUT], activation[TOTAL - NUM_OUT + 1]), activation[TOTAL - NUM_OUT + 2]) - (activation[TOTAL - NUM_OUT] + activation[TOTAL - NUM_OUT + 1] + activation[TOTAL - NUM_OUT + 2]) + MIN(MIN(activation[TOTAL - NUM_OUT], activation[TOTAL - NUM_OUT + 1]), activation[TOTAL - NUM_OUT + 2]));

            avout[winpos][0] += conf * activation[TOTAL - NUM_OUT];
            avout[winpos][1] += conf * activation[TOTAL - NUM_OUT + 1];
            avout[winpos][2] += conf * activation[TOTAL - NUM_OUT + 2];
            confsum[winpos] += conf;
        }
    }

    for (winpos = 0; winpos < seqlen; winpos++) {
        avout[winpos][0] /= confsum[winpos];
        avout[winpos][1] /= confsum[winpos];
        avout[winpos][2] /= confsum[winpos];
        if (avout[winpos][0] >= MAX(avout[winpos][1], avout[winpos][2]))
            predsst[winpos] = 'C';
        else if (avout[winpos][2] >= MAX(avout[winpos][0], avout[winpos][1]))
            predsst[winpos] = 'E';
        else
            predsst[winpos] = 'H';
    }
    QString pFilePath = U2::AppContext::getAppSettings()->getUserAppsSettings()->getUserTemporaryDirPath() + QDir::separator() + "output.ss";
    FILE *pFile = fopen(pFilePath.toLatin1().constData(), "w");
    if (!pFile) {
        fail("failed opening file for writing");
    }

    for (winpos = 0; winpos < seqlen; winpos++)
        fprintf(pFile, "%4d %c %c  %6.3f %6.3f %6.3f\n", winpos + 1, seq.constData()[winpos], predsst[winpos], avout[winpos][0], avout[winpos][1], avout[winpos][2]);
    fclose(pFile);

    // Deallocate buffers
    free(predsst);
    for (int i = 0; i < seqlen; ++i) {
        free(avout[i]);
    }
    free(avout);
    free(confsum);
}

#define BUFSIZE 256

/* Read PSI AA frequency data */
int PsiPassOne::getmtx() {
    int j, naa;

    QTextStream stream(matrixFile);
    qDebug("%s", qPrintable(matrixFile->fileName()));

    stream >> naa;
    if (naa == 0) {
        fail("Bad mtx file - no sequence length!");
    }

    if (naa > MAXSEQLEN)
        fail("Input sequence too long!");

    stream >> seq;
    if (seq.size() == 0) {
        fail("Bad mtx file - no sequence!");
    }

    while (!stream.atEnd()) {
        QByteArray line;
        line = stream.readLine().toLatin1();
        char *buf = line.data();
        if (!strncmp(buf, "-32768 ", 7)) {
            for (j = 0; j < naa; j++) {
                if (sscanf(buf, "%*d%d%*d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%*d%d", &profile[j][ALA], &profile[j][CYS], &profile[j][ASP], &profile[j][GLU], &profile[j][PHE], &profile[j][GLY], &profile[j][HIS], &profile[j][ILE], &profile[j][LYS], &profile[j][LEU], &profile[j][MET], &profile[j][ASN], &profile[j][PRO], &profile[j][GLN], &profile[j][ARG], &profile[j][SER], &profile[j][THR], &profile[j][VAL], &profile[j][TRP], &profile[j][TYR]) != 20)
                    fail("Bad mtx format!");
                line = stream.readLine().toLatin1();
                if (line.size() == 0)
                    break;
                buf = line.data();
            }
        }
    }

    return naa;
}

int PsiPassOne::runPsiPass() {
    seqlen = getmtx();

    init();

    predict();

    return 0;
}
