/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2015 Douglas L. Theobald

    This file is part of THESEUS.

    THESEUS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as
    published by the Free Software Foundation, either version 3 of
    the License, or (at your option) any later version.

    THESEUS is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with THESEUS in the file 'COPYING'. If not, see
    <http://www.gnu.org/licenses/>.

    -/_|:|_|_\-
*/
#if __STDC__ != 1
    #error NOT a Standard C environment
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __linux__
  #include <getopt.h>
#endif
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <time.h>
#include "distfit.h"
#include "error.h"
#include "DLTutils.h"
#include "pdbMalloc.h"
#include "pdbIO.h"
#include "pdbUtils.h"
#include "pdbStats.h"
#include "DLTmath.h"
#include "msa.h"
//#include "theseus_sa.h"
#include "crush.h"
/* #include "lodmats.h" */
#include "FragCds.h"
#include "QuarticHornFrag.h"
#include <gsl/gsl_math.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_psi.h>
#include "NWfill.h"


extern double       s_dssp8[8][8];
extern NWalgo      *nw_algo;
extern CdsArray    *cdsA; /* main array of selected pdb cds, never modified */
extern NWtable    **nw_tableA;
extern int          crush_again;
extern double      *egap;



// Forward declarations.
int ChargeCoordsFromSeqIDs(int *, int, const PDBCds *, Cds *);
int EStatxParamsFromStruct(int, const Cds *, const PDBCds *, Cds *);



/* fill table with scores */
void
NWfill(NWtable *nw_table, Cds *cds1, Cds *cds2, double *var)
{
    int             nx, ny;

    nx = nw_table->nx = cds1->vlen;
    ny = nw_table->ny = cds2->vlen;

    //printf("\nnx:%d  ny:%d, nx+ny:%d\n", nx, ny, nx+ny);fflush(NULL);

    NWalloc(nw_table, nx, ny);

    /* score by interatomic distances */
    if (nw_algo->distance == 1)
        SuperDistFill(nw_table, cds1, cds2, var);

    /* score by local window of structural similarity, RMSD from superposition of fragments */
    if (nw_algo->procrustes == 1)
        ProcrustesFill(nw_table, cds1, cds2);

    /* score by residue specific local charge resulting only from D, E, K, R side chains */
    if (nw_algo->raw_charge == 1)
        SimpleChargeFill(nw_table, cds1, cds2);

    /* score by residue specific contact order */
    if (nw_algo->corder == 1)
        ContactOrderFill(nw_table, cds1, cds2);

    /* score by per residue psi/chi angle similarity */
    if (nw_algo->angles == 1)
        AnglesFill(nw_table, (const PDBCds *) nw_table->pdbc0, (const PDBCds *) nw_table->pdbc1);

    /* score by amino acid similarity, as given in a PAM or BLOSUM matrix */
    if (nw_algo->blosum)
        BlosumFill(nw_table, cds1, cds2);

    /* score by residue-specific secondary structure similarity */
    if (nw_algo->dsspflag == 1)
        DsspFill(nw_table);

    /* calculate Karlin-Altschul K+ parameter */
//     if (nw_algo->KA == 1)
//         nw_table->Kplus = KA_Kplus(nw_table);
//     else
//         nw_table->Kplus = 0.005;
}


