/*
    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/>.

    -/_|:|_|_\-
*/
#include "theseus.h"
#include "theseuslib.h"

#include "StructAlign.h"
#include "NWfill.h"

extern CdsArray       *cdsA; /* main array of selected pdb cds, never modified */
extern PDBCdsArray    *pdbA; /* pdb file coordinate info, much of it unused */
extern Algorithm      *algo;
extern NWalgo         *nw_algo;
extern Statistics     *stats;

extern NWtable        *nw_table;
extern PDBCdsArray    *alignA;
extern int             alignlen;
extern int             crush_again;

extern double          ave_gapscore;
extern double         *egap;

extern SAData        **sa_data;
extern pthread_t      *callThd;
extern pthread_attr_t  attr;
extern int             thrdnum;


static const char atoms0[] = ":CA :C1*:C1'";
static const char atoms1[] = ":N  :C  :O  :CA :";


static int
AltLocSelxn(int altLoc)
{
    if (altLoc == ' ' ||
        altLoc == 'A' ||
        altLoc == '1')
        return(1);
    else
        return(0);
}


static int
RecordSelxn(char *record)
{
    if (strncmp(record, "ATOM  ", 6) == 0 ||
        strncmp(record, "HETATM", 6) == 0)
        return(1);
    else
        return(0);
}


static int
AtomSelxn(char *name, int mode)
{
    switch(mode)
    {
        case 0: /* "CA :P  " */
            if (strstr(atoms0, name))
                return(1);
            break;
        case 1: /* "N  :C  :O  :CA :" */
            if (strstr(atoms1, name))
                return(1);
            break;
    }

    return(0);
}



void
GetNWCds(CdsArray *nwcdsA, CdsArray *cdsA)
{
    int             nw_vlen, vlen;
    const int       cnum = cdsA->cnum;
    int             i, j, k;
    Cds            *nwcdsi = NULL;
    const Cds      *cdsi = NULL;

    CdsArrayAllocNum(nwcdsA, cnum);

    for (i = 0; i < cnum; ++i)
    {
        cdsi = (const Cds *) cdsA->cds[i];

        vlen = cdsi->vlen;
        nw_vlen = 0;
        for (j = 0; j < vlen; ++j)
        {
            if (cdsi->nu[j] == 1)
                ++nw_vlen;
        }

        nwcdsi = nwcdsA->cds[i] = CdsInit();
        CdsAlloc(nwcdsi, nw_vlen);

        strcpy(nwcdsi->filename, cdsi->filename);

        for (j = k = 0; j < vlen && k < nw_vlen; ++j)
        {
            if (cdsi->nu[j] == 1)
            {
                strncpy(nwcdsi->resName[k], cdsi->resName[j], 3);
                nwcdsi->chainID[k] = cdsi->chainID[j];
                nwcdsi->resSeq[k]  = cdsi->resSeq[j];
                nwcdsi->x[k]       = cdsi->x[j];
                nwcdsi->y[k]       = cdsi->y[j];
                nwcdsi->z[k]       = cdsi->z[j];
                nwcdsi->o[k]       = cdsi->o[j];
                nwcdsi->b[k]       = cdsi->b[j];
                ++k;
            }
        }
    }

    nwcdsA->avecds = CdsInit();
    CdsAlloc(nwcdsA->avecds, cdsA->avecds->vlen);
    CdsCopyAll(nwcdsA->avecds, cdsA->avecds);
    //PrintCds(nwcdsA->cds[0]);
    //PrintCds(nwcdsA->cds[1]);
    //PrintCds(nwcdsA->avecds);
    //exit(0);
}


