/***************************************************************************
**
**  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: 2004-04-19
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "TimeRangeParameters.h"
#include "StationSignals.h"
#include "GeoSignal.h"
#include "TimePick.h"

namespace GeopsyCore {

  const QString TimeRangeParameters::xmlTimeRangeParametersTag="TimeRangeParameters";

  /*!
    \class TimeRangeParameters TimeRangeParameters.h
    \brief Parameters to define time limits

    Defines time limits according to signal properties. It can be relative to t0, end, time picks or absolute.
  */

  // Automatic registration of TimeRangeParameters upon loading of the library
  REGISTER_METATYPE(TimeRangeParameters)

  TimeRangeParameters::TimeRangeParameters()
    : TimeRange(DateTime::minimumTime, DateTime::maximumTime),
      AbstractParameters(),
      XMLClass()
  {
    TRACE;
    _deltaT=0.0;
    _startType=Signal;
    _endType=Signal;
  }

  TimeRangeParameters::TimeRangeParameters(const TimeRangeParameters& o)
    : TimeRange(o),
      AbstractParameters(o),
      XMLClass()
  {
    TRACE;
    _startPick=o._startPick;
    _endPick=o._endPick;
    _deltaT=o._deltaT;
    _startType=o._startType;
    _endType=o._endType;
    _reference=o._reference;
  }

  QString TimeRangeParameters::type2String(Type t)
  {
    switch(t) {
    case Absolute:
      break;
    case Delta:
      return "Delta";
    case Signal:
      return "Signal";
    case Pick:
      return "Pick";
    }
    return "Absolute";
  }

  TimeRangeParameters::Type TimeRangeParameters::string2Type(QString t)
  {
    if(t.count()<1) return Signal;
    t=t.toLower();
    switch(t[0].unicode()) {
    case 'a':
      return Absolute;
    case 'd':
      return Delta;
    default:
      return Signal;
    case 'p':
      return Pick;
    case '0':           // Compatibility
      return Absolute;
    case '1':           // Compatibility
      return Signal;
    case '2':           // Compatibility
      return Delta;
    case '3':           // Compatibility
      return Pick;
    }
  }

  TimeRange TimeRangeParameters::absoluteRange(const SubSignalPool * subPool) const
  {
    return TimeRange(absoluteStart(subPool), absoluteEnd(subPool));
  }

  TimeRange TimeRangeParameters::absoluteRange(const StationSignals * sigs) const
  {
    DateTime t1, t2;
    if(_startType==Delta && _endType!=Delta) {
      t2=absoluteEnd(sigs->lastValidSignal());
      t1=t2.shifted(-_deltaT);
    } else if(_endType==Delta && _startType!=Delta) {
      t1=absoluteStart(sigs->firstValidSignal());
      t2=t1.shifted(_deltaT);
    } else {
      t1=absoluteStart(sigs->firstValidSignal());
      t2=absoluteEnd(sigs->lastValidSignal());
    }
    return TimeRange(t1, t2);
  }

  TimeRange TimeRangeParameters::absoluteRange(const GeopsyCore::Signal * sig) const
  {
    return TimeRange(absoluteStart(sig), absoluteEnd(sig));
  }

  void TimeRangeParameters::xml_attributes(XML_ATTRIBUTES_ARGS) const
  {
    Q_UNUSED(context)
    static const QString keys[]={"startType", "endType", "startPick", "endPick", "deltaT", "start", "end", "reference"};
    attributes.add(keys[0], startTypeString());
    attributes.add(keys[1], endTypeString());
    attributes.add(keys[2], startPick());
    attributes.add(keys[3], endPick());
    attributes.add(keys[4], QString::number(deltaT()));
    if(_startType==Absolute) {
      attributes.add(keys[5], start().toString());
    }
    if(_endType==Absolute) {
      attributes.add(keys[6], end().toString());
    }
    attributes.add(keys[7], reference());
  }

  bool TimeRangeParameters::xml_setAttributes(XML_SETATTRIBUTES_ARGS)
  {
    Q_UNUSED(context)
    bool ok=true;
    XMLRestoreAttributeIterator it;
    for(it=attributes.begin(); it!=attributes.end();it++) {
      const StringSection& att=it.key();
      switch(att[0].unicode()) {
      case 's':
        if(att.size()>5) {
          if(att[5].unicode()=='T') {
            setStartType(it.value().toString());
          } else {
            setStartPick(it.value().toString());
          }
        } else {
          setStart(it.value().toTime(DateTime::defaultFormat, &ok));
        }
        break;
      case 'e':
        if(att.size()>5) {
          if(att[5].unicode()=='T') {
            setEndType(it.value().toString());
          } else {
            setEndPick(it.value().toString());
          }
        } else {
          setStart(it.value().toTime(DateTime::defaultFormat, &ok));
        }
        break;
      case 'd':
        setDeltaT(it.value().toDouble(&ok));
        break;
      case 'r':
        setReference(it.value().toString());
        break;
      default:
        App::log(tr("Bad attribute: %1\n").arg(att.toString()) );
        return false;
      }
    }
    return ok;
  }

  DateTime TimeRangeParameters::absoluteStart(const GeopsyCore::Signal * sig) const
  {
    TRACE;
    switch(_startType) {
    case Signal:
      if(sig) {
        return sig->startTime();
      }
      break;
    case Delta:
      if(_endType==Delta) {
        return DateTime::null;
      } else {
        return absoluteEnd(sig).shifted(-_deltaT);
      }
    case Pick:
      if(sig) {
        return sig->metaData<TimePick>().value(_startPick);
      }
      break;
    case Absolute:
      break;
    }
    return start();
  }

  DateTime TimeRangeParameters::absoluteEnd(const GeopsyCore::Signal * sig) const
  {
    TRACE;
    switch(_endType) {
    case Signal:
      if(sig) {
        return sig->endTime();
      }
      break;
    case Delta:
      if(_startType==Delta) {
        return DateTime::null;
      } else {
        return absoluteStart(sig).shifted(_deltaT);
      }
    case Pick:
      if(sig) {
        return sig->metaData<TimePick>().value(_endPick);
      }
      break;
    case Absolute:
      break;
    }
    return end();
  }

  DateTime TimeRangeParameters::absoluteStart(const SubSignalPool * subPool) const
  {
    TRACE;
    switch(_startType) {
    case Signal:
      if(subPool) {
        TimeRange r=subPool->timeRange();
        return r.start();
      }
      break;
    case Delta:
      if(_endType==Delta) {
        return DateTime::null;
      } else {
        return absoluteEnd(subPool).shifted(-_deltaT);
      }
    case Pick:
      if(subPool) {
        return subPool->firstTimePick(_startPick);
      }
      break;
    case Absolute:
      break;
    }
    return start();
  }

  DateTime TimeRangeParameters::absoluteEnd(const SubSignalPool * subPool) const
  {
    TRACE;
    switch(_endType) {
    case Signal:
      if(subPool) {
        TimeRange r=subPool->timeRange();
        return r.end();
      }
      break;
    case Delta:
      if(_startType==Delta) {
        return DateTime::null;
      } else {
        return absoluteStart(subPool).shifted(_deltaT);
      }
    case Pick:
      if(subPool) {
        return subPool->lastTimePick(_endPick);
      }
      break;
    case Absolute:
      break;
    }
    return end();
  }

  QString TimeRangeParameters::toString(PARAMETERS_TOSTRING_ARGS_IMPL) const
  {
    TRACE;
    QString log;
    QLocale locale(QLocale::c()); // avoid ',' in stored files
    log+="# TYPEs:\n"
         "#   - Signal: from the start or to the end of signal (TEXT are useless)\n"
         "#   - Delta: a fixed duration counted from the start or the end (e.g. TEXT=1h).\n"
         "#   - Pick: from or to a time pick (TEXT=time pick name).\n"
         "#   - Absolute: from or to a fixed time (e.g. TEXT=20170314115338.00)\n";
    log+=prefix+"FROM_TIME_TYPE"+suffix+"="+startTypeString()+"\n";
    switch(_startType) {
    case Pick:
      log+=prefix+"FROM_TIME_TEXT"+suffix+"="+_startPick+"\n";
      break;
    case Delta:
      log+=prefix+"FROM_TIME_TEXT"+suffix+"="+Number::secondsToDuration(_deltaT, -1, &locale)+"\n";
      break;
    case Absolute:
      log+=prefix+"FROM_TIME_TEXT"+suffix+"="+start().toString()+"\n";
      break;
    default:
      log+=prefix+"FROM_TIME_TEXT"+suffix+"=0s\n"; // Useless but just to inform user that this variable exists
      break;
    }
    log+="# TYPEs: Signal, Delta, Absolute\n";
    log+=prefix+"TO_TIME_TYPE"+suffix+"="+endTypeString()+"\n";
    switch(_endType) {
    case Pick:
      log+=prefix+"TO_TIME_TEXT"+suffix+"="+_endPick+"\n";
      break;
    case Delta:
      log+=prefix+"TO_TIME_TEXT"+suffix+"="+Number::secondsToDuration(_deltaT, -1, &locale)+"\n";
      break;
    case Absolute:
      log+=prefix+"TO_TIME_TEXT"+suffix+"="+end().toString()+"\n";
      break;
    default:
      log+=prefix+"TO_TIME_TEXT"+suffix+"=0s\n"; // Useless but just to inform user that this variable exists
      break;
    }
    log+=prefix+"REFERENCE="+reference()+"\n";
    return log;
  }

  int TimeRangeParameters::keywordCount(PARAMETERS_KEYWORDCOUNT_ARGS) const
  {
    return 6+AbstractParameters::keywordCount();
  }

  void TimeRangeParameters::collectKeywords(PARAMETERS_COLLECTKEYWORDS_ARGS)
  {
    TRACE;
    int baseIndex=AbstractParameters::keywordCount();
    keywords.add(prefix+"FROM_TIME_TYPE"+suffix, this, baseIndex);
    keywords.add(prefix+"FROM_TIME_TEXT"+suffix, this, baseIndex+1);
    keywords.add(prefix+"TO_TIME_TYPE"+suffix, this, baseIndex+2);
    keywords.add(prefix+"TO_TIME_TEXT"+suffix, this, baseIndex+3);
    keywords.add(prefix+"REFERENCE"+suffix, this, baseIndex+4);
    keywords.add(prefix+"USE_FIRST_PICK_ONLY"+suffix, this, baseIndex+5);  // kept for compatibility
  }

  bool TimeRangeParameters::setValue(PARAMETERS_SETVALUE_ARGS)
  {
    TRACE;
    QLocale locale(QLocale::c()); // avoid ',' in stored files
    bool ok=true;
    switch(index-AbstractParameters::keywordCount()) {
    case 0:
      setStartType(value);
      return true;
    case 1:
      switch(_startType) {
      case Pick:
        _startPick=value;
        break;
      case Delta:
        _deltaT=Number::durationToSeconds(value, ok, &locale);
        break;
      case Absolute: {
          DateTime t;
          ok=t.fromString(value);
          if(ok) {
            setStart(t);
          }
        }
        break;
      default:
        break;
      }
      return ok;
    case 2:
      setEndType(value);
      return true;
    case 3:
      switch(_endType) {
      case Pick:
        _endPick=value;
        break;
      case Delta:
        _deltaT=Number::durationToSeconds(value, ok, &locale);
        break;
      case Absolute: {
          DateTime t;
          ok=t.fromString(value);
          if(ok) {
            setEnd(t);
          }
        }
        break;
      default:
        break;
      }
      return ok;
    case 4:
      _reference=value;
      return true;
    case 5:       // kept for compatibility
      obsoleteKeyword(keywords, 5);
      return true;
    default:
      break;
    }
    return AbstractParameters::setValue(index, value, unit, keywords);
  }

  QString TimeRangeParameters::toShortString() const
  {
    TRACE;
    QString args="[";
    switch(startType()) {
    case Absolute:
      args+="abs ";
      args+=start().toString();
      break;
    case Signal:
      args+="signal";
      break;
    case Delta:
      args+="delta ";
      args+=Number::secondsToDuration(deltaT());
      break;
    case Pick:
      args+="pick ";
      args+=startPick();
      break;
    }
    args+="; ";
    switch(endType()) {
    case Absolute:
      args+="abs ";
      args+=end().toString();
      break;
    case Signal:
      args+="signal";
      break;
    case Delta:
      args+="delta ";
      args+=Number::secondsToDuration(deltaT());
      break;
    case Pick:
      args+="pick ";
      args+=endPick();
      break;
    }
    args+="]";
    return args;
  }

  void TimeRangeParameters::fromShortString(QString s)
  {
    TRACE;
    // Default value
    setStartType(Signal);
    setEndType(Signal);
    // Remove useless spaces
    s=s.trimmed();
    // Remove brackets at both ends
    if(s.startsWith("[")) {
      s=s.mid(1, -1);
    }
    if(s.endsWith("]")) {
      s.chop(1);
    }
    QString from=s.section(';', 0, 0).trimmed();
    QString to=s.section(';', 1, 1).trimmed();
    bool ok=true;
    QLocale locale(QLocale::c());
    if(from.startsWith("abs ")) {
      setStartType(Absolute);
      DateTime t;
      ok=t.fromString(from.mid(4, -1));
      setStart(t);
    } else if(from.startsWith("delta ")) {
      setStartType(Delta);
      setDeltaT(Number::durationToSeconds(from.mid(6, -1), ok, &locale));
    } else if(from.startsWith("pick ")) {
      setStartType(Pick);
      setStartPick(from.mid(5, -1));
    }
    if(!ok) {
      setStartType(Signal);
      setEndType(Signal);
    }
    ok=true;
    if(to.startsWith("abs ")) {
      setEndType(Absolute);
      DateTime t;
      ok=t.fromString(to.mid(4, -1));
      setEnd(t);
    } else if(to.startsWith("delta ")) {
      setEndType(Delta);
      setDeltaT(Number::durationToSeconds(to.mid(6, -1), ok, &locale));
    } else if(to.startsWith("pick ")) {
      setEndType(Pick);
      setEndPick(to.mid(5, -1));
    }
    if(!ok) {
      setEndType(Signal);
    }
  }

} // namespace GeopsyCore
