// $Id: rangex.cpp,v 1.32 2018/01/03 21:33:02 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 <iostream>

#if 0                                   // RSGNOTE: Not currently used.
#include <cstdlib>                      // For std::atol() in ToRangePair
#endif

#include "local_build.h"

#include "rangex.h"
#include "stringx.h"
#include "registry.h"

using std::string;
using std::make_pair;

//------------------------------------------------------------------------------------
// Print intervals as half-open (ie, lower limit included, upper limit excluded).
// Prints interval as "[n1,n2)".

string ToString(rangepair rpair)
{
    // Note!  This prints rangepairs in "internal units", not "user units".
    // For "user units", call ToStringUserUnits() instead.

    return "[" + ToString(rpair.first) + "," + ToString(rpair.second) + ")";
}

//------------------------------------------------------------------------------------
// Print intervals as half-open (ie, lower limit included, upper limit excluded).
// Prints interval as "[n1,n2)" and several as "[n1,n2) , [n3,n4)".

string ToString(rangeset rset)
{
    // Note!  This prints rangesets in "internal units", not "user units".
    // For "user units", call ToStringUserUnits() instead.

    rangeset::iterator rpair = rset.begin();

    if (rpair == rset.end())
    {
        return "(none)";
    }

    string retval = ToString(*rpair);
    ++rpair;

    for ( ; rpair != rset.end() ; ++rpair)
    {
        retval += " , " + ToString(*rpair);
    }

    return retval;
}

//------------------------------------------------------------------------------------
// Debugging function.
// This is needed only because RecRange::PrintLinks calls it in the Littlelinks version.
// Print intervals as half-open (ie, lower limit included, upper limit excluded).
// Prints interval as "[n1,n2)" and several as "[n1,n2) , [n3,n4)".

#ifndef RUN_BIGLINKS

string ToString(linkrangeset rset)
{
    // Note!  This prints rangesets in "internal units", not "user units".
    // For "user units", call ToStringUserUnits() instead.

    linkrangeset::iterator rpair = rset.begin();

    if (rpair == rset.end())
    {
        return "(none)";
    }

    string retval = ToString(*rpair);
    ++rpair;

    for ( ; rpair != rset.end() ; ++rpair)
    {
        retval += " , " + ToString(*rpair);
    }

    return retval;
}

#endif // RUN_BIGLINKS

//------------------------------------------------------------------------------------
// Print intervals as half-open (ie, lower limit included, upper limit excluded) 
// incremented by 1 to make the intervals consistent with GraphML conventions
// Prints interval as "[n1,n2)".

string ToGraphMLString(rangepair rpair)
{
    // Note!  This prints rangepairs in "internal units", not "user units".
    // For "user units", call ToStringUserUnits() instead.
    // Note also: it is NOT (rpair.second + 1) because this is indexes not live sites
    return "[" + ToString(rpair.first+1) + "," + ToString(rpair.second) + ")";
}

//------------------------------------------------------------------------------------
// Print intervals as half-open (ie, lower limit included, upper limit excluded).
// incremented by 1 to make the intervals consistent with GraphML conventions
// Prints interval as "[n1,n2)" and several as "[n1,n2) , [n3,n4)".

string ToGraphMLString(rangeset rset)
{
    // Note!  This prints rangesets in "internal units", not "user units".
    // For "user units", call ToStringUserUnits() instead.

    rangeset::iterator rpair = rset.begin();

    if (rpair == rset.end())
    {
        return "(none)";
    }

    string retval = ToGraphMLString(*rpair);
    ++rpair;

    for ( ; rpair != rset.end() ; ++rpair)
    {
        retval += " , " + ToGraphMLString(*rpair);
    }

    return retval;
}

//------------------------------------------------------------------------------------
// Print intervals as fully closed (ie, both lower and upper limits included).
// Prints interval as "n1:n2" and several as "n1:n2, n3:n4".