CdsArray
*GetNWCdsSel(const PDBCdsArray *pdbA)
{
    int             vlen, slxnlen = 0;
    int            *selection_index = NULL; /* array of ints corresponding to selected atoms */
    Cds            *cdsi = NULL;
    const PDBCds   *pdbi = NULL;
    int             i, j, lower, upper;

    if (pdbA->cnum < 2)
    {
        fprintf(stderr, "\n ERROR23: Number of cds in pdbfiles is less than 2. \n\n");
        exit(EXIT_FAILURE);
    }

    CdsArrayAllocNum(cdsA, pdbA->cnum);

    for (i = 0; i < pdbA->cnum; ++i)
    {
        lower = 0;
        upper = vlen = pdbA->cds[i]->vlen;

        if (upper - 4 < lower)
        {
            fprintf(stderr, "\n ERROR: upper residue bound must be at least 3 greater than the lower bound. \n\n");
            exit(EXIT_FAILURE);
        }

        selection_index = (int *) calloc (vlen, sizeof(int));
        if (selection_index == NULL)
        {
            perror("\n ERROR");
            fprintf(stderr, " ERROR: could not allocate memory for selection_index in GetCdsSelection(). \n\n");
            exit(EXIT_FAILURE);
        }

        pdbi = (const PDBCds *) pdbA->cds[i];

        slxnlen = 0;
        for (j = 0; j < vlen; ++j)
        {
            if (RecordSelxn(pdbi->record[j]) &&
                AltLocSelxn(pdbi->altLoc[j]) &&
                AtomSelxn(pdbi->name[j], algo->atoms))
            {
                selection_index[slxnlen] = j;
                ++slxnlen;
            }
        }

        cdsi = cdsA->cds[i] = CdsInit();
        CdsAlloc(cdsi, slxnlen);

        cdsi->model = pdbi->model;
        strcpy(cdsi->filename, pdbi->filename);

        for (j = 0; j < slxnlen; ++j)
        {
            strncpy(cdsi->resName[j], pdbi->resName[selection_index[j]], 3);
            cdsi->chainID[j] = pdbi->chainID[selection_index[j]];
            cdsi->resSeq[j]  = pdbi->resSeq[selection_index[j]];
            cdsi->x[j]       = pdbi->x[selection_index[j]];
            cdsi->y[j]       = pdbi->y[selection_index[j]];
            cdsi->z[j]       = pdbi->z[selection_index[j]];
            cdsi->o[j]       = pdbi->occupancy[selection_index[j]];
            cdsi->b[j]       = pdbi->tempFactor[selection_index[j]];
        }

        MyFree(selection_index);
    }

//    cdsA->vlen = slxnlen;
    cdsA->avecds = CdsInit();
//    CdsAlloc(cdsA->avecds, slxnlen);
    /*PrintCds(cdsA->cds[0]);
    PrintCds(cdsA->cds[1]);*/

    return (cdsA);
}

#if 0
static CdsArray
*StructAlignObsolete(PDBCdsArray *pdbA, int i0, int i1, NWtable *nw_table)
{
    cdsA = GetNWCdsSel((const PDBCdsArray *) pdbA);

    nw_table->pdbc0 = pdbA->cds[i0];
    nw_table->pdbc1 = pdbA->cds[i1];
    nw_table->cds0  = cdsA->cds[i0];
    nw_table->cds1  = cdsA->cds[i1];

    if (nw_algo->dsspflag == 1)
    {
        nw_table->ss0 = dssplite(nw_table->pdbc0->x, nw_table->pdbc0->y, nw_table->pdbc0->z,
                                 nw_table->pdbc0->resName, nw_table->pdbc0->resSeq,
                                 nw_table->pdbc0->name, nw_table->pdbc0->vlen);

        nw_table->ss1 = dssplite(nw_table->pdbc1->x, nw_table->pdbc1->y, nw_table->pdbc1->z,
                                 nw_table->pdbc1->resName, nw_table->pdbc1->resSeq,
                                 nw_table->pdbc1->name, nw_table->pdbc1->vlen);
    }

    NWfill(nw_table, nw_table->cds0, nw_table->cds1, cdsA->var);

#define CalcGapScore    1

#if (CalcGapScore == 1)
    /* This code was previously in place but not working right: nx & ny were both (inappropriately) zero.
       As a result, ave_gapscore was zero. Once I changed the code to use the right nx & ny, it caused
       problems downstream. I think that was mainly because the ave_gapscore was negative thousands
       instead of a negative value between zero-ish and one-ish. I think the total that is calculated
       should be divided by the count of gaps that contribute to the total (or some such divisor) to get
       a value in the right range. Then again, ave_gapscore is only used in one place in the code, and
       since it was never calculated correctly here, it seems to me that it could be dispensed with
       entirely. There is a command line option to set a value for it, and that could be left in place,
       but it was probably never used anyways.
       - RAR, 2015-05-11
    */
    double    **fmat = nw_table->fmat; // 2015-05-09 RAR: Not valid until after NWFill (so don't initialize prior!)
    int         nx = nw_table->nx; // Likewise
    int         ny = nw_table->ny; // Likewise
    int         i, j;

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

    //     for (i = 0; i < nx; ++i)
    //         for (j = 0; j < ny; ++j)
    //             fmat[i+1][j+1] -= ave_gapscore;
#else
    ave_gapscore = -0.5;  // The old code produced a 0, which was not terrible (but only by coincidence since there was an error).
#endif

    NWscore(nw_table);
    NWtraceback(nw_table);

#if (CalcGapScore == 0)
    // RAR 2015-05-11  I stole this from StructAlignTh, to see if it would mess anything up. It doesn't.
    double      egapi;
    int         ngaps;
    int         j, k;
    NWtable    *nwti = nw_table;

    egapi = 0.0;
    ngaps = 0;
    for (j = 1; j <= nwti->nx; ++j)
    {
        for (k = 1; k <= nwti->ny; ++k)
        {
            if (nwti->pmat[j][k] == 'l') // u for only gaps in mean, l for gaps in seq
            {
                egapi += nwti->fmat[j][k];
                ngaps++;
            }
        }
    }

    egapi /= ngaps;
    egap[i1] = egapi; // In the original code it had "nwti->cindex" instead of "i1".
#endif

    //    PrintScores(nw_table);
    //    PrintSumScores(nw_table);
    //    PrintTraceback(nw_table);


    //    printf("\nI===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-I");

    return (cdsA);
}
#endif


