// $Id: chainparam.cpp,v 1.43 2018/01/03 21:32:54 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: Peter Beerli, Mary Kuhner, Jon Yamato and Joseph Felsenstein */


#include <cassert>
#include <numeric>                      // for std::accumulate in ChainParameters::GetNAllChains()

#include "arranger.h"
#include "chainparam.h"
#include "defaults.h"
#include "errhandling.h"
#include "stringx.h"                    // for ToXML()
#include "xml_strings.h"                // for ToXML()

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

using namespace std;

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

ChainParameters::ChainParameters(
    DoubleVec1d temperatures,
    long        tempinterval,
    bool        tempadapt,
    long        nChains_initial,
    long        nSamples_initial,
    long        interval_initial,
    long        nDiscard_initial,
    long        nChains_final,
    long        nSamples_final,
    long        interval_final,
    long        nDiscard_final,
    long        nreps,
    bool        bayesian,
    double      dropTiming,
    double      sizeTiming,
    double      hapTiming,
    double      probhapTiming,
    double      bayesTiming,
    double      locusTiming,
    double      zilchTiming,
    double      stairTiming,
    double      epochsizeTiming
//  double      epochnudgeTiming  Add this if it passes testing
    )
    :  m_arrangers(dropTiming, sizeTiming, hapTiming, probhapTiming, bayesTiming,
                   locusTiming, zilchTiming, stairTiming, epochsizeTiming, 0.0),
// JREMOVE -- epochnudge testing code
// the 0.0 is the testing timing for epochnudge arranger, fix this if we
// want to actually add this one
// end JREMOVE
       m_temperatures(temperatures),
       m_tempinterval(tempinterval),
       m_tempadapt(tempadapt),
       m_nChains_initial(nChains_initial),
       m_nSamples_initial(nSamples_initial),
       m_interval_initial(interval_initial),
       m_nDiscard_initial(nDiscard_initial),
       m_nChains_final(nChains_final),
       m_nSamples_final(nSamples_final),
       m_interval_final(interval_final),
       m_nDiscard_final(nDiscard_final),
       m_nreps(nreps),
       m_bayesian(bayesian),
       m_runprofilereps(false /* JDEBUG--get from menu/xml! */)
{
    SortAndNormalizeTemperatures();

    // Everything that is being asserted here should be prevented by logic in the user-menus and xml
    // reading part of the code.  So, if you get an assert here, you need to add something there.

    assert(!(m_arrangers.empty()));     // at least one arranger
    assert(ValidTemperatures());        // at least one temperatures,
                                        // lowest temperature == 1.0
                                        // temperatures ascending
    assert(m_tempinterval >= 0L);       // tempinterval non-negative

    assert(m_nChains_initial >=  0L);   // initial chains non-negative
    assert(m_nSamples_initial >= 0L);   // initial samples non-negative
    assert(m_interval_initial >  0L);   // initial chains positive
    assert(m_nDiscard_initial >= 0L);   // initial chains non-negative
    assert(m_nChains_final   >=  0L);   // initial chains non-negative
    assert(m_nSamples_final   >= 0L);   // initial samples non-negative
    assert(m_interval_final   >  0L);   // initial chains positive
    assert(m_nDiscard_final   >= 0L);   // initial chains non-negative
    assert(m_nChains_initial + m_nChains_final > 0);    // at least one chain

    assert(m_nreps > 0);                // at least one replicate

}

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

ChainParameters::~ChainParameters()
{
    // intentionally blank
} // ChainParameters::~ChainParameters

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

void
ChainParameters::SortAndNormalizeTemperatures()
{
    if(!(m_temperatures.empty()))
    {
        sort(m_temperatures.begin(),m_temperatures.end());
        double lowestTemp = m_temperatures[0];
        assert(lowestTemp > 0.0);
        transform(m_temperatures.begin(),m_temperatures.end(),
                  m_temperatures.begin(),
                  bind2nd(divides<double>(),lowestTemp));
        // m_temperatures[0] should now be 1.0, but let's
        // eliminate any problems with rounding
        m_temperatures[0] = 1.0;

    }
}

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

