/*
    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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include "Error.h"
#include "lodmats.h"
#include "pdbMalloc.h"
#include "pdbUtils.h"
#include "Cds.h"
#include "PDBCds.h"
#include "MultiPose2MSA.h"
#include "ProcGSLSVD.h"
#include "ProcGSLSVDNu.h"
#include "msa.h"


static int
RangeSelxn(int index, int *lower, int *upper, int range_num);

static int
AtomSelxn(char *name, int mode);

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);
}


static int
RangeSelxn(int index, int *lower, int *upper, int range_num)
{
    int             i;

    for (i = 0; i < range_num; ++i)
    {
        if (index >= lower[i] && index <= upper[i] )
            return (1);
    }

    return(0);
}


static void
VerifyAlignmentVsPDBs(PDBCdsArray *pdbA, CdsArray *baseA, MSA *msa,
                      const int atomsel, int *map)
{
    int             i, j, len, aalen;
    const int       cnum = pdbA->cnum;
    const int       seqlen = msa->seqlen;
    char           *seq = NULL;

    for (i = 0; i < cnum; ++i)
    {
        seq = msa->seq[map[i]];
        len = 0;
        for (j = 0; j < seqlen; ++j)
            if (seq[j] != '-')
                ++len;

        aalen = 0;
        for (j = 0; j < pdbA->cds[i]->vlen; ++j)
            if (AtomSelxn(pdbA->cds[i]->name[j], atomsel) &&
                AltLocSelxn(pdbA->cds[i]->altLoc[j]))
                ++aalen;

        baseA->cds[i]->aalen = aalen;

        if (len != aalen)
        {
            fprintf(stderr, "\n\n  ERROR1122: PDB file '%s' and sequence '%s' in '%s'",
                    pdbA->cds[i]->filename, msa->name[map[i]], msa->filename);
            fprintf(stderr, "\n            have different lengths (%d vs %d)\n\n",
                    aalen, len);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }
}


/* Dumps the indices from a multiple sequence alignment into the segID field of pdb cds
   2010-10-12 */
void
Align2segID(PDBCdsArray *pdbA)
{
    int             i, j, k;
    const int       cnum = pdbA->cnum;
    int            *map = pdbA->seq2pdb->map;
    MSA            *msa = pdbA->seq2pdb->msa;
    char           *seq = NULL;
    int             resSeq = 0;
    char            alnindex[5];
    PDBCds         *cdsi = NULL;

    for (i = 0; i < cnum; ++i)
    {
        cdsi = pdbA->cds[i];
        k = 0; /* PDBCds index */
        j = 0; /* MSA index */
        seq = msa->seq[map[i]];
/* printf("\n\n%s\n%s", cdsi->filename, seq); */
        for (j = 0, k = 0; j < msa->seqlen && k < cdsi->vlen; ++j)
        {
            if (seq[j] != '-') /* not a gap */
            {
                resSeq = cdsi->resSeq[k];
                sprintf(alnindex, "%04d", j+1);

/* printf("\n%s %4d %4d %4d %4d %s", cdsi->segID[k], i, k, resSeq, cdsi->resSeq[k], alnindex); */
/* fflush(NULL); */

                while(cdsi->resSeq[k] == resSeq)
                {
                    strncpy(cdsi->segID[k], alnindex, 4);
                    //printf("\n%s %4d %4d %s %4d %c %5d", cdsi->segID[k], i, k, alnindex, resSeq, seq[j], cdsi->vlen);
                    //fflush(NULL);

                    ++k;

                    if (k >= cdsi->vlen)
                        break;
                }
            }
        }
    }
}


/* Dumps the sqrt of the variance in the B-factor column of the pdb file, based on corresponding
   residues in the sequence alignment.
   2010-10-12 */
