// $Id: parameter.cpp,v 1.51 2018/01/03 21:33:04 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 "local_build.h"                // for definition of LAMARC_NEW_FEATURE_RELATIVE_SAMPLING

#include "parameter.h"
#include "forcesummary.h"
#include "prior.h"
#include "registry.h"
#include "mathx.h"
#include "random.h"
#include "stringx.h"
#include "ui_vars_prior.h"
#include "ui_strings.h"                 // for kludgy Parameter::GetUserName()

using namespace std;

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

class RegionGammaInfo;

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

double ResultStruct::GetMLE(long region) const
{
    assert(region >= 0 && static_cast<unsigned long>(region) < mles.size());
    return mles[region];
} // GetMLE

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

double ResultStruct::GetOverallMLE() const
{
    // if there is exactly one region, we will return the regional
    // mle as the overall mle.  If there are multiple regions and
    // no overall mle, something is wrong.
    assert(!(overallmle.empty() && mles.size() != 1));

    if (overallmle.empty()) return mles[0];
    else return overallmle[0];
} // GetOverallMLE

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

DoubleVec1d ResultStruct::GetAllMLEs()  const
{
    assert(!mles.empty());
    DoubleVec1d result = mles;
    if (!overallmle.empty()) result.push_back(overallmle[0]);
    return result;
} // GetAllMLEs

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

const ProfileStruct& ResultStruct::GetProfile(long region) const
{
    assert(region >= 0 && static_cast<unsigned long>(region) < profiles.size());
    return profiles[region];
} // GetProfile

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

const ProfileStruct& ResultStruct::GetOverallProfile() const
{
    // if there is exactly one region, we will return its profile.
    // otherwise, it is an error to ask for overalls if there aren't
    // any.

    assert(!(overallprofile.empty() && profiles.size() != 1));

    if (overallprofile.empty()) return profiles[0];
    else return overallprofile[0];
} // GetOverallProfile

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

vector<ProfileStruct> ResultStruct::GetAllProfiles() const
{
    assert(!profiles.empty());
    vector<ProfileStruct> result = profiles;
    result.push_back(overallprofile[0]);
    return result;
} // GetAllProfiles

const ProfileLineStruct& ResultStruct::GetProfileFor(double centile,
                                                     long reg) const
{
    if (reg == registry.GetDataPack().GetNRegions())
    {
        return GetOverallProfile().GetProfileLine(centile);
    }
    return GetProfile(reg).GetProfileLine(centile);
}

void ResultStruct::AddMLE(double mle, long region)
{
    //We have to replace the MLE sometimes when reading from a summary file.
    if (region < static_cast<long>(mles.size()))
    {
        mles[region] = mle;
    }
    else
    {
        assert(region == static_cast<long>(mles.size()));
        mles.push_back(mle);
    }
}

void ResultStruct::AddOverallMLE(double mle)
{
    if (overallmle.size() > 0)
    {
        overallmle[0] = mle;
    }
    else
    {
        overallmle.push_back(mle);
    }
}

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

Parameter::Parameter(   const ParamStatus& status,
                        unsigned long paramvecIndex,
                        const string sname,
                        const string lname,
                        force_type thisforce,
                        method_type meth,
                        proftype prof,
                        const UIVarsPrior & uiprior,
                        double truevalue
    )
    : m_status(status),
      m_paramvecIndex(paramvecIndex),
      m_shortname(sname),
      m_name(lname),
      m_force(thisforce),
      m_method(meth),
      m_profiletype(prof),
      m_truevalue(truevalue),
      m_prior(uiprior)
{
    if (m_status.Status() == pstat_constant)
    {
        m_profiletype = profile_NONE;
        m_name += " (held constant)";
    } // The names of grouped parameters are set in registry.cpp later.
} // Parameter constructor

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

Parameter::Parameter(const ParamStatus& status, unsigned long paramvecIndex)
    : m_status(status),
      m_paramvecIndex(paramvecIndex),
      m_method(method_PROGRAMDEFAULT),
      m_profiletype(profile_NONE),
      m_prior(status)
{
    if (m_status.Valid())
    {
        throw implementation_error("Tried to create a valid parameter object using the invalid parameter constructor.");
    }
} // Invalid parameter constructor

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