void
ProcrustesFill(NWtable *nw_table, Cds *cds1, Cds *cds2)
{
    int             nx, ny; /* endx, endy are exclusive (as usual) */
    int             i, j, matoffset;
    const int       fraglen = nw_algo->fraglen;
    int             tmpx, tmpy;
    FragCds        *frag1 = NULL, *frag2 = NULL;
    double        **distmat1 = NULL, **distmat2 = NULL;
    double        **Quaternion = NULL;
    double        **tmpmat = NULL, *b = NULL, *z = NULL;
    double         *evals = NULL, *coeff = NULL;
    double         *r = malloc(4 * sizeof(double)); /* only needs dimension 3 */
    double         *V = malloc(4 * sizeof(double)); /* only needs dimension 3 */
    double        **M = MatAlloc(4,4); /* only needs dimension 3 */
    double        **Q = MatAlloc(4,4); /* only needs dimension 3 */
    double        **tmpmat2 = MatAlloc(4,4); /* only needs dimension 3 */

    frag1 = FragCdsAlloc(fraglen);
    frag2 = FragCdsAlloc(fraglen);

    matoffset = (fraglen - 1) / 2;

    distmat1 = MatAlloc(fraglen, fraglen);
    distmat2 = MatAlloc(fraglen, fraglen);

    nx = nw_table->nx;
    ny = nw_table->ny;

    Quaternion = MatAlloc(4, 4);
    tmpmat = MatAlloc(4,4);
    b = malloc(4 * sizeof(double));
    z = malloc(4 * sizeof(double));

    coeff = (double *) calloc(5, sizeof(double));
    evals = (double *) calloc(4, sizeof(double));

    /* score by local structural similarity, as judged by RMSD^2 from a superposition
       of a window of residues surrounding each residue in one protein vs all others
       in the other protein */

    /* the box around the inner "matoffset" box is
       all set to zero by calloc when allocated */
    for (i = matoffset; i < nx - matoffset; ++i)
    {
        for (j = matoffset; j < ny - matoffset; ++j)
        {
            tmpx = i - matoffset;
            tmpy = j - matoffset;

            memcpy(&frag1->x[0], &cds1->x[tmpx], fraglen * sizeof(double));
            memcpy(&frag1->y[0], &cds1->y[tmpx], fraglen * sizeof(double));
            memcpy(&frag1->z[0], &cds1->z[tmpx], fraglen * sizeof(double));
            memcpy(&frag2->x[0], &cds2->x[tmpy], fraglen * sizeof(double));
            memcpy(&frag2->y[0], &cds2->y[tmpy], fraglen * sizeof(double));
            memcpy(&frag2->z[0], &cds2->z[tmpy], fraglen * sizeof(double));

            if (nw_algo->center_ca == 0)
            {
                CenterFragCA(frag1);
                CenterFragCA(frag2);
            }
            else
            {
                CenterFrag(frag1);
                CenterFrag(frag2);
            }

            nw_table->vmat[i+1][j+1] = QuarticHornFrag(frag1, frag2, coeff);
        }
    }


//     int tmplen;
//     for (i = 0; i < nx; ++i)
//     {
//         for (j = 0; j < ny; ++j)
//         {
//             tmpx = i - matoffset;
//
//             if (tmpx < 0)
//             {
//                 tmpx = 0;
//                 tmplen = i + matoffset - tmpx;
//             }
//             else if (tmpx > nx - fraglen)
//             {
//                 tmplen = nx - tmpx;
//             }
//
//             tmpy = j - matoffset;
//
//             if (tmpy < 0)
//             {
//                 tmpy = 0;
//                 tmplen = j + matoffset - tmpy;
//             }
//             else if (tmpy > ny - fraglen)
//             {
//                 tmplen = ny - tmpy;
//             }
//
//             memcpy(frag1->x, &cds1->x[tmpx], tmplen * sizeof(double));
//             memcpy(frag1->y, &cds1->y[tmpx], tmplen * sizeof(double));
//             memcpy(frag1->z, &cds1->z[tmpx], tmplen * sizeof(double));
//             memcpy(frag2->x, &cds2->x[tmpy], tmplen * sizeof(double));
//             memcpy(frag2->y, &cds2->y[tmpy], tmplen * sizeof(double));
//             memcpy(frag2->z, &cds2->z[tmpy], tmplen * sizeof(double));
//
//             if (nw_algo->center_ca == 0)
//             {
//                 CenterFragCA(frag1);
//                 CenterFragCA(frag2);
//             }
//             else
//             {
//                 CenterFrag(frag1);
//                 CenterFrag(frag2);
//             }
//
//             nw_table->vmat[i+1][j+1] = QuarticHornFrag(frag1, frag2, coeff) / (tmplen - 1.0);
//         }
//     }

/*     printf("\n%d %d %e", count, count2, (double)count/(double)count2); */

//     if (nw_algo->bayes == 1)
//     {
// /*         start_time = seconds(); */
//         BayesScores(nw_table);
// /*         end_time = seconds(); */
// /*         milliseconds = (double) (end_time - start_time) / 0.001; */
// /*         printf("\n    -> %.3f ms bayes log odds score calculation ", milliseconds); */
// /*         fflush(NULL); */
//     }
//     else if (nw_algo->logodds == 1)
//     {
//         /* start_time = seconds(); */
//         LogOddsScores(nw_table);
// /*         end_time = seconds(); */
// /*         milliseconds = (double) (end_time - start_time) / 0.001; */
// /*         printf("\n    -> %.3f ms log odds score calculation (qfast/bsearch) ", milliseconds); */
// /*         fflush(NULL); */
//     }
//     else if (nw_algo->logodds == 2)
//     {
//         MLScores(nw_table);
//     }
//     else /* straight scores */
//     {
//         for (i = 0; i < nx; ++i)
//             for (j = 0; j < ny; ++j)
//                 nw_table->fmat[i+1][j+1] = nw_table->vmat[i+1][j+1];
//     }

    MLScores(nw_table);

//     for (i = 0; i < nx; ++i)
//         for (j = 0; j < ny; ++j)
//             nw_table->fmat[i+1][j+1] = -nw_table->vmat[i+1][j+1];

    /* write out a file containing all the pairwise fragment RMSDs and RMSDs^2 */
//     FILE *fp = fopen("distances.txt", "w");
//     for (i = matoffset; i < nx - matoffset; ++i)
//         for (j = matoffset; j < ny - matoffset; ++j)
//             fprintf(fp, "%-16.10f %-16.10f %-16.10f\n",
//                     sqrt(nw_table->vmat[i+1][j+1]), nw_table->vmat[i+1][j+1], nw_table->fmat[i+1][j+1]);
//
//     fclose(fp);

    FragCdsFree(&frag1);
    FragCdsFree(&frag2);
    MyFree(coeff);
    MyFree(evals);
    MatDestroy(&Quaternion);
    MatDestroy(&tmpmat);
    MyFree(b);
    MyFree(z);
    MatDestroy(&distmat1);
    MatDestroy(&distmat2);

    MyFree(r);
    MyFree(V);
    MatDestroy(&tmpmat2);
    MatDestroy(&Q);
    MatDestroy(&M);
}


void
SuperDistFill(NWtable *nw_table, Cds *cds1, Cds *cds2, double *var)
{
    int             i, j;
    double         *lnvar = NULL, *invvar = NULL;
    double          sqrdist, tmpx, tmpy, tmpz;
    const int       nx = nw_table->nx, ny = nw_table->ny;
//    const double    phi = 2.0*stats->hierarch_p1;
//    const double    ndf = algo->ndf;
//    const double    term = (3.0 + ndf)/2.0;
//    const double    pi_term = -1.5 * log(2.0 * M_PI);
//    const double    digamma_term = 1.5 * (gsl_sf_psi(3.0*cnum + ndf) - log(3.0*cnum + ndf));
    const double  **c1 = NULL, **c2 = NULL;
    double        **fmat = nw_table->fmat;


    lnvar  = calloc((nx+1), sizeof(double));
    invvar = calloc((nx+1), sizeof(double));

    for (i = 0; i < nx; ++i)
    {
        lnvar[i+1] = 3.0 * log(var[i]);
        invvar[i+1] = 1.0 / var[i];
    }

    for (i = 0; i < nx; ++i)
    {
        c1 = (const double **) cds1->wc;

        for (j = 0; j < ny; ++j)
        {
            c2 = (const double **) cds2->wc;

            tmpx = c2[0][j] - c1[0][i];
            tmpy = c2[1][j] - c1[1][i];
            tmpz = c2[2][j] - c1[2][i];

            #ifdef FP_FAST_FMA
            sqrdist = fma(tmpx, tmpx, fma(tmpy, tmpy, tmpz * tmpz));
            #else
            sqrdist = tmpx*tmpx + tmpy*tmpy + tmpz*tmpz;
            #endif

            // DLT FIX --- most costly line in prog?
            fmat[i+1][j+1] = -0.5 * (sqrdist*invvar[i+1] + lnvar[i+1]); // DLT --- EM version
        }
    }

    MyFree(lnvar);
    MyFree(invvar);
}