void
StructAlign(PDBCdsArray *pdbA, int iCommon, CdsArray *cdsA, NWtable **nwTableA)
{
//#if (DEBUG == 1)
    char            fNamePt1[FILENAME_MAX];
    char            fNamePt2[FILENAME_MAX];
//#endif
    NWtable    *nwti = NULL;
    int         cnum = cdsA->cnum;
    int         ii, jj, iPartner;

    iPartner = 0;
    for (ii = 0; ii < cnum; ++ii)
    {
        if (ii == iCommon)
            continue;

        nwti = nwTableA[iPartner];
        nwti->cindex = iPartner;
        nwti->msa->seqnum = 2;
        nwti->pdbc0 = pdbA->cds[iCommon];
        nwti->pdbc1 = pdbA->cds[ii];
        nwti->cds0  = cdsA->cds[iCommon];
        nwti->cds1  = cdsA->cds[ii];

        if (nw_algo->dsspflag == 1)
        {
            nwti->ss0 = dssplite(nwti->pdbc0->x, nwti->pdbc0->y, nwti->pdbc0->z,
                                 nwti->pdbc0->resName, nwti->pdbc0->resSeq,
                                 nwti->pdbc0->name, nwti->pdbc0->vlen);

            nwti->ss1 = dssplite(nwti->pdbc1->x, nwti->pdbc1->y, nwti->pdbc1->z,
                                 nwti->pdbc1->resName, nwti->pdbc1->resSeq,
                                 nwti->pdbc1->name, nwti->pdbc1->vlen);
        }

        NWfill(nwti, nwti->cds0, nwti->cds1, cdsA->var);
        NWscore(nwti);
        NWtraceback(nwti);

        strcpy(nwti->msa->name[0], cdsA->cds[iCommon]->filename);
        strcpy(nwti->msa->name[1], cdsA->cds[ii]->filename);

//#if (DEBUG == 1)
        // Write the pairwise alignments for debugging purposes.
        sprintf(fNamePt1, "Pairwise_%s", nwti->msa->name[0]);
        jj = strlen(fNamePt1);

        if (jj > FILENAME_MAX / 2)
            jj = FILENAME_MAX / 2;
        else
            jj -= 4; // To get rid of the filename extension

        fNamePt1[jj] = 0;
        sprintf(fNamePt2, "%s_%s", fNamePt1, nwti->msa->name[1]);
        jj = strlen(fNamePt2);

        if (jj >= FILENAME_MAX)
            jj = FILENAME_MAX - 1;
        else
            jj -= 4; // To get rid of the filename extension

        fNamePt2[jj] = 0;
        writea2m(nwti->msa, 0, nwti->msa->seqlen, fNamePt2);
//#endif

        iPartner++;
    }
}


static void
*struct_align_pth(void *sa_data_ptr)
{
    SAData        *sa_data = (SAData *) sa_data_ptr;
    const int      iPartner =(const int) sa_data->iPartner;
    const int      ii = (const int) sa_data->ii;
    const int      iCommon = (const int) sa_data->iCommon;
    NWtable       *nwti = NULL;

    nwti = sa_data->nwTableA[iPartner];
    nwti->cindex = sa_data->iPartner;
    nwti->msa->seqnum = 2;
    nwti->pdbc0 = sa_data->pdbA->cds[iCommon];
    nwti->pdbc1 = sa_data->pdbA->cds[ii];
    nwti->cds0  = sa_data->cdsA->cds[iCommon];
    nwti->cds1  = sa_data->cdsA->cds[ii];

    NWfill(nwti, nwti->cds0, nwti->cds1, cdsA->var);
    NWscore(nwti);
    NWtraceback(nwti);

    strcpy(nwti->msa->name[0], sa_data->cdsA->cds[iCommon]->filename);
    strcpy(nwti->msa->name[1], sa_data->cdsA->cds[ii]->filename);

//     printf("StructAlign thread %3d DONE\n", ii);
//     fflush(NULL);

    pthread_exit((void *) 0);
}


