// $Id: LamarcDS.cpp,v 1.41 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 <stdio.h>
#include <vector>

#include "Converter_LamarcDS.h"
#include "Converter_PopulationDS.h"
#include "Converter_DataSourceException.h"
#include "stringx.h"
#include "random.h"
#include "constants.h"
#include "defaults.h"
#include "xml_strings.h"

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

using namespace std;

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

LamarcDS::LamarcDS (RegionDS& region)
    :  m_coalescenceStartValues(defaults::theta),
       m_coalescenceMethod(defaults::thetaMethod),
       m_coalescenceMaxEvents(defaults::coalEvents),
       m_migrationStartValues(defaults::migration),
       m_migrationMethod(defaults::migMethod),
       m_migrationMaxEvents(defaults::migEvents),
       m_replicates(defaults::replicates),
       m_temperatures(1.0),
       m_swapInterval(1),
       m_resimulating(1.0),
       m_initialNumber(defaults::initNChains),
       m_initialSamples(defaults::initNSamples),
       m_initialDiscard(defaults::initDiscard),
       m_initialInterval(defaults::initInterval),
       m_finalNumber(defaults::finalNChains),
       m_finalSamples(defaults::finalNSamples),
       m_finalDiscard(defaults::finalDiscard),
       m_finalInterval(defaults::finalInterval),
       m_verbosity("verbose"),
       m_progverbosity("normal"),
       m_echo("true"),
       m_profile("false"),
       m_posterior("false"),
       m_seed(1005),
       m_outputFile("outfile"),
       m_inSummaryFile("insumfile"),
       m_outSummaryFile("outsumfile"),
       m_useInSummaryFile(0),
       m_useOutSummaryFile(0)
{
    validateRegionName(region);

    // Push it onto the map.
    string regionName = region.getName();
    m_regions.insert(make_pair(regionName, region));
}                               // LamarcDS::LamarcDS

//------------------------------------------------------------------------------------
// TODO:  Test this.  Its a last minute addition.
LamarcDS::LamarcDS ()
    :  m_coalescenceStartValues(defaults::theta),
       m_coalescenceMethod(defaults::thetaMethod),
       m_coalescenceMaxEvents(1000),
       m_migrationStartValues(defaults::migration),
       m_migrationMethod(defaults::migMethod),
       m_migrationMaxEvents(defaults::migEvents),
       m_replicates(defaults::replicates),
       m_temperatures(1.0),
       m_swapInterval(1),
       m_resimulating(1.0),
       m_initialNumber(defaults::initNChains),
       m_initialSamples(defaults::initNSamples),
       m_initialDiscard(defaults::initDiscard),
       m_initialInterval(defaults::initInterval),
       m_finalNumber(defaults::finalNChains),
       m_finalSamples(defaults::finalNSamples),
       m_finalDiscard(defaults::finalDiscard),
       m_finalInterval(defaults::finalInterval),
       m_verbosity("verbose"),
       m_progverbosity("normal"),
       m_echo("true"),
       m_profile("false"),
       m_posterior("false"),
       m_seed(1005),
       m_outputFile("outfile"),
       m_inSummaryFile("insumfile"),
       m_outSummaryFile("outsumfile"),
       m_useInSummaryFile(0),
       m_useOutSummaryFile(0)
{
}                               // LamarcDS::LamarcDS

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

LamarcDS::~LamarcDS ()
{
}                               // LamarcDS::~LamarcDS

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

void LamarcDS::EraseRegions()
{
    m_regions.erase(m_regions.begin(),m_regions.end());
} // LamarcDS::EraseRegions

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

RegionMap::const_iterator
LamarcDS::getFirstRegion() const
{
    return m_regions.begin();
}

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

RegionMap::const_iterator
LamarcDS::getLastRegion() const
{
    return m_regions.end();
}

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

RegionMap::iterator
LamarcDS::getFirstRegion()
{
    return m_regions.begin();
}

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