int
ChargeCoordsFromSeqIDs(int *resSeqList, int listLen, const PDBCds *inPDB, Cds *cdsOut)
{
    double      xMeh = 0, yMeh = 0, zMeh = 0;
    double      xYay[2], yYay[2], zYay[2];
    int         listItem, pdbItem;
    int         countOfMeh, countOfYay;
    int         goodness = 0;

    cdsOut->vlen = listLen;
    cdsOut->x = malloc(listLen * sizeof(double));
    cdsOut->y = malloc(listLen * sizeof(double)); // The optimizer will take care of these seemingly-redundant expressions.
    cdsOut->z = malloc(listLen * sizeof(double));
    if ((cdsOut->x == NULL) || (cdsOut->y == NULL) || (cdsOut->z == NULL))
    {
        perror("\n ERROR");
        printf("\n ERROR: Failed to allocate coord storage (3 x %d doubles) in ChargeCoordsFromSeqIDs().\n", listLen);
        exit(EXIT_FAILURE);
    }

    pdbItem = 0;
    for (listItem = 0; listItem < listLen; listItem++)
    {
        // Find resSeqList[listItem] in inPDB. Calculate x, y, z from the charged O's or N(s).
        countOfMeh = 0;
        countOfYay = 0;
        while (inPDB->resSeq[pdbItem] <= resSeqList[listItem])
        {
            if (inPDB->resSeq[pdbItem] == resSeqList[listItem])
            {
                char        nameChar0 = inPDB->name[pdbItem][0];
                char        nameChar1 = inPDB->name[pdbItem][1];
                switch (nameChar0)
                {
                    case 'C':
                        if ((countOfMeh == 0) && (nameChar1 == 'A')) // C alpha
                        {
                            xMeh = inPDB->x[pdbItem]; // Worst case scenario: we're told that this is a ...
                            yMeh = inPDB->y[pdbItem]; // ... charged residue but we can't find the side ...
                            zMeh = inPDB->z[pdbItem]; // ... chain atoms-- use C alpha if all else fails.
                            countOfMeh = 1;
                        }
                        else if (nameChar1 == 'B') // C beta
                        {
                            xMeh = inPDB->x[pdbItem]; // C beta is less inappropriate than C alpha but ...
                            yMeh = inPDB->y[pdbItem]; // ... these scenarios should never happen anyways ...
                            zMeh = inPDB->z[pdbItem]; // ... but a typo of even one character actually could.
                            countOfMeh = 1;
                        }
                        break;

                    case 'O':
                        if ((nameChar1 == 'D') || (nameChar1 == 'E')) // Asp has OD1, OD2, Glu has OE1, OE2.
                        {
                            xYay[countOfYay] = inPDB->x[pdbItem];
                            yYay[countOfYay] = inPDB->y[pdbItem];
                            zYay[countOfYay] = inPDB->z[pdbItem];
                            countOfYay++;
                        }

                    case 'N':
                        if ((nameChar1 == 'H') || (nameChar1 == 'Z')) // Arg has 'NH1' and 'NH2', Lys has 'NZ'
                        {
                            xYay[countOfYay] = inPDB->x[pdbItem];
                            yYay[countOfYay] = inPDB->y[pdbItem];
                            zYay[countOfYay] = inPDB->z[pdbItem];
                            countOfYay++;
                        }
                }
            }
            pdbItem++;

            if (countOfYay == 2)
                break;
        }

        if (countOfYay == 2)
        {
            cdsOut->x[listItem] = (xYay[0] + xYay[1]) / 2.0;
            cdsOut->y[listItem] = (yYay[0] + yYay[1]) / 2.0;
            cdsOut->z[listItem] = (zYay[0] + zYay[1]) / 2.0;
            goodness++; // More wholesome goodness!
        }
        else if (countOfYay == 1)
        {
            cdsOut->x[listItem] = xYay[0];
            cdsOut->y[listItem] = yYay[0];
            cdsOut->z[listItem] = zYay[0];
            goodness++; // It's all good.
        }
        else // if (countOfMeh > 0)
        {
            cdsOut->x[listItem] = xMeh;
            cdsOut->y[listItem] = yMeh;
            cdsOut->z[listItem] = zMeh;
            // Not so good (doesn't count towards our goodness rating)!  :-(
        }
    }

    return goodness;
}