string ToStringUserUnits(rangepair rpair)
{
    // Note!  This prints rangepairs in "user units" instead of "internal units".
    // In other words, if the range is <0,34>, this is an "open-upper-end" interval,
    // and the actual active sites are 0:33.
    //
    // Furthermore, if the user is expecting to have no zeroes in the output, we
    // need to convert the 0 to -1, and display "-1:33" instead.

    rpair.second--;                     // Print last included value, not first excluded value.
    rpair = ToNoZeroesIfNeeded(rpair);

    string retval = ToString(rpair.first);
    if (rpair.second > rpair.first)
    {
        retval += ":" + ToString(rpair.second);
    }

    return retval;
}

//------------------------------------------------------------------------------------
// Print intervals as fully closed (ie, both lower and upper limits included).
// Prints interval as "n1:n2" and several as "n1:n2, n3:n4".

string ToStringUserUnits(rangeset rset)
{
    // Note!  This prints rangesets in "user units" instead of "internal units".
    // In other words, if the range is <0,34>, this is an "open-upper-end" interval,
    // and the actual active sites are 0:33.
    //
    // Furthermore, if the user is expecting to have no zeroes in the output, we
    // need to convert the 0 to -1, and display "-1:33" instead.

    rangeset::iterator rpair = rset.begin();

    if (rpair == rset.end())
    {
        return "(none)";
    }

    string retval = ToStringUserUnits(*rpair);
    ++rpair;

    for ( ; rpair != rset.end() ; ++rpair)
    {
        retval += ", " + ToStringUserUnits(*rpair);
    }

    return retval;
}

rangepair ToRangePair(string & instr)
{
    rangepair retpair = make_pair(0, 0);
    unsigned int cdx = instr.find(',');
    long stval;
    long ndval;

    if (cdx == instr.length())
    {
        // Throw bad rangepair error - JMFIX
        std::cerr << "ToRangePair: instr \"" << instr << "\" has no \":\" so it is not a valid rangepair." << std::endl;
    }
    else
    {
        // Extract range (reads as half-open interval, closed at lower end and open at upper end).
        stval = std::atol(instr.substr(0, cdx).c_str());
        ndval = std::atol(instr.substr(cdx+1, instr.length()).c_str()); // Convert to open upper end.
        // Fix start index if the zero had been eliminated.
        if (registry.GetConvertOutputToEliminateZeroes() && (stval < 0))
        {
            ++stval;
        }
        retpair = make_pair(stval, ndval);
    }
    return retpair;
}

rangeset ToRangeSet(string & instr)
{
    rangeset retset;
    unsigned int stidx = 1;
    unsigned int sdx = instr.find('[');
    unsigned int rdx = instr.find(')');
    string internal;

    while (rdx < instr.length())
    {
        // Internal pairs.
        internal = instr.substr(stidx, rdx - 1);
        retset = AddPairToRange(ToRangePair(internal), retset);
        sdx = instr.find('[', rdx); 
        rdx = instr.find(')', sdx); 
        stidx = sdx + 1;
    }
    return retset;
}


//------------------------------------------------------------------------------------
// Returns a RANGESET as a set containing a single RANGEPAIR, that defined by its two arguments.
// Note that the arguments define a half-open interval, INCLUDING the lower and EXCLUDING the upper argument.

rangeset MakeRangeset(long int low, long int high)
{
    rangeset retset;
    retset.insert(make_pair(low, high));

    return retset;
}

//------------------------------------------------------------------------------------
// Returns a LINKRANGESET as a set containing a single LINKRANGEPAIR, that defined by its two arguments.
// Note that the arguments define a half-open interval, INCLUDING the lower and EXCLUDING the upper argument.

linkrangeset MakeRangeset(unsigned long int low, unsigned long int high)
{
    linkrangeset retset;
    retset.insert(make_pair(low, high));

    return retset;
}

//------------------------------------------------------------------------------------
// Returns a RANGESET as the SET UNION of the first argument (a RANGEPAIR) and the second argument (a RANGESET).