string Parameter::GetUserName() const
{
    return m_shortname;
} // GetUserName

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

bool Parameter::IsEasilyBayesianRearrangeable() const
{
    // currently epoch boundary times are not casually movable
    // and require their own seperate arranger (the EpochSizeArranger)
    return (IsVariable() && m_force != force_DIVERGENCE);
} // IsEasilyBayesianRearrangeable

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

pair<double, double> Parameter::DrawFromPrior() const
{
    return m_prior.RandomDraw();
} // DrawFromPrior

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

bool Parameter::IsZeroTrueMin()
{
    if (m_force == force_GROW)
        return false;
    return true;
}

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

vector<centilepair> Parameter::GetPriorLikes(long region) const
{

    assert(IsValidParameter());
    vector<centilepair> answer;

    const ProfileStruct& profile = m_results.GetProfile(region);
    vector<ProfileLineStruct>::const_iterator it;

    for (it = profile.profilelines.begin();
         it != profile.profilelines.end();
         ++it)
    {
        centilepair newpair(it->percentile, it->loglikelihood);
        answer.push_back(newpair);
    }
    return answer;
} // GetPriorLikes

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

vector<centilepair> Parameter::GetOverallPriorLikes() const
{
    assert(IsValidParameter());

    vector<centilepair> answer;

    const ProfileStruct& profile = m_results.GetOverallProfile();
    vector<ProfileLineStruct>::const_iterator it;

    for (it = profile.profilelines.begin();
         it != profile.profilelines.end();
         ++it)
    {
        centilepair newpair(it->percentile, it->loglikelihood);
        answer.push_back(newpair);
    }
    return answer;
} // GetOverallPriorLikes

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

vector<vector<centilepair> > Parameter::GetProfiles(long region) const
{
    assert(IsValidParameter());

    const ProfileStruct& profile = m_results.GetProfile(region);
    vector<ProfileLineStruct>::const_iterator line;

    vector<vector<centilepair> > answer;
    vector<centilepair> answerline;

    long parameter;
    long numparams = 0;
    // hack, sorry
    for (line = profile.profilelines.begin(); line != profile.profilelines.end(); ++line)
    {
        if (!line->profparam.empty())
        {
            numparams = line->profparam.size();
            break;
        }
    }

    // if this assert fires, profiles have been requested but none
    // whatsoever can be found....
    // assert(numparams > 0);
    // LS NOTE:  In a bayesian analysis, having no profiles is expected--we
    //  use GetCIs and GetLikes, etc. instead.  It's convenient to call this
    //  routine anyway, so I don't have to special-case the calling code in
    //  runreports.  Hence, commenting out the above 'assert'.

    //long numparams = profile.profilelines[0].profparam.size();

    for (parameter = 0; parameter < numparams; ++parameter)
    {
        for (line = profile.profilelines.begin();
             line != profile.profilelines.end();
             ++line)
        {
            double thisparam = line->profparam[parameter];
            centilepair newpair(line->percentile, thisparam);
            answerline.push_back(newpair);
        }
        answer.push_back(answerline);
        answerline.clear();
    }

    return answer;

} // GetProfiles

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

vector<vector<centilepair> > Parameter::GetOverallProfile() const
{
    assert(IsValidParameter());

    vector<vector<centilepair> > answer;
    vector<centilepair> answerline;

    const ProfileStruct& profile = m_results.GetOverallProfile();
    vector<ProfileLineStruct>::const_iterator line;

    long parameter;
    // hack, sorry
    long numparams = profile.profilelines[0].profparam.size();

    for (parameter = 0; parameter < numparams; ++parameter)
    {
        for (line = profile.profilelines.begin();
             line != profile.profilelines.end();
             ++line)
        {
            double thisparam = line->profparam[parameter];
            centilepair newpair(line->percentile, thisparam);
            answerline.push_back(newpair);
        }
        answer.push_back(answerline);
        answerline.clear();
    }

    return answer;

} // GetOverallProfile

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