void
StructAlignPth(PDBCdsArray *pdbA, int iCommon, CdsArray *cdsA, NWtable **nwTableA, SAData **sa_data, const int thrdnum)
{
    int            ii, iPartner, rc = 0;

    iPartner = 0;
    for (ii = 0; ii < thrdnum ; ++ii)
    {
        if (ii == iCommon)
            continue;

        sa_data[ii]->ii = ii;
        sa_data[ii]->iPartner = iPartner;
        sa_data[ii]->pdbA = pdbA;
        sa_data[ii]->iCommon = iCommon;
        sa_data[ii]->cdsA = cdsA;
        sa_data[ii]->nwTableA = nwTableA;

        rc = pthread_create(&callThd[ii], &attr, struct_align_pth, (void *) sa_data[ii]);

        if (rc)
        {
            printf("ERROR811: return code from pthread_create() %d is %d\n", ii, rc);
            exit(EXIT_FAILURE);
        }

        iPartner++;
    }

    iPartner = 0;
    for (ii = 0; ii < thrdnum; ++ii)
    {
        if (ii == iCommon)
            continue;

        rc = pthread_join(callThd[ii], (void **) NULL);

        if (rc)
        {
            printf("ERROR812: return code from pthread_join() %d is %d\n", ii, rc);
            exit(EXIT_FAILURE);
        }

        iPartner++;
    }

    return;
}


/* score the table, summing var values and recording pointers */
/* for standard BLAST BLOSUM62 matrix, set gappen to 3.812 and extend
   to 0.090909 */
void
NWscore(NWtable *nw_table)
{
    int             i, j;
    const int       nx = nw_table->nx, ny = nw_table->ny;
    double          gappen, us, ls, ds;
//    double          extend = nw_algo->extend;
    double        **smat = nw_table->smat;
    char          **pmat = nw_table->pmat;
//    double        **gmat = nw_table->gmat;
    const double  **fmat = (const double **) nw_table->fmat;

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

    if (nw_algo->gap == 1)
    {
        if (nw_algo->open != 0.0)
            gappen = nw_algo->open;
        else
            gappen = -3.34;
    }
    else
    {
        gappen = 0.0;
    }

    //gappen = ave_gapscore - 3.0; // 3 = (4 + 2)/2
    gappen = ave_gapscore - 6.0;
    //gappen = -3.34;

    /* score the table */
    /* if gaps start as 'open', set gmat[i][j] = 0.0 */
    smat[0][0] = 0.0;
    pmat[0][0] = 'n';
    //gmat[0][0] = 1.0; //gmat already initialized to 0

    for (i = 1; i <= nx; ++i) /* j = 0, 0 column, cells to left are index j = -1, all zero score */
    {
        smat[i][0] = gappen * i;
        pmat[i][0] = 'u';
        //gmat[i][0] = 1.0;  //gmat already initialized to 0
    }

    for (j = 1; j <= ny; ++j)
    {
        smat[0][j] = gappen * j;
        pmat[0][j] = 'l';
        //gmat[0][j] = 1.0;  //gmat already initialized to 0
    }

    for (i = 1; i <= nx; ++i)
    {
        for (j = 1; j <= ny; ++j)
        {
            /* calculate scores; up & left are gaps */
            /* up is gap in i seq0 (mean), left is gap in j seq1 */
            ds = smat[i-1][j-1] + fmat[i][j];
            us = smat[i-1][j]   + gappen; //(gappen * gmat[i-1][j]);
            ls = smat[i][j-1]   + gappen; //(gappen * gmat[i][j-1]);

            /* choose best score */
            if (ds >= us)
            {
                if (ds >= ls)
                {
                    smat[i][j] = ds;
                    pmat[i][j] = 'd';
                    //gmat[i][j] = 1.0; /* gap closed */
                }
                else
                {
                    smat[i][j] = ls;
                    pmat[i][j] = 'l';
                    //gmat[i][j] = extend;
                }
            }
            else
            {
                if (us >= ls)
                {
                    smat[i][j] = us;
                    pmat[i][j] = 'u';
                    //gmat[i][j] = extend;
                }
                else
                {
                    smat[i][j] = ls;
                    pmat[i][j] = 'l';
                    //gmat[i][j] = extend;
                }
            }
            //printf("%3d %3d % 12.3f % 12.3f\n", i, j, fmat[i][j], smat[i][j]);
        }
    }

    nw_table->nw_score = nw_table->smat[nx][ny];
}


/* score the table, summing var values and recording pointers */
/* for standard BLAST BLOSUM62 matrix, set gappen to 3.812 and extend
   to 0.090909 */