double ChainParameters::GetArrangerTiming(const string& atype) const
{
    return m_arrangers.GetArrangerTiming(atype);
}

StringVec1d ChainParameters::GetAllStringsForActiveArrangers() const
{
    return m_arrangers.GetAllStringsForActiveArrangers();
}

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

ArrangerVec ChainParameters::CloneArrangers() const
{
    return m_arrangers;
}

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

StringVec1d ChainParameters::ToXML(unsigned long nspaces) const
{
    // JDEBUG--need to add m_runprofilereps to this!
    StringVec1d xmllines;
    string line = MakeIndent(MakeTag(xmlstr::XML_TAG_CHAINS),nspaces);
    xmllines.push_back(line);

    nspaces += INDENT_DEPTH;
    // replicates
    string mytag(MakeTag(xmlstr::XML_TAG_REPLICATES));
    line = MakeIndent(mytag,nspaces) + ToString(GetNReps()) + MakeCloseTag(mytag);
    xmllines.push_back(line);

    // bayesianism
    mytag =MakeTag(xmlstr::XML_TAG_BAYESIAN_ANALYSIS);
    line = MakeIndent(mytag,nspaces) + ToString(IsBayesian()) + MakeCloseTag(mytag);
    xmllines.push_back(line);

    // heating
    line = MakeIndent(MakeTag(xmlstr::XML_TAG_HEATING),nspaces);
    xmllines.push_back(line);

    nspaces += INDENT_DEPTH;
    mytag = MakeTag(xmlstr::XML_TAG_HEATING_STRATEGY);
    line = MakeIndent(mytag,nspaces) + ToStringTF(GetTempAdapt()) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_TEMPERATURES);
    line = MakeIndent(mytag,nspaces) + ToString(GetAllTemperatures(),4) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_SWAP_INTERVAL);
    line = MakeIndent(mytag,nspaces) + ToString(GetTempInterval()) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    nspaces -= INDENT_DEPTH;

    line = MakeIndent(MakeCloseTag(xmlstr::XML_TAG_HEATING),nspaces);
    xmllines.push_back(line);

    // strategy
    line = MakeIndent(MakeTag(xmlstr::XML_TAG_STRATEGY),nspaces);
    xmllines.push_back(line);

    nspaces += INDENT_DEPTH;

    // MDEBUG needs updating every time an Arranger is added, not ideal!
    mytag = MakeTag(xmlstr::XML_TAG_RESIMULATING);
    line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::DROP)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_TREESIZE);
    line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::TREESIZE)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_HAPLOTYPING);
    line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::HAP)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_LOCUSARRANGER);
    line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::LOCUS)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_EPOCHSIZEARRANGER);
    line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::EPOCHSIZE)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    if (IsBayesian())
    {
        mytag = MakeTag(xmlstr::XML_TAG_BAYESIAN);
        line = MakeIndent(mytag,nspaces) + ToString(GetArrangerTiming(arrangerstrings::BAYES)) + MakeCloseTag(mytag);
        xmllines.push_back(line);
    }
    // DENOVO deliberately left out
    nspaces -= INDENT_DEPTH;

    line = MakeIndent(MakeCloseTag(xmlstr::XML_TAG_STRATEGY),nspaces);
    xmllines.push_back(line);

    // initial chains
    line = MakeIndent(MakeTag(xmlstr::XML_TAG_INITIAL),nspaces);
    xmllines.push_back(line);

    nspaces += INDENT_DEPTH;
    mytag = MakeTag(xmlstr::XML_TAG_NUMBER);
    line = MakeIndent(mytag,nspaces) + ToString(GetNChains(0L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_SAMPLES);
    line = MakeIndent(mytag,nspaces) + ToString(GetNSamples(0L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_DISCARD);
    line = MakeIndent(mytag,nspaces) + ToString(GetNDiscard(0L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_INTERVAL);
    line = MakeIndent(mytag,nspaces) + ToString(GetInterval(0L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    nspaces -= INDENT_DEPTH;

    line = MakeIndent(MakeCloseTag(xmlstr::XML_TAG_INITIAL),nspaces);
    xmllines.push_back(line);

    // final chains
    line = MakeIndent(MakeTag(xmlstr::XML_TAG_FINAL),nspaces);
    xmllines.push_back(line);

    nspaces += INDENT_DEPTH;
    mytag = MakeTag(xmlstr::XML_TAG_NUMBER);
    line = MakeIndent(mytag,nspaces) + ToString(GetNChains(1L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_SAMPLES);
    line = MakeIndent(mytag,nspaces) + ToString(GetNSamples(1L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_DISCARD);
    line = MakeIndent(mytag,nspaces) + ToString(GetNDiscard(1L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    mytag = MakeTag(xmlstr::XML_TAG_INTERVAL);
    line = MakeIndent(mytag,nspaces) + ToString(GetInterval(1L)) + MakeCloseTag(mytag);
    xmllines.push_back(line);
    nspaces -= INDENT_DEPTH;

    line = MakeIndent(MakeCloseTag(xmlstr::XML_TAG_FINAL),nspaces);
    xmllines.push_back(line);
    nspaces -= INDENT_DEPTH;

    // ending close tag
    line = MakeIndent(MakeCloseTag(xmlstr::XML_TAG_CHAINS),nspaces);
    xmllines.push_back(line);

    return xmllines;
}

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

long
ChainParameters::GetNChains(long i) const
{
    if(i == defaults::initial)    return m_nChains_initial;
    if(i == defaults::final)      return m_nChains_final;
    assert(false);
    throw implementation_error("bad chain type");
}

long
ChainParameters::GetNSamples(long i) const
{
    if(i == defaults::initial)    return m_nSamples_initial;
    if(i == defaults::final)      return m_nSamples_final;
    assert(false);
    throw implementation_error("bad chain type");
}

long
ChainParameters::GetInterval(long i) const
{
    if(i == defaults::initial)    return m_interval_initial;
    if(i == defaults::final)      return m_interval_final;
    assert(false);
    throw implementation_error("bad chain type");
}

long
ChainParameters::GetNDiscard(long i) const
{
    if(i == defaults::initial)    return m_nDiscard_initial;
    if(i == defaults::final)      return m_nDiscard_final;
    assert(false);
    throw implementation_error("bad chain type");
}

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

bool ChainParameters::ValidTemperatures() const
{
    if(m_temperatures.empty())
    {
        return false;
    }
    if(m_temperatures[0] != 1.0)
    {
        return false;
    }
    DoubleVec1d::const_iterator iter;
    for(iter = m_temperatures.begin(); iter != m_temperatures.end(); iter++)
    {
        if(*iter < 1.0)
        {
            return false;
        }
        DoubleVec1d::const_iterator nextIter = iter+1;
        if(nextIter != m_temperatures.end())
        {
            if(*iter >= *nextIter)
            {
                return false;
            }
        }
    }
    return true;
}

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

long ChainParameters::GetNAllChains() const
{
    return m_nChains_initial + m_nChains_final;
} // GetNAllChains

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

bool ChainParameters::RunProfileReps() const
{
    return ((IsBayesian()) ? false : m_runprofilereps);
} // RunProfileReps

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

long ChainParameters::GetNProfileReps(long nparams) const
{
    long nreps(0);

    if (RunProfileReps())
    {
        // for each parameter, 1 replicate at 5% and 1 at 95%.
        nreps = 2 * nparams;
    }

    return nreps;

} // GetNProfileReps

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

long ChainParameters::GetNRepsAndNProfileReps(long nparams) const
{
    return GetNReps() + GetNProfileReps(nparams);
} // GetNRepsAndNProfileReps

//____________________________________________________________________________________
