// $Id: gc_parser.cpp,v 1.29 2018/01/03 21:32:56 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: Mary Kuhner, Jon Yamato, and Joseph Felsenstein */


#include <cassert>

#include "cnv_strings.h"
#include "gc_default.h"
#include "gc_file.h"
#include "gc_file_util.h"
#include "gc_infile_err.h"
#include "gc_parse.h"
#include "gc_parse_block.h"
#include "gc_parse_locus.h"
#include "gc_parse_pop.h"
#include "gc_parse_sample.h"
#include "gc_parser.h"
#include "gc_sequential_data.h"
#include "gc_strings.h"

#include "wx/log.h"
#include "wx/wfstream.h"
#include "wx/tokenzr.h"
#include "wx/txtstrm.h"

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

GCParser::GCParser(const GCDataStore& dataStore)
    :
    m_dataStore(dataStore),
    m_linesRead(0),
    m_fileStreamPointer(NULL),
    m_textStreamPointer(NULL)
{
}

GCParser::~GCParser()
{
    delete m_textStreamPointer;
    delete m_fileStreamPointer;
}

void
GCParser::SetUpStreams(wxString fileName)
{
    if(! ::wxFileExists(fileName))
    {
        throw gc_file_missing_error(fileName);
    }

    m_fileStreamPointer = new wxFileInputStream(fileName);

    if(!m_fileStreamPointer->Ok())
    {
        throw gc_file_read_error(fileName);
    }

    m_textStreamPointer = new wxTextInputStream(*m_fileStreamPointer);
}

bool
GCParser::HasContent(const wxString& line) const
{
    wxString mungeMe = line;
    mungeMe.Trim(true);
    return(!(mungeMe.IsEmpty()));
}

wxString
GCParser::ReadLine(bool skipBlankLines)
{
    m_linesRead++;
    wxString line =  ReadLineSafely(m_fileStreamPointer,m_textStreamPointer);
    while(skipBlankLines && (!HasContent(line)) )
    {
        m_linesRead++;
        line =  ReadLineSafely(m_fileStreamPointer,m_textStreamPointer);
    }
    return line;
}

void
GCParser::CheckNoExtraData()
{
    try
    {
        wxString line = ReadLine();
    }
    catch(const gc_eof& e)
        // we want this to happen, so return
    {
        return;
    }

    // Oops! didn't see an eof, there must be extra data
    throw gc_extra_file_data();
}

// pass in populationRef, expected number of sequences, locusRef, interleaved

void
GCParser::FillDataInterleaved(  GCParseBlock &         block,
                                GCParseLocus &         locus,
                                size_t                 expectedNumSequences)
{

    for(size_t sequenceIndex = 0; sequenceIndex < expectedNumSequences; sequenceIndex++)
        // read in first line for each sequence
    {
        wxString line = ReadLine();
        wxString label = line.Left(gcdefault::migrateSequenceNameLength);
        label = label.Strip();
        wxString data  = line.Remove(0,gcdefault::migrateSequenceNameLength);

        GCParseSample & sample = MakeSample(block,sequenceIndex,m_linesRead,label);
        AddDataToSample(sample,locus,data);

    }

    while(block.HasIncompleteSequences())
    {
        block.GetParse().m_multiLineSeenInFile = true;
        for(size_t sequenceIndex = 0; sequenceIndex < expectedNumSequences; sequenceIndex++)
        {
            wxString data  = ReadLine();
            GCParseSample & sample = block.FindSample(sequenceIndex);
            AddDataToSample(sample,locus,data);
        }
    }
}

void
GCParser::FillDataNonInterleaved(   GCParseBlock &          block,
                                    GCParseLocus &         locus,
                                    size_t                  expectedNumSequences)
{
    for(size_t sequenceIndex = 0; sequenceIndex < expectedNumSequences; sequenceIndex++)
        // read in first line for each sequence
    {
        wxString line  = ReadLine();
        wxString label = line.Left(gcdefault::migrateSequenceNameLength);
        label = label.Strip();
        wxString data  = line.Remove(0,gcdefault::migrateSequenceNameLength);

        GCParseSample & sample = MakeSample(block,sequenceIndex,m_linesRead,label);
        AddDataToSample(sample,locus,data);
        while(sample.GetLength() < block.GetLocusRef().GetNumMarkers())
        {
            block.GetParse().m_multiLineSeenInFile = true;
            data  = ReadLine();
            try
            {
                AddDataToSample(sample,locus,data);
            }
            catch(const gc_illegal_dna_character& e)
            {
                throw gc_too_few_markers(e.what());
            }
        }
    }
}

void
GCParser::FillData( GCParse &               parseData,
                    size_t                  popIndex,
                    size_t                  locIndex,
                    GCInterleaving          interleaving,
                    size_t                  expectedNumSequences)
{
    const GCParsePop   & popRef   = parseData.GetParsePop(popIndex);
    GCParseLocus & locusRef = parseData.GetParseLocus(locIndex);
    GCParseBlock & blockRef = AddBlock(parseData,popRef,locusRef,expectedNumSequences);

    if(interleaving == interleaving_SEQUENTIAL)
    {
        FillDataNonInterleaved(blockRef,locusRef,expectedNumSequences);
        return;
    }

    if(interleaving == interleaving_INTERLEAVED)
    {
        FillDataInterleaved(blockRef,locusRef,expectedNumSequences);
        return;
    }

    assert(false);
}