RegionMap::iterator
LamarcDS::getLastRegion()
{
    return m_regions.end();
}

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

void
LamarcDS::addRegion(RegionDS& region)
{
    validateRegionName(region);

    //  This method may add populations to region, or all of m_regions or both.
    validateNewRegion(region);

    string regionName = region.getName();

    if (m_regions.find(regionName) == m_regions.end())
    {
        //  The region is a new one, so stuff it in.
        m_regions.insert(make_pair(region.getName(), region));
    }

    else
    {
        // Since we already have a region of this name, we're going to have to add only the
        // populations that differ (if any) within the region.
        // remember, if the population was anonymous, its considered to be unique.
        // The anonymous population were given unique names when they were entered into the region.
        RegionMap::iterator regionIt = m_regions.find(regionName);
        RegionDS& existingRegion = (regionIt->second);

        PopMap::iterator i;
        for (i = region.getFirstPopulation(); i != region.getLastPopulation(); i++)
        {
            // Add the populations.  If the population already exists, it won't be added.
            // Individuals within that population may be added, however.
            // SEE THE PopulationDS DOCUMENTATION ON THIS

            existingRegion.addPopulation(i->second);
        }
    }
}

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

void
LamarcDS::validateNewRegion(RegionDS& region)
{
    //  Lets go through the populations in each region
    //  If any population found in one doesn't exist in the other, add it.
    //  NOTE:  This can be changed to throw if the populations dont match.  But this seemed
    //  better.

    RegionMap::iterator i = m_regions.begin();
    //  This should never happen, but lets be safe.
    if (i == m_regions.end())
    {
        // Its an empty set, so no validation is necessary.
        return;
    }

    // Store the populations to be added.
    // Don't add them right away because of iterator invalidation.
    vector<string> popsToAddToNewRegion;
    vector<string> popsToAddToOldRegions;

    // Use the populations of the first region in the map. (Remember. They should all be the same)
    RegionDS& existingRegion = (i->second);

    PopMap::iterator iNewPops = region.getFirstPopulation();
    PopMap::iterator iOldPops = existingRegion.getFirstPopulation();

    while ((iNewPops != region.getLastPopulation()) && (iOldPops != existingRegion.getLastPopulation()))
    {
        // Compare the string names of the populations.
        // They should be alphabetical in the map, so if one is less than the other
        // the other doesn't have that population name.
        // add it then increment the iterator that is less.
        // bad explanation.  Sorry.

        if ((iNewPops->first) < (iOldPops->first))
        {
            //      cout << (iNewPops->first) << " < " << (iOldPops->first) << endl;
            popsToAddToOldRegions.push_back(iNewPops->first);
            iNewPops++;
        }
        else if ((iNewPops->first) > (iOldPops->first))
        {
            //      cout << (iNewPops->first) << " > " << (iOldPops->first) << endl;
            popsToAddToNewRegion.push_back(iOldPops->first);
            iOldPops++;
        }
        else
        {
            //      cout << (iNewPops->first) << " = " << (iOldPops->first) << endl;
            // If they're Identical, increment both counters
            iOldPops++;
            iNewPops++;
        }
    }
    // By now, one (or both) of the iterators have reached map.end()
    // check them both.  If one isn't at map.end, add its remaining pops to the other's
    // region list.
    while (iNewPops != region.getLastPopulation())
    {
        popsToAddToOldRegions.push_back(iNewPops->first);
        iNewPops++;
    }

    while (iOldPops != existingRegion.getLastPopulation())
    {
        popsToAddToNewRegion.push_back(iOldPops->first);
        iOldPops++;
    }

    // Now our lists are full.
    // Now we add those populations to the regions
    // Remember that if these Populations exist in any existing regions, they'll be ignored.
    // See the documentation on RegionDS on this.
    // Thats why we only have to compare the new region with the FIRST existing one.

    vector<string>::const_iterator iPopName;

    for (iPopName = popsToAddToNewRegion.begin(); iPopName != popsToAddToNewRegion.end(); iPopName++)
    {
        PopulationDS pop(*iPopName);
        region.addPopulation(pop);
    }

    for (iPopName = popsToAddToOldRegions.begin(); iPopName != popsToAddToOldRegions.end(); iPopName++)
    {
        RegionMap::iterator j;

        for (j = m_regions.begin(); j != m_regions.end(); j++)
        {
            PopulationDS pop(*iPopName);

            RegionDS& curRegion = j->second;
            curRegion.addPopulation(pop);
        }
    }

    //  And we're done.  *whew*
    return;
}