void
NWscoreTh(NWtable *nw_table)
{
    int             i, j;
    const int       nx = nw_table->nx, ny = nw_table->ny;
    const int       cnum = cdsA->cnum;
    double          lgappen, us, ls, ds;
//    double          lnpi_g, lnpi_m;
    const double    phi = 2.0*stats->hierarch_p1;
//    const double    ndf = algo->ndf;
    const double    lnexpvar = -1.5 * log(phi);
//    double          extend = nw_algo->extend;
    double        **smat = nw_table->smat;
    char          **pmat = nw_table->pmat;
//    double        **gmat = nw_table->gmat;
    const double  **fmat = (const double **) nw_table->fmat;
    //const double    ugappen = lnexpvar - (4.0 + cnum) / 2.0;
    //const double    ugappen = lnexpvar - (4.0 + cnum);
    const double    ugappen = lnexpvar - (4.0 + cnum);

//     lnpi_g = log(nw_algo->pi_g);
//     lnpi_m = log(nw_algo->pi_m);

//    lnpi_g = lnpi_m = 0.0;

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

//     if (nw_algo->gap == 1)
//     {
//         if (nw_algo->open != 0.0)
//             gappen = nw_algo->open;
//         else
//             gappen = -3.34;
//     }
//     else
//     {
//         gappen = 0.0;
//     }

    lgappen = nw_algo->open;

//     printf("\n  ugappen: % f", ugappen);
//     printf("\n  lgappen: % f", lgappen);

//gappen -= 4.0;

    /* score the table */
    /* if gaps start as 'open', set gmat[i][j] = 0.0 */
    smat[0][0] = 0.0;
    pmat[0][0] = 'n';
    //gmat[0][0] = 1.0; //gmat already initialized to 0

    for (i = 1; i <= nx; ++i) /* j = 0, 0 column, cells to left are index j = -1, all zero score */
    {
        smat[i][0] = ugappen * i; //gappen * i;
        pmat[i][0] = 'u';
        //gmat[i][0] = 1.0;  //gmat already initialized to 0
    }

    for (j = 1; j <= ny; ++j)
    {
        smat[0][j] = lgappen * j;
        pmat[0][j] = 'l';
        //gmat[0][j] = 1.0;  //gmat already initialized to 0
    }

    for (i = 1; i <= nx; ++i)
    {
        for (j = 1; j <= ny; ++j)
        {
            /* calculate scores; up & left are gaps.
               up is gap in i seq0 (mean),
               left is gap in j seq1 */
            ds = smat[i-1][j-1] + fmat[i][j];
            us = smat[i-1][j]   + ugappen; //(gappen * gmat[i-1][j]);
            ls = smat[i][j-1]   + lgappen; //(gappen * gmat[i][j-1]);

//printf("%-4d TRACEBACK[%5d %5d] --- ds:% 16.2f us:% 16.2f ls:% 16.2f\n", crush_again, i, j, ds, us, ls); fflush(NULL);

            /* choose best score */
            if (ds >= us)
            {
                if (ds >= ls)
                {
                    smat[i][j] = ds;
                    pmat[i][j] = 'd';
                    //gmat[i][j] = 1.0; /* gap closed */
                    //printf("%-4d TRACEBACK[%5d %5d] --- ds:% 16.2f us:% 16.2f ls:% 16.2f\n", crush_again, i, j, ds, us, ls); fflush(NULL);
                }
                else
                {
                    smat[i][j] = ls;
                    pmat[i][j] = 'l';
                    //gmat[i][j] = extend;
                    //printf("%-4d TRACEBACK[%5d %5d] --- ds:% 16.2f us:% 16.2f ls:% 16.2f\n", crush_again, i, j, ds, us, ls); fflush(NULL);
                }
            }
            else
            {
                if (us >= ls)
                {
                    smat[i][j] = us;
                    pmat[i][j] = 'u';
                    //gmat[i][j] = extend;
                }
                else
                {
                    smat[i][j] = ls;
                    pmat[i][j] = 'l';
                    //gmat[i][j] = extend;
                }
            }
        }
    }

    nw_table->nw_score = nw_table->smat[nx][ny];
}


