/*
    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 <time.h>
#include <limits.h>
#include "Error.h"
#include "pdbMalloc.h"
#include "Cds.h"
#include "PDBCds.h"
#include "pdbStats.h"
#include "pdbUtils.h"
#include "pdbIO.h"
#include "distfit.h"
#include "DLTmath.h"


static int
AtomSelxn2(char *name, int mode, char *useratoms);

static int
RangeSelxn2(int chainID, char *chains, int resSeq, int *lower, int *upper, int range_num);

static void
XPLORcorrections(PDBCds *pdbcds, int record);

static void
ScanPDBLine(char *buff, PDBCds *cp, int j, int amber);

static int
ScanTPSLine(char *buff, PDBCds *cp, int j);

static int
GetSlxnLen(PDBCdsArray *pdbA, const int crds, Algorithm *algo, char *chains,
           int *lower, int *upper, const int range_num, int *selection_index, int rev);

static void
PrintCds2File(FILE *pdbfile, Cds *cds);

static void
PrintNuPDBCds(FILE *pdbfile, PDBCds *pdbcds);

static int
IsNameCAorP(char *name);

static void
PrintTPSCds(FILE *pdbfile, PDBCds *pdbcds);

static void
PrintTheseusModelHeader(FILE *pdbfile);

static void
PrintModelFileStats(FILE *pdbfile, PDBCdsArray *pdbA, Algorithm *algo, Statistics *stats);

static void
WritePDBCdsFile(PDBCds *cds, char *file_name);


static const char atoms0[] = "CA :C1*:C1'";
static const char atoms1[] = "N  :C  :O  :CA :P  :C1*:C1':O3*:O3':O5*:O5':C3*:C3':C4*:C4':C5*:C5'";
static const char atoms3[] = "CA :CB :P  :C1*:C1'";


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
AtomSelxn2(char *name, int mode, char *useratoms)
{
    const char     *atoms = NULL;

    if (useratoms)
        mode = 10;

    if (mode == 4)
    {
        if (strchr(name, 'H') == NULL)
            return (1);
        else
            return (0);
    }

    switch(mode)
    {
        case 0: /* "CA :P  " */
            atoms = atoms0;
            break;
        case 1: /* "N  :C  :O  :CA :P  :O3*:O5*:C3*:C4*:C5*:" */
            atoms = atoms1;
            break;
        case 2: /* all atoms */
            return (1);
            break;
        case 3: /* "CA :CB :P  " */
            atoms = atoms3;
            break;
        case 10: /* user defined atom strings */
            atoms = useratoms;
            break;
    }

    if (strstr(atoms, name))
        return (1);
    else
        return (0);
}


static int
RangeSelxn2(int chainID, char *chains, int resSeq, int *lower, int *upper, int range_num)
{
    int             i;

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

    return(0);
}


/* A bunch of XPLOR-correcting silliness, mostly hydrogen format stuff */
static void
XPLORcorrections(PDBCds *pdbcds, int record)
{
    char              tmpname[5];

    /* assigne chainID based upon segID */
    if (!isalpha(pdbcds->chainID[record]) && isalpha(pdbcds->segID[record][0]))
        pdbcds->chainID[record] = pdbcds->segID[record][0];

    /* fix hydrogens and primes for nucleic acids */
    if (strncmp(pdbcds->name[record], "H5'", 3) == 0)
    {
        memcpy(pdbcds->name[record], "H5*", 3);
        pdbcds->Hnum[record] = '1';
    }
    else if (strncmp(pdbcds->name[record], "5''", 3) == 0)
    {
        memcpy(pdbcds->name[record], "H5*", 3);
        pdbcds->Hnum[record] = '2';
    }
    else if (strncmp(pdbcds->name[record], "H2'", 3) == 0)
    {
        memcpy(pdbcds->name[record], "H2*", 3);
        pdbcds->Hnum[record] = '1';
    }
    else if (strncmp(pdbcds->name[record], "2''", 3) == 0)
    {
        memcpy(pdbcds->name[record], "H2*", 3);
        pdbcds->Hnum[record] = '2';
    }

    /* fix all remaining primes */
    if (pdbcds->name[record][2] == '\'')
        pdbcds->name[record][2] = '*';

    if (pdbcds->Hnum[record] == 'H' && pdbcds->name[record][2] != '*')
    {
        /*printf("\n    before: Hnum = %c, name = %3s  --  serial = %d ",
               pdbcds->Hnum[record], pdbcds->name[record], pdbcds->serial[record]);*/
        pdbcds->Hnum[record] = pdbcds->name[record][2];
        tmpname[0] = 'H';
        tmpname[1] = '\0';
        strcat(tmpname, pdbcds->name[record]);
        memcpy(pdbcds->name[record], tmpname, 3);
        /*printf("\n    after:  Hnum = %c, name = %3s",
               pdbcds->Hnum[record], pdbcds->name[record]);*/
    }
    else if (pdbcds->name[record][0] == 'H' && isdigit(pdbcds->name[record][2]))
    {
        /*printf("\n    before: Hnum = %c, name = %3s  --  serial = %d ",
               pdbcds->Hnum[record], pdbcds->name[record], pdbcds->serial[record]);*/
        pdbcds->Hnum[record] = pdbcds->name[record][2];
        pdbcds->name[record][2] = ' ';
        /*printf("\n    after:  Hnum = %c, name = %3s",
               pdbcds->Hnum[record], pdbcds->name[record]);*/
    }

    /* assign atomic element field */
    pdbcds->element[record][1] = pdbcds->name[record][0];

    /* change nucleic acid base names from three-letter to one-letter */
    if (     strncmp(pdbcds->resName[record], "ADE", 3) == 0)
        memcpy(pdbcds->resName[record], "  A", 3);
    else if (strncmp(pdbcds->resName[record], "CYT", 3) == 0)
        memcpy(pdbcds->resName[record], "  C", 3);
    else if (strncmp(pdbcds->resName[record], "GUA", 3) == 0)
        memcpy(pdbcds->resName[record], "  G", 3);
    else if (strncmp(pdbcds->resName[record], "THY", 3) == 0)
        memcpy(pdbcds->resName[record], "  T", 3);
    else if (strncmp(pdbcds->resName[record], "URA", 3) == 0)
        memcpy(pdbcds->resName[record], "  U", 3);
    else if (strncmp(pdbcds->resName[record], "URI", 3) == 0)
        memcpy(pdbcds->resName[record], "  U", 3);
}

/* http://www.wwpdb.org/documentation/format33/sect9.html */

/*
Coordinate Section, ATOM, Version 3.3: July, 2011

COLUMNS        DATA  TYPE    FIELD        DEFINITION
-------------------------------------------------------------------------------------
 1 -  6        Record name   "ATOM  "
 7 - 11        Integer       serial       Atom  serial number.
13 - 16        Atom          name         Atom name.
17             Character     altLoc       Alternate location indicator.
18 - 20        Residue name  resName      Residue name.
22             Character     chainID      Chain identifier.
23 - 26        Integer       resSeq       Residue sequence number.
27             AChar         iCode        Code for insertion of residues.
31 - 38        Real(8.3)     x            Orthogonal coordinates for X in Angstroms.
39 - 46        Real(8.3)     y            Orthogonal coordinates for Y in Angstroms.
47 - 54        Real(8.3)     z            Orthogonal coordinates for Z in Angstroms.
55 - 60        Real(6.2)     occupancy    Nuupancy.
61 - 66        Real(6.2)     tempFactor   Temperature  factor.
77 - 78        LString(2)    element      Element symbol, right-justified.
79 - 80        LString(2)    charge       Charge  on the atom.
*/


