// $Id: PhylipConverter.cpp,v 1.32 2018/01/03 21:32:57 mkkuhner Exp $

/*
Copyright 2002-2005 Mary K. Kuhner, Peter Beerli, and Joseph Felsenstein

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

/* Authors: Patrick Colacurcio, Peter Beerli, Mary Kuhner, Jon Yamato 
/* Authors: Patrick Colacurcio, Peter Beerli, Mary Kuhner, Jon Yamato 
and Joseph Felsenstein */


#include <iostream>
#include <fstream>
#include <sstream>
#include <cctype>

#include "Converter_PhylipConverter.h"
#include "Converter_DataSourceException.h"
#include "Converter_LamarcDS.h"
#include "Converter_RegionDS.h"
#include "Converter_PopulationDS.h"
#include "Converter_IndividualDS.h"
#include "Converter_Sequence.h"
#include "stringx.h"

#ifdef DMALLOC_FUNC_CHECK
#include "/usr/local/include/dmalloc.h"
#endif

using namespace std;

//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------

PhylipConverter::PhylipConverter(const string& fileName,
                                 const bool interleaved)
    //  :  m_lamarc(lamarc),
    :  ConverterIf(),
       m_fileName(fileName),
       m_interleaved(interleaved),
       m_inFile(fileName.c_str(), ios::in)
{

    // Read the input file

    if (!m_inFile)
    {
        // Cant find the infile.
        // Bail
        throw FileNotFoundError ("Sorry.  Can't find the file: " + fileName + ".  \nPlease check your path.");
    }

    SetDataType("");  // Phylip files have no idea what they are....

} // PhylipConverter ctor

//------------------------------------------------------------------------------------