void
Align2Vars(PDBCdsArray *pdbA, CdsArray *cdsA)
{
    int             i, j, k, m;
    const int       cnum = pdbA->cnum;
    int            *map = pdbA->seq2pdb->map;
    MSA            *msa = pdbA->seq2pdb->msa;
    char           *seq = NULL;
    int             resSeq = 0;
    PDBCds         *cdsi = NULL;
    int            *singletons = pdbA->seq2pdb->singletons;

    for (i = 0; i < cnum; ++i)
        for (j = 0; j < pdbA->cds[i]->vlen; ++j)
            pdbA->cds[i]->tempFactor[j] = 0.0;

    for (i = 0; i < cnum; ++i)
    {
        cdsi = pdbA->cds[i];
        k = 0; /* PDBCds index */
        j = 0; /* MSA index */
        m = 0; /* cds variances index */
        seq = msa->seq[map[i]];
/* printf("\n\n%s\n%s", cdsi->filename, seq); */
        /* if we put "m < cdsA->vlen" in here also, we miss hanging singletons (at the end of a
           MSA).  So we check that m is OK before accessing it. */
        while (j < msa->seqlen && k < cdsi->vlen)
        {
            if (singletons[j])
            {
                if (seq[j] == '-') /* IS a gap */
                {
                    ++j;
                }
                else
                {
                    resSeq = cdsi->resSeq[k];

    /* printf("\n%s %4d %4d %4d %4d %s", cdsi->segID[k], i, k, resSeq, cdsi->resSeq[k], alnindex); */
    /* fflush(NULL); */

                    while(cdsi->resSeq[k] == resSeq)
                    {
                        cdsi->tempFactor[k] = 0.0;

                        ++k;

                        if (k >= cdsi->vlen)
                            break;
                    }

                    ++j;
                }
            }
            else
            {
                if (seq[j] != '-') /* NOT a gap */
                {
                    resSeq = cdsi->resSeq[k];

    /* printf("\n%s %4d %4d %4d %4d %s", cdsi->segID[k], i, k, resSeq, cdsi->resSeq[k], alnindex); */
    /* fflush(NULL); */
                    if (m < cdsA->vlen)
                    {
                        while(cdsi->resSeq[k] == resSeq)
                        {
                            cdsi->tempFactor[k] = sqrt(cdsA->var[m]);

                            ++k;

                            if (k >= cdsi->vlen)
                                break;
                        }
                    }

                    ++j;
                    ++m;
                }
                else
                {
                    ++j;
                    ++m;
                }
            }
        }
    }
}


