/***************************************************************************
**
**  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: 2010-07-23
**  Copyright: 2010-2019
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "TimePick.h"
#include "MetaDataFactory.h"

namespace GeopsyCore {

  /*!
    \class TimePick TimePick.h
    \brief Time pick vector for Signal

    Optional meta data
  */

  QMap<QString, int> TimePick::_name2index;
  QList<TimePick::Name> TimePick::_names;

  REGISTER_METADATA(TimePick)

  QStringList TimePick::subNames() const
  {
    QStringList names;
    names << xmlTimePickTag;
    return names;
  }

  QString TimePick::name(int, const QString& index) const
  {
    return xmlTimePickTag+"[\""+index+"\"]";
  }

  QString TimePick::baseName(int) const
  {
    return xmlTimePickTag;
  }

  QString TimePick::title(int, const QString& index) const
  {
    return tr("Pick[\"%1\"]").arg(index);
  }

  bool TimePick::hasIndex(int, const QString& index) const
  {
    if(registered(index)) {
      int i=pickIndex(index);
      return _values.contains(i);
    } else {
      return false;
    }
  }

  bool TimePick::operator==(const MetaData& o) const
  {
    const TimePick& ro=reinterpret_cast<const TimePick&>(o);
    return _values==ro._values;
  }

  void TimePick::operator=(const MetaData& o)
  {
    const TimePick& ro=reinterpret_cast<const TimePick&>(o);
    _values=ro._values;
  }

  QVariant TimePick::data(int, const QString& index) const
  {
    if(registered(index)) {
      int i=pickIndex(index);
      return _values[i].toString(DateTime::defaultUserFormat);
    } else {
      return 0.0;
    }
  }

  bool TimePick::setData(int, const QString& index, const QVariant& val)
  {
    TRACE;
    if(!index.isEmpty()) {
      int i=pickIndex(index);
      return _values[i].fromString(val.toString(), DateTime::defaultUserFormat);
    } else {
      return false;
    }
  }

  int TimePick::compare(int, const QString& index, const MetaData &o) const
  {
    TRACE;
    const TimePick& otp=static_cast<const TimePick&>(o);
    if(registered(index)) {
      int i=pickIndex(index);
      if(_values[i]<otp._values[i]) {
        return -1;
      } else if(_values[i]>otp._values[i]) {
        return 1;
      } else {
        return 0;
      }
    } else {
      return 0;
    }
  }

  QString TimePick::toString(const QString& index) const
  {
    TRACE;
    if(registered(index)) {
      int i=pickIndex(index);
      QMap<int, DateTime>::const_iterator it=_values.find(i);
      if(it!=_values.end()) {
        return it.value().toString(DateTime::defaultFormat);
      }
    }
    return "0.0";
  }

  bool TimePick::fromString(const QString& index, QString string)
  {
    TRACE;
    int i=pickIndex(index);
    return _values[i].fromString(string, DateTime::defaultFormat);
  }

  DateTime TimePick::value(const QString& name) const
  {
    TRACE;
    int index=pickIndex(name);
    QMap<int, DateTime>::const_iterator it=_values.find(index);
    if(it!=_values.end()) {
      return it.value();
    } else {
      return DateTime();
    }
  }

  void TimePick::removeValue(const QString& name)
  {
    TRACE;
    int index=pickIndex(name);
    if(pickLocked(index)) {
      App::log(tr("WARNING: trying to remove a locked time pick ('%1').\n").arg(name) );
    } else {
      _values.remove(index);
    }
  }

  void TimePick::setValue(const QString& name, DateTime t)
  {
    TRACE;
    int index=pickIndex(name);
    if(pickLocked(index)) {
      App::log(tr("WARNING: trying to modify a locked time pick ('%1').\n").arg(name) );
    } else {
      _values[index]=t;
    }
  }

  /*!
    Returns the list of available pick names.
  */
  QStringList TimePick::names() const
  {
    TRACE;
    QStringList l;
    for(QMap<int, DateTime>::const_iterator it=_values.begin(); it!=_values.end(); it++) {
      l.append(_names[it.key()].value());
    }
    return l;
  }

  /*!
    \internal
  */
  TimePick::Name& TimePick::pickName(int index)
  {
    TRACE;
    ASSERT(index<_names.count() && index>=0);
    return _names[index];
  }

  /*!
    \internal
  */
  int TimePick::pickIndex(const QString& name)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(name);
    if(it!=_name2index.end()) {
      return it.value();
    } else {
      int index=_names.count();
      _name2index.insert(name, index);
      _names.append(name);
      ASSERT(_names.count()==_name2index.count());
      return index;
    }
  }

  bool TimePick::pickLocked(int index)
  {
    TRACE;
    return _names.at(index).locked();
  }

  bool TimePick::pickLocked(const QString& name)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(name);
    if(it!=_name2index.end()) {
      return pickLocked(it.value());
    } else {
      return true;
    }
  }

  void TimePick::setPickLocked(const QString& name, bool l)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(name);
    if(it!=_name2index.end()) {
      _names[it.value()].setLocked(l);
    }
  }

  /*!
    Returns the next pick name after \a name in alphabetical order.
  */
  QString TimePick::nextUnlockedPick(const QString& name)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(name);
    if(it!=_name2index.end()) {
      it++;
      while(it!=_name2index.end()) {
        if(!pickLocked(it.value())) {
          return it.key();
        }
        it++;
      }
    } else if(!name.isEmpty()) {
      pickIndex(name); // force registration of this new name
      return name;
    }
    // Loop from beginning
    it=_name2index.begin();
    while(it!=_name2index.end()) {
      if(!pickLocked(it.value())) {
        return it.key();
      }
      it++;
    }
    // If provided name was not locked, the function should have already returned.
    return QString();
  }

  /*!
    Returns the previous pick name before \a name in alphabetical order.
  */
  QString TimePick::previousUnlockedPick(const QString& name)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(name);
    QMap<QString, int>::const_iterator rbegin=--_name2index.begin();
    if(it!=_name2index.end()) {
      it--;
      while(it!=rbegin) {
        if(!pickLocked(it.value())) {
          return it.key();
        }
        it--;
      }
    } else if(!name.isEmpty()) {
      pickIndex(name); // force registration of this new name
      return name;
    }
    // Loop from the end
    it=--_name2index.end();
    while(it!=rbegin) {
      if(!pickLocked(it.value())) {
        return it.key();
      }
      it--;
    }
    // If provided name was not locked, the function should have already returned.
    return QString();
  }

  void TimePick::renamePick(const QString& oldName, const QString& newName)
  {
    TRACE;
    QMap<QString, int>::const_iterator it=_name2index.find(oldName);
    if(it!=_name2index.end()) {
      int index=it.value();
      _name2index.remove(oldName);
      _name2index.insert(newName, index);
      _names[index]=newName;
    } else {
      App::log(tr("No pick named '%1', rename skipped.\n").arg(oldName) );
    }
  }

  QStringList TimePick::unlockedNames()
  {
    TRACE;
    QStringList names;
    for(QMap<QString, int>::const_iterator it=_name2index.begin(); it!=_name2index.end(); it++) {
      if(!pickLocked(it.value())) {
        names.append(it.key());
      }
    }
    return names;
  }

  void TimePick::writeProperties(XMLStream& s, const XMLSaveAttributes& attributes) const
  {
    TRACE;
    static const QString key("index");
    XMLSaveAttributes att(attributes);
    QString& value=att.add(key);
    for(QMap<int, DateTime>::const_iterator it=_values.begin(); it!=_values.end(); it++) {
      value=_names.at(it.key()).value();
      writeProperty(s, xml_tagName(), att, it.value().toString(DateTime::defaultFormat));
    }
  }

  // TODO: fix implementation with QJSEngine