vector<centilepair> Parameter::GetCIs(long region) const
{
    assert(IsValidParameter());

    vector<centilepair> answer;

    const ProfileStruct& profile = m_results.GetProfile(region);
    vector<ProfileLineStruct>::const_iterator it;

    for (it = profile.profilelines.begin();
         it != profile.profilelines.end();
         ++it)
    {
        centilepair newpair(it->percentile, it->profilevalue);
        answer.push_back(newpair);
    }
    return answer;

} // GetCIs

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

vector<centilepair> Parameter::GetOverallCIs() const
{
    assert(IsValidParameter());

    vector<centilepair> answer;

    const ProfileStruct& profile = m_results.GetOverallProfile();
    vector<ProfileLineStruct>::const_iterator it;

    for (it = profile.profilelines.begin();
         it != profile.profilelines.end();
         ++it)
    {
        centilepair newpair(it->percentile, it->profilevalue);
        answer.push_back(newpair);
    }
    return answer;

} // GetOverallCIs

bool Parameter::CentileIsExtremeLow(double centile, long reg) const
{
    return m_results.GetProfileFor(centile, reg).isExtremeLow;
}

bool Parameter::CentileIsExtremeHigh(double centile, long reg) const
{
    return m_results.GetProfileFor(centile, reg).isExtremeHigh;
}

bool Parameter::CentileHadWarning(double centile, long reg) const
{
    return m_results.GetProfileFor(centile, reg).maximizerWarning;
}

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

void Parameter::AddProfile(const ProfileStruct& prof, likelihoodtype like)
{
    assert(IsValidParameter());
    switch (like)
    {
        case ltype_ssingle:
            m_results.AddProfile(prof);
            break;
        case ltype_replicate:
            m_results.AddProfile(prof);
            break;
        case ltype_region:
            m_results.AddOverallProfile(prof);
            break;
        case ltype_gammaregion:
        {
            RegionGammaInfo *pRegionGammaInfo = registry.GetRegionGammaInfo();
            if (!pRegionGammaInfo ||
                !pRegionGammaInfo->CurrentlyPerformingAnalysisOverRegions())
            {
                string msg = "Parameter::AddProfile() was told to add a profile ";
                msg += "for an overall estimate over all genomic regions with ";
                msg += "gamma-distributed background mutation rates, but the ";
                msg += "necessary RegionGammaInfo object was not found, or was ";
                msg += "found in the \"off\" state, neither of which should happen.";
                throw implementation_error(msg);
            }
            m_results.AddOverallProfile(prof);
            if (!pRegionGammaInfo->HaveProfile() && force_REGION_GAMMA == m_force)
                pRegionGammaInfo->AddProfile(prof);
            break;
        }
    }
} // AddProfile

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

// Initialization of static variable, needs to be in .cpp
bool ParamVector::s_locked = false;

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

// This constructor can make a read-only ParamVector

ParamVector::ParamVector(bool readonly)
    : m_readonly(readonly),
      forcesum(registry.GetForceSummary())
{
    if (m_readonly)
    {
        parameters = forcesum.GetAllParameters();
    }
    else
    {
        assert(!s_locked);        // attempt to check out a second set of parameters!
        parameters = forcesum.GetAllParameters();
        s_locked = true;
    }

    const RegionGammaInfo *pRegionGammaInfo = registry.GetRegionGammaInfo();
    if (pRegionGammaInfo && pRegionGammaInfo->CurrentlyPerformingAnalysisOverRegions())
    {
        UIVarsPrior gammaprior(force_REGION_GAMMA);
        Parameter paramAlpha(pRegionGammaInfo->GetParamStatus(),
                             parameters.size(),
                             "alpha",
                             "alpha (shape parameter for gamma over regions)",
                             force_REGION_GAMMA,
                             method_PROGRAMDEFAULT,
                             pRegionGammaInfo->GetProfType(),
                             gammaprior,
                             FLAGDOUBLE);

        if (pRegionGammaInfo->HaveMLE())
        {
            paramAlpha.AddOverallMLE(pRegionGammaInfo->GetMLE());
            if (pRegionGammaInfo->HaveProfile())
                paramAlpha.AddProfile(pRegionGammaInfo->GetProfile(), ltype_gammaregion);
        }

        parameters.push_back(paramAlpha);
    }

} // ParamVector

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

ParamVector::~ParamVector()
{
    if (m_readonly) return;

    assert(s_locked);        // how did it get unlocked before destruction??
    forcesum.SetAllParameters(parameters);
    s_locked = false;

} // ParamVector destructor

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