/*  #########################
 Describe the protein in electrostatic terms: both the electrostatic potential and its gradient at each
 landmark. In the Cds structure, I use "b" to represent the potential. X, y, & z represent the unit vector
 for the gradient of the potential, and "o" is the magnitude of that gradient.
######################### */
#define ESTATX_VECTORS 0
int
EStatxParamsFromStruct(int nRes, const Cds *inStruct, const PDBCds *inPDB, Cds *eParams)
{
    Cds         posCds = {{0}};
    Cds         negCds = {{0}};
    int        *posResSeq = NULL;
    int        *negResSeq = NULL;
    int         posCount = 0;
    int         negCount = 0;
    int         iii, jjj;
    int         goodness = 0;

    /*  First make lists of the charges (two lists: + and - ). We don't know the list lengths a priori
        but we know a maximum value for it, so allocate that much (which is nonetheless quite modest).
    */
    posResSeq = malloc(nRes * sizeof(int)); // Temp list for positively charged residues.
    negResSeq = malloc(nRes * sizeof(int)); // Temp list for negatively charged residues.
    if ((posResSeq == NULL) || (negResSeq == NULL))
    {
        perror("\n ERROR");
        printf("\n ERROR: Failed to allocate temp storage (2 x %d ints) in SimpleChargeFill().\n", nRes);
        exit(EXIT_FAILURE);
    }

    // Now find the charged residues. Store their indices in the temp lists, and determine the total counts.
    for (iii = 0; iii < nRes; iii++)
    {
        if (strncmp(inStruct->resName[iii], "ASP", 3) == 0)
        {
            negResSeq[negCount] = inStruct->resSeq[iii];
            negCount++;
        }
        else if (strncmp(inStruct->resName[iii], "GLU", 3) == 0)
        {
            negResSeq[negCount] = inStruct->resSeq[iii];
            negCount++;
        }
        else if (strncmp(inStruct->resName[iii], "LYS", 3) == 0)
        {
            posResSeq[posCount] = inStruct->resSeq[iii];
            posCount++;
        }
        else if (strncmp(inStruct->resName[iii], "ARG", 3) == 0)
        {
            posResSeq[posCount] = inStruct->resSeq[iii];
            posCount++;
        }
    }

    // Allocate and populate posCds and negCds with the coordinates of the charges.
    if (posCount > 0)
        goodness = ChargeCoordsFromSeqIDs(posResSeq, posCount, inPDB, &posCds);

    if (negCount > 0)
        goodness += ChargeCoordsFromSeqIDs(negResSeq, negCount, inPDB, &negCds);

    // Done with the indices now.
    free(posResSeq);
    free(negResSeq);

    if (posCount + negCount > 0)
    {
        double      distSqr, invDistSqr, invDist3rd;
        double      xx, yy, zz, bb; // Temporary values
        double      bmin, bmax;
#if (ESTATX_VECTORS == 1)
        double      dx, dy, dz; // Differences
        int         iPrev, iNext; // Tedious but essential bookkeeping
        const int   iLast = nRes - 1;
#endif

        /*  Allocate space, then use the coords of the charges to calculate e-static parameters for the
         landmarks (typically alpha carbons) of this structure.
         */
        eParams->vlen = nRes;
        eParams->b = calloc(nRes, sizeof(double));
        eParams->o = calloc(nRes, sizeof(double));
        eParams->x = calloc(nRes, sizeof(double));
        eParams->y = calloc(nRes, sizeof(double));
        eParams->z = calloc(nRes, sizeof(double));
        if ((eParams->y == NULL) || (eParams->x == NULL) || (eParams->z == NULL) || (eParams->b == NULL) || (eParams->o == NULL))
        {
            perror("\n ERROR");
            printf("\n ERROR: Failed to allocate eParams storage (5 x %d doubles) in SimpleChargeFill().\n", nRes);
            exit(EXIT_FAILURE);
        }

        if (goodness < posCount + negCount) // Don't give up if a random typo in a PDF file leads to an imperfect result.
            printf("\n Warning: Approximate locations were used for %d of %d charged residues in EStatxParamsFromStruct().\n", iii - goodness, iii);

        // For each landmark in inStruct, calculate e-static parameter(s) from the sum of the weighted
        // contributions of the positive & negative influences.
        if (posCount > 0)
        {
            for (iii = 0; iii < nRes; iii++)
            {
                /* Calculate a vector that would point from inStruct[iii] (x, y, z) towards posCds[jjj]
                 (x, y, z) with a length inversely proportional to the square of the distance between them.
                 Do this for each posCds[jjj], summing the intermediate results into eParams[iii], which at
                 the end of the calculations is an (origin-based) offset from inStruct[iii], and does not
                 include the coordinates of inStruct[iii] in it (and may need to be added later on below).
                 */
                xx = yy = zz = bb = 0.0;
                for (jjj = 0; jjj < posCount; jjj++)
                {
                    distSqr = SqrCdsDist(inStruct, iii, &posCds, jjj);
                    if (distSqr < 1.0) // Bond length < 1.0 ?!?!? Presumably an error (e.g., due to a typo).
                    {                  // Let's keep it non-fatal. Let dist = dist^2 = dist^3 = 1.0
                        bb += 1.0;
                        xx += posCds.x[jjj] - inStruct->x[iii];
                        yy += posCds.y[jjj] - inStruct->y[iii];
                        zz += posCds.z[jjj] - inStruct->z[iii];
                    }
                    else
                    {
                        invDistSqr = 1.0 / distSqr;
                        bb += invDistSqr;
                        invDist3rd = invDistSqr * sqrt(invDistSqr);
                        xx += invDist3rd * (posCds.x[jjj] - inStruct->x[iii]); // Vector length is proportional
                        yy += invDist3rd * (posCds.y[jjj] - inStruct->y[iii]); // to dist but we need 1/dist^2
                        zz += invDist3rd * (posCds.z[jjj] - inStruct->z[iii]); // so divide by dist^3.
                    }
                }
                eParams->x[iii] = xx; // These are just intermediate values.
                eParams->y[iii] = yy;
                eParams->z[iii] = zz;
                eParams->b[iii] = bb;
            }

            // Free the temporary memory structures we created.
            free(posCds.x);
            free(posCds.y);
            free(posCds.z);
        }

        if (negCount > 0)
        {
            for (iii = 0; iii < nRes; iii++)
            {
                xx = yy = zz = bb = 0.0;
                for (jjj = 0; jjj < negCount; jjj++)
                {
                    distSqr = SqrCdsDist(inStruct, iii, &negCds, jjj);
                    if (distSqr < 1.0) // See corresponding notes above.
                    {
                        bb += 1.0;
                        xx += negCds.x[jjj] - inStruct->x[iii];
                        yy += negCds.y[jjj] - inStruct->y[iii];
                        zz += negCds.z[jjj] - inStruct->z[iii];
                    }
                    else
                    {
                        invDistSqr = 1.0 / distSqr;
                        bb += invDistSqr;
                        invDist3rd = invDistSqr * sqrt(invDistSqr);
                        xx += invDist3rd * (negCds.x[jjj] - inStruct->x[iii]); // See corresponding note above.
                        yy += invDist3rd * (negCds.y[jjj] - inStruct->y[iii]);
                        zz += invDist3rd * (negCds.z[jjj] - inStruct->z[iii]);
                    }
                }
                // As above, but note that the influence of the negative charges is subtracted, not added.
                eParams->x[iii] -= xx;
                eParams->y[iii] -= yy;
                eParams->z[iii] -= zz;
                eParams->b[iii] -= bb;
            }

            // Free the temporary memory structures we created.
            free(negCds.x);
            free(negCds.y);
            free(negCds.z);
        }

        // Now scale the b's (the raw electrostatic potentials) into the range of [0, 1.0]
        bmin = 100000.0;
        bmax = -bmin;
        for (iii = 0; iii < nRes; iii++)
        {
            bb = eParams->b[iii];
            if (bb < bmin)
                bmin = bb;
            else if (bb > bmax)
                bmax = bb;
        }
        bmin = 0.999 * bmin; // Just a smidgen lower (the relative differences calculations prefer non-zero denominators-- huh!).
        bb = 1.0 / (bmax - bmin);
        for (iii = 0; iii < nRes; iii++)
            eParams->b[iii] = bb * (eParams->b[iii] - bmin);

        for (iii = 0; iii < nRes; iii++)
        {
            // Now calculate the magnitude of the gradient vector. Save that scalar in "o".
            xx = eParams->x[iii]; // Use xx, yy, zz to temporarily store the offsets that had been stored in eParams->x, y, z above.
            yy = eParams->y[iii];
            zz = eParams->z[iii];
            eParams->o[iii] = bb = sqrt(xx*xx + yy*yy + zz*zz);
#if (ESTATX_VECTORS == 1)
            // Next, normalize x, y, and z so they comprise a unit vector.
            xx /= bb;
            yy /= bb;
            zz /= bb;
            // eParams->x[iii] = xx;
            // eParams->y[iii] = yy;
            // eParams->z[iii] = zz;

            /* At this point, we have calculated a unit offset vector (x, y, z) for each landmark. This
             vector is rotation dependent!!! IOW, if you had two IDENTICAL structures that were rotated
             differently, the vectors-- although equivalent(!)-- would appear dissimilar during casual
             comparison. Here, I will correct that problem by replacing the offset coordinates with simple
             distances to three nearby points. Those distances are rotation-INDEPENDENT, and by triangulation,
             uniquely identify the vector (completely deterministic, and with no loss of information).
             */
            eParams->x[iii] = 1.0; // The distance from inStruct[iii] to inStruct[iii] + offset (that is, the length of the offset).
            xx += inStruct->x[iii]; // OK, now replace the xx, yy, zz offsets with x[iii] PLUS those offsets.
            yy += inStruct->y[iii];
            zz += inStruct->z[iii];
            if (iii == 0)
            {
                iNext = 1;
                iPrev = 1;
                // For iPrev, I could use 2, which would give me a unique length, but if the corresponding
                // landmark in the other struct actually HAS a previous landmark, the length to it is
                // probably closer to that of iii+1 than iii+2.
            }
            else
            {
                iPrev = iii - 1;
                if (iii == iLast)
                {
                    iNext = iLast - 1;
                    // Similarly to that above for iPrev, I could use iLast - 2, which would give me a
                    // unique length, but if the corresponding landmark in the other struct actually HAS
                    // a successor landmark, the length to it is probably closer to that of iii-1 than iii-2.
                }
                else
                    iNext = iii + 1;
            }
            dx = xx - inStruct->x[iPrev];
            dy = yy - inStruct->y[iPrev];
            dz = zz - inStruct->z[iPrev];
            eParams->y[iii] = sqrt(dx*dx + dy*dy + dz*dz); // The distance from x[iii] + offset to x[iii - 1]
            dx = xx - inStruct->x[iNext];
            dy = yy - inStruct->y[iNext];
            dz = zz - inStruct->z[iNext];
            eParams->z[iii] = sqrt(dx*dx + dy*dy + dz*dz); // The distance from x[iii] + offset to x[iii + 1]
#endif
        }
    }

    return (posCount + negCount); // Zero if there were NO charged residues in the entire list.
}