void
LamarcDS::validateRegionName(RegionDS& region) const
{
    //  First, if the name of the region is whitespace or an empty string, get it a new name
    string regionName = region.getName();
    int strPosition = regionName.find_first_not_of (" ");

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

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

string
LamarcDS::getUniqueName() const
{
    // 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 regionName(name);
    regionName = "Region " + regionName;

    if (doesRegionNameExist(regionName))
    {
        return getUniqueName();
    }

    return regionName;
}

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

bool
LamarcDS::doesRegionNameExist(const string& name) const
{
    if (m_regions.find(name) == m_regions.end())
    {
        //  The region name doesn't exist, so return false
        return false;
    }
    return true;
}

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

bool LamarcDS::HasNonContiguousData() const
{
    RegionMap::const_iterator region;
    for(region = getFirstRegion(); region != getLastRegion(); ++region)
    {
        if (region->second.HasNonContiguousData())
            return true;
    }

    return false;

} // LamarcDS::HasNonContiguousData

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

bool LamarcDS::HasSNPs() const
{
    RegionMap::const_iterator region;
    for(region = getFirstRegion(); region != getLastRegion(); ++region)
    {
        if (region->second.HasSNPs())
            return true;
    }

    return false;

} // LamarcDS::HasNonContiguousData

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

void
LamarcDS::mergeTo(LamarcDS& lamarc)
{
    RegionMap::iterator i;

    for (i = lamarc.getFirstRegion(); i != lamarc.getLastRegion(); i++)
    {
        addRegion(i->second);
    }
}

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

void LamarcDS::ReorderUsing(RegByPopMap newmap)
{
    LamarcDS newlamarcds;

    RegByPopMap::iterator region;
    for(region = newmap.begin(); region != newmap.end(); ++region)
    {
        // WARNING warning -- put in a temporary model right now,
        // this will be phased out once the xml no longer needs
        // to have a datamodel in it to be read by Lamarc.
        ModelDS newmodel(getFirstRegion()->second.getDataModel());

        RegionDS newregion(region->first,newmodel);

        PopIterMap::iterator pop;
        for(pop = region->second.begin();
            pop != region->second.end(); ++pop)
        {
            PopulationDS newpop(pop->first);
            vector<popmapiterator>::iterator dataset;
            for(dataset = pop->second.begin();
                dataset != pop->second.end(); ++dataset)
            {
                newpop.AddIndividuals((*dataset)->second.GetAllIndividuals());
            }
            newregion.addPopulation(newpop);
        }

        newlamarcds.addRegion(newregion);
    }

    EraseRegions();
    mergeTo(newlamarcds);

} // LamarcDS::ReorderUsing

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

long
LamarcDS::numRegions() const
{
    return m_regions.size();
}

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

long LamarcDS::GetNUnits() const
{
    long npops = 0;
    RegionMap::const_iterator region;
    for (region = getFirstRegion(); region != getLastRegion(); ++region)
    {
        npops += region->second.GetNRealPopulations();
    }

    return npops;

} // LamarcDS::GetNUnits

//
//------------------------------------------------------------------------------------
//  Parameter setters

void
LamarcDS::setCoalescenceStartValues(const double coalescenceStartValues)
{
    m_coalescenceStartValues = coalescenceStartValues;
}

void
LamarcDS::setCoalescenceMethod(method_type coalescenceMethod)
{
    m_coalescenceMethod = coalescenceMethod;
}

void
LamarcDS::setCoalescenceMaxEvents(const long coalescenceMaxEvents)
{
    m_coalescenceMaxEvents = coalescenceMaxEvents;
}

void
LamarcDS::setReplicates(const long replicates)
{
    m_replicates = replicates;
}

void
LamarcDS::setTemperatures(const double temperatures)
{
    m_temperatures = temperatures;
}

void
LamarcDS::setSwapInterval(const long swapInterval)
{
    m_swapInterval = swapInterval;
}

void
LamarcDS::setResimulating(const double resimulating)
{
    m_resimulating = resimulating;
}

void
LamarcDS::setInitialNumber(const long initialNumber)
{
    m_initialNumber = initialNumber;
}

void
LamarcDS::setInitialSamples(const long initialSamples)
{
    m_initialSamples = initialSamples;
}

void
LamarcDS::setInitialDiscard(const long initialDiscard)
{
    m_initialDiscard = initialDiscard;
}

void
LamarcDS::setInitialInterval(const long initialInterval)
{
    m_initialInterval = initialInterval;
}

void
LamarcDS::setFinalNumber(const long finalNumber)
{
    m_finalNumber = finalNumber;
}

void
LamarcDS::setFinalSamples(const long finalSamples)
{
    m_finalSamples = finalSamples;
}

void
LamarcDS::setFinalDiscard(const long finalDiscard)
{
    m_finalDiscard = finalDiscard;
}

void
LamarcDS::setFinalInterval(const long finalInterval)
{
    m_finalInterval = finalInterval;
}

void
LamarcDS::setVerbosity(const string& verbosity)
{
    m_verbosity = verbosity;
}

void
LamarcDS::setProgVerbosity(const string& progverbosity)
{
    m_progverbosity = progverbosity;
}

void
LamarcDS::setEcho(const string& echo)
{
    m_echo = echo;
}

void
LamarcDS::setProfile(const string& profile)
{
    m_profile = profile;
}

void
LamarcDS::setPosterior(const string& posterior)
{
    m_posterior = posterior;
}

void
LamarcDS::setSeed(const long seed)
{
    m_seed = seed;
}

void
LamarcDS::setOutputFile(const string& outputFile)
{
    m_outputFile = outputFile;
}

void
LamarcDS::setInSummaryFile(const string& inSummaryFile)
{
    m_inSummaryFile = inSummaryFile;
}

void
LamarcDS::setOutSummaryFile(const string& outSummaryFile)
{
    m_outSummaryFile = outSummaryFile;
}

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

string
LamarcDS::getXML(unsigned int numTabs) const
{
    string lamarcXML;

    addTabs(numTabs, lamarcXML);
    lamarcXML = lamarcXML + "<lamarc version=\"" + VERSION + "\">\n";

    addTabs(numTabs, lamarcXML);
    lamarcXML = lamarcXML + "<!-- Created from the LamarcDS DataStore -->\n";

    addTabs(numTabs, lamarcXML);
    lamarcXML = lamarcXML + "<!-- -->\n";

    addTabs(numTabs, lamarcXML);
    lamarcXML = lamarcXML + MakeTag(xmlstr::XML_TAG_DATA) + "\n";

    ++numTabs;

    // Now write out the regions
    RegionMap::const_iterator i;
    for (i = m_regions.begin(); i != m_regions.end(); i++)
    {
        lamarcXML+= (i->second).getXML(numTabs);
    }

    --numTabs;
    addTabs(numTabs, lamarcXML);
    lamarcXML += MakeCloseTag(xmlstr::XML_TAG_DATA) + "\n";

#ifndef JSIM
    --numTabs;
    addTabs(numTabs, lamarcXML);
    lamarcXML += "</lamarc>\n";
#endif

    return lamarcXML;

}

//____________________________________________________________________________________