#if 0
  QScriptValue TimePick::toScriptValue(QScriptEngine * engine, const TimePickSignal& sig)
  {
    const TimePick * t=static_cast<const TimePick *>(sig.scriptSignal()->signal()->metaData(_id));
    QScriptValue a=engine->newArray();
    for(QMap<int, double>::const_iterator it=t->_values.begin(); it!=t->_values.end(); it++) {
      a.setProperty(pickName(it.key()), it.value());
    }
    return a;
  }

  void TimePick::fromScriptValue(const QScriptValue &value, TimePickSignal& sig)
  {
    TimePick * t=static_cast<TimePick *>(sig.scriptSignal()->signal()->metaData(_id));
    QScriptValueIterator it(value);
    while (it.hasNext()) {
      it.next();
      App::log("set timePick[" << it.name() << "]" << endl;
      t->setValue(it.name(), it.value().toNumber());
    }
  }

  void TimePick::registerScriptTypes(QScriptEngine * engine)
  {
    qScriptRegisterMetaType<TimePickSignal>(engine, toScriptValue, fromScriptValue);
  }

  void TimePick::addScriptProperties(SignalHeaderObject * sig)
  {
    sig->setProperty("timePick", QVariant::fromValue(TimePickSignal(sig)));
  }

  class TimePickObject: public QObject, public QScriptable
  {
    Q_OBJECT
  public:
    TimePickObject(QObject * parent=nullptr);

    double value(const QString& name) const {thisTimePick()->value(name);}
    void setValue(const QString& name, double v);
  signals:
    void dataChanged(TimePick * tp);
  private:
    inline TimePick * thisTimePick() const;
  }

  inline TimePick * TimePickObject::thisTimePick() const
  {
    return qscriptvalue_cast<TimePick *>(thisObject().data());
  }

  void TimePickObject::setValue(const QString& name, double v)
  {
    TRACE;
    TimePick * tp=thisTimePick();
    tp->setValue(v);
    emit(dataChanged(tp));
  }

  class TimePickClass: public QScriptClass
  {
  public:
    TimePickClass(QScriptEngine * engine) : QScriptClass(engine) {}

    virtual QString	name() const {return "TimePick";}
    virtual QueryFlags	queryProperty(const QScriptValue & object, const QScriptString & name, QueryFlags flags, uint * id);
    virtual QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
    virtual QScriptValue setProperty(const QScriptValue& object, const QScriptString& name, uint id);
  };

  QueryFlags	TimePickClass::queryProperty(const QScriptValue & object, const QScriptString & name, QueryFlags flags, uint * id)
  {
    qscriptvalue_cast<Signal *>(thisObject().data());
  }

  QScriptValue TimePickClass::property(const QScriptValue& object, const QScriptString& name, uint id)
  {

  }

  QScriptValue TimePickClass::setProperty(const QScriptValue& object, const QScriptString& name, uint id)
  {

  }

  bool TimePickObject::event(QEvent * e)
  {
    if(e->type()==QEvent::DynamicPropertyChange) {
      QDynamicPropertyChangeEvent * mye=static_cast<QDynamicPropertyChangeEvent *>(e);
      TimePick * t=static_cast<TimePick *>(_sig->signal()->metaData(_id));
      if()
      mye->propertyName();
    } else {
      return QObject::event(e);
    }
  }
#endif

} // namespace GeopsyCore
