/***************************************************************************
**
**  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: 2018-11-06
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "SeismicEventTable.h"

namespace GeopsyCore {

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

    Full description of class still missing
  */

  const QString SeismicEventTable::xmlSeismicEventTableTag="SeismicEventTable";

  /*!
    Description of constructor still missing
  */
  SeismicEventTable::SeismicEventTable()
    : QObject(nullptr)
  {
    TRACE;
    _modified=false;
  }

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

  bool SeismicEventTable::contains(const SeismicEvent& e) const
  {
    int i=firstIndexOf(e.time());
    if(i>=0) {
      int n=count();
      while(i<n && SortedVector<DateTime, SeismicEvent>::at(i)->time()==e.time()) {
        if(SortedVector<DateTime, SeismicEvent>::at(i)->isSamePosition(e)) {
          return true;
        }
        i++;
      }
    }
    return false;
  }

  /*!
    Add a new event if it does not already exist.

    A pointer to newly created event is returned.
  */
  const SeismicEvent * SeismicEventTable::add(const SeismicEvent& e)
  {
    TRACE;
    if(!contains(e)) {
      SeismicEvent * n=new SeismicEvent(e);
      insert(n);
      emit eventAdded(n);
      _modified=true;
      return n;
    } else {
      return nullptr;
    }
  }

  void SeismicEventTable::remove(const SeismicEvent& e)
  {
    TRACE;
    DateTime t=e.time();
    int i=firstIndexOf(t);
    if(i>=0) {
      while(i<count() && at(i)->time()==t) {
        SeismicEvent * r=at(i);
        if(r->isSamePosition(e)) {
          if(!r->isLocked()) {
            SortedVector<DateTime, SeismicEvent>::remove(i);
            emit eventRemoved(r);
            delete r;
            _modified=true;
          }
        }
        i++;
      }
    }
  }

  /*!
    Return all events between \a from and \a to.
  */
  QList<const SeismicEvent *> SeismicEventTable::events(const TimeRange& r) const
  {
    TRACE;
    QList<const SeismicEvent *> list;
    int n=count();
    int i=firstIndexAfter(r.start()); // firstIndexOf cannot be used because if an exact match is not found -1
                                      // is returned. In this case, we are not looking for an exact match.
    while(i>0 && SortedVector<DateTime, SeismicEvent>::at(i-1)->time()==r.start()) {
      i--;
    }
    if(i>=0) {
      while(i<n && SortedVector<DateTime, SeismicEvent>::at(i)->time()<r.end()) {
        list.append(SortedVector<DateTime, SeismicEvent>::at(i));
        i++;
      }
    }
    return list;
  }

  /*!
    Convenient function to return all events occuring during a SparseTimeRange.
  */
  QList<const SeismicEvent *> SeismicEventTable::events(const SparseTimeRange& r) const
  {
    TRACE;
    QList<const SeismicEvent *> list;
    const ::QVector<TimeRange>& rl=r.ranges();
    for(::QVector<TimeRange>::const_iterator it=rl.begin(); it!=rl.end(); it++) {
      list.append(events(*it));
    }
    return list;
  }

  /*!
    Return all events between \a from and \a to (inclusive).
  */
  QList<const SeismicEvent *> SeismicEventTable::events(const TimeRange& r, const Point& receiver, double maximumSlowness) const
  {
    TRACE;
    QList<const SeismicEvent *> list;
    int n=count();
    DateTime t;
    TimeRange er;
    for(int i=0; i<n; i++) {
      const SeismicEvent * e=SortedVector<DateTime, SeismicEvent>::at(i);
      t=e->time();
      er.setStart(t);
      t.addSeconds(e->position().distanceTo(receiver)*maximumSlowness);
      er.setEnd(t);
      if(r.intersects(er)) {
        list.append(e);
      }
    }
    return list;
  }

  /*!
    Return all events that signal \a sig may have recorded.

    If \a maximumSlowness is not null events fired before the signal start can be selected
    if there are not located too far away and in the past.
  */
  QList<const SeismicEvent *> SeismicEventTable::events(const Signal * sig, double maximumSlowness) const
  {
    TRACE;
    if(maximumSlowness==0.0) {
      return events(sig->timeRange());
    } else {
      QList<const SeismicEvent *> list;
      int n=count();
      for(int i=0; i<n; i++) {
        const SeismicEvent * e=SortedVector<DateTime, SeismicEvent>::at(i);
        DateTime t=e->time();
        t.addSeconds(e->position().distanceTo(sig->receiver())*maximumSlowness);
        if(sig->timeRange().contains(t)) {
          list.append(e);
        }
      }
      return list;
    }
  }

  /*!
    Return all events that occur exactly at \a t.
  */
  QList<const SeismicEvent *> SeismicEventTable::eventsAt(const DateTime& t) const
  {
    TRACE;
    QList<const SeismicEvent *> list;
    int n=count();
    int i=firstIndexOf(t);
    while(i>0 && SortedVector<DateTime, SeismicEvent>::at(i-1)->time()==t) {
      i--;
    }
    if(i>=0) {
      while(i<n && SortedVector<DateTime, SeismicEvent>::at(i)->time()==t) {
        list.append(SortedVector<DateTime, SeismicEvent>::at(i));
        i++;
      }
    }
    return list;
  }

  void SeismicEventTable::clear()
  {
    TRACE;
    for(int i=count()-1; i>=0; i--) {
      emit eventRemoved(at(i));
    }
    SortedVector<DateTime, SeismicEvent>::clear(true);
  }

  void SeismicEventTable::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
  {
    int n=count();
    for(int i=0; i<n; i++) {
      SortedVector<DateTime, SeismicEvent>::at(i)->xml_save(s, context);
    }
  }

  XMLMember SeismicEventTable::xml_member(XML_MEMBER_ARGS)
  {
    Q_UNUSED(attributes)
    Q_UNUSED(context)
    if(tag=="SeismicEvent") {
      SeismicEvent * e=new SeismicEvent;
      append(e);
      emit eventAdded(e);
      return XMLMember(e);
    }
    return XMLMember(XMLMember::Unknown);
  }

  bool SeismicEventTable::xml_polish(XML_POLISH_ARGS)
  {
    Q_UNUSED(context)
    sort();
    _modified=false;
    return true;
  }

} // namespace GeopsyCore