rangeset AddPairToRange(const rangepair & addpart, const rangeset & rset)
{
    rangeset retset = rset;

    rangepair low  = make_pair(addpart.first, addpart.first);
    rangepair high = make_pair(addpart.second, addpart.second);

    long int newlow = low.first;
    long int newhigh = high.second;

    rangesetiter early = retset.lower_bound(low);
    if (early != retset.begin())
    {
        --early;
        if (early->second >= low.first)
        {
            //'low' falls within early's interval
            newlow = early->first;
        }
    }

    rangesetiter late = retset.upper_bound(high);
    //We need to increment this late.first == high.first
    if (late != retset.end())
    {
        if (late->first == high.first)
        {
            ++late;
        }
    }
    if (late != retset.begin())
    {
        --late;
        if (late->second > high.first)
        {
            //'high' falls within late's interval
            newhigh = late->second;
        }
    }

    early = retset.lower_bound(make_pair(newlow, newlow + 1));
    late  = retset.upper_bound(make_pair(newhigh - 1, newhigh));

    retset.erase(early, late);
    retset.insert(make_pair(newlow, newhigh));

    return retset;
}

//------------------------------------------------------------------------------------
// Returns a LINKRANGESET as the SET UNION of the first argument (a LINKRANGEPAIR)
// and the second argument (a LINKRANGESET).

linkrangeset AddPairToRange(const linkrangepair & addpart, const linkrangeset & rset)
{
    linkrangeset retset = rset;

    linkrangepair low  = make_pair(addpart.first, addpart.first);
    linkrangepair high = make_pair(addpart.second, addpart.second);

    unsigned long int newlow = low.first;
    unsigned long int newhigh = high.second;

    linkrangesetiter early = retset.lower_bound(low);
    if (early != retset.begin())
    {
        --early;
        if (early->second >= low.first)
        {
            //'low' falls within early's interval
            newlow = early->first;
        }
    }

    linkrangesetiter late = retset.upper_bound(high);
    //We need to increment this late.first == high.first
    if (late != retset.end())
    {
        if (late->first == high.first)
        {
            ++late;
        }
    }
    if (late != retset.begin())
    {
        --late;
        if (late->second > high.first)
        {
            //'high' falls within late's interval
            newhigh = late->second;
        }
    }

    early = retset.lower_bound(make_pair(newlow, newlow + 1));
    late  = retset.upper_bound(make_pair(newhigh - 1, newhigh));

    retset.erase(early, late);
    retset.insert(make_pair(newlow, newhigh));

    return retset;
}

//------------------------------------------------------------------------------------
// Returns a RANGESET as the SET DIFFERENCE of the second argument (a RANGESET)
// and the first argument (a RANGEPAIR).

rangeset RemovePairFromRange(const rangepair & removepart, const rangeset & rset)
{
    rangeset retset;
    for (rangeset::iterator range = rset.begin(); range != rset.end(); ++range)
    {
        if ((range->first >= removepart.first) && (range->second <= removepart.second))
        {
            //Don't add it.
        }
        else
        {
            if ((range->first < removepart.first) && (range->second > removepart.second))
            {
                //Add two outside halves.
                retset.insert(make_pair(range->first, removepart.first));
                retset.insert(make_pair(removepart.second, range->second));
            }
            else if ((range->second > removepart.first) && (range->second <= removepart.second))
            {
                //Add only the first half.
                retset.insert(make_pair(range->first, removepart.first));
            }
            else if ((range->first >= removepart.first) && (range->first <= removepart.second))
            {
                //Add only the second half.
                retset.insert(make_pair(removepart.second, range->second));
            }
            else
            {
                //Add the whole thing.
                retset.insert(*range);
            }
        }
    }
    return retset;
}

//------------------------------------------------------------------------------------
// Returns a LINKRANGESET as the SET DIFFERENCE of the second argument (a LINKRANGESET)
// and the first argument (a LINKRANGEPAIR).