void
NWtraceback(NWtable *nw_table)
{
    int             i, j, k, len;
    char            cellptr = 'n';
    int            *alignment_space = NULL;
    int           **alignment = NULL;
    double         *align_score = NULL;
    MSA            *msa = nw_table->msa;
    const int       nx = nw_table->nx, ny = nw_table->ny;
    const Cds      *cds0 = nw_table->cds0;
    const Cds      *cds1 = nw_table->cds1;
    const double  **fmat = (const double **) nw_table->fmat;
    const char    **pmat = (const char **) nw_table->pmat;

//    PrintVars(nw_table);
//    PrintScores(nw_table);
//    PrintSumScores(nw_table);
//    PrintTraceback(nw_table);

    /* get the length of the alignment */
    i = nx;
    j = ny;
    len = 0;
    while(1)
    {
        cellptr = pmat[i][j];

        if (cellptr == 'n')
            break;
        //printf("1 cellptr:%c  i:%d  j:%d  len:%d\n", cellptr, i, j, len);

        switch (cellptr)
        {
            case 'd':
                --i;
                --j;
                break;
            case 'u':
                --i;
                break;
            case 'l':
                --j;
                break;
            default:
                printf("\n  BAD TRACEBACK: i:%d j:%d len:%d\n", i, j, len);
                break;
        }

        ++len;

//        printf("2 cellptr:%c  i:%4d  j:%4d  len:%4d\n", cellptr, i, j, len);
//        fflush(NULL);
    }

    if (len < 5)
    {
        printf("\n Length of alignment (%d) is less than five. No good. \n\n", len);
        exit(EXIT_FAILURE);
    }

    nw_table->alignlen = len;
    MSAalloc(nw_table->msa, 2, len, 512);

    alignment_space = (int *) calloc(len * 2 , sizeof(int));
    alignment = nw_table->alignment = (int **) calloc(len, sizeof(int *));
    align_score = nw_table->align_score = (double *) calloc(len, sizeof(double));

    for (i = 0; i < len; i++)
        alignment[i] = alignment_space + (i * 2);

    /* traceback and record the alignment */
    i = nx;
    j = ny;
    for(k = len-1; k >= 0; --k)
    {
        cellptr = pmat[i][j];

        if (cellptr == 'n')
            break;

        switch (cellptr)
        {
            case 'd':
                alignment[k][0] = i-1;
                alignment[k][1] = j-1;
                align_score[k] = fmat[i][j];

// printf("\n%3d %3d %3d: %c %s %s", i, j, k, cellptr, "ALA", cds1->resName[j]);
// fflush(NULL);

                --i;
                --j;
                break;
            case 'u':
                alignment[k][0] = i-1; /* -1 is a gap */
                alignment[k][1] = (-1);
                align_score[k] = fmat[i][j];
                --i;
                break;
            case 'l':
                alignment[k][0] = (-1);
                alignment[k][1] = j-1; /* -1 is a gap */
                align_score[k] = fmat[i][j];
                --j;
                break;
        }
    }

    if (0)
    {
        /* print out the alignment vertically to the screen */
        putchar('\n');
        for (i = 0; i < len; ++i)
        {
            if (alignment[i][0] == -1)
            {
                printf("       -GAP- <===> %-4d %-3s",
                       cds1->resSeq[alignment[i][1]],
                       cds1->resName[alignment[i][1]]);
                putchar('\n');
            }
            else if (alignment[i][1] == -1)
            {
                printf("    %3s %4d <===> -GAP-",
                       cds0->resName[alignment[i][0]],
                       cds0->resSeq[alignment[i][0]]);
                putchar('\n');
            }
            else
            {
                printf("    %3s %4d <===> %-4d %-3s",
                       cds0->resName[alignment[i][0]],
                       cds0->resSeq[alignment[i][0]],
                       cds1->resSeq[alignment[i][1]],
                       cds1->resName[alignment[i][1]]);
                putchar('\n');
            }
            fflush(NULL);
        }
        printf("\nEND vert alignment\n");
        fflush(NULL);
    }

    /* record the multiple sequence alignment in the MSA structure */
    for (i = 0; i < len; ++i)
    {
        if (alignment[i][0] == -1)
        {
            msa->seq[0][i] = '-';
            msa->seq[1][i] = aa3toaa1(cds1->resName[alignment[i][1]]);
        }
        else if (alignment[i][1] == -1)
        {
            msa->seq[0][i] = aa3toaa1(cds0->resName[alignment[i][0]]);
            msa->seq[1][i] = '-';
        }
        else
        {
            msa->seq[0][i] = aa3toaa1(cds0->resName[alignment[i][0]]);
            msa->seq[1][i] = aa3toaa1(cds1->resName[alignment[i][1]]);
        }
// printf("%c %c %4d\n", msa->seq[0][i],  msa->seq[1][i], i);
// fflush(NULL);
    }

    msa->seq[0][len] = '\0';
    msa->seq[1][len] = '\0';
    msa->seqlen = len;
    msa->seqnum = 2;
}