Parameter& ParamVector::operator[](long index)
{
    assert(ParamVector::s_locked);  // should only be unlocked in 'const' context

    // bounds checking
    assert(index >= 0);
    assert(index < static_cast<long>(parameters.size()));

    return parameters[index];

} // ParamVector operator[]

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

const Parameter& ParamVector::operator[](long index) const
{
    assert(index >= 0);
    assert(index < static_cast<long>(parameters.size()));

    return parameters[index];

} // ParamVector operator[] const

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

paramlistcondition ParamVector::CheckCalcProfiles() const
{
    vector <Parameter> :: const_iterator pit;
    long ok=0;
    long nok=0;
    for(pit=parameters.begin(); pit != parameters.end(); pit++)
    {
        if(pit->IsValidParameter())
        {
            switch(pit->GetProfileType())
            {
                case profile_FIX:
                case profile_PERCENTILE:
                    ok++;
                    break;
                case profile_NONE:
                    nok++;
                    continue;
            }
        }
    }
    long sum = ok + nok;
    if(nok==sum)
        return paramlist_NO;
    else
    {
        if(ok==sum)
            return paramlist_YES;
        else
            return paramlist_MIX;
    }
}

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

paramlistcondition ParamVector::CheckCalcPProfiles() const
{
    vector <Parameter> :: const_iterator pit;
    long fok=0;
    long pok=0;
    long nok=0;
    for(pit=parameters.begin(); pit != parameters.end(); pit++)
    {
        if(pit->IsValidParameter())
        {
            switch(pit->GetProfileType())
            {
                case profile_FIX:
                    fok++;
                    break;
                case profile_PERCENTILE:
                    pok++;
                    break;
                case profile_NONE:
                    nok++;
                    continue;
            }
        }
    }
    long sum = fok + pok + nok;
    if(nok==sum)
        return paramlist_NO;
    else
    {
        if(pok==sum)
            return paramlist_YES;
        else
            return paramlist_MIX;
    }
}

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

long ParamVector::NumProfiledParameters() const
{
    vector<Parameter>::const_iterator it;

    long result = 0;

    for (it = parameters.begin(); it != parameters.end(); ++it)
    {
        if (it->IsProfiled())
            ++result;
    }

    return result;
} // NumProfiledParameters

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

long ParamVector::NumVariableParameters() const
{
    vector<Parameter>::const_iterator it;

    long result = 0;

    for (it = parameters.begin(); it != parameters.end(); ++it)
    {
        ParamStatus mystatus = ParamStatus(it->GetStatus());
        if (mystatus.Varies()) ++result;
    }

    return result;
} // NumVariableParameters

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

long ParamVector::ChooseSampleParameterIndex(Random * randomSource) const
{
#ifdef LAMARC_NEW_FEATURE_RELATIVE_SAMPLING

    long totalWeight = 0;
    vector<Parameter>::const_iterator it;
    for (it = parameters.begin(); it != parameters.end(); ++it)
    {
        if (it->IsEasilyBayesianRearrangeable())
        {
            long thisRate = it->GetPrior().GetSamplingRate();
            assert(thisRate > 0); // EWFIX -- really must handle this.
            totalWeight += thisRate;
        }
    }
    assert(totalWeight > 0);

    long weightedIndex = randomSource->Long(totalWeight);

    long remainingWeight = weightedIndex;
    long chosenIndex = 0;
    for (it = parameters.begin(); it != parameters.end(); ++it)
    {
        long thisWeight = it->GetPrior().GetSamplingRate();
        remainingWeight -= thisWeight;
        if (remainingWeight < 0)
        {
            return chosenIndex;
        }
        chosenIndex++;
    }
    assert(false);
    return FLAGLONG;

#else
    long chosen;
    while (true)
    {
        long chosen = randomSource->Long(size());
        if (operator[](chosen).IsEasilyBayesianRearrangeable()) return chosen;
        //LS DEBUG:  Potential for an infinite loop here if no parameter is set
        // to be variable, but this should be caught when exiting the menu.

    }
    assert(false);
    return(FLAGLONG);
#endif

}

//____________________________________________________________________________________