linkrangeset RemovePairFromRange(const linkrangepair & removepart, const linkrangeset & rset)
{
    linkrangeset retset;
    for (linkrangeset::iterator range = rset.begin(); range != rset.end(); ++range)
    {
        if ((range->first >= removepart.first) && (range->second <= removepart.second))
        {
            //Don't add it.
        }
        else
        {
            if ((range->first < removepart.first) && (range->second > removepart.second))
            {
                //Add two outside halves.
                retset.insert(make_pair(range->first, removepart.first));
                retset.insert(make_pair(removepart.second, range->second));
            }
            else if ((range->second > removepart.first) && (range->second <= removepart.second))
            {
                //Add only the first half.
                retset.insert(make_pair(range->first, removepart.first));
            }
            else if ((range->first >= removepart.first) && (range->first <= removepart.second))
            {
                //Add only the second half.
                retset.insert(make_pair(removepart.second, range->second));
            }
            else
            {
                //Add the whole thing.
                retset.insert(*range);
            }
        }
    }
    return retset;
}

//------------------------------------------------------------------------------------
// Returns a RANGESET as the SET DIFFERENCE of the second argument (a RANGESET)
// and the first argument (a RANGESET).

rangeset RemoveRangeFromRange(const rangeset & removerange, const rangeset & rset)
{
    rangeset retset = rset;

    for (rangeset::iterator removepair = removerange.begin(); removepair != removerange.end(); removepair++)
    {
        retset = RemovePairFromRange(*removepair, retset);
    }

    return retset;
}

//------------------------------------------------------------------------------------
// Returns a LINKRANGESET as the SET DIFFERENCE of the second argument (a LINKRANGESET)
// and the first argument (a LINKRANGESET).

linkrangeset RemoveRangeFromRange(const linkrangeset & removerange, const linkrangeset & rset)
{
    linkrangeset retset = rset;

    for (linkrangeset::iterator removepair = removerange.begin(); removepair != removerange.end(); removepair++)
    {
        retset = RemovePairFromRange(*removepair, retset);
    }

    return retset;
}

//------------------------------------------------------------------------------------
// LS NOTE:
// These functions originally written for range.cpp, but ended up being needed elsewhere.
//
// Note that 'Union' (used to be 'OR' but I got confused too often) actually does the exact same thing that
// 'AddRangeToRange' used to, but much faster, so I took out AddRangeToRange entirely.  Perhaps there's a
// similar way to speed up RemoveRangeFromRange?  We'll see if it shows up in the profiler.

rangeset Union(const rangeset & set1, const rangeset & set2)
{
    if (set1.empty()) return set2;
    if (set2.empty()) return set1;

    rangeset mergeset;
    merge(set1.begin(), set1.end(), set2.begin(), set2.end(), inserter(mergeset, mergeset.begin()));

    rangeset newset;
    rangeset::iterator rit;

    for(rit = mergeset.begin(); rit != mergeset.end(); ++rit)
    {
        ORAppend(*rit, newset);
    }

    return newset;
} // OR

//------------------------------------------------------------------------------------
// Helper function for 'Union'

void ORAppend(rangepair newrange, rangeset & oldranges)
{
    if (oldranges.empty())
    {
        oldranges.insert(newrange);
        return;
    }

    rangeset::iterator last = --oldranges.end();

    assert(newrange.first >= last->first); // Expect sequential, sorted input.

    if (newrange.second <= last->second) return; // New is contained in old.

    if (newrange.first > last->second)  // New is after old.
    {
        oldranges.insert(oldranges.end(), newrange);
        return;
    }

    newrange.first = last->first;       // New starts within old and extends past it.
    oldranges.erase(last);
    oldranges.insert(oldranges.end(), newrange);

    return;
} // ORAppend

//------------------------------------------------------------------------------------
// Used to be 'AND' until I got confused too often.

rangeset Intersection(const rangeset & mother, const rangeset & father)
{
    rangeset::const_iterator m = mother.begin();
    rangeset::const_iterator f = father.begin();

    rangeset result;
    rangepair newpair;

    if (mother.empty() || father.empty()) return result;

    while (true)
    {
        newpair.first = std::max((*m).first, (*f).first);
        newpair.second = std::min((*m).second, (*f).second);

        if (newpair.first < newpair.second)
            result.insert(result.end(),newpair);

        if ((*m).second < (*f).second) ++m;
        else ++f;

        if (m == mother.end() || f == father.end()) return result;
    }
} // Intersection

