/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  GeopsyCore is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-06-01
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "SparseTimeRange.h"

namespace GeopsyCore {

/*!
  \class SparseTimeRange SparseTimeRange.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

const double SparseTimeRange::_epsilon=1e-10;

/*!
  Description of constructor still missing
*/
SparseTimeRange::SparseTimeRange()
{
  _n2=1;
}

SparseTimeRange::SparseTimeRange(const SparseTimeRange& r)
    : QVector<TimeRange>(r)
{
  _n2=r._n2;
}

SparseTimeRange::SparseTimeRange(const TimeRange& r)
{
  _n2=1;
  append(r);
}

/*!
  Description of destructor still missing
*/
SparseTimeRange::~SparseTimeRange()
{
  TRACE;
}

void SparseTimeRange::shift(double dt)
{
  TRACE;
  for(int i=count()-1;i>=0;i--) {
    operator[](i).shift(dt);
  }
}


/*!
  Round start and end or each block to match \a t0 and sampling \a period.
*/
void SparseTimeRange::round(const DateTime& rt, double period)
{
  TRACE;
  ASSERT(period>0.0);
  double frequency=1.0/period;
  for(int i=count()-1;i>=0;i--) {
    operator[](i).round(rt, period, frequency);
  }
}

void SparseTimeRange::scale(const DateTime& center, double factor)
{
  TRACE;
  ASSERT(factor>0.0);
  for(int i=count()-1;i>=0;i--) {
    operator[](i).scale(center, factor);
  }
}

/*!
  \a index must be the index of the time range that ends after the start of \a r.
  Compute it with index(). \a index is increased by one only if there are removed
  ranges and if the first range is cut (used only by SparseKeepSignal).`
*/
int SparseTimeRange::remove(int& index, const TimeRange& r)
{
  TRACE;
  TimeRange& rStart=operator[](index);
  if(r.start()>rStart.start() && r.start()<rStart.end()) {
    if(r.end()<rStart.end(_epsilon)) {
      insert(index, TimeRange(rStart.start(), r.start()));
      operator[](index+1).setStart(r.end());
      return 0;
    } else {
      rStart.setEnd(r.start());
    }
    index++;
  }
  int nRemoved=0;
  if(index<count() && r.start()<at(index).start(_epsilon)) {
    for(int i=index;i<count() && at(i).end()<r.end(_epsilon);i++) {
      nRemoved++;
    }
    QVector<TimeRange>::remove(index, nRemoved);
    int lowerN2=_n2 >> 1;
    while(count()<lowerN2) {
      _n2=lowerN2;
      lowerN2=_n2 >> 1;
    }
  }
  if(index<count()) {
    TimeRange& rEnd=operator[](index);
    if(r.end()>rEnd.start() && r.end()<rEnd.end()) {
      rEnd.setStart(r.end());
    }
  }
  return nRemoved;
}

/*!
  Removes all gaps smaller than \a dt seconds
*/
void SparseTimeRange::removeGaps(double dt)
{
  TRACE;
  if(dt<=0.0) return;
  for(int i=count()-2;i>=0;i--) {
    TimeRange& r1=operator[](i);
    const TimeRange& r2=at(i+1);
    if(r1.end().secondsTo(r2.start())<dt) {
      r1.setEnd(r2.end());
      remove(i+1);
    }
  }
}

/*!
  Removes all blocks smaller than \a dt seconds
*/
void SparseTimeRange::removeBlocks(double dt)
{
  TRACE;
  if(dt<=0.0) return;
  for(int i=count()-1; i>=0; i--) {
    TimeRange& r=operator[](i);
    if(r.lengthSeconds()<dt) {
      remove(i);
    }
  }
}

/*!
  Removes all ranges that overlap with \a r
*/
void SparseTimeRange::remove(const SparseTimeRange& r)
{
  TRACE;
  for(const_iterator it=r.begin(); it!=r.end(); it++) {
    remove(*it);
  }
}

/*!
  \fn void SparseTimeRange::remove(const TimeRange& r)
  Removes all ranges that overlap with \a r
*/

bool SparseTimeRange::contains(const DateTime& t) const
{
  TRACE;
  int i=index(t);
  if(i<0) {
    return false;
  } else if(i<count()-1) {
    return t>=at(i).start(-_epsilon);
  } else {
    return t<=at(i).end(_epsilon);
  }
}

SparseTimeRange SparseTimeRange::intersection(int i, const TimeRange& r) const
{
  TRACE;
  SparseTimeRange res;
  if(r.end()<at(i).start(_epsilon) || r.start(_epsilon)>at(i).end()) {
    return res;
  }
  for(;i<count() && at(i).end()<r.end();i++) {
    res.append(at(i));
  }
  // Add the last block that had its end after r.end() but can have a start() before.
  if(i<count() && at(i).start()<r.end()) {
    res.append(at(i));
  }
  if(!res.isEmpty()) {
    TimeRange& resStart=res.first();
    if(resStart.start()<r.start()) {
      resStart.setStart(r.start());
    }
    TimeRange& resEnd=res.last();
    if(resEnd.end()>r.end()) {
      resEnd.setEnd(r.end());
    }
  }
  return res;
}

/*!
  Returns the intersection between this and \a r.
*/
SparseTimeRange SparseTimeRange::intersection(const SparseTimeRange& r) const
{
  TRACE;
  SparseTimeRange res;
  for(const_iterator it=r.begin();it!=r.end();it++) {
    SparseTimeRange tmp=intersection(*it);
    // We can append directly (without add()) because *it are already sorted
    // and not continuous
    for(iterator itRes=tmp.begin();itRes!=tmp.end();itRes++) {
      res.append(*itRes);
    }
  }
  return res;
}

/*!
  Returns all time ranges of this hit by \a r.
*/
SparseTimeRange SparseTimeRange::hit(const SparseTimeRange& r) const
{
  TRACE;
  SparseTimeRange res;
  for(const_iterator it=begin(); it!=end(); it++) {
    if(r.intersects(*it)) {
      res.append(*it);
    }
  }
  return res;
}

/*!
  Returns all time ranges of this hit by \a r.
*/
SparseTimeRange SparseTimeRange::hit(const TimeRange& r) const
{
  TRACE;
  SparseTimeRange res;
  for(const_iterator it=begin(); it!=end(); it++) {
    if(r.intersects(*it)) {
      res.append(*it);
    }
  }
  return res;
}

SparseTimeRange SparseTimeRange::invert(bool infiniteEnds) const
{
  TRACE;
  SparseTimeRange res;
  int n=count();
  if(n>0) {
    if(infiniteEnds) {
      res.append(TimeRange(DateTime::minimumTime, at(0).start()));
    }
    for(int i=1; i<n; i++) {
      res.append(TimeRange(at(i-1).end(), at(i).start()));
    }
    if(infiniteEnds) {
      res.append(TimeRange(at(n-1).end(), DateTime::maximumTime));
    }
  }
  return res;
}

SparseTimeRange SparseTimeRange::invert(const TimeRange& r) const
{
  TRACE;
  SparseTimeRange res;
  if(isEmpty()) {
    res.append(r);
  } else {
    int i=index(r.start());
    if(at(i).start()>r.start(_epsilon)) {
      res.append(TimeRange(r.start(), at(i).start()));
    }
    for(i++;i<count() && at(i).end()<r.end();i++) {
      res.append(TimeRange(at(i-1).end(), at(i).start()));
    }
    if(i<count()) { // Add the last gap
      res.append(TimeRange(at(i-1).end(), at(i).start()));
    } else {
      if(at(i-1).end(_epsilon)<r.end()) {
        res.append(TimeRange(at(i-1).end(), r.end()));
      }
    }
  }
  return res;
}

/*!
  Add \a r to this. Overlaps are left as gaps if \a acceptOverlap is false.
*/
void SparseTimeRange::add(const SparseTimeRange& r, bool acceptOverlap)
{
  TRACE;
  int n=r.count();
  for(int i=0; i<n; i++) {
    add(r.at(i), acceptOverlap);
  }
}

/*!
  Add \a r to this. Overlaps are left as gaps if \a acceptOverlap is false.
*/
void SparseTimeRange::add(const TimeRange& r, bool acceptOverlap)
{
  TRACE;
  if(r.lengthSeconds()==0.0) return;
  ASSERT(r.start()<r.end());
  /*
    S=start of R
    E=end of R

    Trivial cases: (accepting or rejecting overlaps)

     A. current list is empty             just add
     B. S is after the end of last block  just add
     C. S is at the end of last block     set end of last block to E

    Normal cases:

     i-1         i      i+1
    -----S     -----     -----  A
    -----  S   -----     -----  B
    -----     S-----     -----  C
    -----      --S--     -----  D

      Case A can be reduced to case B, if we remove first block and R is merged with first block.
      Accepting overlaps:
        Case B:

     i-1         i        i+1
    -----  S E -----     -----  O:B:A  insert R and return
    -----  S  E-----     -----  O:B:B  set i start to S and return
    -----  S   --E--     -----  O:B:C  set i start to S and return
    -----  S   -----E    -----  O:B:D  set i start to S and return
    -----  S   -----  E  -----  O:B:E  remove i, loop over i

        Case C:

     i-1         i        i+1
    -----     S--E--     -----  O:C:A  return
    -----     S-----E    -----  O:C:B  return
    -----     S-----  E  -----  O:C:C  remove i, loop over i

        Case D:

     i-1         i        i+1
    -----      --SE-     -----  O:D:A  return
    -----      --S--E    -----  O:D:B  return
    -----      --S--  E  -----  O:D:C  remove i, set S to i start, loop over i

      Rejecting overlaps:
        Case B:

     i-1         i        i+1
    -----  S E -----     -----  o:B:A  insert R and return
    -----  S  E-----     -----  o:B:B  set i start to S and return
    -----  S   --E--     -----  o:B:C  insert block from S to i start, set i start to E and return
    -----  S   -----E    -----  o:B:D  set i start to S, i end to i start and return
    -----  S   -----  E  -----  o:B:E  set i start to S, i end to i start, set S to i end, loop over i

      Case C:

     i-1         i        i+1
    -----     S--E--     -----  o:C:A  set i start to E and return
    -----     S-----E    -----  o:C:B  remove i and return
    -----     S-----  E  -----  o:C:C  remove i, set S to i end, loop over i

      Case D:

     i-1         i        i+1
    -----      --SE-     -----  o:D:A  set i end to S, insert R modified from E to i end and return
    -----      --S--E    -----  o:D:B  set i end to S and return
    -----      --S--  E  -----  o:D:C  set i end to S, set S to i end, loop over i

    Block i+1 is disregarded in all cases. It is considered only when performing a loop over i.
    Last cases (*:B:E, *:C:C and *:D:C) assume only that E is larger than i end.
  */

  // Trivial cases
  if(isEmpty()) {
    append(r);                                                 // Trivial case A
    return;
  }
  DateTime t1, t2;
  int i=index(r.start());
  if(i==count()-1) {
    DateTime t=at(i).end(_epsilon);
    if(r.start()>t) {
      append(r);                                               // Trivial case B
      return;
    } else {
      if(r.start()<t && at(i).end()<r.start(_epsilon)) { // start~=last end
        operator[](i).setEnd(r.end());                           // Trivial case C
        return;
      }
    }
  }

  TimeRange rm(r);
  // Case reduction A to B
  if(i>0 && rm.start()<at(i-1).end(_epsilon)) {        // rm.start()~=at(i).end()
    rm.setStart(at(i-1).start());
    remove(i-1);
    i--;
  }

  if(acceptOverlap) {
    for( ;i<count();) {
      TimeRange& cur=operator[](i);
      if(rm.start(_epsilon)<cur.start()) {
        if(rm.end(_epsilon)<cur.start()) {
          insert(i, rm);                                       // Case O:B:A
          return;
        } else if (rm.end()<cur.end(_epsilon)) {
          cur.setStart(rm.start());                            // Cases O:B:B to O:B:D
          return;
        } else {
          remove(i);                                           // Case O:B:E
        }
      } else if(rm.start()<cur.start(_epsilon)) {
        if(rm.end()<cur.end(_epsilon)) {
          return;                                              // Cases O:C:A and O:C:B
        } else {
          remove(i);                                           // Case O:C:C
        }
      } else {
        if(rm.end()<cur.end(_epsilon)) {
          return;                                              // Cases O:D:A and O:D:B
        } else {
          rm.setStart(cur.start());
          remove(i);                                           // Case O:C:C
        }
      }
    }
  } else {
    for( ;i<count();) {
      TimeRange& cur=operator[](i);
      if(rm.start(_epsilon)<cur.start()) {
        DateTime t=rm.end(_epsilon);
        if(t<cur.start()) {
          insert(i, rm);                                       // Case o:B:A
          return;
        } else if (t<cur.end()) {
          if(rm.end()<cur.start(_epsilon)) {
            cur.setStart(rm.start());                          // Case o:B:B
            return;
          } else {
            DateTime t=cur.start();                              // Case o:B:C
            cur.setStart(rm.end());
            rm.setEnd(t);
            insert(i, rm);
            return;
          }
        } else if(rm.end()<cur.end(_epsilon)) {
          DateTime t=cur.start();                                // Case o:B:D
          cur.setStart(rm.start());
          cur.setEnd(t);
          return;
        } else {
          DateTime t=cur.start();                                // Case o:B:E
          cur.setStart(rm.start());
          rm.setStart(cur.end());
          cur.setEnd(t);
          i++;
        }
      } else if(rm.start()<cur.start(_epsilon)) {
        if(rm.end()<cur.end(_epsilon)) {
          if(rm.end(_epsilon)<cur.end()) {
            cur.setStart(rm.end());                            // Case o:C:A
            return;
          } else {
            remove(i);                                         // Case o:C:B
            return;
          }
        } else {
          rm.setStart(cur.end());
          remove(i);                                           // Case o:C:C
        }
      } else {
        if(rm.end()<cur.end(_epsilon)) {
          if(rm.end(_epsilon)<cur.end()) {
            DateTime t=rm.end();                                 // Case o:D:A
            rm.setEnd(rm.start());
            rm.setStart(cur.start());
            cur.setStart(t);
            insert(i, rm);
            return;
          } else {
            cur.setEnd(rm.start());                            // Case o:D:B
            return;
          }
        } else {
          DateTime t=rm.start();                                 // Case o:D:C
          rm.setStart(cur.end());
          cur.setEnd(t);
          i++;
        }
      }
    }
  }
  append(rm);
}

void SparseTimeRange::clear()
{
  QVector<TimeRange>::clear();
  _n2=1;
}

void SparseTimeRange::append(const TimeRange& r)
{
  QVector<TimeRange>::append(r);
  if(count()==_n2) _n2=_n2 << 1;
}

void SparseTimeRange::insert(int before, const TimeRange& r)
{
  QVector<TimeRange>::insert(before, r);
  if(count()==_n2) _n2=_n2 << 1;
}

void SparseTimeRange::remove(int index)
{
  QVector<TimeRange>::remove(index);
  int lowerN2=_n2 >> 1;
  if(count()<lowerN2) _n2=lowerN2;
}

/*!
  Returns the index of the time range that ends after \a t.
  If \a t is less than the end of first range, 0 is returned.
  If \a t is greater or equal to the start of last range, count()-1 is returned.
*/
int SparseTimeRange::index(const DateTime& t) const
{
  TRACE;
  DateTime ts(t);
  ts.addFewSeconds(1e-10); // Shift a little bit
  int n=count();
  int i=_n2-1;
  int step2=_n2 >> 1;
  while(step2>0) {
    if(i>=n) {
      i-=step2;
    } else if(ts<at(i).end()) {
      if(ts>=at(i-1).end()) {
        break;
      }
      i-=step2;
    } else {
      i+=step2;
    }
    step2=step2 >> 1;
  }
  return i;
}

/*!
  For all ranges, checks returned index for start, middle and end points.
*/
void SparseTimeRange::testIndex() const
{
  TRACE;
  int n=count(), i;
  for(i=0; i<n-1; i++) {
    ASSERT(index(at(i).start())==i);
    ASSERT(index(at(i).center())==i);
    ASSERT(index(at(i).end())==i+1);
  }
  ASSERT(index(at(i).start())==i);
  ASSERT(index(at(i).center())==i);
  ASSERT(index(at(i).end())==i);
}

void SparseTimeRange::printDebug() const
{
  TRACE;
  int n=count();
  if(n>0) {
    printf("%i sub-ranges from %s to %s (n2=%i)\n", n,
           first().start().toString().toLatin1().data(),
           last().end().toString().toLatin1().data(), _n2);
    for(int i=0; i<n; i++) {
      printf("  sub-range %i from %s to %s\n", i,
             at(i).start().toString().toLatin1().data(),
             at(i).end().toString().toLatin1().data());
    }
  } else {
    printf("no sub-ranges\n");
  }
}

/*!
  Split into continuous TimeRange with min/max length specifications.
*/
QVector<TimeRange> SparseTimeRange::split(double minLength, double maxLength) const
{
  TRACE;
  if(maxLength==0.0) {
    maxLength=std::numeric_limits<double>::infinity();
  }
  QVector<TimeRange> res;
  for(const_iterator it=begin(); it!=end(); it++) {
    TimeRange r=*it;
    while(r.lengthSeconds()>maxLength) {
      TimeRange rt=r.truncated(maxLength);
      res.append(rt);
      r.setStart(rt.end());
    }
    if(r.lengthSeconds()>minLength) {
      res.append(r);
    }
  }
  return res;
}

QString SparseTimeRange::toString() const
{
  TRACE;
  QString s;
  for(const_iterator it=begin(); it!=end(); it++) {
    s+=it->toString();
    s+=" ";
  }
  return s;
}

bool SparseTimeRange::fromString(const QString& s)
{
  TRACE;
  clear();
  LineParser lp(s);
  lp.setSkipEmpty(true);
  int n=lp.count();
  bool ok=true;
  for(int i=0; i<n; i+=2) {
    add(TimeRange(lp.toTime(i, DateTime::defaultFormat, ok),
                  lp.toTime(i+1, DateTime::defaultFormat, ok)));
  }
  return ok;
}

/*!
  Used when loading old Geopsy databases, kept for compatibility
*/
bool SparseTimeRange::fromString(const DateTime& ref, const QString& s)
{
  TRACE;
  clear();
  LineParser lp(s);
  lp.setSkipEmpty(true);
  int n=lp.count();
  bool ok=true;
  for(int i=0; i<n; i+=2) {
    add(TimeRange(ref.shifted(lp.toDouble(i, ok)),
                  ref.shifted(lp.toDouble(i+1, ok))));
  }
  return ok;
}

/*!
  Return the total length in seconds, sum of the length of individual time ranges.
*/
double SparseTimeRange::totalLength() const
{
  TRACE;
  double sum=0.0;
  for(const_iterator it=begin(); it!=end(); it++) {
    TimeRange r=*it;
    sum+=r.lengthSeconds();
  }
  return sum;
}

} // namespace GeopsyCore
