/***************************************************************************
**
**  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: 2011-05-16
**  Copyright: 2011-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "AbstractSignalGroup.h"
#include "MasterSignalGroup.h"
#include "SubSignalPool.h"
#include "SignalGroupFolder.h"
#include "SignalGroup.h"
#include "SignalResultsFactory.h"
#include "AbstractSignalResults.h"
#include "SignalDatabase.h"
#include "GeoSignal.h"
#include "SignalGroupFactory.h"

namespace GeopsyCore {

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

    Full description of class still missing
  */

  /*!
    Constructor with \a parent group.

    if reportParentAboutToBeChanged() and reportParentChanged() are re-implemented
    \a parent must be null.

    \sa MasterSignalGroup()
  */
  AbstractSignalGroup::AbstractSignalGroup(AbstractSignalGroup * parent)
    : TreeContainer(), _modified(false)
  {
    TRACE;
    _id=-1;
    // report...() functions are re-implemented only in MasterSignalGroup
    // For all other classes, these calls are not virtual, hence
    // a constructor call is fine.
    // Current parent is null, the access to Master is possible only through parent.
    if(parent) {
      parent->reportParentAboutToBeChanged(this, parent);
      TreeContainer::setParent(parent);
      parent->reportParentChanged(this, nullptr);
      setId(-1);
    }
  }

  /*!
    Reparent this group. Modified flag is changed if parent is changed.
    New parent must belong to the same tree as the current parent. To move
    one group from one tree to another, first set parent to null and then
    insert it in the new tree.
  */
  void AbstractSignalGroup::setParent(AbstractSignalGroup * p)
  {
    TRACE;
    AbstractSignalGroup * oldp=parent();
    if(p!=oldp) {
      if(isValidParent(p)) { // Check circular references
        ASSERT(!oldp || oldp->isSameMaster(p));
        if(p) {
          p->reportParentAboutToBeChanged(this, p);
          TreeContainer::setParent(p);
          p->reportParentChanged(this, oldp);
          setModified(true);
          if(id()<0) {
            setId(-1);
          }
        } else {
          oldp->reportParentAboutToBeChanged(this, p);
          TreeContainer::setParent(p);
          oldp->reportParentChanged(this, oldp);
          oldp->setModified(true); // Old parent lost a child, changing modification status of this group has no effect
                                   // on global modification status of the tree because this group is likely to be deleted.
        }
      } else {
        App::log(tr("SignalGroup::setParent: circular reference detected\n") );
      }
    }
  }

  const MasterSignalGroup * AbstractSignalGroup::master() const
  {
    TRACE;
    const AbstractSignalGroup * g=this;
    const AbstractSignalGroup * pg=parent();
    while(pg) {
      g=pg;
      pg=g->parent();
    }
    return static_cast<const MasterSignalGroup *>(g);
  }

  MasterSignalGroup * AbstractSignalGroup::master()
  {
    TRACE;
    AbstractSignalGroup * g=this;
    AbstractSignalGroup * pg=parent();
    while(pg) {
      g=pg;
      pg=g->parent();
    }
    return static_cast<MasterSignalGroup *>(g);
  }

  /*!
    Returns true if \a g1 and \a g2 belongs to the same tree.
  */
  bool AbstractSignalGroup::isSameMaster(const AbstractSignalGroup * o) const
  {
    TRACE;
    if(o) {
      return master()==o->master();
    } else {
      return true;
    }
  }

  /*!
    Sets group's name.
  */
  void AbstractSignalGroup::setName(const QString& n)
  {
    TRACE;
    if(n!=_name) {
      _name=n;
      setModified(true);
    }
  }

  /*!
    Returns group's full path.
  */
  QString AbstractSignalGroup::pathName() const
  {
    TRACE;
    const AbstractSignalGroup * p=parent();
    if(p) {
      return p->pathNameHelper()+"/"+name();
    } else {
      return name();
    }
  }

  /*!
    Returns group's full path.
    For root, returns a null string to avoid "//..." in path names.

    \internal
  */
  QString AbstractSignalGroup::pathNameHelper() const
  {
    TRACE;
    const AbstractSignalGroup * p=parent();
    if(p) {
      return p->pathNameHelper()+"/"+name();
    } else {
      return QString();
    }
  }

  /*!
    Prints a list of all children paths. Root folder is ignored.
  */
  void AbstractSignalGroup::printList(QString prefix) const
  {
    TRACE;
    if(parent()) {
      if(!prefix.isEmpty()) {
        prefix+="/";
      }
      prefix+=name();
      App::log(prefix+"\n");
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      static_cast<AbstractSignalGroup *>(*it)->printList(prefix);
    }
  }

  /*!
    \a minimumCount is the minimum number of signals in a group to be included in the returned list.
  */
  QList<const AbstractSignalGroup *> AbstractSignalGroup::list() const
  {
    QList<const AbstractSignalGroup *> l;
    if(hasOwnSignal() && childrenCount()==0 && stored()) {
      l.append(this);
      return l;
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      l.append(g->list());
    }
    return l;
  }

  /*!
    Sets user comments about this group.
  */
  void AbstractSignalGroup::setComments(const QString& c)
  {
    TRACE;
    if(c!=comments()) {
      _comments=c;
      setModified(true);
    }
  }

  /*!
    Sets group ID.
  */
  void AbstractSignalGroup::setId(int id)
  {
    TRACE;
    _id=-1; // remove this group from the list of ids
    _id=master()->uniqueId(id);
  }

  /*!
    Count the number of groups with \a id. \a newId is modified to return
    the highest value encounter in all children groups.
  */
  int AbstractSignalGroup::countId(int id, int& newId) const
  {
    TRACE;
    int n=0;
    for(const_iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      n+=g->countId(id, newId);
    }
    if(_id==id) {
      n++;
    }
    if(_id>newId) {
      newId=_id;
    }
    return n;
  }

  /*!
    Returns true if this group has been modified since last save/load.
    If not modified, it still returns true if one of its children is modified.
  */
  bool AbstractSignalGroup::isModified() const
  {
    TRACE;
    if(_modified) return true;
    for(const_iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      if(g->isModified()) {
        return true;
      }
    }
    return false;
  }

  /*!
    If \a m is true, it sets the modified flag for this group, else
    modified flags are set to false for this group and all its children.
  */
  void AbstractSignalGroup::setModified(bool m)
  {
    TRACE;
    if(m) {
      _modified=true;
      reportDataChanged(this);
    } else {
      resetModified();
    }
  }

  /*!
    Set modified flag to false for this group and all its children.
    \internal
  */
  void AbstractSignalGroup::resetModified()
  {
    TRACE;
    if(_modified) {
      _modified=false;
      reportDataChanged(this);
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      static_cast<AbstractSignalGroup *>(*it)->resetModified();
    }
  }

  /*!
    Used to display an icon related to the type of group.
    For core groups (SignalGroupFolder and SignalGroup) a null value
    is returned, and standard icon are displayed.

    In any re-implementation, never return a newly allocated icon. Instead
    return a pointer to a static QIcon.
  */
  const QIcon * AbstractSignalGroup::icon() const
  {
    return nullptr;
  }

  void AbstractSignalGroup::groupPath(QString& name, QString& subName) const
  {
    int sepIndex=name.indexOf("/");
    switch(sepIndex) {
    case -1:
      break;
    case 0: // First character is '/'
      name=name.mid(1);
      sepIndex=name.indexOf("/");
      if(sepIndex>-1) {
        subName=name.mid(sepIndex+1);
        name=name.left(sepIndex);
      }
      break;
    default:
      subName=name.mid(sepIndex+1);
      name=name.left(sepIndex);
      break;
    }
  }

  /*!
    Returns all groups whose name matches \a name.
  */
  QList<const AbstractSignalGroup *> AbstractSignalGroup::find(const QRegExp& name) const
  {
    TRACE;
    QList<const AbstractSignalGroup *> l;
    for(const_iterator it=begin(); it!=end(); ++it) {
      // With Qt 5.6 and gcc 4.4, writing everything in a single line does not compile
      // Apparently confusion with the non-const version of this function.
      const AbstractSignalGroup * g=static_cast<const AbstractSignalGroup *>(*it);
      l.append(g->find(name));
    }
    if(this->name().contains(name)) {
      l.append(this);
    }
    return l;
  }

  /*!
    Returns all groups whose name matches \a name.
  */
  QList<AbstractSignalGroup *> AbstractSignalGroup::find(const QRegExp& name)
  {
    TRACE;
    QList<AbstractSignalGroup *> l;
    for(iterator it=begin(); it!=end(); ++it) {
      l.append(static_cast<AbstractSignalGroup *>(*it)->find(name));
    }
    if(this->name().contains(name)) {
      l.append(this);
    }
    return l;
  }

  /*!
    Returns group with \a name if any in the children list. Name may contains '/' to access sub-groups.
    \a name may start with '/' but all group names are always absolute, there is not relative paths.
  */
  const AbstractSignalGroup * AbstractSignalGroup::find(QString name) const
  {
    TRACE;
    QString subName;
    groupPath(name, subName);
    for(const_iterator it=begin(); it!=end(); ++it) {
      const AbstractSignalGroup * g=static_cast<const AbstractSignalGroup *>(*it);
      if(g->name()==name) {
        if(subName.isEmpty())
          return g;
        else
          return g->find(subName);
      }
    }
    return nullptr;
  }

  /*!
    Returns group with \a name if any in the children list. Name may contains '/' to access sub-groups.
    \a name may start with '/' but all group names are always absolute, there is not relative paths.
  */
  AbstractSignalGroup * AbstractSignalGroup::find(QString name)
  {
    TRACE;
    QString subName;
    groupPath(name, subName);
    for(iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      if(g->name()==name) {
        if(subName.isEmpty())
          return g;
        else
          return g->find(subName);
      }
    }
    return nullptr;
  }

  /*!
    Returns group with \a id if any in the children list.
  */
  const AbstractSignalGroup * AbstractSignalGroup::find(int id) const
  {
    TRACE;
    if(_id==id) {
      return this;
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      const AbstractSignalGroup * g=static_cast<const AbstractSignalGroup *>(*it)->find(id);
      if(g) {
        return g;
      }
    }
    return nullptr;
  }

  /*!
    Returns group with \a id if any in the children list.
  */
  AbstractSignalGroup * AbstractSignalGroup::find(int id)
  {
    TRACE;
    if(_id==id) {
      return this;
    }
    for(iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it)->find(id);
      if(g) {
        return g;
      }
    }
    return nullptr;
  }

  /*!
    Returns the list of groups in the children list which contains \a sig.

    Currently this is used only for reporting to user group ownership of
    temporary signals.

    If \a sig is null, the list of all groups containing directly signal is returned.
  */
  QList<const AbstractSignalGroup *> AbstractSignalGroup::find(const Signal * sig) const
  {
    TRACE;
    QList<const AbstractSignalGroup *> groups;
    find(groups, sig);
    return groups;
  }

  /*!
    Recursive version of find(const Signal * sig).
    \internal
  */
  void AbstractSignalGroup::find(QList<const AbstractSignalGroup *>& groups, const Signal * sig) const
  {
    TRACE;
    if(directlyContains(sig)) {
      groups.append(this);
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      const AbstractSignalGroup * g=static_cast<const AbstractSignalGroup *>(*it);
      g->find(groups, sig);
    }
  }


  /*!
    \fn bool AbstractSignalGroup::hasOwnSignal() const
    Returns true if there are signals owned only by this group.
  */

  /*!
    Returns the number of signals contained in all its children.
  */
  int AbstractSignalGroup::signalCount() const
  {
    TRACE;
    int nSigs=0;
    for(const_iterator it=begin(); it!=end(); ++it) {
      nSigs+=static_cast<AbstractSignalGroup *>(*it)->signalCount();
    }
    return nSigs;
  }

  /*!
    Returns true if this group contains signal \a sig.

    \sa directlyContains()
  */
  bool AbstractSignalGroup::contains(const Signal * sig) const
  {
    TRACE;
    if(directlyContains(sig)) {
      return true;
    }
    for(const_iterator it=begin(); it!=end(); ++it) {
      if(static_cast<AbstractSignalGroup *>(*it)->contains(sig)) {
        return true;
      }
    }
    return false;
  }

  /*!
    Returns true if this group directly contains signal \a sig.
    For instance, a folder containing a group with signal \a sig does not directly contain
    the signal.
    \sa contains(), find()
  */
  bool AbstractSignalGroup::directlyContains(const Signal * sig) const
  {
    TRACE;
    Q_UNUSED(sig)
    return false;
  }

  /*!
    Returns the list of signals contained in all its children.
  */
  SubSignalPool AbstractSignalGroup::subPool() const
  {
    TRACE;
    SubSignalPool subpool;
    for(const_iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      subpool.addSubPool(g->subPool());
    }
    subpool.setName(name());
    return subpool;
  }

  /*!
    Removes one signal \a sig from group.
  */
  void AbstractSignalGroup::removeSignal(Signal * sig)
  {
    TRACE;
    for(const_iterator it=begin();it!=end();++it) {
      static_cast<AbstractSignalGroup *>(*it)->removeSignal(sig);
    }
  }

  /*!
    Reports a parent change to the top of the tree.
    To exploit this feature, you must have a MasterSignalGroup at the top of the tree.

    reportParentAboutToBeChanged() and reportParentChanged() must be called one after the
    other with the same group and \a g cannot be this.

    \internal
  */
  void AbstractSignalGroup::reportParentAboutToBeChanged(AbstractSignalGroup * g, AbstractSignalGroup * newParent) const
  {
    // If current parent is null nothing is reported to Master group.
    // There must be at least one of new and old parent that is not null.
    ASSERT(g!=this);
    const AbstractSignalGroup * p=parent();
    if(p) {
      p->reportParentAboutToBeChanged(g, newParent);
    }
  }

  /*!
    Reports a parent change to the top of the tree.
    To exploit this feature, you must have a MasterSignalGroup at the top of the tree.

    reportParentAboutToBeChanged() and reportParentChanged() must be called one after the
    other with the same group and \a g cannot be this.

    \internal
  */
  void AbstractSignalGroup::reportParentChanged(AbstractSignalGroup * g, AbstractSignalGroup * oldParent) const
  {
    // If new parent is null nothing is reported to Master group.
    // There must be at least one of new and old parent that is not null.
    ASSERT(g!=this);
    const AbstractSignalGroup * p=parent();
    if(p) {
      p->reportParentChanged(g, oldParent);
    }
  }

  /*!
    Reports a data modification to the top of the tree.
    To exploit this feature, you must have a MasterSignalGroup at the top of the tree.

    \internal
  */
  void AbstractSignalGroup::reportDataChanged(AbstractSignalGroup * g) const
  {
    const AbstractSignalGroup * p=parent();
    if(p) {
      p->reportDataChanged(g);
    }
  }

  /*!
    Converts id values according to \a ids. This is necessary after loading
    groups from an XML stream.
  */
  bool AbstractSignalGroup::convertIds(const QHash<int, Signal *>& ids)
  {
    TRACE;
    for(iterator it=begin(); it!=end(); ++it) {
      if(!static_cast<AbstractSignalGroup *>(*it)->convertIds(ids)) {
        return false;
      }
    }
    return true;
  }

  void AbstractSignalGroup::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    TRACE;
    TreeContainer::xml_writeProperties(s, context);
    writeProperty(s, "id", id());
    writeProperty(s, "name", name());
    writeProperty(s, "comments", comments());
  }

  void AbstractSignalGroup::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
  {
    TRACE;
    Q_UNUSED(context)
    for(const_iterator it=begin(); it!=end(); ++it) {
      AbstractSignalGroup * g=static_cast<AbstractSignalGroup *>(*it);
      if(g->stored()) {
        g->xml_save(s, context);
      }
    }
  }

  XMLMember AbstractSignalGroup::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    Q_UNUSED(attributes)
    if(tag.size()>0) {
      switch (tag[0].unicode()) {
      case 'c':
        if(tag=="comments") return XMLMember(1);
        break;
      case 'n':
        if(tag=="name") return XMLMember(0);
        break;
      case 'i':
        if(tag=="id") return XMLMember(2);
        break;
      case 'f':
        if(tag=="folder") return XMLMember(XMLMember::Skip);         // Kept for compatibility
        break;
      default: {
          AbstractSignalGroup * g=SignalGroupFactory::instance()->create(tag.toString());
          if(g) {
            g->setParent(this);
            return XMLMember(g);
          }
        }
        break;
      }
    }
    // Custom group types
    int id=SignalResultsFactory::instance()->id(tag.toString());
    if(id>-1) {
      AbstractSignalResults * r=SignalResultsFactory::instance()->create(id);
      r->TreeContainer::setParent(this); // Avoid modified flag
      return XMLMember(r);
    } else {
      App::log(tr("Unknown result type '%1', probably a missing plugin.\n"
                  "You may loose some results if you save the database!\n").arg(tag.toString()));
      SignalDatabase * db=static_cast<SignalDatabase *>(context);
      db->setName(QString()); // Forces saveAs() and avoid overwriting
      return XMLMember(new SignalGroup(this));
    }
  }

  bool AbstractSignalGroup::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    switch (memberID) {
    case 0:
      setName(content.toString());
      return true;
    case 1:
      setComments(content.toString());
      return true;
    case 2:
      setId(content.toInt());
      return true;
    default:
      return TreeContainer::xml_setProperty(memberID-3, tag, attributes, content, context);
    }
  }

  void AbstractSignalGroup::xml_polishChild(XML_POLISHCHILD_ARGS)
  {
    TRACE;
    SignalDatabase * db=static_cast<SignalDatabase *>(context);
    if(db->version()<5) { // Kept for compatibility
      SignalGroup * g=static_cast<SignalGroup *>(child);
      if(!g->hasIds()) { // A group with obsolete folder property set to true and no ids
        SignalGroupFolder * f=new SignalGroupFolder(g->parent());
        f->setName(g->name());
        f->setComments(g->comments());
        f->takeChildren(g);
        g->setParent(nullptr);
        delete g;
      }
    }
  }

} // namespace GeopsyCore