//------------------------------------------------------------------------------------
// Counts the sites (or any other type of object represented as a PAIR of INTs) in a RANGESET.

long int CountSites(const rangeset & rset)
{
    if (rset.empty()) return 0L;

    long int count(0L);

    for (rangeset::const_iterator rpair = rset.begin(); rpair != rset.end(); ++rpair)
    {
        count += rpair->second - rpair->first;
    }

    return count;
}

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

#if 0 // Apparently unused.

rangeset SubtractFromRangeset(const rangeset & rset, long int offset)
{
    long int invOffset = 0 - offset;
    return AddToRangeset(rset, invOffset);
}

#endif

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

#if 0 // Apparently unused.

rangeset AddToRangeset(const rangeset & rset, long int offset)
{
    rangeset::const_iterator range;
    rangeset newRangeSet;

    for(range = rset.begin(); range != rset.end(); ++range)
    {
        long int newFirst = range->first + offset;
        long int newSecond = range->second + offset;

        newRangeSet.insert(make_pair(newFirst, newSecond));
    }

    return newRangeSet;
}

#endif

//------------------------------------------------------------------------------------
// Tests whether second argument (an INDEX) is within any of the intervals indicated by the pairs
// in the rangeset which is the first argument.
// This function is applied only to SITE rangesets, not to LINK (Big or Little) rangesets.

bool IsInRangeset(const rangeset & targetset, long int target)
{
    if (targetset.empty())
        return false;

    rangeset::const_iterator range;

    for(range = targetset.begin(); range != targetset.end(); ++range)
    {
        if (range->second <= target) continue;
        if (range->first <= target) return true;
        break;
    }

    return false;
} // IsInRangeset

//------------------------------------------------------------------------------------
// Tests whether second argument (an INDEX) is within any of the intervals indicated by the pairs
// in the rangeset which is the first argument.
// This function is applied only to SITE rangesets, not to LINK (Big or Little) rangesets.

#if 0 // RSGDEBUG: Enabled for debugging but called (only on RecRange objects) in commented-out section of debugging function.
#ifndef RUN_BIGLINKS

bool IsInRangeset(const linkrangeset & targetset, signed long int target)
{
    if (targetset.empty())
        return false;

    linkrangeset::const_iterator range;

    for(range = targetset.begin(); range != targetset.end(); ++range)
    {
        if (static_cast<signed long int>(range->second) <= target) continue;
        if (static_cast<signed long int>(range->first) <= target) return true;
        break;
    }

    return false;
} // IsInRangeset

#endif // RUN_BIGLINKS
#endif // RSGDEBUG

//------------------------------------------------------------------------------------
// These functions are to be used when converting site labels for display to
// the user, and from *menu* input from the user.  It is assumed that in the
// XML, the 'user input' has already been converted to the 'sequential' version.

long int ToSequentialIfNeeded(long int input)
{
    //Note:  input might equal zero if it was the upper end of a rangepair,
    // since rangepairs are open-ended by default.  In other words, if the user
    // types in "-20:-1" in the menu, the first thing that happens is that these
    // numbers are converted to the pair <-20, 0>.  To convert these values to
    // the 'sequential' numbering system, this needs to be converted to the
    // pair <-19, 1>.

    if (registry.GetConvertOutputToEliminateZeroes() && (input <= 0))
    {
        input++;
    }

    return input;
}

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

long int ToNoZeroesIfNeeded(long int input)
{
    if (registry.GetConvertOutputToEliminateZeroes() && (input <= 0))
    {
        input--;
    }

    return input;
}

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

rangepair ToSequentialIfNeeded(rangepair input)
{
    if (registry.GetConvertOutputToEliminateZeroes())
    {
        input.first = ToSequentialIfNeeded(input.first);
        input.second = ToSequentialIfNeeded(input.second);
    }

    return input;
}

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

rangepair ToNoZeroesIfNeeded(rangepair input)
{
    if (registry.GetConvertOutputToEliminateZeroes())
    {
        input.first = ToNoZeroesIfNeeded(input.first);
        input.second = ToNoZeroesIfNeeded(input.second);
    }

    return input;
}

//____________________________________________________________________________________