/*  #########################
    Modify the Needleman-Wunsch table using scores based on a comparison of the two proteins using
    this novel electrostatic representation.
    ######################### */
void
SimpleChargeFill(NWtable *nw_table, Cds *cds1, Cds *cds2)
{
    Cds             params1 = {{0}},   params2 = {{0}}; // Cds struct is convenient for holding the e-statics params.
    int             totChgs1,       totChgs2;

    // For each structure, make a list of characteristic values from a weighted sum of those charges.
    totChgs1 = EStatxParamsFromStruct(nw_table->nx, nw_table->cds0, nw_table->pdbc0, &params1);
    if (totChgs1 > 0)
    {
        // If neither structure has any charged residues whatsoever, this function will have no effect (so let's just skip it).
        // If exactly one struct has charges, this calculation will tend to make the other struct avoid them (which may not be helpful).
        totChgs2 = EStatxParamsFromStruct(nw_table->ny, nw_table->cds1, nw_table->pdbc1, &params2);
        if (totChgs2 > 0)
        {
            double        **codmat = NULL;
            double          tmpVal;
#if (ESTATX_VECTORS == 1)
            double          xx, yy, zz;
#endif
            int             iii, jjj;
            const int       numX = nw_table->nx, numY = nw_table->ny;

            // Generate the nw_table entries from all the pairwise score comparisons
            codmat = MatAlloc(numX, numY);

            // Use relative differences to compare the two sets of values.
            for (iii = 0; iii < numX; iii++)
                for (jjj = 0; jjj < numY; jjj++)
                {
                    tmpVal  = fabs(params1.b[iii] - params2.b[jjj]) / (params1.b[iii] + params2.b[jjj]); // Range of [0, 1.0]
                    tmpVal += fabs(params1.o[iii] - params2.o[jjj]) / (params1.o[iii] + params2.o[jjj]); // Range of [0, 1.0]
#if (ESTATX_VECTORS == 1)
                    xx = params1.x[iii] - params2.x[jjj];
                    yy = params1.y[iii] - params2.y[jjj];
                    zz = params1.z[iii] - params2.z[jjj];
                    tmpVal += 0.5 * sqrt(xx*xx + yy*yy + zz*zz);   // Range of [0, 1.0]
                    tmpVal *= 0.3333; // Each calculation above added something in the range [0, 1.0]
#else
                    tmpVal *= 0.5; // The relative differences calculations each give us values in the range [0, 1.0]
#endif
                    codmat[iii][jjj] = tmpVal;
                }

#if (DEBUG == 1)
            /*
            for (iii = 30; iii < 70; iii++)
            {
                for (jjj = 30; jjj < 70; jjj++)
                    printf("\t%f", codmat[iii][jjj]);
                printf("\n");
            }
            printf("\n###########\n");
             */
#endif

            for (iii = 0; iii < numX; iii++)
                for (jjj = 0; jjj < numY; jjj++)
                    nw_table->fmat[iii+1][jjj+1] -= codmat[iii][jjj];

            MatDestroy(&codmat);

            // free up any memory we borrowed.
            MyFree(params2.x);
            MyFree(params2.y);
            MyFree(params2.z);
            MyFree(params2.b);
        }

        MyFree(params1.x);
        MyFree(params1.y);
        MyFree(params1.z);
        MyFree(params1.b);
    }
}