static void
ScanPDBLine(char *buff, PDBCds *cp, int j, int amber)
{
    char           *endline = NULL;
    //char            amber_atom_name = buff[12];

    endline = strpbrk(&buff[54], "\r\n\f");
    if (endline) /* kill the newline, if there */
        *endline = '\0';

    //if (!isdigit(amber_atom_name) && !isspace(amber_atom_name))
    if (amber)  /* for nonstandard PDB files, with atom name in wrong column (shifted one to the left, such as with AMBER8, hosers) */
    {
        /*       1         2         3         4         5         6         7         8
        12345678901234567890123456789012345678901234567890123456789012345678901234567890
        ATOM    145  N   VAL A  25      32.433  16.336  57.540  1.00 11.92      A1   N
        ATOM    146  CA  VAL A  25      31.132  16.439  58.160  1.00 11.85      A1   C
        ATOM  15088 1HG1 ILE    28      -3.430   4.303  -6.057  1.00  0.00           H
        ATOM   7580 HD23 LEU  1724     111.285  90.889 -61.535
        */
                                                      /* #  COLs  LEN  DATA TYPE    FIELD      DEFINITION */
        sscanf(&buff[0],  "%6c",   cp->record[j]);    /* 1  0-5   6    Record name  "ATOM  " */
        sscanf(&buff[6],  "%5u",  &cp->serial[j]);    /* 2  6-10  5    Integer      serial     Atom serial number. */
        sscanf(&buff[12], "%3c",   cp->name[j]);      /* 4  12-14 4(3) Atom         name       Atom name. -- unofficial, nonstandard PDB (AMBER8 format, hosers) */
        sscanf(&buff[15], "%1c",  &cp->Hnum[j]);      /* 3  15    1      hydrogen number, (!official). */
        sscanf(&buff[16], "%1c",  &cp->altLoc[j]);    /* 5  16    1    Character    altLoc     Alternate location indicator. */
        sscanf(&buff[17], "%3c",   cp->resName[j]);   /* 6  17-19 3    Residue name resName    Residue name. */
        sscanf(&buff[20], "%1c",  &cp->xchainID[j]);  /* 7  20    1    Character    xchainID   Chain identifier (!official). */
        sscanf(&buff[21], "%1c",  &cp->chainID[j]);   /* 8  21    1    Character    chainID    Chain identifier. */
        sscanf(&buff[22], "%4d",  &cp->resSeq[j]);    /* 9  22-25 4    Integer      resSeq     Residue sequence number. */
        sscanf(&buff[26], "%1c",  &cp->iCode[j]);     /* 10 26    1    AChar        iCode      Code for insertion of residues. */
        sscanf(&buff[30], "%8lf", &cp->x[j]);         /* 11 30-37 8    Real(8.3)    x          Orthogonal coordinates for X */
        sscanf(&buff[38], "%8lf", &cp->y[j]);         /* 12 38-45 8    Real(8.3)    y          Orthogonal coordinates for Y */
        sscanf(&buff[46], "%8lf", &cp->z[j]);         /* 13 46-53 8    Real(8.3)    z          Orthogonal coordinates for Z */
        sscanf(&buff[54], "%6lf", &cp->occupancy[j]); /* 14 54-59 6    Real(6.2)    occupancy  Nuupancy. */
        sscanf(&buff[60], "%6lf", &cp->tempFactor[j]);/* 15 60-65 6    Real(6.2)    tempFactor Temperature factor. */
        sscanf(&buff[72], "%4c",   cp->segID[j]);     /* 16 72-75 4    LString(4)   segID      Segment identifier, left-just. */
        sscanf(&buff[76], "%2c",   cp->element[j]);   /* 17 76-77 2    LString(2)   element    Element symbol, right-just. */
        sscanf(&buff[78], "%2c",   cp->charge[j]);    /* 18 78-79 2    LString(2)   charge     Charge on the atom. */
    }
    else /* standard PDB format http://www.wwpdb.org/documentation/format33/sect9.html#ATOMl */
//     {
//         /*        1         2         3         4         5         6         7         8
//         012345678901234567890123456789012345678901234567890123456789012345678901234567890
//         ATOM    145  N   VAL A  25      32.433  16.336  57.540  1.00 11.92      A1   N
//         ATOM    146  CA  VAL A  25      31.132  16.439  58.160  1.00 11.85      A1   C
//         ATOM  15088 1HG1 ILE    28      -3.430   4.303  -6.057  1.00  0.00           H
//         ATOM   7580 HD23 LEU  1724     111.285  90.889 -61.535
//         */
//                                                       /* #  COLs  LEN  DATA TYPE    FIELD      DEFINITION */
//         sscanf(&buff[0],  "%6c",   cp->record[j]);    /* 1  0-5   6    Record name  "ATOM  " */
//         sscanf(&buff[6],  "%5u",  &cp->serial[j]);    /* 2  6-10  5    Integer      serial     Atom serial number. */
//         sscanf(&buff[12], "%1c",  &cp->Hnum[j]);      /* 3  12    1      hydrogen number, usu (!official). */
//         sscanf(&buff[13], "%3c",   cp->name[j]);      /* 4  13-15 4(3) Atom         name       Atom name. */
//         sscanf(&buff[16], "%1c",  &cp->altLoc[j]);    /* 5  16    1    Character    altLoc     Alternate location indicator. */
//         sscanf(&buff[17], "%3c",   cp->resName[j]);   /* 6  17-19 3    Residue name resName    Residue name. */
//         sscanf(&buff[20], "%1c",  &cp->xchainID[j]);  /* 7  20    1    Character    xchainID   Chain identifier (!official). */
//         sscanf(&buff[21], "%1c",  &cp->chainID[j]);   /* 8  21    1    Character    chainID    Chain identifier. */
//         sscanf(&buff[22], "%4d",  &cp->resSeq[j]);    /* 9  22-25 4    Integer      resSeq     Residue sequence number. */
//         sscanf(&buff[26], "%1c",  &cp->iCode[j]);     /* 10 26    1    AChar        iCode      Code for insertion of residues. */
//         sscanf(&buff[30], "%8lf", &cp->x[j]);         /* 11 30-37 8    Real(8.3)    x          Orthogonal coordinates for X */
//         sscanf(&buff[38], "%8lf", &cp->y[j]);         /* 12 38-45 8    Real(8.3)    y          Orthogonal coordinates for Y */
//         sscanf(&buff[46], "%8lf", &cp->z[j]);         /* 13 46-53 8    Real(8.3)    z          Orthogonal coordinates for Z */
//         sscanf(&buff[54], "%6lf", &cp->occupancy[j]); /* 14 54-59 6    Real(6.2)    occupancy  Nuupancy. */
//         sscanf(&buff[60], "%6lf", &cp->tempFactor[j]);/* 15 60-65 6    Real(6.2)    tempFactor Temperature factor. */
//         sscanf(&buff[72], "%4c",   cp->segID[j]);     /* 16 72-75 4    LString(4)   segID      Segment identifier, left-just. */
//         sscanf(&buff[76], "%2c",   cp->element[j]);   /* 17 76-77 2    LString(2)   element    Element symbol, right-just. */
//         sscanf(&buff[78], "%2c",   cp->charge[j]);    /* 18 78-79 2    LString(2)   charge     Charge on the atom. */
//
//         //printf("%4d:%4d %d %d \'%s\'\n", i, j, cp->serial[j], cp->Hnum[j], cp->name[j]);
//     }
    {
        sscanf(buff,  "%6c%5u%*1c%1c%3c%1c%3c%1c%1c%4d%1c%*3c%8lf%8lf%8lf%6lf%6lf%*6c%4c%2c%2c",
          cp->record[j],    /* 1  0-5   6    Record name  "ATOM  " */
         &cp->serial[j],    /* 2  6-10  5    Integer      serial     Atom serial number. */
         &cp->Hnum[j],      /* 3  12    1      hydrogen number, usu (!official). */
          cp->name[j],      /* 4  13-15 4(3) Atom         name       Atom name. */
         &cp->altLoc[j],    /* 5  16    1    Character    altLoc     Alternate location indicator. */
          cp->resName[j],   /* 6  17-19 3    Residue name resName    Residue name. */
         &cp->xchainID[j],  /* 7  20    1    Character    xchainID   Chain identifier (!official). */
         &cp->chainID[j],   /* 8  21    1    Character    chainID    Chain identifier. */
         &cp->resSeq[j],    /* 9  22-25 4    Integer      resSeq     Residue sequence number. */
         &cp->iCode[j],     /* 10 26    1    AChar        iCode      Code for residue insertion. */
         &cp->x[j],         /* 11 30-37 8    Real(8.3)    x          Orthogonal coordinates for X */
         &cp->y[j],         /* 12 38-45 8    Real(8.3)    y          Orthogonal coordinates for Y */
         &cp->z[j],         /* 13 46-53 8    Real(8.3)    z          Orthogonal coordinates for Z */
         &cp->occupancy[j], /* 14 54-59 6    Real(6.2)    occupancy  Nuupancy. */
         &cp->tempFactor[j],/* 15 60-65 6    Real(6.2)    tempFactor Temperature factor. */
          cp->segID[j],     /* 16 72-75 4    LString(4)   segID      Segment identifier, left-just. */
          cp->element[j],   /* 17 76-77 2    LString(2)   element    Element symbol, right-just. */
          cp->charge[j]);   /* 18 78-79 2    LString(2)   charge     Charge on the atom. */
    }
}