void
NWtracebackTh(NWtable *nw_table)
{
    int             i, j, k, len;
    char            cellptr = 'n';
    int            *alignment_space = NULL;
    int           **alignment = NULL;
    double         *align_score = NULL;
    MSA            *msa = nw_table->msa;
    const int       nx = nw_table->nx, ny = nw_table->ny;
//    const Cds      *cds0 = nw_table->cds0;
    const Cds      *cds1 = nw_table->cds1;
    const double  **fmat = (const double **) nw_table->fmat;
    const char    **pmat = (const char **) nw_table->pmat;

// const double    phi = 2.0*stats->hierarch_p1;
// const double    ndf = algo->ndf;
// const double    lnexpvar = -1.5 * log(phi / (ndf));
// const double  **smat = (const double **) nw_table->smat;
// double          gappen, us, ls, ds;
// if (nw_algo->gap == 1)
// {
//  if (nw_algo->open != 0.0)
//      gappen = nw_algo->open;
//  else
//      gappen = 3.34;
// }
// else
// {
//  gappen = 0.0;
// }

//printf("\nTRACEBACK%5d\n\n", crush_again); fflush(NULL);

//     PrintVars(nw_table);
//     PrintScores(nw_table);
//     PrintSumScores(nw_table);
//     PrintTraceback(nw_table);

    /* get the length of the alignment */
    i = nx;
    j = ny;
    len = 0;
    while(1)
    {
        cellptr = pmat[i][j];

        if (cellptr == 'n')
            break;
        //printf("1 cellptr:%c  i:%d  j:%d  len:%d\n", cellptr, i, j, len);

        switch (cellptr)
        {
            case 'd':
                --i;
                --j;
                break;
            case 'u':
                --i;
                break;
            case 'l':
                --j;
                break;
            default:
                printf("\n  BAD TRACEBACK: i:%d j:%d len:%d\n", i, j, len);
                break;
        }

        ++len;

//        printf("2 cellptr:%c  i:%4d  j:%4d  len:%4d\n", cellptr, i, j, len);
//        fflush(NULL);
    }

    if (len < 5)
    {
        printf("\n Length of alignment (%d) is less than five. No good. \n\n", len);
        exit(EXIT_FAILURE);
    }

    nw_table->alignlen = len;
    MSAalloc(nw_table->msa, 2, len, 512);

    alignment_space = (int *) calloc(len * 2 , sizeof(int));
    alignment = nw_table->alignment = (int **) calloc(len, sizeof(int *));
    align_score = nw_table->align_score = (double *) calloc(len, sizeof(double));

    for (i = 0; i < len; i++)
        alignment[i] = alignment_space + (i * 2);

    /* traceback and record the alignment */
    i = nx;
    j = ny;
    for(k = len-1; k >= 0; --k)
    {
        cellptr = pmat[i][j];

        if (cellptr == 'n')
            break;

        switch (cellptr)
        {
            case 'd':
                alignment[k][0] = i-1;
                alignment[k][1] = j-1;
                align_score[k] = fmat[i][j];

// printf("\n%3d %3d %3d: %c %s %s", i, j, k, cellptr, "ALA", cds1->resName[j]);
// fflush(NULL);

// ds = smat[i-1][j-1] + fmat[i][j];
// us = smat[i-1][j]   + lnexpvar - 4.0; //(gappen * gmat[i-1][j]);
// ls = smat[i][j-1]   + gappen; //(gappen * gmat[i][j-1]);
// printf("%-4d TRACEBACK[%5d %5d: %5d] --- ds:% 16.2f us:% 16.2f ls:% 16.2f\n", crush_again, i, j, k, ds, us, ls); fflush(NULL);

                --i;
                --j;
                break;
            case 'u':
                alignment[k][0] = i-1; /* -1 is a gap */
                alignment[k][1] = (-1);
                align_score[k] = fmat[i][j];
                --i;
                break;
            case 'l':
                alignment[k][0] = (-1);
                alignment[k][1] = j-1; /* -1 is a gap */
                align_score[k] = fmat[i][j];
                --j;
                break;
        }
    }

    /* record the multiple sequence alignment in the MSA structure */
    for (i = 0; i < len; ++i)
    {
        if (alignment[i][0] == -1)
        {
            msa->seq[0][i] = '-';
            msa->seq[1][i] = aa3toaa1(cds1->resName[alignment[i][1]]);
        }
        else if (alignment[i][1] == -1)
        {
            //msa->seq[0][i] = aa3toaa1(cds0->resName[alignment[i][0]]);
            msa->seq[0][i] = 'X';
            msa->seq[1][i] = '-';
        }
        else
        {
            //>msa->seq[0][i] = aa3toaa1(cds0->resName[alignment[i][0]]);
            msa->seq[0][i] = 'X';
            msa->seq[1][i] = aa3toaa1(cds1->resName[alignment[i][1]]);
        }
// printf("%c %c %4d\n", msa->seq[0][i],  msa->seq[1][i], i);
// fflush(NULL);
    }

    msa->seq[0][len] = '\0';
    msa->seq[1][len] = '\0';
    msa->seqlen = len;
    msa->seqnum = 2;
}