void PhylipConverter::ProcessData()
{
    string numSeqsString;
    string seqLengthString;
    long numSequences = 0;
    long sequenceLength = 0;
    bool usesWeights = false;

    string someString;
    string firstLine;
    //  Lets get the first line and put it into its own stream
    if (getLine(m_inFile, firstLine))
    {
    }
    else
    {
        throw FileFormatError ("Your file appears to be empty.");
    }

    istringstream firstLineStream(firstLine);

    //  First, lets get the number of sequences.
    if (getNumber(firstLineStream, someString))
    {
        if (!IsInteger(someString))
        {
            // Bail.
            throw FileFormatError ("Your file is not in the correct Phylip format.  \nThe first token must be an integer signaling the number of sequences in the file.");
        }
        if (!FromString(someString, numSequences)) numSequences = 0L;
        someString = "";
    }
    else
    {
        // Bail.
        throw FileFormatError ("Your file is not in the correct Phylip format.  \nThe first token must be an integer signaling the number of sequences in the file.");
    }

    //  Next, lets get the length of the sequences.
    if (getNumber(firstLineStream, someString))
    {
        if (!IsInteger(someString))
        {
            // Bail.
            throw FileFormatError ("Your file is not in the correct Phylip format.  \nThe second token must be an integer signaling the length of the sequences in the file.");
        }
        if (!FromString(someString, sequenceLength)) sequenceLength = 0L;
        someString = "";
    }
    else
    {
        // Bail.
        throw FileFormatError ("Your file is not in the correct Phylip format.  \nThe second token must be an integer signaling the length of the sequences in the file.");
    }

    //  Its possible that there will be a weights key.
    if (getWord(firstLineStream, someString))
    {
        if (CaselessStrCmp(someString,"W"))
        {
            //  This file will use weights
            usesWeights = true;
            someString = "";
        }
    }

    //  Now, if there are weights, lets skip em.
    //  first, get the 'weights' word.
    if (usesWeights)
    {
        if (getToken(m_inFile, someString))
        {
        }
        else
        {
            throw FileFormatError ("Your file is not in the correct Phylip format.  \nPlease check your file for formatting errors.");
        }

        //  Get the weights.
        //  This should be the length of the sequence, skipping newlines and whitespace.
        if (!getNextNNonWhiteSpace (m_inFile, someString, sequenceLength))
        {
            throw FileFormatError ("Your file is not in the correct Phylip format.  \nThe converter was expecting to find a list of Weights.");
        }
    }

    //  Great.  Now we should be ready to grab the individuals
    string indSeq;

    skipWhiteSpace(m_inFile);

    vector<string> names;
    vector<string> sequences;

    if (m_interleaved)
    {
        long basesread = 0;

        for ( int i = 0; i < numSequences; i++)
        {
            string lineString;
            string sequenceStr;

            // get the first line
            if (getLine (m_inFile, lineString))
            {
                istringstream indNameStream(lineString);
                string indName;

                // Get the first 10 characters.
                if (!getNextNChars(indNameStream, indName, 10))
                    throw FileFormatError ("Your file is not in the correct Phylip format.  \nPlease check your file and retry.");

                //  Get the rest of the line.  (don't know whether there will be spaces, so get as tokens
                while (getToken(indNameStream, sequenceStr)) {};

                //  After getting the name and the sequence
                //  1) reset the seqName string
                //  2) stuff both the name and the sequence into the containers
                //  3) skip whitespace so the next line is ready to go

                StripTrailingSpaces(indName);
                names.push_back(indName);
                sequences.push_back(sequenceStr);
                basesread = sequenceStr.length();
                skipWhiteSpace(m_inFile);
            }
        }

        //  Now, get the rest of the interleaved sequences;
        bool continuePullingSequence = true;

        //  If there are no sequences or all the bases are read, don't pull.
        if (numSequences == 0 || basesread >= sequenceLength)
            continuePullingSequence = false;

        int i = 0;

        while (continuePullingSequence)
        {
            string lineString;
            if (getLine (m_inFile, lineString))
            {
                istringstream indNameStream(lineString);
                string sequenceStr;

                //  Get the rest of the line.  (don't know whether there will be spaces, so get as tokens
                while (getToken(indNameStream, sequenceStr)) {};

                sequences[i%numSequences] = sequences[i%numSequences] + sequenceStr;

                skipWhiteSpace(m_inFile);
            }
            else
            {
                throw FileFormatError ("Your file is not in the correct Phylip format.  \nIt appears that one of the sequences is too short.");
            }

            if( (i%numSequences) == (numSequences - 1))
            {
                //              cout << names[i%numSequences] << " length " << sequences[i%numSequences].length() << ":"
                //                   << sequences[i%numSequences] << endl;

                if( (long)(sequences[i%numSequences].length()) >= sequenceLength)
                {
                    continuePullingSequence = false;
                }
            }
            i++;
        }

    }

    //  If its not interleaved, its a bit easier.
    else
    {
        skipWhiteSpace(m_inFile);

        for ( int i = 0; i < numSequences; i++)
        {
            string indName;
            string sequenceStr;

            // Get the first 10 characters.
            if (!getNextNChars(m_inFile, indName, 10))
                throw FileFormatError ("Your file is not in the correct Phylip format.  \nPlease check your file and retry.");

            //  Get characters until we've gone through the sequence length
            if (!getNextNNonWhiteSpace(m_inFile, sequenceStr, sequenceLength))
            {
                throw FileFormatError ("Your file is not in the correct Phylip format.  \nIt appears that one of the sequences is too short.");
            }

            // got the name, got the sequence.  Stuff em into an individual, and put that in our
            // population

            //    cout << "Name: " << indName << ": " << sequenceStr << endl;
            names.push_back(indName);
            sequences.push_back(sequenceStr);

            //  Clean up for the next sequence
            skipWhiteSpace(m_inFile);
        }
    }

    //  Now, Put together the LamarcDS
    PopulationDS pop ("");
    //  Add a comment for this population
    string comment = "Origin file: " + m_fileName;
    pop.setComment(comment);

    long seq, nseqs = sequences.size();
    for(seq = 0; seq < nseqs; ++seq)
    {
        IndividualDS individual(names[seq],sequences[seq],m_datatype);
        pop.addIndividual(individual);
    }

    //  Add the region
    ModelDS model;
#ifndef JSIM
    // test file name to make sure not too long
    // sprintf into name string
    RegionDS region (buildName(m_fileName,"--",3,0), model, pop);
#else
    RegionDS region (string("Ocharinka"), model, pop);
#endif

    m_lamarc.addRegion(region);

#if 0
    while ( getToken(m_inFile, someString))
    {
        cout << someString << endl;
        someString = "";
    }
#endif
}                               // PhylipConverter::ProcessData

//------------------------------------------------------------------------------------

PhylipConverter::~PhylipConverter ()
{
}                               // PhylipConverter::~PhylipConverter

//------------------------------------------------------------------------------------

//  Okay.  Here is the deal.  Send in a lamarc, and it will merge this converter's lamarc into yours.
//  If you want a lamarc of your own, send in an empty one.  I can't return a lamarc or do assignment
//  on a lamarc because I'm having really, really weird template issues.
void
PhylipConverter::addConvertedLamarcDS (LamarcDS& lamarc)
{
    lamarc.mergeTo(m_lamarc);
}

//____________________________________________________________________________________