/* reads all coordinate fields for each model in a pdb file */
int
ReadPDBCds(char *pdbfile_name, PDBCdsArray *pdbA, int cds_i, int modelnum, int amber, int fix_atom_names)
{
    int             bufflen = 256;
    char           *buff = NULL;
    int             i, j, pos;
    FILE           *pdbfile = NULL;
    char            pdbdir[FILENAME_MAX], dirpdbfile_name[FILENAME_MAX];
    PDBCds        **cds = pdbA->cds;
    PDBCds         *cp = NULL;


    buff = calloc(bufflen, sizeof(char));
    if (buff == NULL)
    {
        perror("\n\n  ERROR");
        fprintf(stderr, "\n\n  ERROR_071: could not allocate memory for buff. \n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    buff[bufflen-1] = '\0';

    pdbfile = fopen(pdbfile_name, "r");
    if (pdbfile == NULL)
    {
        if (getenv("PDBDIR"))
        {
            strncpy(pdbdir, getenv("PDBDIR"), FILENAME_MAX);
            strncpy(dirpdbfile_name, pdbdir, FILENAME_MAX - 1);
            strncat(dirpdbfile_name, pdbfile_name, FILENAME_MAX - strlen(pdbfile_name) - 1);

            pdbfile = fopen(dirpdbfile_name, "r");
        }

        if (pdbfile == NULL)
        {
            perror("\n\n  ERROR");
            fprintf(stderr, "\n\n  ERROR71: file \"%s\" not found. \n", pdbfile_name);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < modelnum; ++i)
    {
        pos = cds_i + i;
        cp = cds[pos];

        /* move to next model */
        if (modelnum > 1)
        {
            while (fgets(buff, bufflen, pdbfile))
            {
                if (strncmp(buff, "MODEL", 5) == 0 ||
                    strncmp(buff, "REMARK FRAME:", 13) == 0 ||
                    strncmp(buff, "REMARK  OPLS", 12) == 0)
                {
                    cp->model = i;
                    break;
                }
            }
        }

        mystrncpy(cp->filename, pdbfile_name, FILENAME_MAX - 1);

        /* read in the model's cds */
        j = 0;
        while (fgets(buff, bufflen, pdbfile))
        {
            if (j >= cp->vlen ||
                strncmp(buff, "ENDMDL", 6) == 0 ||
                strncmp(buff, "END", 3) == 0)
            {
                break;
            }

            /* if the line is an ATOM or HETATM record */
            if (RecordSelxn(buff))
            {
                ScanPDBLine(buff, cp, j, amber);
                if (fix_atom_names)
                    XPLORcorrections(cp, j);

                if (isfinite(cp->occupancy[j]) == 0)
                {
                    cp->occupancy[j] = 1.0;
                    cp->nu[j] = 1;
                }

                ++j;
            }
        }
    }

    //PrintPDBCds(stdout, pdbA->cds[0]);
    MyFree(buff);
    fclose(pdbfile);

    return(EXIT_SUCCESS);
}


static int
ScanTPSLine(char *buff, PDBCds *cp, int j)
{
//    char           *endline = NULL;
    int             numret;

//     endline = strpbrk(&buff[54], "\r\n\f");
//     if (endline) /* kill the newline, if there */
//         *endline = '\0';

    strncpy(cp->record[j], "ATOM  ", 6);
    cp->serial[j] = j;
    cp->Hnum[j] = ' ';
    strncpy(cp->name[j], "CA ", 3);
    cp->altLoc[j] = ' ';
    strncpy(cp->resName[j], "LM ", 3);
    cp->xchainID[j] = ' ';
    cp->chainID[j] = 'A';
    cp->resSeq[j] = j;
    cp->iCode[j] = ' ';
    numret = sscanf(buff, "%lf%lf%lf", &cp->x[j], &cp->y[j], &cp->z[j]);
    cp->occupancy[j] = 1.0;
    cp->tempFactor[j] = 1.0;
    strncpy(cp->segID[j], "    ", 4);
    strncpy(cp->element[j], "  ", 2);
    strncpy(cp->charge[j], "  ", 2);
    cp->nu[j] = 1;

    return(numret);
}


/* reads coordinates for each model in a Rohlf-style tps morphometrics file */
int
ReadTPSCds(char *pdbfile_name, PDBCdsArray *pdbA, int cds_i, int modelnum)
{
    int             bufflen = 256;
    char           *buff = NULL;
    int             i, j, pos, numret;
    FILE           *pdbfile = NULL;
    char            pdbdir[FILENAME_MAX], dirpdbfile_name[FILENAME_MAX];
    PDBCds        **cds = pdbA->cds;
    PDBCds         *cp = NULL;


    buff = calloc(bufflen, sizeof(char));
    if (buff == NULL)
    {
        perror("\n\n  ERROR");
        fprintf(stderr, "\n\n  ERROR_071: could not allocate memory for buff. \n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    buff[bufflen-1] = '\0';

    pdbfile = fopen(pdbfile_name, "r");
    if (pdbfile == NULL)
    {
        if (getenv("PDBDIR"))
        {
            strncpy(pdbdir, getenv("PDBDIR"), FILENAME_MAX);
            strncpy(dirpdbfile_name, pdbdir, FILENAME_MAX - 1);
            strncat(dirpdbfile_name, pdbfile_name, FILENAME_MAX - strlen(pdbfile_name) - 1);

            pdbfile = fopen(dirpdbfile_name, "r");
        }

        if (pdbfile == NULL)
        {
            perror("\n\n  ERROR");
            fprintf(stderr, "\n\n  ERROR71: file \"%s\" not found. \n", pdbfile_name);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < modelnum; ++i)
    {
        pos = cds_i + i;
        cp = cds[pos];

        /* move to next model */
        if (modelnum > 1)
        {
            while (fgets(buff, bufflen, pdbfile))
            {
                if (strncmp(buff, "LM3", 3) == 0)
                {
                    cp->model = i;
                    break;
                }
            }
        }

        // printf("i=%d\n", i);
        // fflush(NULL);

        /* read in the model's cds */
        j = 0;
        while (fgets(buff, bufflen, pdbfile))
        {
            //printf("BUFF[%d]:%s\n", j, buff);fflush(NULL);
            if (j >= cp->vlen ||
                (strncmp(buff, "IMAGE", 5) == 0) ||
                (strncmp(buff, "ID", 2) == 0) ||
                (strncmp(buff, "SCALE", 5) == 0))
            {
                //printf("INNERBUFF:%s\n", buff);fflush(NULL);
                if (strncmp(buff, "ID", 2) == 0)
                {
                    sscanf(buff, "ID=%s", cp->filename);
                    //printf("ID=%s\n", cp->filename);fflush(NULL);
                }

                break;
            }
            else
            {
                numret = ScanTPSLine(buff, cp, j);
                //printf("LM %4d [%4d]: %16f %16f %16f\n", j, numret, cp->x[j], cp->y[j], cp->z[j]);

                if (numret == 3)
                    ++j;
            }
        }

        //mystrncpy(cp->filename, pdbfile_name, FILENAME_MAX - 1);
    }

    //PrintPDBCds(stdout, pdbA->cds[0]);

    MyFree(buff);
    fclose(pdbfile);

    return(EXIT_SUCCESS);
}


PDBCdsArray
*GetTPSCds(char **argv_array, int narguments)
{
    int                  i, j, k;
    int                 *models_per_tps = NULL;
    int                 *len_array = NULL;
    int                  cnum, models, vlen, last_vlen;
    char                 tpsfile_name[FILENAME_MAX], pdbdir[FILENAME_MAX],
                         dirtpsfile_name[FILENAME_MAX];
    char                 buff[256];
    FILE                *tpsfile = NULL;
    PDBCdsArray         *pdbA = NULL;

    if (getenv("PDBDIR"))
        strncpy(pdbdir, getenv("PDBDIR"), FILENAME_MAX);

    vlen = last_vlen = 0;

    models_per_tps = (int *) calloc(narguments, sizeof(int));
    len_array = (int *) calloc(narguments, sizeof(int));
    if (models_per_tps == NULL || len_array == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR1: could not allocate memory for 'models_per_tps' or 'len_array' in GetTPSCds(). \n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    /* count models and atoms, verify consistency and allocate */
    cnum = 0;
    for (i = 0; i < narguments; ++i)
    {
        strncpy(tpsfile_name, argv_array[i], 255);

        tpsfile = fopen(tpsfile_name, "r");
        if (tpsfile == NULL)
        {
            strncpy(dirtpsfile_name, pdbdir, FILENAME_MAX - 1);
            strncat(dirtpsfile_name, tpsfile_name, FILENAME_MAX - strlen(tpsfile_name) - 1);

            tpsfile = fopen(dirtpsfile_name, "r");

            if (tpsfile == NULL)
            {
                perror("\n  ERROR");
                fprintf(stderr, "\n  ERROR73: file \"%s\" not found. \n", tpsfile_name);
                PrintTheseusTag();
                exit(EXIT_FAILURE);
            }
        }

        models = 0;
        last_vlen = 0;
        while (fgets(buff, (int) sizeof(buff), tpsfile))
        {
            if (strncmp(buff, "LM3", 3) == 0) // DLT
            {
                sscanf(buff,  "LM3=%d", &vlen);

//                 printf("\nvlen=%d\n", vlen);
//                 fflush(NULL);

                if ((models > 0) && (vlen != last_vlen))
                {
                    fprintf(stderr,
                            "\n  ERROR1005: cds #%d in \"%s\" has a different landmark number (%d) than the preceding cds (%d). \n",
                            models + 1, tpsfile_name, vlen, last_vlen);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }
                else if (vlen < 3)
                {
                    fprintf(stderr,
                            "\n  ERROR1006: cds #%d in \"%s\" has no selected landmarks. \n",
                            models + 1, tpsfile_name);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }
                else
                {
                    last_vlen = vlen;
                }

                ++models;
            }
        }

//         printf("\nmodels=%d\n",models);
//         fflush(NULL);

        if (models)
        {
            models_per_tps[i] = models;
            cnum += models;
        }
        else
        {
            models_per_tps[i] = 1;
            ++cnum;
            vlen = 0;
            rewind(tpsfile);

            while (fgets(buff, sizeof(buff), tpsfile))
            {
                if (strncmp(buff, "LM3", 3) == 0) // DLT
                {
                    sscanf(buff,  "LM3=%d", &vlen);

//                     printf("\nvlen=%d\n", vlen);
//                     fflush(NULL);
                }
                else if ((strncmp(buff, "IMAGE", 5) == 0) ||
                         (strncmp(buff, "ID", 2) == 0) ||
                         (strncmp(buff, "SCALE", 5) == 0))
                {
                    break;
                }
            }
        }

        fclose(tpsfile);

        if (vlen < 3)
        {
            fprintf(stderr, "\n  ERROR1007: 'vlen' is too small (%d) in GetTPSCds()", vlen);
            fprintf(stderr, "\n  Too few landmarks read. \n");
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        len_array[i] = vlen;
//         printf("\n vlen[%d] = %d\n", i, vlen);
    }

    pdbA = PDBCdsArrayInit();
    PDBCdsArrayAllocNum(pdbA, cnum);

    k = 0;
    for (i = 0; i < narguments; ++i)
    {
        for (j = 0; j < models_per_tps[i]; ++j)
        {
            PDBCdsAlloc(pdbA->cds[k], len_array[i]);
            ++k;
        }
    }

    /* read in all cds for all pdb files */
    for (i = 0, j = 0; i < narguments && j < cnum; ++i)
    {
        strncpy(tpsfile_name, argv_array[i], FILENAME_MAX - 1);
        ReadTPSCds(tpsfile_name, pdbA, j, models_per_tps[i]);
        j += models_per_tps[i];
    }

    MyFree(models_per_tps);
    MyFree(len_array);

    return(pdbA);
}


static int
GetSlxnLen(PDBCdsArray *pdbA, const int j, Algorithm *algo, char *chains,
           int *lower, int *upper, const int range_num, int *selection_index, int rev)
{
    int             i, vlen;
    const PDBCds   *pdbj = pdbA->cds[j];

    vlen = 0;
    for (i = 0; i < pdbj->vlen; ++i)
    {
/*         printf("\n%s, %s, %c, %c, %d", */
/*                pdbj->record[i], */
/*                pdbj->name[i], */
/*                pdbj->altLoc[i], */
/*                pdbj->chainID[i], */
/*                pdbj->resSeq[i]); */
/*         fflush(NULL); */
        if (RecordSelxn(pdbj->record[i]) &&
            AtomSelxn2(pdbj->name[i], algo->atoms, algo->atomslxn) &&
            AltLocSelxn(pdbj->altLoc[i]) &&
            RangeSelxn2(pdbj->chainID[i], chains, pdbj->resSeq[i], lower, upper, range_num) == rev) /* DLT debug FIX THIS!!!! */
        {
            if (selection_index)
                selection_index[vlen] = i;
            ++vlen;
        }
    }

    return(vlen);
}


void
GetCdsSelection(CdsArray *baseA, PDBCdsArray *pdbA)
{
    int             vlen, last_len;
    int            *selection_index = NULL; /* array of ints corresponding to selected atoms */
    int             i, j;
    char           *chains = NULL; /* array of chars holding chain IDs of ranges in the selection criteria */
    int            *upper = NULL, *lower = NULL; /* an array of ints holding the upper and lower range bounds */
    int             selection_len;
    char          **endptr = NULL;
    int             range_num = 1; /* number of residue ranges specified in selection */
    char          **selections = NULL; /* an array of range_num strings to hold each range selection */
    char            delims[] = ":";

    if (algo->selection)
    {
        selection_len = strlen(algo->selection);

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

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

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

        /* if the user specified XPLOR/CNS-style atoms, translate them to standard PDB format (DNA primes should be "*", not "'") */
        j = strlen(algo->selection);
        for (i = 0; i < j; ++i)
            if (algo->selection[i] == '\'')
                algo->selection[i] = '*';

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

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

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

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

            if (isalpha(selections[j][i]))
            {
                chains[j] = toupper(selections[j][i]);
                ++i;
            }
            else
                chains[j] = 0;

            if (isalpha(selections[j][i]))
            {
                fprintf(stderr, "\n  ERROR1002: incorrect format for chainID selection (too many characters). \n");
                Usage(0);
                exit(EXIT_FAILURE);
            }

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

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

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

                upper[j] = (int) strtol(&selections[j][i], endptr, 10);
            }
            else
            {
                lower[j] = 0;
                upper[j] = pdbA->vlen - 1;
            }
        }
    }
    else
    {
        range_num = 1;
        selections = (char **) calloc(1, sizeof(char *));
        lower      = (int *)   calloc(1, sizeof(int));
        upper      = (int *)   calloc(1, sizeof(int));
        chains     = (char *)  calloc(1, sizeof(char));
        selections[0] = (char *) calloc(128, sizeof(char));
        if (selections == NULL || lower == NULL || upper == NULL || chains == NULL || selections[0] == NULL)
        {
            perror("\n  ERROR");
            fprintf(stderr, "\n  ERROR1003: could not allocate memory for selections in GetCdsSelection(). \n");
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        lower[0] = -INT_MAX;
        upper[0] = INT_MAX;
    }

/*     if (upper[0] - 4 < lower[0]) */
/*     { */
/*         fprintf(stderr, "\n  ERROR876: upper residue bound must be at least 3 greater than the lower bound. %d %d \n", */
/*                          upper[0], lower[0]); */
/*         PrintTheseusTag(); */
/*         exit(EXIT_FAILURE); */
/*     } */

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

    /* First count the number of selected atoms for each structure, and make sure they are all the same */
    last_len = vlen = 0;
    for (i = 0; i < pdbA->cnum; ++i)
    {
//         printf("\n%s, %s, %c, %c, %d",
//                pdbA->cds[i]->record[i],
//                pdbA->cds[i]->name[i],
//                pdbA->cds[i]->altLoc[i],
//                pdbA->cds[i]->chainID[i],
//                pdbA->cds[i]->resSeq[i]);
//         fflush(NULL);
        last_len = vlen;
        if (algo->revsel == 0)
            vlen = GetSlxnLen(pdbA, i, algo, chains, lower, upper, range_num, NULL, 1);
        else
            vlen = GetSlxnLen(pdbA, i, algo, chains, lower, upper, range_num, NULL, 0);

        if (i > 0 && vlen != last_len)
        {
            fprintf(stderr,
                    "\n  ERROR1: length of selection is (%d) different from preceding cds (%d) in GetCdsSelection() \n", vlen, i+1);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        if (vlen < 3)
        {
            fprintf(stderr,
                    "\n  ERROR3: 'vlen' is too small (%d) in GetCdsSelection(); not enough atoms selected in cds %d. \n", vlen, i+1);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    /* Now allocate based on this length */
    /* baseA = CdsArrayInit(); */ /* NB!!: we don't need to initialize, since the pointer was passed above */
    CdsArrayAlloc(baseA, pdbA->cnum, vlen);
    CdsArraySetup(baseA);

    /* Now get the selections again and read them in this time */
    for (j = 0; j < pdbA->cnum; ++j)
    {
        if (algo->revsel == 0)
            vlen = GetSlxnLen(pdbA, j, algo, chains, lower, upper, range_num, selection_index, 1);
        else
            vlen = GetSlxnLen(pdbA, j, algo, chains, lower, upper, range_num, selection_index, 0);

        baseA->cds[j]->model = pdbA->cds[j]->model;
        strncpy(baseA->cds[j]->filename, pdbA->cds[j]->filename, FILENAME_MAX - 1);

        for (i = 0; i < baseA->vlen; ++i)
        {
            strncpy(baseA->cds[j]->resName[i], pdbA->cds[j]->resName[selection_index[i]], 3);
            baseA->cds[j]->chainID[i] = pdbA->cds[j]->chainID[selection_index[i]];
            baseA->cds[j]->resSeq[i] = pdbA->cds[j]->resSeq[selection_index[i]];
            baseA->cds[j]->x[i]      = pdbA->cds[j]->x[selection_index[i]];
            baseA->cds[j]->y[i]      = pdbA->cds[j]->y[selection_index[i]];
            baseA->cds[j]->z[i]      = pdbA->cds[j]->z[selection_index[i]];
            baseA->cds[j]->o[i]      = pdbA->cds[j]->occupancy[selection_index[i]];
            baseA->cds[j]->b[i]      = pdbA->cds[j]->tempFactor[selection_index[i]];
            baseA->cds[j]->nu[i]     = 1;
        }
    }

/* PrintCds(baseA->cds[0]); */
/* PrintCds(baseA->cds[1]); */

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

    MyFree(selections);
    MyFree(selection_index);
    MyFree(upper);
    MyFree(lower);
    MyFree(chains);
}


PDBCdsArray
*GetPDBCds(char **argv_array, int narguments, int fmodel, int amber, int fix_atom_names)
{
    int                  i, j, k;
    int                 *models_per_pdb = NULL;
    int                 *len_array = NULL;
    int                  cnum, models, vlen, last_vlen;
    char                 pdbfile_name[FILENAME_MAX], pdbdir[FILENAME_MAX],
                         dirpdbfile_name[FILENAME_MAX];
    char                 buff[256];
    FILE                *pdbfile = NULL;
    PDBCdsArray      *pdbA = NULL;

    if (getenv("PDBDIR"))
        strncpy(pdbdir, getenv("PDBDIR"), FILENAME_MAX);

    vlen = last_vlen = 0;

    models_per_pdb = (int *) calloc(narguments, sizeof(int));
    len_array = (int *) calloc(narguments, sizeof(int));
    if (models_per_pdb == NULL || len_array == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR1: could not allocate memory for 'models_per_pdb' or 'len_array' in GetPDBCds(). \n");
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    /* count models and atoms, verify consistency and allocate */
    cnum = 0;
    for (i = 0; i < narguments; ++i)
    {
        strncpy(pdbfile_name, argv_array[i], 255);

        pdbfile = fopen(pdbfile_name, "r");
        if (pdbfile == NULL)
        {
            strncpy(dirpdbfile_name, pdbdir, FILENAME_MAX - 1);
            strncat(dirpdbfile_name, pdbfile_name, FILENAME_MAX - strlen(pdbfile_name) - 1);

            pdbfile = fopen(dirpdbfile_name, "r");

            if (pdbfile == NULL)
            {
                perror("\n  ERROR");
                fprintf(stderr, "\n  ERROR73: file \"%s\" not found. \n", pdbfile_name);
                PrintTheseusTag();
                exit(EXIT_FAILURE);
            }
        }

        models = 0;
        last_vlen = 0;
        while (fgets(buff, (int) sizeof(buff), pdbfile))
        {
            if (strncmp(buff, "MODEL", 5) == 0 ||
                strncmp(buff, "REMARK FRAME:", 13) == 0 ||
                strncmp(buff, "REMARK  OPLS", 12) == 0)
            {
                vlen = 0;
                while (fgets(buff, sizeof(buff), pdbfile))
                {
                    if ((strncmp(buff, "END", 3) == 0) || (strncmp(buff, "ENDMDL", 6) == 0))
                        break;
                    else if (RecordSelxn(buff))
                        ++vlen;
                }

                /* printf("\n vlen1 = %d", vlen); */
                if (fmodel)
                    break;
                else if ((models > 0) && (vlen != last_vlen))
                {
                    fprintf(stderr,
                            "\n  WARNING1005: cds #%d in \"%s\" has a different atom number (%d) than the preceding cds (%d). \n",
                            models + 1, pdbfile_name, vlen, last_vlen);
                }
                else if (vlen < 3)
                {
                    fprintf(stderr,
                            "\n  WARNING1006: cds #%d in \"%s\" has no selected atoms. \n",
                            models + 1, pdbfile_name);
                    PrintTheseusTag();
                    exit(EXIT_FAILURE);
                }
                else
                {
                    last_vlen = vlen;
                }

                ++models;
            }
        }

        if (models && fmodel == 0)
        {
            models_per_pdb[i] = models;
            cnum += models;
        }
        else if (models && fmodel == 1) /* read only the first model */
        {
            models_per_pdb[i] = 1;
            cnum += 1;
        }
        else
        {
            models_per_pdb[i] = 1;
            ++cnum;
            vlen = 0;
            rewind(pdbfile);

            while (fgets(buff, sizeof(buff), pdbfile))
            {
                if (strncmp(buff, "END", 3) == 0 || (strncmp(buff, "ENDMDL", 6) == 0))
                    break;
                else if (RecordSelxn(buff))
                    ++vlen;
            }
        }

        fclose(pdbfile);

        if (vlen < 3)
        {
            fprintf(stderr, "\n  ERROR_002: 'vlen' is too small (%d) in GetPDBCds()", vlen);
            fprintf(stderr, "\n  Too few atoms read. \n");
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        len_array[i] = vlen;
        //printf("\n vlen[%d] = %d", i, vlen);
    }

    pdbA = PDBCdsArrayInit();
    PDBCdsArrayAllocNum(pdbA, cnum);

    k = 0;
    for (i = 0; i < narguments; ++i)
    {
        for (j = 0; j < models_per_pdb[i]; ++j)
        {
            /* pdbA->cds[k] = PDBCdsInit(); */ /* already initialized and partially allocated above */
            PDBCdsAlloc(pdbA->cds[k], len_array[i]);
            ++k;
        }
    }

    /* read in all cds for all pdb files */
    for (i = 0, j = 0; i < narguments && j < cnum; ++i)
    {
        strncpy(pdbfile_name, argv_array[i], FILENAME_MAX - 1);
        ReadPDBCds(pdbfile_name, pdbA, j, models_per_pdb[i], amber, fix_atom_names);
        j += models_per_pdb[i];
    }

    MyFree(models_per_pdb);
    MyFree(len_array);

    return(pdbA);
}


void
PrintCds(Cds *cds)
{
    int             i;

    fprintf(stdout, "\n veclen = %d \n", cds->vlen);
    fprintf(stdout, " filename = %s \n", cds->filename);
    fprintf(stdout, " model = %d \n",    cds->model);

    for (i = 0; i < cds->vlen; ++i)
    {
        fprintf(stdout,
                "%4d %3s %4d %8.3f %8.3f %8.3f  %8.3f\n",
                i+1,
                cds->resName[i],
                cds->resSeq[i],
                cds->x[i],
                cds->y[i],
                cds->z[i],
                cds->o[i]);
    }

    printf(" END   \n\n");
    fflush(NULL);
}


/* Prints out a set of PDB coordinates (one model)
   adding TER cards when appropriate
   and renumbering the 'serial' field from 1
   it can handle two character chain IDs (xchainID & chainID) */
void
PrintPDBCds(FILE *pdbfile, PDBCds *pdbcds)
{
    int             i;
    unsigned int    serial = 1;

    for (i = 0; i < pdbcds->vlen; ++i)
    {
                /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

        fprintf(pdbfile,
               /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                "%-6.6s%5u %1c%3.3s%1c%-3.3s%1c%1c%4d%1c   %8.3f%8.3f%8.3f%6.2f%6.2f      %-4.4s%2.2s%2.2s\n",
                pdbcds->record[i],   serial,               pdbcds->Hnum[i],
                pdbcds->name[i],     pdbcds->altLoc[i],    pdbcds->resName[i],
                pdbcds->xchainID[i], pdbcds->chainID[i],   pdbcds->resSeq[i],
                pdbcds->iCode[i],    pdbcds->x[i],         pdbcds->y[i],
                pdbcds->z[i],        pdbcds->occupancy[i], pdbcds->tempFactor[i],
                pdbcds->segID[i],    pdbcds->element[i],   pdbcds->charge[i]);
        // fflush(NULL);
        ++serial;

        /* add TER cards at end of ATOM chains */
        /* first expression evaluates whether at end of internal ATOM chain */
        /* second expression evaluates if at end of cds and ATOM chain */
        if (
            ((i + 1) <  pdbcds->vlen && RecordSelxn(pdbcds->record[i]) &&
             (pdbcds->chainID[i] != pdbcds->chainID[i+1] || pdbcds->xchainID[i] != pdbcds->xchainID[i+1]))
           ||
            ((i + 1) == pdbcds->vlen && RecordSelxn(pdbcds->record[i]))
           ||
            (i < pdbcds->vlen - 1 &&
            IsNameCAorP(pdbcds->name[i]) &&
            IsNameCAorP(pdbcds->name[i+1]) &&
            strncmp(pdbcds->name[i], pdbcds->name[i+1], 3) != 0)
           )
        {
            fprintf(pdbfile,
                    "%-6.6s%5u      %-3.3s%1c%1c%4d%1c\n",
                    "TER",                  serial              ,    pdbcds->resName[i],
                    pdbcds->xchainID[i], pdbcds->chainID[i],   pdbcds->resSeq[i],
                    pdbcds->iCode[i]);
            ++serial;
        }
    }

    fflush(NULL);
}


static void
PrintCds2File(FILE *pdbfile, Cds *cds)
{
    int             i;
    unsigned int    serial = 1;

    for (i = 0; i < cds->vlen; ++i)
    {
                /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

        fprintf(pdbfile,
               /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                "ATOM",   serial,
                "CA ", cds->resName[i],
                cds->chainID[i],   cds->resSeq[i],
                cds->x[i],         cds->y[i],     cds->z[i],
                cds->o[i], cds->b[i]);
        /* fflush(NULL); */
        ++serial;

        /* add TER cards at end of ATOM chains */
        /* first expression evaluates whether at end of internal ATOM chain */
        /* second expression evaluates if at end of cds and ATOM chain */
/*         if ( */
/*             ((i + 1) <  cds->vlen &&  */
/*               ( strncmp(cds->record[i], "ATOM  ", 6) == 0 || strncmp(cds->record[i], "HETATM", 6) == 0 ) */
/*             && */
/*              (cds->chainID[i] != cds->chainID[i+1] || cds->xchainID[i] != cds->xchainID[i+1])) */
/*            || */
/*             ((i + 1) == cds->vlen &&  */
/*              ( strncmp(cds->record[i], "ATOM  ", 6) == 0 || strncmp(cds->record[i], "HETATM", 6) == 0 )) */
/*            || */
/*             (i < cds->vlen - 1 && */
/*             IsNameCAorP(cds->name[i]) && */
/*             IsNameCAorP(cds->name[i+1]) && */
/*             strncmp(cds->name[i], cds->name[i+1], 3) != 0) */
/*            ) */
/*         { */
/*             fprintf(pdbfile, */
/*                     "%-6.6s%5u      %-3.3s%1c%1c%4d%1c\n", */
/*                     "TER",                  serial              ,    cds->resName[i], */
/*                     cds->xchainID[i], cds->chainID[i],   cds->resSeq[i], */
/*                     cds->iCode[i]); */
/*             ++serial; */
/*         } */
    }

    fflush(NULL);
}


static void
PrintNuPDBCds(FILE *pdbfile, PDBCds *pdbcds)
{
    int             i;
    unsigned int    serial = 1;

    for (i = 0; i < pdbcds->vlen; ++i)
    {
        if (pdbcds->nu[i])
        {
                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */
            fprintf(pdbfile,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u %1c%3.3s%1c%-3.3s%1c%1c%4d%1c   %8.3f%8.3f%8.3f%6.2f%6.2f      %-4.4s%2.2s%2.2s\n",
                    pdbcds->record[i],   serial,                  pdbcds->Hnum[i],
                    pdbcds->name[i],     pdbcds->altLoc[i],    pdbcds->resName[i],
                    pdbcds->xchainID[i], pdbcds->chainID[i],   pdbcds->resSeq[i],
                    pdbcds->iCode[i],    pdbcds->x[i],         pdbcds->y[i],
                    pdbcds->z[i],        pdbcds->occupancy[i], pdbcds->tempFactor[i],
                    pdbcds->segID[i],    pdbcds->element[i],   pdbcds->charge[i]);
            /* fflush(NULL); */
            ++serial;

            /* add TER cards at end of ATOM chains */
            /* first expression evaluates whether at end of internal ATOM chain */
            /* second expression evaluates if at end of cds and ATOM chain */
            if (
                ((i + 1) <  pdbcds->vlen && RecordSelxn(pdbcds->record[i])
                &&
                 (pdbcds->chainID[i] != pdbcds->chainID[i+1] || pdbcds->xchainID[i] != pdbcds->xchainID[i+1]))
               ||
                ((i + 1) == pdbcds->vlen && RecordSelxn(pdbcds->record[i]))
               ||
                (i < pdbcds->vlen - 1 &&
                IsNameCAorP(pdbcds->name[i]) &&
                IsNameCAorP(pdbcds->name[i+1]) &&
                strncmp(pdbcds->name[i], pdbcds->name[i+1], 3) != 0)
               )
            {
                fprintf(pdbfile,
                        "%-6.6s%5u      %-3.3s%1c%1c%4d%1c\n",
                        "TER",                  serial              ,    pdbcds->resName[i],
                        pdbcds->xchainID[i], pdbcds->chainID[i],   pdbcds->resSeq[i],
                        pdbcds->iCode[i]);
                ++serial;
            }
        }
    }

    fflush(NULL);
}


static int
IsNameCAorP(char *name)
{
    if (strlen(name) < 3)
        return(0);
    else if (strncmp(name, "CA ", 3) == 0 || strncmp(name, "P  ", 3) == 0)
        return(1);
    else
        return(0);
}


char
*WriteFileMap(PDBCdsArray *myPDBs, const char *filemap_name)
{
    FILE           *filemap = NULL;
    char            tmpStr[1024];
    char           *fNameStr;
    int             ii, tmpLen;

    /* ////////////////////////////////////////////////////////////// */
    tmpLen = strlen(filemap_name) + 9;
    fNameStr = malloc(tmpLen * sizeof(char));
    if (!fNameStr)
    {
        perror("\n\n  ERROR");
        fprintf(stderr, "\n\n  ERROR_071: could not allocate a string of length %d in WriteFileMap. \n", tmpLen);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    sprintf(fNameStr, "%s.filemap", filemap_name);
    filemap = fopen(fNameStr, "w");
    if (filemap == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s.filemap' for writing. \n", filemap_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (ii = 0; ii < myPDBs->cnum; ++ii)
    {
        tmpLen = strlen(myPDBs->cds[ii]->filename);
        strncpy(tmpStr, myPDBs->cds[ii]->filename, (tmpLen + 1) * sizeof(char));
        tmpLen--;
        while (tmpLen)
        {
            if (tmpStr[tmpLen] == '.')
            {
                tmpStr[tmpLen] = '\0';
                break;
            }
            tmpLen--;
        }
        fprintf(filemap, "%s %s\n", myPDBs->cds[ii]->filename, tmpStr);
    }

    fclose(filemap);

    return fNameStr;
}


void
WriteModelFile(PDBCdsArray *pdbA, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */
    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile ==NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);

    for (i = 0; i < pdbA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintPDBCds(pdbfile, pdbA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


/* Prints out a set of PDB coordinates (one model)
   adding TER cards when appropriate
   and renumbering the 'serial' field from 1
   it can handle two character chain IDs (xchainID & chainID) */
static void
PrintTPSCds(FILE *pdbfile, PDBCds *pdbcds)
{
    int             i;

    fprintf(pdbfile, "LM3=%d\n", pdbcds->vlen);

    for (i = 0; i < pdbcds->vlen; ++i)
    {
                /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

        fprintf(pdbfile, "%-.20f %-.20f %-.20f\n", pdbcds->x[i], pdbcds->y[i], pdbcds->z[i]);
        // fflush(NULL);
    }

    fflush(NULL);
}


void
WriteTheseusTPSModelFile(PDBCdsArray *pdbA, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */
    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < pdbA->cnum; ++i)
    {
        PrintTPSCds(pdbfile, pdbA->cds[i]);
//        fprintf(pdbfile, "ID=%d\n\n", i+1);
        fprintf(pdbfile, "ID=%s\n\n", pdbA->cds[i]->filename);
    }

    fclose(pdbfile);
}


/* writes a pdb file of the average cds */
void
WriteAveTPSCdsFile(PDBCdsArray *pdbA, char *outfile_name)
{
    FILE           *pdbfile = NULL;

    /* char            avecds_filename[512]; */

    /* strcpy(avecds_filename, getroot(outfile_name)); */
    /* strcat(outfile_name, "_ave.pdb"); */

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile ==NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n\n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTPSCds(pdbfile, pdbA->avecds);
    fprintf(pdbfile, "ID=%s\n\n", "ave");

    fclose(pdbfile);
}


void
WriteTheseusModelFile(PDBCdsArray *pdbA, Algorithm *algo, Statistics *stats, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */

/*     pdbfile = fopen(outfile_name, "w"); */
/*     if (pdbfile ==NULL) */
/*     { */
/*         perror("\n  ERROR"); */
/*         fprintf(stderr, */
/*                 "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name); */
/*         PrintTheseusTag(); */
/*         exit(EXIT_FAILURE); */
/*     } */

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    PrintModelFileStats(pdbfile, pdbA, algo, stats);

    for (i = 0; i < pdbA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintPDBCds(pdbfile, pdbA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


void
WriteTheseusModelFileNoStats(PDBCdsArray *pdbA, Algorithm *algo, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */

/*     pdbfile = fopen(outfile_name, "w"); */
/*     if (pdbfile ==NULL) */
/*     { */
/*         perror("\n  ERROR"); */
/*         fprintf(stderr, */
/*                 "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name); */
/*         PrintTheseusTag(); */
/*         exit(EXIT_FAILURE); */
/*     } */

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);

    for (i = 0; i < pdbA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintPDBCds(pdbfile, pdbA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


void
WriteTheseusCdsModelFile(CdsArray *cdsA, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    fprintf(pdbfile, "REMARK   Log Likelihood          %11.2f\n", stats->logL);
    fprintf(pdbfile, "REMARK   Log Marginal Likelihood %11.2f\n", stats->mlogL);
    //PrintModelFileStats(pdbfile, pdbA, algo, stats);

    for (i = 0; i < cdsA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintCds2File(pdbfile, cdsA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


void
OverWriteTheseusCdsModelFile(CdsArray *cdsA, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */

    pdbfile = fopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    fprintf(pdbfile, "REMARK   Log Likelihood          %11.2f\n", stats->logL);
    fprintf(pdbfile, "REMARK   Log Marginal Likelihood %11.2f\n", stats->mlogL);
    fprintf(pdbfile, "REMARK   RMSD                    %22.6f\n", stats->ave_paRMSD);
    //PrintModelFileStats(pdbfile, pdbA, algo, stats);

    for (i = 0; i < cdsA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintCds2File(pdbfile, cdsA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


void
WriteTheseusPDBFiles(PDBCdsArray *pdbA, Algorithm *algo, Statistics *stats, char *fname)
{
    FILE           *pdbfile = NULL;
    int             i;
    char            outfile_name[FILENAME_MAX];

    for (i = 0; i < pdbA->cnum; ++i)
    {
        strncpy(outfile_name, fname, strlen(fname)+1);
        strcat(outfile_name, "_");
        strncat(outfile_name, pdbA->cds[i]->filename, strlen(pdbA->cds[i]->filename)+1);
        //strcpy(outfile_name, getroot(pdbA->cds[i]->filename));
        //strcat(outfile_name, "_ths.pdb");

        pdbfile = myfopen(outfile_name, "w");
        if (pdbfile == NULL)
        {
            perror("\n  ERROR");
            fprintf(stderr,
                    "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }

        PrintTheseusModelHeader(pdbfile);
        PrintModelFileStats(pdbfile, pdbA, algo, stats);
        PrintPDBCds(pdbfile, pdbA->cds[i]);
        fprintf(pdbfile, "END   \n");
        fclose(pdbfile);
    }
}


void
WriteOlveModelFile(PDBCdsArray *pdbA, Algorithm *algo, Statistics *stats, char *outfile_name)
{
    FILE           *pdbfile = NULL;
    int             i;

    /* ////////////////////////////////////////////////////////////// */
    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile ==NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    PrintModelFileStats(pdbfile, pdbA, algo, stats);

    for (i = 0; i < pdbA->cnum; ++i)
    {
        fprintf(pdbfile, "MODEL     %4d\n", i+1);
        PrintNuPDBCds(pdbfile, pdbA->cds[i]);
        fprintf(pdbfile, "ENDMDL\n");
    }
    fprintf(pdbfile, "END   \n");

    fclose(pdbfile);
}


static void
PrintTheseusModelHeader(FILE *pdbfile)
{
    time_t          tod;

    time(&tod);

    fprintf(pdbfile, "REMARK   3                                                                      \n");
    fprintf(pdbfile, "REMARK   3 REFINEMENT.                                                          \n");
    fprintf(pdbfile, "REMARK   3   PROGRAM     : THESEUS %-10s                                   \n", VERSION);
    fprintf(pdbfile, "REMARK   3   AUTHORS     : DOUGLAS THEOBALD                                     \n");
    fprintf(pdbfile, "REMARK   3  OTHER REFINEMENT REMARKS: MAXIMUM LIKELIHOOD SUPERPOSITION          \n");
    fprintf(pdbfile, "REMARK   3                                                                      \n");

    fprintf(pdbfile, "REMARK ===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-=\n");
    fprintf(pdbfile, "REMARK + File made by THESEUS                                                  +\n");
    fprintf(pdbfile, "REMARK + Multiple maximum likelihood superpositioning                          +\n");
    fprintf(pdbfile, "REMARK + Author Douglas L. Theobald                                            +\n");
    fprintf(pdbfile, "REMARK + dtheobald@brandeis.edu                                                +\n");
    fprintf(pdbfile, "REMARK =-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===\n");
    fprintf(pdbfile, "REMARK\n");
    fprintf(pdbfile, "REMARK   THESEUS v%s run by user '%-.21s' at %s", VERSION, getenv("USER"), asctime(localtime(&tod)));
    fprintf(pdbfile, "REMARK   on machine '%-.55s'\n", getenv("HOST"));
    fprintf(pdbfile, "REMARK   in directory '%-.55s'\n", getenv("PWD"));
    fprintf(pdbfile, "REMARK =-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===\n");
    fprintf(pdbfile, "REMARK\n");
}


static void
PrintModelFileStats(FILE *pdbfile, PDBCdsArray *pdbA, Algorithm *algo, Statistics *stats)
{
    int             i;

    for (i = 0; i < strlen(algo->cmdline); i += 71)
        fprintf(pdbfile, "REMARK   %-.71s\n", &algo->cmdline[i]);
    fprintf(pdbfile, "REMARK\n");
    for (i = 0; i < pdbA->cnum; ++i)
        fprintf(pdbfile, "REMARK   MODEL     %4d    %s  %6d\n", i+1, pdbA->cds[i]->filename, pdbA->cds[i]->vlen);
    fprintf(pdbfile, "REMARK\n");
    fprintf(pdbfile, "REMARK =-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===\n");
    fprintf(pdbfile, "REMARK   unweighted <sigma>         %10.5f\n", stats->stddev);
    fprintf(pdbfile, "REMARK   Classical pairwise <RMSD>  %10.5f\n", stats->ave_paRMSD);
    fprintf(pdbfile, "REMARK   Maximum Likelihood <RMSD>  %10.5f\n", stats->mlRMSD);
    if (algo->hierarch)
        fprintf(pdbfile, "REMARK   Hierarchical var (%3.2e, %3.2e) chi^2  %10.5f\n",
                stats->hierarch_p1, stats->hierarch_p2, stats->hierarch_chi2);
    fprintf(pdbfile, "REMARK   Log Marginal Likelihood %11.2f\n", stats->mlogL);
    fprintf(pdbfile, "REMARK   AIC                     %11.2f\n", stats->AIC);
    fprintf(pdbfile, "REMARK   BIC                     %11.2f\n", stats->BIC);

    fprintf(pdbfile, "REMARK   Rotational, translational, covar chi^2      %11.2f\n",
            stats->chi2);

    if (algo->hierarch)
    {
        fprintf(pdbfile, "REMARK   Hierarchical var (%3.2e, %3.2e) chi^2 %11.2f\n",
                stats->hierarch_p1, stats->hierarch_p2, stats->hierarch_chi2);
        fprintf(pdbfile, "REMARK   Omnibus chi^2                               %11.2f\n",
                stats->omnibus_chi2);
    }

    fprintf(pdbfile, "REMARK   skewness               %7.3f\n", stats->skewness[3]);
    fprintf(pdbfile, "REMARK   skewness Z-value       %7.3f\n", fabs(stats->skewness[3]/stats->SES));
    fprintf(pdbfile, "REMARK   kurtosis               %7.3f\n", stats->kurtosis[3]);
    fprintf(pdbfile, "REMARK   kurtosis Z-value       %7.3f\n", fabs(stats->kurtosis[3]/stats->SEK));
    fprintf(pdbfile, "REMARK =-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===\n");
    fprintf(pdbfile, "NUMMDL    %-d\n", pdbA->cnum);
}


void
WriteCdsFile(Cds *cds, char *outfile_name)
{
    PDBCdsArray *pdbA = PDBCdsArrayInit();
    PDBCdsArrayAlloc(pdbA, 0, cds->vlen);
    CopyCds2PDB(pdbA->avecds, cds);
    WritePDBCdsFile(pdbA->avecds, outfile_name);
    PDBCdsArrayDestroy(&pdbA);
}


static void
WritePDBCdsFile(PDBCds *cds, char *file_name)
{
    FILE           *pdbfile = NULL;

    pdbfile = myfopen(file_name, "w");
    if (pdbfile ==NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", file_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    PrintPDBCds(pdbfile, cds);
    fprintf(pdbfile, "END   \n\n");
    fflush(NULL);

    fclose(pdbfile);
}


void
WriteAveCdsFile(CdsArray *cdsA, char *outfile_name)
{
    PDBCdsArray *pdbA = PDBCdsArrayInit();
    PDBCdsArrayAlloc(pdbA, 0, cdsA->vlen);
    CopyCds2PDB(pdbA->avecds, cdsA->avecds);
    WriteAvePDBCdsFile(pdbA, outfile_name);
    PDBCdsArrayDestroy(&pdbA);
}


void
WriteAveCds(CdsArray *cdsA, char *outfile_name)
{
    FILE           *pdbfile = NULL;

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    PrintCds2File(pdbfile, cdsA->avecds);
    fprintf(pdbfile, "END   \n\n");
    fflush(NULL);
    fclose(pdbfile);
}


/* writes a pdb file of the average cds */
void
WriteAvePDBCdsFile(PDBCdsArray *pdbA, char *outfile_name)
{
    FILE           *pdbfile = NULL;

    /* char            avecds_filename[512]; */

    /* strcpy(avecds_filename, getroot(outfile_name)); */
    /* strcat(outfile_name, "_ave.pdb"); */

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile ==NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n\n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    PrintTheseusModelHeader(pdbfile);
    PrintPDBCds(pdbfile, pdbA->avecds);
    fprintf(pdbfile, "END   \n\n");
    fflush(NULL);

    fclose(pdbfile);
}


void
WriteBinPDBCdsArray(PDBCdsArray *pdbA)
{
    int             i, cnum, vlen;
    FILE           *fp = NULL;

    fp = fopen("theseus.bin", "wb");

    cnum = pdbA->cnum;
    vlen = pdbA->vlen;

    fwrite("theseus binary", sizeof(char), 14, fp);
    fwrite(&cnum, sizeof(int), 1, fp);
    fwrite(&vlen, sizeof(int), 1, fp);

/*     fwrite(pdbA->var,    sizeof(double), vlen, fp); */
/*     fwrite(pdbA->paRMSD, sizeof(double), cnum, fp); */

    for (i = 0; i < cnum; ++i)
        WriteBinPDBCds(pdbA->cds[i], fp);

    WriteBinPDBCds(pdbA->avecds, fp);

    fclose(fp);
}


PDBCdsArray
*ReadBinPDBCdsArray(char *filename)
{
    int             i, cnum, vlen;
    FILE           *fp = NULL;
    PDBCdsArray    *pdbA = NULL;
    char            bincheck[14];

    fp = fopen(filename, "rb");

    if (fp == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr, "\n  ERROR738: file \"%s\" not found. \n", filename);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    fread(bincheck, sizeof(char), 14, fp);

    if (strncmp(bincheck, "theseus binary", 14) != 0)
    {
        perror("\n  ERROR");
        fprintf(stderr, "\n  ERROR748: file \"%s\" does not appear to be a THESEUS binary data file. \n", filename);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    fread(&cnum, sizeof(int), 1, fp);
    fread(&vlen, sizeof(int), 1, fp);

    pdbA = PDBCdsArrayInit();
    PDBCdsArrayAllocNum(pdbA, cnum);

/*     fread(pdbA->var,    sizeof(double), vlen, fp); */
/*     fread(pdbA->paRMSD, sizeof(double), cnum, fp); */

    for (i = 0; i < cnum; ++i)
        ReadBinPDBCds(pdbA->cds[i], fp);

    ReadBinPDBCds(pdbA->avecds, fp);

    fclose(fp);
    return(pdbA);
}


void
WriteBinPDBCds(PDBCds *pdbcds, FILE *fp)
{
    int             vlen;

    vlen = pdbcds->vlen;

    fwrite(&vlen, sizeof(int), 1, fp);

    fwrite(pdbcds->filename, sizeof(char), FILENAME_MAX, fp);
    fwrite(&pdbcds->model, sizeof(int), 1, fp);

    WriteBinMatrix(pdbcds->matrix, 3, 3, fp);
    fwrite(pdbcds->translation, sizeof(double), 3, fp);

    fwrite(pdbcds->serial,     sizeof(int),    vlen, fp);
    fwrite(pdbcds->Hnum,       sizeof(char),   vlen, fp);
    fwrite(pdbcds->altLoc,     sizeof(char),   vlen, fp);
    fwrite(pdbcds->xchainID,   sizeof(char),   vlen, fp);
    fwrite(pdbcds->chainID,    sizeof(char),   vlen, fp);
    fwrite(pdbcds->resSeq,     sizeof(int),    vlen, fp);
    fwrite(pdbcds->iCode,      sizeof(char),   vlen, fp);
    fwrite(pdbcds->x,          sizeof(double), vlen, fp);
    fwrite(pdbcds->y,          sizeof(double), vlen, fp);
    fwrite(pdbcds->z,          sizeof(double), vlen, fp);
    fwrite(pdbcds->occupancy,  sizeof(double), vlen, fp);
    fwrite(pdbcds->tempFactor, sizeof(double), vlen, fp);

    fwrite(pdbcds->record_space,  sizeof(char), 8 * vlen, fp);
    fwrite(pdbcds->name_space,    sizeof(char), 4 * vlen, fp);
    fwrite(pdbcds->resName_space, sizeof(char), 4 * vlen, fp);
    fwrite(pdbcds->segID_space,   sizeof(char), 8 * vlen, fp);
    fwrite(pdbcds->element_space, sizeof(char), 4 * vlen, fp);
    fwrite(pdbcds->charge_space,  sizeof(char), 4 * vlen, fp);
}


void
ReadBinPDBCds(PDBCds *pdbcds, FILE *fp)
{
    int             vlen;

    fread(&vlen, sizeof(int), 1, fp);
    PDBCdsAlloc(pdbcds, vlen);

    fread(pdbcds->filename, sizeof(char), FILENAME_MAX, fp);
    fread(&pdbcds->model, sizeof(int), 1, fp);
    ReadBinMatrix(pdbcds->matrix, fp);
    fread(pdbcds->translation, sizeof(double), 3, fp);

    fread(pdbcds->serial,     sizeof(int),    vlen, fp);
    fread(pdbcds->Hnum,       sizeof(char),   vlen, fp);
    fread(pdbcds->altLoc,     sizeof(char),   vlen, fp);
    fread(pdbcds->xchainID,   sizeof(char),   vlen, fp);
    fread(pdbcds->chainID,    sizeof(char),   vlen, fp);
    fread(pdbcds->resSeq,     sizeof(int),    vlen, fp);
    fread(pdbcds->iCode,      sizeof(char),   vlen, fp);
    fread(pdbcds->x,          sizeof(double), vlen, fp);
    fread(pdbcds->y,          sizeof(double), vlen, fp);
    fread(pdbcds->z,          sizeof(double), vlen, fp);
    fread(pdbcds->occupancy,  sizeof(double), vlen, fp);
    fread(pdbcds->tempFactor, sizeof(double), vlen, fp);

    fread(pdbcds->record_space,  sizeof(char), 8 * vlen, fp);
    fread(pdbcds->name_space,    sizeof(char), 4 * vlen, fp);
    fread(pdbcds->resName_space, sizeof(char), 4 * vlen, fp);
    fread(pdbcds->segID_space,   sizeof(char), 8 * vlen, fp);
    fread(pdbcds->element_space, sizeof(char), 4 * vlen, fp);
    fread(pdbcds->charge_space,  sizeof(char), 4 * vlen, fp);
}


void
WriteBinMatrix(double **mat, int rows, int cols, FILE *fp)
{
    fwrite(&rows, sizeof(int), 1, fp);
    fwrite(&cols, sizeof(int), 1, fp);
    fwrite(mat[0], sizeof(double), rows * cols, fp);
}


void
ReadBinMatrix(double **mat, FILE *fp)
{
    int             rows, cols;

    fread(&rows, sizeof(int), 1, fp);
    fread(&cols, sizeof(int), 1, fp);
    fread(mat[0], sizeof(double), rows * cols, fp);
}


int
ConvertLele_freeform(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, k, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double        *vals = calloc(dim, sizeof(double));

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("lele.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);

        for (j = 0; j < lmarks; ++j)
        {
            for (k = 0; k < dim; ++k)
            {
                numscanned = fscanf(fp0, "%le ", &vals[k]);
                //printf("\n**** %f", vals[k]);
                //fflush(NULL);

                if (numscanned < 1 || numscanned == EOF)
                {
                    fprintf(stderr,
                            "\n ERROR6968: %d number of coordinates on line %d \n",
                            numscanned, lines);
                    exit(EXIT_FAILURE);
                }
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], vals[2],
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n\n");
    fclose(fp0);
    fclose(fp1);
    MyFree(vals);

    return(1);
}


int
ConvertDryden(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double         vals[2];
    char           line[512];

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("dryden.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

/*     *length = 0; */
/*     while(1) */
/*     { */
/*         ch = getc(fp); */
/*  */
/*         if (ch == EOF || ch == '\n') */
/*             ++(*length); */
/*  */
/*         if (ch == EOF) */
/*             break; */
/*     } */
/*  */
/*     array = calloc((*length + 1), sizeof(double)); */

/*     rewind(fp); */

    fgets(line, 512, fp0);

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);

        fscanf(fp0, "%*s");

        for (j = 0; j < lmarks; ++j)
        {
            numscanned = fscanf(fp0, "%le %le ", &vals[0], &vals[1]);

            if (numscanned < dim || numscanned == EOF)
            {
                fprintf(stderr,
                        "\n ERROR6968: %d number of coordinates on line %d \n",
                        numscanned, lines);
                exit(EXIT_FAILURE);
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], 0.0,
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n\n");
    fclose(fp0);
    fclose(fp1);

    return(1);
}


int
ConvertLele(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double         vals[3];
    char           line[512];

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("lele.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

/*     *length = 0; */
/*     while(1) */
/*     { */
/*         ch = getc(fp); */
/*  */
/*         if (ch == EOF || ch == '\n') */
/*             ++(*length); */
/*  */
/*         if (ch == EOF) */
/*             break; */
/*     } */
/*  */
/*     array = calloc((*length + 1), sizeof(double)); */

/*     rewind(fp); */

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);

        for (j = 0; j < lmarks; ++j)
        {
            fgets(line, 512, fp0);
            numscanned = sscanf(line, "%le %le %le ", &vals[0], &vals[1], &vals[2]);

            if (numscanned < dim || numscanned == EOF)
            {
                fprintf(stderr,
                        "\n ERROR6968: %d number of coordinates on line %d \n",
                        numscanned, lines);
                exit(EXIT_FAILURE);
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], vals[2],
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n\n");
    fclose(fp0);
    fclose(fp1);

    return(1);
}


void
WriteLeleModelFile(PDBCdsArray *pdbAr)
{
    FILE           *pdbfile = NULL;
    int             i, j;
    char            outfile_name[] = "lele.txt";

    pdbfile = myfopen(outfile_name, "w");
    if (pdbfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < pdbAr->cnum; ++i)
    {
        for (j = 0; j < pdbAr->vlen; ++j)
        {
            fprintf(pdbfile, "%.3f\t%.3f\t%.3f\n",
                    pdbAr->cds[i]->x[j],
                    pdbAr->cds[i]->y[j],
                    pdbAr->cds[i]->z[j]);
        }
    }

    fprintf(pdbfile, "\n");

    fclose(pdbfile);
}


void
WriteInstModelFile(char *fext, CdsArray *cdsA)
{
    int          i;
    PDBCdsArray *mpA = PDBCdsArrayInit();
    char        *fext_name = NULL;

    PDBCdsArrayAlloc(mpA, cdsA->cnum, cdsA->vlen);

    for (i = 0; i < mpA->cnum; ++i)
        CopyCds2PDB(mpA->cds[i], cdsA->cds[i]);

    fext_name = mystrcat(algo->rootname, fext);
    WriteTheseusModelFileNoStats(mpA, algo, fext_name);

    MyFree(fext_name);
    PDBCdsArrayDestroy(&mpA);
}


void
WriteEdgarSSM(CdsArray *cdsA)
{
    #include "pdbSSM.h"

    printf("    Calculating SSM ... \n");
    fflush(NULL);


    SSM *ssm = SSMInit();
    SSMAlloc(ssm, cdsA);
    SSMCalc(ssm, cdsA);

    printf("    Writing SSM ... \n");
    fflush(NULL);

    WriteSSM(ssm);
    SSMDestroy(&ssm);
}


void
WriteOlveFiles(CdsArray *cdsA)
{
    char           *olve_name = NULL;
    PDBCdsArray    *olveA = NULL;
    Cds           **cds = cdsA->cds;
    const int       cnum = cdsA->cnum;
    const int       vlen = cdsA->vlen;
    int             i;

    printf("    Writing Olve's files ... \n");
    fflush(NULL);

    olveA = PDBCdsArrayInit();
    PDBCdsArrayAlloc(olveA, cnum, vlen);

    for (i = 0; i < cnum; ++i)
        CopyCds2PDB(olveA->cds[i], cds[i]);

    olve_name = mystrcat(algo->rootname, "_olve.pdb");
    WriteOlveModelFile(olveA, algo, stats, olve_name);
    MyFree(olve_name);
    PDBCdsArrayDestroy(&olveA);
}


/* Write out a taxa distance matrix in NEXUS format */
void
WriteDistMatTree(CdsArray *cdsA)
{
    #include "DistMat.h"

    DISTMAT        *distmat = NULL;
    Cds           **cds = cdsA->cds;
    const int       cnum = cdsA->cnum;
    const int       vlen = cdsA->vlen;
    double          sum;
    int             i, j,k;
    int             cnt;
    char            num[32];
    char           *ptr = NULL;
    char           *tree_name = NULL;

    distmat = DISTMATalloc(cnum);

    for (i = 0; i < cnum; ++i)
    {
        strcpy(distmat->taxa[i], cds[i]->filename);
        ptr = strrchr(distmat->taxa[i], '.');
        if (ptr)
            *ptr = '\0';
        sprintf(num, "_%d", i);
        strcat(distmat->taxa[i], num);
    }

//         for (i = 0; i < cnum; ++i)
//         {
//             for (j = 0; j <= i; ++j)
//             {
//                 sum = 0.0;
//                 for (k = 0; k < vlen; ++k)
//                     sum += SqrCdsDistMahal2((const Cds *) cds[i], k,
//                                             (const Cds *) cds[j], k,
//                                             (const double) scratchA->w[k]);
//
//                 distmat->dist[i][j] = sqrt(sum);
//             }
//         }

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < i; ++j)
        {
            sum = 0.0;
            cnt = 0;
            for (k = 0; k < vlen; ++k)
            {
                if (cds[i]->nu[k] && cds[j]->nu[k])
                {
                    cnt += cdsA->w[k];
                    sum += SqrCdsDistMahal2((const Cds *) cds[i], k,
                                            (const Cds *) cds[j], k,
                                            (const double) cdsA->w[k]);
                }
            }

            distmat->dist[i][j] = sqrt(sum/(3.0*cnt));
        }
    }

    tree_name = mystrcat(algo->rootname, "_ML_tree.nxs");
    print_NX_distmat(distmat, tree_name);
    MyFree(tree_name);

//         //double total = 0.0;
//         for (i = 0; i < cnum; ++i)
//         {
//             for (j = 0; j < i; ++j)
//             {
//                 sum = 0.0;
//                 for (k = 0; k < vlen; ++k)
//                     sum += SqrCdsDist((const Cds *) cds[i], k,
//                                       (const Cds *) cds[j], k);
//
//                 distmat->dist[i][j] = sqrt(sum/vlen);
//                 //total += sum/vlen;
//             }
//         }

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < i; ++j)
        {
            sum = 0.0;
            cnt = 0;
            for (k = 0; k < vlen; ++k)
            {
                if (cds[i]->nu[k] && cds[j]->nu[k])
                {
                    ++cnt;
                    sum += SqrCdsDist((const Cds *) cds[i], k,
                                      (const Cds *) cds[j], k);
                }
            }

            distmat->dist[i][j] = sqrt(sum/cnt);
        }
    }

    //printf("\nrmsd? %g\n", sqrt(total/((cnum*cnum-  cnum)/2))); // verified same as paRMSD

    tree_name = mystrcat(algo->rootname, "_LS_tree.nxs");
    print_NX_distmat(distmat, tree_name);

    MyFree(tree_name);
    DISTMATdestroy(&distmat);
}