void
PrintVars(NWtable *nw_table)
{
    int             i, j;

    putchar('\n');
    for (i = 0; i < nw_table->nx; ++i)
    {
        for (j = 0; j < nw_table->ny; ++j)
        {
            printf("%6.2lf ", nw_table->vmat[i+1][j+1]);
        }
        putchar('\n');
    }
    putchar('\n');
}


void
PrintScores(NWtable *nw_table)
{
    int             i, j;

    putchar('\n');
    for (i = 0; i < nw_table->nx; ++i)
    {
        for (j = 0; j < nw_table->ny; ++j)
        {
            printf("% 9.1lf ", nw_table->fmat[i+1][j+1]);
        }
        putchar('\n');
    }
    putchar('\n');
}


void
PrintSumScores(NWtable *nw_table)
{
    int             i, j;

    putchar('\n');
    for (i = 0; i < nw_table->nx; ++i)
    {
        for (j = 0; j < nw_table->ny; ++j)
        {
            printf("% 9.1lf ", nw_table->smat[i+1][j+1]);
        }
        putchar('\n');
    }
    putchar('\n');
}


void
PrintTraceback(NWtable *nw_table)
{
    int             i, j;

    putchar('\n');
    for (i = 0; i <= nw_table->nx; ++i)
    {
        for (j = 0; j <= nw_table->ny; ++j)
        {
            printf("%c ", nw_table->pmat[i][j]);
        }
        putchar('\n');
    }
    putchar('\n');
}


NWtable
*NWinit(void)
{
    NWtable        *nw_table = NULL;

    nw_table = (NWtable *) calloc(1, sizeof(NWtable));
    nw_table->msa = (MSA *) calloc(1, sizeof(MSA));
    nw_table->msa_filename = NULL;
    nw_table->align_score = NULL;
    nw_table->alignment = NULL;
    nw_table->nw_score = 0.0;
    nw_table->alignlen = 0;

    return(nw_table);
}


void
NWalloc(NWtable *nw_table, int nx, int ny)
{
    nw_table->vmat = MatAlloc(nx+1, ny+1);
    nw_table->fmat = MatAlloc(nx+1, ny+1);
    nw_table->smat = MatAlloc(nx+1, ny+1);
    //nw_table->gmat = MatAlloc(nx+1, ny+1);
    nw_table->pmat = MatCharAlloc(nx+1, ny+1);
}


void
NWdestroy(NWtable **nw_table_ptr)
{
    if (nw_table_ptr)
    {
        NWtable *nw_table = *nw_table_ptr;

        if (nw_table)
        {
            MyFree(nw_table->alignment[0]);
            MyFree(nw_table->alignment);
            MyFree(nw_table->ss0);
            MyFree(nw_table->ss1);
            MSAdestroy(&nw_table->msa);
            MyFree(nw_table->align_score);

            MatDestroy(&nw_table->vmat);
            MatDestroy(&nw_table->fmat);
            MatDestroy(&nw_table->smat);
            //MatDestroy(&nw_table->gmat);
            MatCharDestroy(&nw_table->pmat);

            MyFree(nw_table);

            nw_table = NULL;
        }
    }
}


NWalgo
*NWalgoInit(void)
{
    NWalgo         *nw_algo = NULL;

    nw_algo = (NWalgo *) calloc(1, sizeof(NWalgo));
    if (nw_algo == NULL)
    {
        perror("\n\n ERROR");
        fprintf(stderr,
                " ERROR6: could not allocate memory in function NWalgoInit(). \n\n");
        exit(EXIT_FAILURE);
    }

    nw_algo->fraglen = 11;
    nw_algo->distance = 0;
    nw_algo->blosum = 0;
    nw_algo->gap = 1;
    nw_algo->gapzeron = 0;
    nw_algo->logodds = 1;
    nw_algo->bayes = 0;
    nw_algo->bayespost = 0;
    nw_algo->passes = 1;
    nw_algo->global = 1;
    nw_algo->center_ca = 0;
    nw_algo->KA = 0;
    nw_algo->dsspflag = 0;
    nw_algo->raw_charge = 0;
    nw_algo->corder = 0;
    nw_algo->procrustes = 0;
    nw_algo->convert = 0;
    nw_algo->open = 0.0;
    nw_algo->extend = 1.0;
    nw_algo->align_dist = 0.0;
    nw_algo->angles = 0;
    nw_algo->pi_g = 0.5;
    nw_algo->pi_m = 0.5;

    return(nw_algo);
}


char
aa3toaa1(char *aa3_s)
{
    int         index_i;
    char        *ptr = NULL;

    ptr = strstr(&aa3[0], aa3_s);

    if (ptr)
    {
        index_i = (ptr - &aa3[0]) / 3;
        return(aa1[index_i]);

    }
    else
    {
        return('X');
    }
}