void
ContactOrderFill(NWtable *nw_table, Cds *cds1, Cds *cds2)
{
    int             i, j;
    double          sqrdist;
    double          contact2 = 72.0; /* squared dist, equivalent to 8.5 angstroms */
    double         *coN1 = NULL, *coN2 = NULL, *coC1 = NULL, *coC2 = NULL;
    double        **codmat = NULL;
    int             nx, ny;
    int             skip;


    skip = 0;

    nx = nw_table->nx;
    ny = nw_table->ny;

    coN1 = (double *) calloc(nx, sizeof(double));
    coN2 = (double *) calloc(ny, sizeof(double));
    coC1 = (double *) calloc(nx, sizeof(double));
    coC2 = (double *) calloc(ny, sizeof(double));

    codmat = MatAlloc(nx, ny);

    for (i = 0; i < nx - skip; ++i)
    {
        for (j = i + skip; j < nx; ++j)
        {
            sqrdist = SqrCdsDist(cds1, i, cds1, j);

            if (sqrdist < contact2)
                coC1[i] += (double) (i-j);
        }
    }

    for (i = 0; i < ny - skip; ++i)
    {
        for (j = i + skip; j < ny; ++j)
        {
            sqrdist = SqrCdsDist(cds2, i, cds2, j);

            if (sqrdist < contact2)
                coC2[i] += (double) (i-j);
        }
    }

    for (i = skip; i < nx; ++i)
    {
        for (j = 0; j < i - skip; ++j)
        {
            sqrdist = SqrCdsDist(cds1, i, cds1, j);

            if (sqrdist < contact2)
                coN1[i] += (double) (j-i);
        }
    }

    for (i = skip; i < ny; ++i)
    {
        for (j = 0; j < i - skip; ++j)
        {
            sqrdist = SqrCdsDist(cds2, i, cds2, j);

            if (sqrdist < contact2)
                coN2[i] += (double) (j-i);
        }
    }

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            codmat[i][j] = 1.0 + fabs(coN1[i] - coN2[j]); // add one for good luck

    if (nw_algo->bayes == 1)
        BayesScoresCO(codmat, nx, ny);
    else if (nw_algo->logodds == 1)
        LogOddsScoresCO(codmat, nx, ny, nw_algo->bayespost);
    else if (nw_algo->logodds == 2)
    {
        for (i = 0; i < nx; ++i)
            for (j = 0; j < ny; ++j)
                codmat[i][j] *= codmat[i][j];

        MLScoresCO(codmat, nx, ny);
    }

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            nw_table->fmat[i+1][j+1] -= codmat[i][j];

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            codmat[i][j] = 1.0 + fabs(coC1[i] - coC2[j]); // add one for good luck

    if (nw_algo->bayes == 1)
        BayesScoresCO(codmat, nx, ny);
    else if (nw_algo->logodds == 1)
        LogOddsScoresCO(codmat, nx, ny, nw_algo->bayespost);
    else if (nw_algo->logodds == 2)
    {
        for (i = 0; i < nx; ++i)
            for (j = 0; j < ny; ++j)
                codmat[i][j] *= codmat[i][j];

        MLScoresCO(codmat, nx, ny);
    }

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            nw_table->fmat[i+1][j+1] -= codmat[i][j];

    MyFree(coN1);
    MyFree(coN2);
    MyFree(coC1);
    MyFree(coC2);
    MatDestroy(&codmat);
}


void
BlosumFill(NWtable *nw_table, Cds *cds1, Cds *cds2)
{
    int             i, j;
    int             index0, index1;
    double          score = 0.0;


    for (i = 0; i < nw_table->nx; ++i)
    {
        index0 = (int)(strstr(&aa3[0], cds1->resName[i]) - &aa3[0]) / 3;
        for (j = 0; j < nw_table->ny; ++j)
        {
            index1 = (int)(strstr(&aa3[0], cds2->resName[j]) - &aa3[0]) / 3;

            if (index0 > 19 || index1 > 19 || index0 < 0 || index1 < 0)
                continue;

            switch(nw_algo->blosum)
            {
                case 30:
                    score = s_blosum30[index0][index1];
                    break;
                case 35:
                    score = s_blosum35[index0][index1];
                    break;
                case 40:
                    score = s_blosum40[index0][index1];
                    break;
                case 50:
                    score = s_blosum50[index0][index1];
                    break;
                case 62:
                    score = s_blosum62[index0][index1];
                    break;
                case 100:
                    score = s_blosum100[index0][index1];
                    break;
                case 'h':
                    score = s_hsdm[index0][index1];
                    break;
                case 's':
                    score = s_simple[index0][index1];
                    break;
                case 'r':
                    score = s_simpler[index0][index1];
                    break;
            }

            nw_table->fmat[i+1][j+1] += score;
        }
    }
}


