// $Id: RegionDS.cpp,v 1.27 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 <cmath>
#include <stdio.h>

#include "Converter_RegionDS.h"
#include "Converter_DataSourceException.h"
#include "random.h"
#include "stringx.h"

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

using namespace std;

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

RegionDS::RegionDS (const string& regionName,
                    const ModelDS& model)
    : m_regionName(regionName),
      m_model(model)
{
    // No populations, so no initialization necessary.
}                               // RegionDS::RegionDS

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

RegionDS::RegionDS (const string& regionName,
                    const ModelDS& model,
                    PopulationDS& population)
    : m_regionName(regionName),
      m_model(model)
{
    // Validate the population name.
    validatePopulationName(population);

    // Push it onto the map.
    string popName = population.getName();
    m_pops.insert(make_pair(popName, population));
}                               // RegionDS::RegionDS

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

RegionDS::~RegionDS ()
{
}                               // RegionDS::~RegionDS

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

PopMap::const_iterator
RegionDS::getFirstPopulation() const
{
    return m_pops.begin();
}

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

PopMap::const_iterator
RegionDS::getLastPopulation() const
{
    return m_pops.end();
}

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

PopMap::iterator
RegionDS::getFirstPopulation()
{
    return m_pops.begin();
}

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

PopMap::iterator
RegionDS::getLastPopulation()
{
    return m_pops.end();
}

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

string
RegionDS::getName() const
{
    return m_regionName;
}

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

long
RegionDS::getNmarkers() const
{
    return m_pops.begin()->second.getSequenceLength();
}

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

long RegionDS::GetNPopulations() const
{
    return m_pops.size();
} // RegionDS::GetNPopulations

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

long RegionDS::GetNRealPopulations() const
{
    long count = 0;
    PopMap::const_iterator pop;
    for(pop = getFirstPopulation(); pop != getLastPopulation(); ++pop)
    {
        if (!pop->second.IsGhost()) ++count;
    }

    return count;

} // RegionDS::GetNRealPopulations

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

vector<string> RegionDS::GetAllSeqNames() const
{
    vector<string> seqnames;

    PopMap::const_iterator pop;
    for(pop = getFirstPopulation(); pop != getLastPopulation(); ++pop)
    {
        vector<string> psnames = pop->second.GetAllSeqNames();
        seqnames.insert(seqnames.end(),psnames.begin(),psnames.end());
    }

    return seqnames;

} // RegionDS::GetAllSeqNames

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

void
RegionDS::setName(const string& name)
{
    m_regionName = name;
}

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

void
RegionDS::setModel(const ModelDS& model)
{
    m_model = model;
}

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

void
RegionDS::setSpacing(const SpacingDS& spacing)
{
    m_spacing = spacing;
}

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

unsigned int
RegionDS::getNumPopulations() const
{
    return m_pops.size();
}

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

void
RegionDS::addPopulation(PopulationDS& pop)
{
    validatePopulationName(pop);
    validateNewPopulation(pop);
    string popName = pop.getName();

    if (m_pops.find(popName) == m_pops.end())
    {
        //  The population is a new one, so stuff it in.
        m_pops.insert(make_pair(pop.getName(), pop));
    }

    else
    {
        // since we already have a population of this name, we're going to have to add only the
        // individuals that differ (if any) within the population.
        // remember, if the individual is anonymous, its considered to be unique.
        // We can have as many anonymous individuals as we want.  The anonymous individuals are
        // given names when they are entered into the population.

        PopMap::iterator popIt = m_pops.find(popName);
        PopulationDS& existingPopulation = (popIt->second);

        list<IndividualDS>::const_iterator i;
        for (i = pop.getFirstIndividual(); i != pop.getLastIndividual(); i++)
        {
            // Add the individuals.  If the individual already exists, it won't be added.
            // if its an anonymous individual, it will be given a new, unique name within
            // This population and added.
            // I'm defining an anonymous individual as having a name that consists only of whitespace
            // SEE THE DOCUMENTATION ON THIS

            existingPopulation.addIndividual(*i);
        }
    }
}

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

typedef pair<string,list<IndividualDS>::iterator> indpair;

// FailToAdd returns population name rather than adding to the population
// directly because of the probable presence of identical sequence names
// with resulting unpredictable add/removal consequences
bool RegionDS::FailToAdd(IndividualDS& individual, string& popname)
{
    bool failure = true;

    vector<string> hapnames = individual.GetHapNames();

    PopMap::iterator popit;
    for(popit = m_pops.begin(); popit != m_pops.end(); ++popit)
    {
        // can't just do removals here as you might only have a subset
        // of the desired sequences (usually this means a user input error)
        // in this population
        vector<indpair> oldinds = popit->second.FindAllSequences(hapnames);

        if (oldinds.empty()) continue;

        failure = false;
        popname = popit->second.getName();
        vector<indpair>::iterator oldind;
        for(oldind = oldinds.begin(); oldind != oldinds.end(); ++oldind)
        {
            // ask the population to remove the sequence from the old individual,
            // then add the returned sequence to the new individual.
            individual.AddSequence(popit->second.
                                   PopSequence(oldind->first,
                                               oldind->second));
        }
        break;

    }

    return failure;

} // RegionDS::FailToAdd

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