GCParse &
GCParser::MakeParse(    GCFile &            fileRef,
                        GCFileFormat        format,
                        gcGeneralDataType   dataType,
                        GCInterleaving      interleaving,
                        wxString            delimiter)
{
    if(format == format_MIGRATE && delimiter.IsEmpty())
        // EWFIX.P3 -- refactor, this is the wrong place for this
        //
        // this means it cannot be microsat
    {
        gcGeneralDataType::iterator i = dataType.find(sdatatype_MICROSAT);
        if(i != dataType.end())
        {
            dataType.erase(i);
            if(dataType.empty())
            {
                throw gc_migrate_missing_msat_delimiter(fileRef.GetName());
            }
        }
    }

    return *(new GCParse(fileRef,format,dataType,interleaving,delimiter));
}

GCParseBlock &
GCParser::AddBlock(GCParse & parse, const GCParsePop & pop, const GCParseLocus & loc,
                   size_t numSequences)
{
    size_t nextIndex = parse.m_blocks.size();
    parse.m_blocks.push_back(new GCParseBlock(&parse,nextIndex,numSequences,pop,loc));
    return *(parse.m_blocks[nextIndex]);
}

GCParseLocus &
GCParser::AddLocus(GCParse & parse, size_t expectedLocusIndex, size_t numMarkers)
{
    size_t nextIndex = parse.m_loci.size();
    assert(nextIndex == expectedLocusIndex);
    parse.m_loci.push_back(
        new GCParseLocus(&parse,nextIndex,m_linesRead,numMarkers));
    return *(parse.m_loci[nextIndex]);
}

GCParsePop &
GCParser::AddPop(GCParse & parse, size_t expectedPopIndex, wxString name)
{
    size_t nextIndex = parse.m_pops.size();
    assert(nextIndex == expectedPopIndex);
    parse.m_pops.push_back( new GCParsePop(&parse,nextIndex,name));
    return *(parse.m_pops[nextIndex]);
}

GCParseSample &
GCParser::MakeSample(   GCParseBlock &  block,
                        size_t          indexInBlock,
                        size_t          lineInFile,
                        wxString        label)
{
    assert(indexInBlock == block.m_samples.size());
    GCParseSample * parseSample = new GCParseSample(block,lineInFile,label);
    block.m_samples.push_back(parseSample);

    if(label.Find(' ') != wxNOT_FOUND)
    {
        block.GetParse().SetHasSpacesInNames();
    }

    return *parseSample;
}

void
GCParser::AddDataToSample(GCParseSample & sample, GCParseLocus & locus, wxString data)
{
    gcGeneralDataType dataType = sample.GetBlock().GetParse().GetDataType();
    assert(dataType.HasAllelic() ^ dataType.HasNucleic());
    if(dataType.HasAllelic())
    {
        AddAllelicDataToSample(sample,locus,data);
    }

    if(dataType.HasNucleic())
    {
        AddNucDataToSample(sample,locus,data);
    }
}

void
GCParser::AddNucDataToSample(GCParseSample & sample, GCParseLocus & locus, wxString data)
{

    if(sample.m_data.size() == 0)
    {
        sample.m_data.push_back(new GCNucData(sample.GetBlock()));
    }

    assert(sample.m_data.size() == 1);  // no haps for Nuc data input
    GCSequentialData * seqData = sample.m_data[0];
    GCNucData * nucData = dynamic_cast<GCNucData*>(seqData);
    assert(nucData != NULL);
    (*nucData).AddMarker(data);

}

void
GCParser::AddAllelicDataToSample(GCParseSample & sample, GCParseLocus & locus, wxString data)
{
    wxString delim = sample.GetBlock().GetParse().GetDelimiter();

    wxStringTokenizer outerTokenizer(data);
    while(outerTokenizer.HasMoreTokens())
    {
        wxString thisToken = outerTokenizer.GetNextToken();
        wxArrayString tokens = makeKalleleTokens(thisToken,delim);

        if(sample.m_data.size() == 0)
        {
            for(size_t i=0; i < tokens.Count(); i++)
            {
                sample.m_data.push_back(new GCAllelicData(sample.GetBlock()));
            }
        }
        if(sample.m_data.size() != tokens.Count())
        {
            throw gc_token_count_mismatch(delim,thisToken,sample.GetLabel(),tokens.Count(),sample.m_data.size());
        }

        for(size_t i=0; i < tokens.Count(); i++)
        {
            GCSequentialData * sd = sample.m_data[i];
            GCAllelicData * adata = dynamic_cast<GCAllelicData*>(sd);
            if(adata == NULL)
            {
                gc_implementation_error e(gcerr::badSequentialDataCast.c_str());
                throw e;
            }
            adata->AddMarker(tokens[i]);
        }
    }
}

wxArrayString
GCParser::makeKalleleTokens(wxString tokensTogether, wxString delim)
{
    wxArrayString retArr;
    if(delim.IsEmpty())
        // break up into characters
    {
        for(size_t i = 0; i < tokensTogether.Len(); i++)
        {
            retArr.Add(tokensTogether[i]);
        }
    }
    else
    {
        tokensTogether.Replace(delim," ",true);
        wxStringTokenizer tokenizer(tokensTogether);
        while(tokenizer.HasMoreTokens())
        {
            wxString nextToken = tokenizer.GetNextToken();
            retArr.Add(nextToken);
        }
    }
    return retArr;
}

void
GCParser::SetDataTypeFromFile(GCParse & parse, gcSpecificDataType type)
{
    parse.SetDataTypeFromFile(type);
}

//____________________________________________________________________________________
