/***************************************************************************
**
**  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: 2006-03-20
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "SignalGroup.h"
#include "SignalDatabase.h"
#include "GeoSignal.h"
#include "SubSignalPool.h"
#include "SignalGroupFactory.h"

namespace GeopsyCore {

  /*!
    \class SignalGroup
    \brief A group is a list of signals

    A list of ids is temporarily allocated for loading groups from an XML
    buffer.
  */

  const QString SignalGroup::xmlSignalGroupTag="Group";

  REGISTER_SIGNALGROUP(SignalGroup, SignalGroup::xmlSignalGroupTag)

  SignalGroup::SignalGroup(AbstractSignalGroup * parent)
    : AbstractSignalGroup(parent)
  {
    TRACE;
    _ids=nullptr;
  }

  SignalGroup::~SignalGroup()
  {
    TRACE;
    delete _ids;
  }

  bool SignalGroup::xml_inherits(const QString& tagName) const
  {
    if(tagName==xmlSignalGroupTag) {
      return true;
    } else {
      return AbstractSignalGroup::xml_inherits(tagName);
    }
  }

  /*!
    \fn int AbstractSignalGroup::hasOwnSignal() const
    Returns true.
  */

  /*!
    \fn int SignalGroup::signalCount() const
    Returns the number of signals.
  */

  /*!
    \fn bool SignalGroup::directlyContains(const Signal * sig) const
    Returns true if it directly contains signal with \a id.
  */

  /*!
    \fn SubSignalPool AbstractSignalGroup::subPool() const
    Returns the list of signals of this group.
  */

  /*!
    Removes one signal \a sig from group.
  */
  void SignalGroup::removeSignal(Signal * sig)
  {
    TRACE;
    int index=_signals.indexOf(sig);
    if(index<0) return;
    _signals.removeAt(index);
    setModified(true);
  }

  /*!
    Sets contents of the group from \a subpool.
  */
  void SignalGroup::setSignals(const SubSignalPool& subPool)
  {
    TRACE;
    _signals=subPool;
    if(name().isEmpty()) {
      if(_signals.name().count()<40) { // Avoid long names like "Selected from..."
        setName(_signals.name());
      }
    } else {
      _signals.setName(name());
    }
    setModified(true);
  }

  /*!
    Add new signals to group. It has no effect if it is a group folder.
  */
  void SignalGroup::addSignals(const SubSignalPool& subPool)
  {
    TRACE;
    _signals.addSubPool(subPool);
    if(name().isEmpty()) {
      setName(_signals.name());
    } else {
      _signals.setName(name());
    }
    setModified(true);
  }

  void SignalGroup::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    TRACE;
    AbstractSignalGroup::xml_writeProperties(s, context);
    QString str;
    for(SubSignalPool::const_iterator it=_signals.begin(); it!=_signals.end(); ++it) {
      str+=QString::number((*it)->id())+" ";
    }
    writeProperty(s, "ids", str);
  }

  XMLMember SignalGroup::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    if(tag=="ids") return XMLMember(0);
    return AbstractSignalGroup::xml_member(tag, attributes, context)+1;
  }

  bool SignalGroup::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    switch (memberID) {
    case 0: {
        if(!_ids) {   // Multiple <ids> tags are merge together
          _ids=new QVector<int>;
        }
        static const QString sep=" \n\t\r";
        const QChar * ptr=0;
        StringSection f=content.nextField(ptr, sep, true);
        while(f.isValid()) {
          _ids->append(f.toInt());
          f=content.nextField(ptr, sep, true);
        }
        return true;
      }
    default:
      return AbstractSignalGroup::xml_setProperty(memberID-1, tag, attributes, content, context);
    }
  }

  /*!
    \fn int AbstractSignalGroup::hasIds() const
    Returns true if signal IDs are currently been assigned to this group.
    It is true only between xml_setProperty() and the next convertIds().

    Used by AbstractSignalGroup::xml_polishChild() for compatibility.
  */

  /*!
    Loads group from database path of \a db and eventually translate loaded IDs using the translation map
    \a translateID (if not null).

    This function is used for compatibility with databases version<3. For these versions, the groups
    were saved in the same directory as the .sdb file.
  */
  void SignalGroup::openV12(int dbVersion, const QDir& dbPath)
  {
    TRACE;
    QFile f(dbPath.absoluteFilePath(name()+".group"));
    if(f.open(QIODevice::ReadOnly)) {
      if(!_ids) {
        _ids=new QVector<int>;
      }
      QDataStream s(&f);
      if(dbVersion==1) {
        s.setByteOrder(QDataStream::LittleEndian); // To read file from YACL
      }
      int id;
      while(!s.atEnd()) {
        s >> id;
        (*_ids) << id;
      }
    } else {
      App::log(tr("Unable to open %1.group for reading\n")
                  .arg(dbPath.absoluteFilePath(name())));
    }
    setModified(true);
  }

  /*!
    Converts ID values according to \a ids.
  */
  bool SignalGroup::convertIds(const QHash<int, Signal *>& ids)
  {
    TRACE;
    if(_ids) {
      _signals.removeAll();
      for(QVector<int>::iterator it=_ids->begin(); it!=_ids->end(); it++) {
        QHash<int, Signal *>::const_iterator itHash=ids.find(*it);
        if(itHash==ids.end()) {
          App::log(tr("Group '%1': unknow signal ID %2\n").arg(pathName()).arg(*it) );
          return false;
        } else {
          _signals.addSignal(itHash.value());
        }
      }
      delete _ids;
      _ids=0;
      _signals.setName(name());
    }
    return AbstractSignalGroup::convertIds(ids);
  }

  void SignalGroup::setName(const QString& n)
  {
    AbstractSignalGroup::setName(n);
    _signals.setName(n);
  }

  bool SignalGroup::directlyContains(const Signal * sig) const
  {
    if(sig) {
      return _signals.contains(sig);
    } else {
      return true;
    }
  }

} // namespace GeopsyCore