bool RegionDS::HasNonContiguousData() const
{
    PopMap::const_iterator pop;
    for(pop = getFirstPopulation(); pop != getLastPopulation(); ++pop)
    {
        if (pop->second.HasNonContiguousData())
            return true;
    }

    return false;

} // RegionDS::HasNonContiguousData

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

bool RegionDS::HasSNPs() const
{
    PopMap::const_iterator pop;
    for(pop = getFirstPopulation(); pop != getLastPopulation(); ++pop)
    {
        if (pop->second.HasSNPs())
            return true;
    }

    return false;

} // RegionDS::HasSNPs

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

void RegionDS::AddIndividual(IndividualDS& ind, const string& popname)
{
    // can't use this because there is no default ctor for PopulationDS
    // m_pops[popname].addIndividual(ind);
    m_pops.find(popname)->second.addIndividual(ind);

} // RegionDS::AddIndividual

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

void
RegionDS::validateNewPopulation(PopulationDS& pop)
{
    PopMap::iterator i = m_pops.begin();
    int regionSequenceLength;

    if (i == m_pops.end())
    {
        // Its an empty set, so no validation is necessary.
        return;
    }
    else
    {
        regionSequenceLength = (i->second).getSequenceLength();
    }

    if (pop.getSequenceLength() == 0)
    {
        // If the new population has a sequence length of 0, set its sequenceLength to the
        // regionSequenceLength and be on your way.
        pop.setFirstSequenceLength(regionSequenceLength);
        return;
    }

    //  If the region's sequence length is 0, (all of its populations have a sequence length of zero)
    //  and the inserting population has a length > 0, set those populationSequence lengths to the
    //  new population's sequence Length.  SEE DOCUMENTATION.
    if (regionSequenceLength == 0)
    {
        for (i = m_pops.begin(); i != m_pops.end(); i++)
        {
            (i->second).setFirstSequenceLength(pop.getSequenceLength());
        }
    }

    // finally, if they both have sequence lengths > 0, and they arent the same, compare and
    // throw if they differ
    else if (regionSequenceLength != pop.getSequenceLength())
    {
        //      string errString = "CurrentRegion: " + m_regionName + " Current PopLength: "
        //        + ToString(pop.getSequenceLength()) + " RegionSequenceLength: "
        //        + ToString(regionSequenceLength);
        //      cout << errString << endl;

        throw InvalidSequenceLengthError("Tried to add a new population to a region with \n a different \
length from the other sequences.");
    }

    return;
}

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

void
RegionDS::validatePopulationName(PopulationDS& pop) const
{
    //  First, if the name of the population is whitespace or an empty string, get it a new name
    string popName = pop.getName();
    int strPosition = popName.find_first_not_of (" ");

    if ((strPosition >= 0) && (strPosition < (int)popName.length()))
    {
        // There is a name that's not just whitespace
        return;
    }
    else
    {
        // Its an anonymous population, so get a unique name for it and set the name
        string newName = getUniqueName();
        pop.setName(newName);
    }
}

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

void
RegionDS::validateFreqs(double a,
                        double g,
                        double c,
                        double t)
{
    double sum = 0;

    sum = a + g + c + t;

    if (fabs(sum-1.0) > 0.001)  // Epsilon taken from /tree/interface.cpp
    {
        throw InvalidFrequenciesError("The frequencies don't add up to 1.");
    }
    else
        return;
}

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

string
RegionDS::getUniqueName() const
{
    // If running in the GUI, we don't yet want a unique name. If
    // we create it now, we won't be able to distinguish between
    // names that were in the files and ones we created.
#ifdef GUI
    string empty = "";
    return empty;
#endif
    // Use the random in lamarc/lib/
    Random random; // uses system clock

    int char1 = abs(random.Long() % 26) + 65;
    int char2 = abs(random.Long() % 26) + 65;
    int char3 = abs(random.Long() % 26) + 65;
    int char4 = abs(random.Long() % 26) + 65;
    char name[6];

    sprintf(name, "%c%c%c%c", char1, char2, char3, char4);

    string popName(name);
    popName = "Population " + popName;

    if (doesPopulationNameExist(popName))
    {
        return getUniqueName();
    }

    return popName;
}

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

bool
RegionDS::doesPopulationNameExist(const string& name) const
{
    if (m_pops.find(name) == m_pops.end())
    {
        //  The population name doesn't exist, so return false
        return false;
    }
    return true;
}

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

string
RegionDS::getXML(unsigned int numTabs) const
{
    string regionXML;

    addTabs(numTabs, regionXML);
    regionXML = regionXML + "<region name=\"" + m_regionName + "\">\n";

    //  Now, lets write out the model..

    ++numTabs; // need to keep this even in non-menu mode to keep the
    // unsigned numTabs at the expected value--otherwise it
    // may go negative

    regionXML += m_spacing.getXML(numTabs);

    // Now write out the populations
    PopMap::const_iterator i;
    for (i = m_pops.begin(); i != m_pops.end(); i++)
    {
        regionXML+= (i->second).getXML(numTabs);
    }

    --numTabs;
    addTabs(numTabs, regionXML);
    regionXML += "</region>\n";

    return regionXML;
}

//____________________________________________________________________________________