void
DsspFill(NWtable *nw_table)
{
    int             i, j;
    int             index0, index1;

    for (i = 0; i < nw_table->nx; ++i)
    {
        for (j = 0; j < nw_table->ny; ++j)
        {
            index0 = (int) (strchr(&ss1[0], nw_table->ss0[i]) - &ss1[0]);
            index1 = (int) (strchr(&ss1[0], nw_table->ss1[j]) - &ss1[0]);

            if (index0 > 7 || index1 > 7 || index0 < 0 || index1 < 0)
                continue;

            nw_table->fmat[i+1][j+1] += s_dssp8[index0][index1];
        }
    }
}


/* Selects a new subset of coordinates based on a key string and a string
   array to test the key against */
static void
CdsKeySelxn(double **newc, int newclen, const PDBCds *oldc,
            const char **teststr, const char *key, int keylen)
{
    int             i, j;

    i = j = 0;
    while(j < newclen)
    {
        if (strncmp(teststr[i], key, keylen) == 0)
        {
            newc[j][0] = oldc->x[i];
            newc[j][1] = oldc->y[i];
            newc[j][2] = oldc->z[i];
            ++j;
        }
        ++i;
    }
}


/* scores the NW matrix based on per residue psi-phi dihedral angle similarity */
void
AnglesFill(NWtable *nw_table, const PDBCds *pdbc1, const PDBCds *pdbc2)
{
    int             i, j;
    double        **mat = NULL, **C1 = NULL, **C2 = NULL;
    double        **O1 = NULL, **O2 = NULL, **N1 = NULL, **N2 = NULL;
    double        **CA1 = NULL, **CA2 = NULL;
    double          tmp;
    int             nx, ny;

    nx = nw_table->nx;
    ny = nw_table->ny;

    mat = MatAlloc(nx, ny);

    C1  = MatAlloc(nx, 3);
    O1  = MatAlloc(nx, 3);
    N1  = MatAlloc(nx, 3);
    CA1 = MatAlloc(nx, 3);

    C2  = MatAlloc(ny, 3);
    O2  = MatAlloc(ny, 3);
    N2  = MatAlloc(ny, 3);
    CA2 = MatAlloc(ny, 3);

    CdsKeySelxn(C1,  nx, pdbc1, (const char **) pdbc1->name, "C  ", 3);
    CdsKeySelxn(C2,  ny, pdbc2, (const char **) pdbc2->name, "C  ", 3);
    CdsKeySelxn(O1,  nx, pdbc1, (const char **) pdbc1->name, "O  ", 3);
    CdsKeySelxn(O2,  ny, pdbc2, (const char **) pdbc2->name, "O  ", 3);
    CdsKeySelxn(N1,  nx, pdbc1, (const char **) pdbc1->name, "N  ", 3);
    CdsKeySelxn(N2,  ny, pdbc2, (const char **) pdbc2->name, "N  ", 3);
    CdsKeySelxn(CA1, nx, pdbc1, (const char **) pdbc1->name, "CA ", 3);
    CdsKeySelxn(CA2, ny, pdbc2, (const char **) pdbc2->name, "CA ", 3);

    /* psi angle */
    for (i = 0; i < nx - 1; ++i)
    {
        for (j = 0; j < ny - 1; ++j)
        {
            tmp = Dihedral(CA2[j], N2[j], C2[j], N2[j+1]) -
                  Dihedral(CA1[i], N1[i], C1[i], N1[i+1]);

            mat[i][j] = tmp*tmp;
            /* printf("\n% f", mat[i][j]); */
        }
    }

    /* phi angle */
    for (i = 1; i < nx; ++i)
    {
        for (j = 1; j < ny; ++j)
        {
            tmp = Dihedral(N2[j], C2[j-1], CA2[j], C2[j]) -
                  Dihedral(N1[i], C1[i-1], CA1[i], C1[i]);

            mat[i][j] += tmp*tmp + 0.1;
            /* printf("\n% f", mat[i][j]); */
        }
    }

    mat[0][ny-1] = 0.1;
    mat[nx-1][0] = 0.1;

    if (nw_algo->bayes == 1)
        BayesScoresCO(mat, nx, ny);
    else if (nw_algo->logodds == 1)
        LogOddsScoresCO(mat, nx, ny, nw_algo->bayespost);
    else if (nw_algo->logodds == 2)
        MLScoresCO(mat, nx, ny);

    for (i = 1; i < nx-1; ++i)
        for (j = 1; j < ny-1; ++j)
            nw_table->fmat[i+1][j+1] += mat[i][j];

    MatDestroy(&mat);
    MatDestroy(&C1);
    MatDestroy(&O1);
    MatDestroy(&N1);
    MatDestroy(&CA1);
    MatDestroy(&C2);
    MatDestroy(&O2);
    MatDestroy(&N2);
    MatDestroy(&CA2);
}


/* Returns the dot product between two 3D vectors */
static double
DotProd(const double *x, const double *y)
{
    return (x[0]*y[0] + x[1]*y[1] + x[2]*y[2]);
}


/* returns the cosine of the angle between two vectors defined by
   four atoms */