static Seq2PDB
*GetMapFile(char *mapfile_name)
{
    Seq2PDB        *seq2pdb = NULL;
    FILE           *mapfile = NULL;
    int             i, numscanned, seqnum, maxseqnum;
    char            line[FILENAME_MAX + 256];
    int             ch;

    seq2pdb = Seq2pdbInit();

    mapfile = fopen(mapfile_name, "r");
    if (mapfile == NULL)
    {
        fprintf(stderr,
                "\n  ERROR691: cannot open alignment -> PDB mapping file \"%s\" \n",
                mapfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    maxseqnum = 0;
    while(1)
    {
        ch = getc(mapfile);

        if (ch == '#')
            while(ch != '\n')
                ch = getc(mapfile);

        if (ch == EOF || ch == '\n')
            ++maxseqnum;

        if (ch == EOF)
            break;
    }
//printf("\nmaxseqnum:%d", maxseqnum);
    Seq2pdbAlloc(seq2pdb, maxseqnum);

    rewind(mapfile);

    for(i = 0, seqnum = 0; i < maxseqnum; ++i)
    {
        if (fgets(line, FILENAME_MAX + 256, mapfile) == NULL)
            break;

        if (line[0] == '#')
            continue;

        if (strlen(line) > 1)
        {
            numscanned = sscanf(line, "%s %s", seq2pdb->pdbfile_name[i], seq2pdb->seqname[i]);

            if (numscanned == 2)
            {
                ++seqnum;
                /* printf("\nline %d (len %d): %s", i, (int) strlen(line), line); */
            }
        }
    }

    seq2pdb->seqnum = seqnum;

    fclose(mapfile);

    return(seq2pdb);
}


/* Constructs an integer vector that maps a Cds structure to a sequence name in the alignment.
   First matches PDB filename to the PDB filename given in the mapfile.
   Every filename in the mapfile is associated with a sequence name of the same index.
   Then the fxn matches the sequence name in the mapfile with a sequence name in the alignment.
   Given these two matchings and the mapfile, the map[] vector can be constructed.
   Finally, given Cds i, then map[i] = k, where k is the index of the corresponding
   sequence in the MSA.
   NB: filename extensions are ignored. */
static void
GetSeq2PDBMap(PDBCdsArray *pdbA, Seq2PDB *seq2pdb, MSA *msa)
{
    int             i, j, k;
    const int       cnum = pdbA->cnum;
    char          **filename_root = NULL;
    char          **msaname_root = NULL;
    char          **mapseqname_root = NULL;
    char          **mappdbname_root = NULL;


    filename_root   = malloc(cnum * sizeof(char *));
    msaname_root    = malloc(msa->seqnum * sizeof(char *));
    mapseqname_root = malloc(seq2pdb->seqnum * sizeof(char *));
    mappdbname_root = malloc(seq2pdb->seqnum * sizeof(char *));

    for (i = 0; i < cnum; ++i)
    {
        filename_root[i] = getroot(pdbA->cds[i]->filename);
    }

    for (j = 0; j < seq2pdb->seqnum; ++j)
    {
        mappdbname_root[j] = getroot(seq2pdb->pdbfile_name[j]);
        mapseqname_root[j] = getroot(seq2pdb->seqname[j]);
    }

    for (k = 0; k < msa->seqnum; ++k)
    {
        msaname_root[k] = getroot(msa->name[k]);
    }

    /* First set all flags in the MSA to 1, and later reset to 0 if the sequences have a
       corresponding PDB file.
       This allows for sequences in the MSA that aren't being superpositioned. */
    for (i = 0; i < msa->seqnum; ++i)
        msa->flag[i] = 1;

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < seq2pdb->seqnum; ++j)
        {
            //printf("\ni:%3d j:%3d -- %s %s", i, j, filename_root, mappdbname_root);

            if (strncmp(filename_root[i], mappdbname_root[j], FILENAME_MAX) == 0)
            {
                for (k = 0; k < msa->seqnum; ++k)
                {
                    //printf("\ni:%3d j:%3d k:%3d -- %s %s %s", i, j, k, filename_root, mapseqname_root, msaname_root);

                    if (strncmp(mapseqname_root[j], msaname_root[k], FILENAME_MAX) == 0) /* now k (alignment) and i (pdb cds) match */
                    {
                        //printf("\nMATCH");
                        pdbA->seq2pdb->map[i] = k;
                        msa->flag[k] = 0; /* reset msa flag to 0 since this structure exists */

                        break;
                    }

                    fflush(NULL);
                }

                if (k == msa->seqnum)
                {
                    fprintf(stderr,
                            "\n  ERROR_689: Sequence #%d (%s) in mapfile has no corresponding sequence in the alignment\n",
                            j+1, seq2pdb->pdbfile_name[j]);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }

                break;
            }
        }

        if (j == seq2pdb->seqnum)
        {
            fprintf(stderr,
                    "\n  ERROR_690: PDB file #%d (%s) has no corresponding sequence in the alignment\n",
                    i+1, pdbA->cds[i]->filename);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < cnum; ++i)
    {
        MyFree(filename_root[i]);
    }

    for (j = 0; j < seq2pdb->seqnum; ++j)
    {
        MyFree(mappdbname_root[j]);
        MyFree(mapseqname_root[j]);
    }

    for (k = 0; k < msa->seqnum; ++k)
    {
        MyFree(msaname_root[k]);
    }

    MyFree(filename_root);
    MyFree(msaname_root);
    MyFree(mapseqname_root);
    MyFree(mappdbname_root);
}


/* Same as above, but no mapfile needed as we assume that the sequence names and
   PDB file names are identical */
static void
DefaultSeq2PDBMap(PDBCdsArray *pdbA, Seq2PDB *seq2pdb, MSA *msa)
{
    int             i, j, same;
    const int       cnum = pdbA->cnum;
    const int       seqnum = msa->seqnum;
    char          **filename_root = NULL, **msaname_root = NULL;

    filename_root = malloc(cnum   * sizeof(char *));
    msaname_root  = malloc(seqnum * sizeof(char *));

    /* First set all flags in the MSA to 1, and later reset to 0 if the sequences have a
       corresponding PDB file.
       This allows for sequences in the MSA that aren't being superpositioned. */
    for (i = 0; i < seqnum; ++i)
        msa->flag[i] = 1;

    for (i = 0; i < cnum; ++i)
        filename_root[i] = getroot(pdbA->cds[i]->filename);

    for (j = 0; j < seqnum; ++j)
        msaname_root[j] = getroot(msa->name[j]);

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < seqnum; ++j)
        {
            same = strncmp(filename_root[i], msaname_root[j], FILENAME_MAX);

            if (same == 0)
            {
                seq2pdb->map[i] = j; /* now j (alignment) and i (pdb cds) match */
                msa->flag[j] = 0; /* reset msa flag to 0 since this structure exists */

                break;
            }
        }

        if (j == seqnum)
        {
            fprintf(stderr,
                    "\n  ERROR690: PDB file #%d (%s) has no corresponding sequence in the alignment\n",
                    i+1, pdbA->cds[i]->filename);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    for (j = 0; j < seqnum; ++j)
        MyFree(msaname_root[j]);

    for (i = 0; i < cnum; ++i)
        MyFree(filename_root[i]);

    MyFree(filename_root);
    MyFree(msaname_root);
}


/* check for singletons, i.e. columns with only one non-gap residue */
void
GetSingletons(int *singletons, MSA *msa)
{
    int             i, j, count;

    for (i = 0; i < msa->seqlen; ++i)
    {
        count = 0;
        for (j = 0; j < msa->seqnum; ++j)
        {
            /* The flag for each sequence was set/unset in GetSeq2PDBMap() or DefaultSeq2PDBMap()
               If flag is set to 1, we ignore it (because it has no PDB structural counterpart) */
            if (msa->seq[j][i] != '-' && msa->flag[j] == 0)
                ++count;
        }

        /* This flags all columns that have either only one or NO residues,
           and both need to be excluded from the superposition */
        if (count == 1 || count == 0)
            singletons[i] = 1;
        else
            singletons[i] = 0;
    }
}


/* check for ubiqs, i.e. columns with no gaps */
static void
GetUbiqs(int *ubiqs, MSA *msa)
{
    int             i, j, count;

    for (i = 0; i < msa->seqlen; ++i)
    {
        count = 0;
        for (j = 0; j < msa->seqnum; ++j)
        {
            /* The flag for each sequence was set/unset in GetSeq2PDBMap() or DefaultSeq2PDBMap()
               If flag is set to 1, we ignore it (because it has no PDB structural counterpart) */
            if (msa->seq[j][i] == '-' && msa->flag[j] == 0)
                ++count;
        }
//printf("\ncount[%d] = %d\n", i, count);
        /* if ANY gaps were counted, set flag.
           NOTE that this does not do anything for the other
           flags -- they are the same as when ubiqs was passed */
        if (count > 0)
            ubiqs[i] = 1;
    }
}


static void
GetCdsFrAlignment(PDBCdsArray *pdbA, CdsArray *baseA,
                  Seq2PDB *seq2pdb, MSA *msa,
                  const int vlen, const int alignlen,
                  int *singletons)
{
    int             j, k, m, n, p, q;
    const int       cnum = pdbA->cnum;
    int            *map = seq2pdb->map;
    int            *upper = pdbA->upper; /* arrays of ints holding the upper and lower range bounds */
    int            *lower = pdbA->lower;
    int             range_num = pdbA->range_num;
    char            altLoc_m;
    char           *name_m = NULL;
    const PDBCds   *pdbj = NULL;
    Cds            *cdsj = NULL;

    for (j = 0; j < cnum; ++j)
    {
        pdbj = (const PDBCds *) pdbA->cds[j];
        cdsj = baseA->cds[j];

        k = map[j];
        m = n = p = 0;
        while(m < pdbj->vlen && n < vlen  && p < alignlen)
        {
            /* m = PDB length */
            /* n = baseA cds length */
            /* p = sequence alignment length */
            /* k = sequence index */
            /* j = pdb and baseA cds index */

/*                             printf("\n1 n:%d(%d) atomname:pdbA->cds[%d]->name[%d] %s", */
/*                                    n, alignlen, j, m, pdbj->name[m]); */
/*                             fflush(NULL); */
/* if (j == cnum - 1) */
/* printf("\naltLoc:%c", pdbj->altLoc[m]); */

            name_m = pdbj->name[m];
            altLoc_m = pdbj->altLoc[m];

            if (AtomSelxn(name_m, algo->atoms) && AltLocSelxn(altLoc_m))
            {
/*                                 printf("\n1 m:%d(%d) msa->seq[%d(%d)][%d(%d)] = %c", */
/*                                        m, pdbj->vlen, k, cnum, n, alignlen, msa->seq[k][n]); */
/*                                 fflush(NULL); */
                if (RangeSelxn(p, lower, upper, range_num) - algo->revsel && /* in-range, or out-of-range if revsel == 1 */
                    singletons[p] == 0) /* not a singleton */
                {
                    if (msa->seq[k][p] != '-') /* not a gap */
                    {
                        strncpy(cdsj->resName[n], pdbj->resName[m], 3);
                        cdsj->chainID[n] = pdbj->chainID[m];
                        cdsj->resSeq[n]  = pdbj->resSeq[m];
                        cdsj->x[n]       = pdbj->x[m];
                        cdsj->y[n]       = pdbj->y[m];
                        cdsj->z[n]       = pdbj->z[m];
                        cdsj->o[n]       = pdbj->occupancy[m];
                        cdsj->b[n]       = pdbj->tempFactor[m];
                        cdsj->nu[n]      = 1;

                        ++m;
                        ++n;
                    }
                    else /* is a gap */
                    {
                        strncpy(cdsj->resName[n], "GAP", 3);
                        cdsj->chainID[n] = pdbj->chainID[m];
                        cdsj->resSeq[n]  = 0;
                        cdsj->x[n]       = 0.0;
                        cdsj->y[n]       = 0.0;
                        cdsj->z[n]       = 0.0;
                        cdsj->o[n]       = 0.0;
                        cdsj->b[n]       = 99.99;
                        cdsj->nu[n]      = 0;

                        ++n;
                    }
                }
                else /* out of range */
                {
                    if (msa->seq[k][p] != '-') /* not a gap */
                    {
                        ++m;
                    }

                }

                ++p;
/*                          if (j == cnum-1) */
/*                                 printf("\n2 m:%d(%d) msa->seq[%d(%d)][%d(%d)] = %c", */
/*                                        m, pdbj->vlen, k, cnum, n, alignlen, msa->seq[k][n]); */
/*                                 fflush(NULL); */
            }
            else /* not the proper atom slxn */
            {
                ++m;
            }
/*                      if (j == cnum-1) */
/*                             printf("\n2 n:%d(%d) atomname:pdbA->cds[%d]->name[%d] %s", */
/*                                    n, alignlen, j, m, pdbj->name[m]); */
/*                             fflush(NULL); */
        }
        /*******************************************************************************************/
        /* if the end of the PDB is before the end of the alignment */
        if (n < vlen && m != 0 && n != 0)
        {
/*          printf("\nHere: m:%4d n:%4d p:%4d j:%4d -- %4d %4d %4d %4d", */
/*                  m, n, p, j, pdbj->vlen, vlen, alignlen, cnum); */

            for (q = n; q < vlen; ++q)
            {
                strncpy(cdsj->resName[q], "GAP", 3);
                cdsj->chainID[q] = pdbj->chainID[m-1];
                cdsj->resSeq[q]  = 0;
                cdsj->x[q]       = 0.0;
                cdsj->y[q]       = 0.0;
                cdsj->z[q]       = 0.0;
                cdsj->o[q]       = 0.0;
                cdsj->b[q]       = 66.66;
                cdsj->nu[n]      = 0;
            }
        }
    }
}


static int
ParseSelxns(PDBCdsArray *pdbA, char *selection, const int alignlen, int *singletons, const int revsel)
{
    int             i, j, singleton_cnt;
    int             selxn_len, vlen;
    char          **endptr = NULL;
    char          **selections = NULL; /* an array of pdbA->range_num strings to hold each range selection */
    char            delims[] = ":";
    int            *upper = NULL, *lower = NULL; /* an array of ints holding the upper and lower range bounds */

    if (selection)
    {
        selxn_len = strlen(selection);

        pdbA->range_num = 1;
        for(i = 0; i < selxn_len; ++i)
        {
            if (selection[i] == ':')
                ++(pdbA->range_num);
        }

        selections = (char **) calloc(pdbA->range_num, sizeof(char *));
        pdbA->lower = lower = (int *)  calloc(pdbA->range_num, sizeof(int));
        pdbA->upper = upper = (int *)  calloc(pdbA->range_num, sizeof(int));
        if (selections == NULL || lower == NULL || upper == NULL)
        {
            perror("\n  ERROR");
            fprintf(stderr, " ERROR: could not allocate memory for selections in GetCdsSelection(). \n\n");
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < pdbA->range_num; ++i)
        {
            selections[i] = (char *) calloc(128, sizeof(char));
            if (selections[i] == NULL)
            {
                perror("\n  ERROR");
                fprintf(stderr, " ERROR: could not allocate memory for selections[] in GetCdsSelection(). \n\n");
                PrintTheseusTag();
                exit(EXIT_FAILURE);
            }
        }

        /* copy each range selection string into the 'selections[]' array */
        mystrncpy(selections[0], strtok(selection, delims), 127);
        for (i = 1; i < pdbA->range_num; ++i)
            mystrncpy(selections[i], strtok(NULL, delims), 127);

/*         for (i = 0; i < pdbA->range_num; ++i) */
/*             printf"\n selections[%d] = %s", i, selections[i]); */

        for (j = 0; j < pdbA->range_num; ++j)
        {
            /* parse residue number range */
            selxn_len = strlen(selections[j]);

            i = 0;
            while(isspace(selections[j][i]) && i < selxn_len)
                ++i;

            if (isdigit(selections[j][i]))
            {
                lower[j] = (int) strtol(&selections[j][i], endptr, 10) - 1;

                while(selections[j][i] != '-' && i < selxn_len)
                    ++i;

                ++i;
                while(isspace(selections[j][i]) && i < selxn_len)
                    ++i;

                if (isdigit(selections[j][i]))
                    upper[j] = (int) strtol(&selections[j][i], endptr, 10) - 1;
                else
                {
                    fprintf(stderr, "\n\n  ERROR987: one of the column selections has no upper bound. \n\n");
                    fprintf(stderr, "  ERROR987: upper limit %d = %s \n\n", j, &selections[j][i]);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }

                if (upper[j] >= alignlen)
                {
                    fprintf(stderr, "\n\n  ERROR988: one of the column selections is out of bounds for the alignment. \n");
                    fprintf(stderr, "  ERROR988: upper limit %d = %d; alignment length = %d \n\n", j, upper[j], alignlen);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }
            }
            else
            {
                lower[j] = 0;
                upper[j] = alignlen - 1;
            }
        }
    }
    else
    {
        pdbA->range_num = 1;
        selections = (char **) calloc(1, sizeof(char *));
        pdbA->lower = lower = (int *) calloc(1, sizeof(int));
        pdbA->upper = upper = (int *) calloc(1, sizeof(int));
        selections[0] = (char *) calloc(128, sizeof(char));
        if (selections == NULL || lower == NULL || upper == NULL || selections[0] == NULL)
        {
            perror("\n  ERROR");
            fprintf(stderr, " ERROR989: could not allocate memory for selections in GetCdsSelection(). \n\n");
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        lower[0] = 0;
        upper[0] = alignlen - 1;
    }

    if (revsel == 0)
    {
        vlen = 0;
        for (j = 0; j < pdbA->range_num; ++j)
            vlen += (upper[j] - lower[j] + 1);

        /* don't count singletons that are in selected ranges */
        for (j = 0; j < pdbA->range_num; ++j)
            for (i = 0; i < alignlen; ++i)
                if (i >= lower[j] && i <= upper[j] && singletons[i])
                    vlen--;
    }
    else
    {
        vlen = alignlen;
        for (j = 0; j < pdbA->range_num; ++j)
            vlen -= (upper[j] - lower[j] + 1);

        singleton_cnt = 0;
        for (i = 0; i < alignlen; ++i)
            if (singletons[i])
                ++singleton_cnt;

        /* don't count singletons that are in (un)selected ranges */
        for (j = 0; j < pdbA->range_num; ++j)
            for (i = 0; i < alignlen; ++i)
                if (i >= lower[j] && i <= upper[j] && singletons[i])
                    singleton_cnt--;

        vlen -= singleton_cnt;
    }

    if (vlen > alignlen)
    {
        fprintf(stderr,
                "\n  ERROR663: selected columns (%d) exceed length of alignment (%d) \n\n",
                vlen, alignlen);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < pdbA->range_num; ++i)
        MyFree(selections[i]);

    MyFree(selections);

    return(vlen);
}


/* extract all coords, ignoring singleton status
  (usually we don't extract singletons, as they are irrelevant to superposition) */
void
Align2MSA_singletons(PDBCdsArray *pdbA, CdsArray *baseA, MSA *msa)
{
    int             i, alnlen, vlen;
    const int       cnum = pdbA->cnum;

    pdbA->seq2pdb = Seq2pdbInit();
    Seq2pdbAlloc(pdbA->seq2pdb, cnum);

    alnlen = msa->seqlen;
    pdbA->seq2pdb->singletons = calloc(alnlen, sizeof(int));

    DefaultSeq2PDBMap(pdbA, pdbA->seq2pdb, msa);

//    GetSingletons(pdbA->seq2pdb->singletons, msa);
//    vlen = ParseSelxns(pdbA, algo->selection, alnlen, pdbA->seq2pdb->singletons, algo->revsel);

    vlen = alnlen;

    /* allocate a CdsArray based on this alignment */
    if (algo->atoms == 0)
    {
        CdsArrayAlloc(baseA, cnum, vlen);
    }
    else
    {
        fprintf(stderr,
                "\n  ERROR683: atom selection must be CAs for superpositioning with a sequence alignment\n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < cnum; ++i)
        strncpy(baseA->cds[i]->filename, pdbA->cds[i]->filename, FILENAME_MAX - 1);

    VerifyAlignmentVsPDBs(pdbA, baseA, msa, algo->atoms, pdbA->seq2pdb->map);
    GetCdsFrAlignment(pdbA, baseA, pdbA->seq2pdb, msa, vlen, alnlen, pdbA->seq2pdb->singletons);
    GetSingletons(pdbA->seq2pdb->singletons, msa);
}


/* Only difference from regular Align2MSA, here we are handed an explicit msa structure
   rather than a msa filename to read */
void
Align2MSA_crush(PDBCdsArray *pdbA, CdsArray *baseA, MSA *msa)
{
    int             i, alnlen, vlen;
    const int       cnum = pdbA->cnum;

    pdbA->seq2pdb = Seq2pdbInit();
    Seq2pdbAlloc(pdbA->seq2pdb, cnum);

    pdbA->seq2pdb->msa = msa;
    MSAdelgap(msa);

//     MSAprint(msa);
//     printf("\nseqlen: %d\n", msa->seqlen);
//     printf("\nseqnum: %d\n", msa->seqnum);
//     fflush(NULL);

    if (msa->seqnum < pdbA->cnum)
    {
        fprintf(stderr,
                "\n  ERROR682: # alignment sequences (%d) < # cds (%d)\n",
                msa->seqnum, pdbA->cnum);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    alnlen = msa->seqlen;
    pdbA->seq2pdb->singletons = calloc(alnlen, sizeof(int));

    DefaultSeq2PDBMap(pdbA, pdbA->seq2pdb, msa);

    GetSingletons(pdbA->seq2pdb->singletons, msa);

    vlen = ParseSelxns(pdbA, algo->selection, alnlen, pdbA->seq2pdb->singletons, algo->revsel);

    /* allocate a CdsArray based on this alignment */
    if (algo->atoms == 0)
    {
        CdsArrayAlloc(baseA, cnum, vlen);
    }
    else
    {
        fprintf(stderr,
                "\n  ERROR683: atom selection must be CAs for superpositioning with a sequence alignment\n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < cnum; ++i)
        strncpy(baseA->cds[i]->filename, pdbA->cds[i]->filename, FILENAME_MAX - 1);

    VerifyAlignmentVsPDBs(pdbA, baseA, msa, algo->atoms, pdbA->seq2pdb->map);
    GetCdsFrAlignment(pdbA, baseA, pdbA->seq2pdb, msa, vlen, alnlen, pdbA->seq2pdb->singletons);
}


void
Align2MSA(PDBCdsArray *pdbA, CdsArray *baseA, char *msafile_name, char *mapfile_name)
{
    int             i, alnlen, vlen;
    const int       cnum = pdbA->cnum;
    MSA            *msa = NULL;

    if (mapfile_name)
    {
        pdbA->seq2pdb = GetMapFile(mapfile_name);
    }
    else
    {
        pdbA->seq2pdb = Seq2pdbInit();
        Seq2pdbAlloc(pdbA->seq2pdb, cnum);
    }

    pdbA->seq2pdb->msa = msa = getmsa(msafile_name);
    MSAdelgap(msa);

//     MSAprint(msa);
//     printf("\nseqlen: %d\n", msa->seqlen);
//     printf("\nseqnum: %d\n", msa->seqnum);
//     fflush(NULL);

    if (msa->seqnum < pdbA->cnum)
    {
        fprintf(stderr,
                "\n  ERROR682: # alignment sequences (%d) < # cds (%d)\n",
                msa->seqnum, pdbA->cnum);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    alnlen = msa->seqlen;
    pdbA->seq2pdb->singletons = calloc(alnlen, sizeof(int));

    if (mapfile_name)
        GetSeq2PDBMap(pdbA, pdbA->seq2pdb, msa);
    else
        DefaultSeq2PDBMap(pdbA, pdbA->seq2pdb, msa);

    GetSingletons(pdbA->seq2pdb->singletons, msa);

    if (algo->missing)
        GetUbiqs(pdbA->seq2pdb->singletons, msa);

    vlen = ParseSelxns(pdbA, algo->selection, alnlen, pdbA->seq2pdb->singletons, algo->revsel);

    /* allocate a CdsArray based on this alignment */
    if (algo->atoms == 0)
    {
        CdsArrayAlloc(baseA, cnum, vlen);
    }
    else
    {
        fprintf(stderr,
                "\n  ERROR683: atom selection must be CAs for superpositioning with a sequence alignment\n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < cnum; ++i)
        strncpy(baseA->cds[i]->filename, pdbA->cds[i]->filename, FILENAME_MAX - 1);

    VerifyAlignmentVsPDBs(pdbA, baseA, msa, algo->atoms, pdbA->seq2pdb->map);
    GetCdsFrAlignment(pdbA, baseA, pdbA->seq2pdb, msa, vlen, alnlen, pdbA->seq2pdb->singletons);
}


void
PDB2MapFile(PDBCdsArray *myPDBs, char *mapfile_name)
{
    FILE           *mapfile = NULL;
    int             i, numscanned, seqnum, maxseqnum;
    char            line[FILENAME_MAX + 256];

    mapfile = fopen(mapfile_name, "w");
    if (mapfile == NULL)
    {
        fprintf(stderr,
                "\n  ERROR_691: cannot write alignment -> PDB mapping file \"%s\" \n",
                mapfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    maxseqnum = 0;

    rewind(mapfile);

    for(i = 0, seqnum = 0; i < maxseqnum; ++i)
    {
        if (fgets(line, FILENAME_MAX + 256, mapfile) == NULL)
            break;

        if (line[0] == '#')
            continue;

        if (strlen(line) > 1)
        {
            Seq2PDB        *seq2pdb = NULL;

            numscanned = sscanf(line, "%s %s", seq2pdb->pdbfile_name[i], seq2pdb->seqname[i]);

            if (numscanned == 2)
            {
                ++seqnum;
                /* printf("\nline %d (len %d): %s", i, (int) strlen(line), line); */
            }
        }
    }

    fclose(mapfile);
}


void
pdb2fst(PDBCdsArray *pdbA)
{
    int             i, j, count, aaindex;
    char           *pindex = NULL;
    FILE           *fp = NULL;
    char            outfile[FILENAME_MAX];
    const PDBCds   *pdbi = NULL;

    for (i = 0; i < pdbA->cnum; ++i)
    {
        pdbi = (const PDBCds *) pdbA->cds[i];
        strncpy(outfile, pdbi->filename, strlen(pdbi->filename)+1);
        strncat(outfile, ".fst", 4);

        fp = fopen(outfile, "w");

        //fprintf(fp, ">%-72s", pdbi->filename);
        fprintf(fp, ">%-s", pdbi->filename);

        count = 0;
        for (j = 0; j < pdbi->vlen; ++j)
        {
            //printf("ATOM %4d:%4d \'%s\'\n", i, j, pdbi->name[j]);

            if (RecordSelxn(pdbi->record[j]) &&
                AtomSelxn(pdbi->name[j], 0) &&
                AltLocSelxn(pdbi->altLoc[j]))
            {
                if (count % 72 == 0)
                    fputc('\n', fp);

                pindex = strstr(aan3, pdbi->resName[j]);
                if (pindex == NULL)
                    fputc('X', fp);
                else
                {
                    aaindex = (int) (pindex - &aan3[0])/3;
                    fputc(aan1[aaindex], fp);
                }

                ++count;
            }
        }

        fputc('\n', fp);

        printf("    %4d %s  (%d aa)\n", i+1, outfile, count);

        fflush(NULL);
        fclose(fp);
    }
}