double
Dihedral(const double *atm1, const double *atm2,
         const double *atm3, const double *atm4)
{
    int             i;
    double          vec1[3], vec2[3];

    for (i = 0; i < 3; ++i)
    {
        vec1[i] = atm2[i] - atm1[i];
        vec2[i] = atm4[i] - atm3[i];
    }

/*     NormVec(vec1); */
/*     NormVec(vec2); */

    return(/* acos */(DotProd((const double *) vec1, (const double *) vec2)));
}


void
LogOddsScoresCO(double **mat, int nx, int ny, int bayespost)
{
    int             i, j, len;
    double         *list = NULL, *listptr = NULL;
/*     double          ave_var; */
/*     int             ncells; */

    len = nx * ny;
    list = (double *) calloc(len, sizeof(double));

    memcpy(list, mat[0], len * sizeof(double));
    qsort(list, len, sizeof(double), dblcmp);

    if(bayespost == 1)
    {
        for (i = 0; i < nx; ++i)
        {
            for (j = 0; j < ny; ++j)
            {
                listptr = bsearch(&mat[i][j], list, len, sizeof(double), dblcmp);
                mat[i][j] = log(1.0 / (2.0 * ((double)(listptr - list + 1) / (double)len)));
            }
        }
    }
    else
    {
        for (i = 0; i < nx; ++i)
        {
            for (j = 0; j < ny; ++j)
            {
                listptr = bsearch(&mat[i][j], list, len, sizeof(double), dblcmp);
                mat[i][j] = log((double)(listptr - list + 1) / (double)len);
                /* printf("\n% f", mat[i][j]); */
            }
        }
    }

    MyFree(list);
}


void
BayesScoresCO(double **mat, int nx, int ny)
{
    int             i, j;
    double          sum, mean, std_dev;

    sum = 0.0;
    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            sum += mat[i][j];

    mean = sum / (double)(nx * ny);

    std_dev = 0.0;
    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            std_dev += SQR(mat[i][j] - mean);

    std_dev /= (double)(nx * ny - 1);

    for (i = 0; i < nx; ++i)
    {
        for (j = 0; j < ny; ++j)
        {
            if (mat[i][j] >= mean)
                mat[i][j] = 0.0;
            else
                mat[i][j] = -(SQR((mat[i][j] - mean) / std_dev)) / 2;
        }
    }
}


void
LogOddsScores(NWtable *nw_table)
{
    int             i, j, k;
    double         *list = NULL, *listptr = NULL, tmp;
    int             len, nx, ny;
    int             offset;

    nx = nw_table->nx;
    ny = nw_table->ny;
    offset = (nw_algo->fraglen - 1) / 2;

    len = (nx - nw_algo->fraglen + 1) * (ny - nw_algo->fraglen + 1);
    list = (double *) calloc(len, sizeof(double));

    k = 0;
    for (i = offset; i < nx - offset; ++i)
    {
        for (j = offset; j < ny - offset; ++j)
        {
            list[k] = nw_table->vmat[i+1][j+1];
            ++k;
        }
    }

    qsort(list, len, sizeof(double), dblcmp);

    for (i = offset; i < nx - offset; ++i)
    {
        for (j = offset; j < ny - offset; ++j)
        {
            listptr = bsearch(&nw_table->vmat[i+1][j+1], list, len, sizeof(double), dblcmp);
            tmp = (double)(listptr - list + 1) / (double)len;
            /* nw_table->fmat[i+1][j+1] = tmp / (1.0 - tmp); */
            /* printf("\n %5.3e %5.3e", tmp, nw_table->mat[i][j]->score); */
            nw_table->fmat[i+1][j+1] = log(tmp);
            //nw_table->mat[i][j]->score = -log(1.0-tmp+0.0001);
            //nw_table->mat[i][j]->score = log(tmp / (1.0 - tmp+0.0001));
        }
    }

    MyFree(list);
}


void
MLScores(NWtable *nw_table)
{
    int             i, j;
    int             nx, ny;
    //int             offset;
    double        **fmat = nw_table->fmat;
    const double  **vmat = (const double **) nw_table->vmat;
    //double          term = -0.5 - 0.5 * log(2.0 * MY_PI);

    nx = nw_table->nx;
    ny = nw_table->ny;
    //offset = (nw_algo->fraglen - 1) / 2;

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            //nw_table->mat[i][j]->score += 0.5*log(nw_table->vmat[i+1][j+1]) + term;
            fmat[i+1][j+1] -= 0.5*log(vmat[i+1][j+1] + 0.3);
}


void
MLScoresCO(double **mat, int nx, int ny)
{
    int             i, j;
    //double          term = 0.5 + 0.5 * log(2.0 * MY_PI);

    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
        {
            //printf("%3d %3d % 16.6f % 16.6f\n", i, j, mat[i][j], log(mat[i][j]));
            mat[i][j] = 0.5 * log(mat[i][j]); // + term;
        }
}


void
BayesScores(NWtable *nw_table)
{
    int             i, j;
    double          sum, mean, std_dev, tmp;
    double        **vmat = nw_table->vmat;
    int             nx, ny;

    nx = nw_table->nx;
    ny = nw_table->ny;

    sum = 0.0;
    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            sum += vmat[i+1][j+1];

    mean = sum / (double)(nx * ny);
    SCREAMF(mean);

    std_dev = 0.0;
    for (i = 0; i < nx; ++i)
        for (j = 0; j < ny; ++j)
            std_dev += SQR(vmat[i+1][j+1] - mean);

    std_dev /= (double)(nx * ny - 1);
    std_dev = sqrt(std_dev);
    SCREAMF(std_dev);

    for (i = 0; i < nx; ++i)
    {
        for (j = 0; j < ny; ++j)
        {
            tmp = (vmat[i+1][j+1] - mean) / std_dev;
            tmp = erfc(-tmp);
            /* mat[i][j]->score = log(tmp / (1.0 - tmp)); */
            //mat[i][j]->score = log(tmp);
            vmat[i+1][j+1] = tmp;
        }
    }
}

