/***************************************************************************
**
**  This file is part of GeopsyGui.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-08-29
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyCore.h>
#include "SignalGroupItem.h"

namespace GeopsyGui {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
SignalGroupItem::SignalGroupItem(QObject * parent)
    : QAbstractItemModel(parent)
{
  TRACE;
  _db=nullptr;
}

void SignalGroupItem::setDatabase(SignalDatabase * db)
{
  TRACE;
  beginResetModel();
  if(_db!=db) {
    _db=db;
    MasterSignalGroup * m=_db->masterGroup();
    connect(m, SIGNAL(destroyed()), this, SLOT(databaseDestroyed()));
    connect(m, SIGNAL(dataChanged(AbstractSignalGroup*)),
            this, SLOT(dataChanged(AbstractSignalGroup*)));
    connect(m, SIGNAL(parentAboutToBeChanged(AbstractSignalGroup*,AbstractSignalGroup*)),
            this, SLOT(parentAboutToBeChanged(AbstractSignalGroup*,AbstractSignalGroup*)));
    connect(m, SIGNAL(parentChanged(AbstractSignalGroup*,AbstractSignalGroup*)),
            this, SLOT(parentChanged(AbstractSignalGroup*,AbstractSignalGroup*)));
  }
  endResetModel();
}

void SignalGroupItem::databaseDestroyed()
{
  TRACE;
  beginResetModel();
  _db=nullptr;
  endResetModel();
}

/*!
  \fn SignalGroupItem::~SignalGroupItem()
  Description of destructor still missing
*/

int SignalGroupItem::rowCount(const QModelIndex& parent) const
{
  TRACE;
  if(!parent.isValid()) {
    return 1;
  } else {
    return group(parent)->childrenCount();
  }
}

/*!
  Currently returns only 1 but may evolve in the future
*/
int SignalGroupItem::columnCount(const QModelIndex&) const
{
  TRACE;
  return 1;
}

QVariant SignalGroupItem::data(const QModelIndex& index, int role) const
{
  TRACE;
  if(!index.isValid()) return QVariant();
  AbstractSignalGroup * g=group(index);
  switch (role) {
  case Qt::EditRole:
  case Qt::DisplayRole:
    return g->name();
  case Qt::ToolTipRole: {
      const QString& c=g->comments();
      if(c.isEmpty()) {
        return tr("%1 signal(s)").arg(g->subPool().count());
      } else {
        return c;
      }
    }
  case Qt::DecorationRole: {
      const QIcon * i=g->icon();
      if(i) {
        return *i;
      } else {
        // These basic group classes do not provide a QIcon
        if(g->xml_inherits(StationGroup::xmlStationGroupTag)) {
          static const QIcon stationIcon(":station_20x20.png");
          return stationIcon;
        } else if(g->hasOwnSignal()){
          static const QIcon signalIcon(":signal_20x20.png");
          return signalIcon;
        } else {
          static const QIcon folderIcon(":folder_20x20.png");
          return folderIcon;
        }
      }
    }
  default:
    return QVariant();
  }
}

bool SignalGroupItem::setData(const QModelIndex& index, const QVariant& value, int role)
{
  TRACE;
  if(!index.isValid()) return false;
  AbstractSignalGroup * g=group(index);
  switch (role) {
  case Qt::EditRole:
    if(g->readOnly()) {
      return false;
    } else {
      g->setName(value.toString());
      return true;
    }
  default:
    return false;
  }
}

QModelIndex SignalGroupItem::parent(const QModelIndex& index) const
{
  TRACE;
  if(!index.isValid()) return QModelIndex();
  AbstractSignalGroup * g=group(index);
  AbstractSignalGroup * parent=g->parent();
  if(parent) {
    AbstractSignalGroup * grandParent=parent->parent();
    if(grandParent) {
      return createIndex(grandParent->rank(parent), 0, parent);
    } else {
      return createIndex(0, 0, parent);
    }
  } else {
    return QModelIndex();
  }
}

QModelIndex SignalGroupItem::index(int row, int column, const QModelIndex& parent) const
{
  TRACE;
  if(!parent.isValid()) {
    if(_db) {
      return createIndex(row, column, static_cast<AbstractSignalGroup *>(_db->masterGroup()));
    } else {
      return QModelIndex();
    }
  } else {
    return createIndex(row, column, group(parent)->childAt(row));
  }
}

AbstractSignalGroup * SignalGroupItem::group(const QModelIndex& index)
{
  TRACE;
  return static_cast<AbstractSignalGroup *>(index.internalPointer());
}

QModelIndex SignalGroupItem::index(AbstractSignalGroup * g) const
{
  TRACE;
  AbstractSignalGroup * parent=g->parent();
  if(parent) {
    return createIndex(parent->rank(g), 0, g);
  } else {
    return createIndex(0, 0, g);
  }
}

Qt::ItemFlags SignalGroupItem::flags(const QModelIndex& index) const
{
  TRACE;
  if(index.isValid()) {
    AbstractSignalGroup * g=group(index);
    if(g->readOnly()) {
      if(g->readOnlyChildren()) {
        return Qt::ItemIsSelectable |
               Qt::ItemIsEnabled;
      } else {
        return Qt::ItemIsSelectable |
               Qt::ItemIsEnabled |
               Qt::ItemIsDropEnabled;
      }
    } else {
      return Qt::ItemIsSelectable |
             Qt::ItemIsEnabled |
             Qt::ItemIsEditable |
             Qt::ItemIsDragEnabled |
             Qt::ItemIsDropEnabled;
    }
  } else {
    return nullptr;
  }
}

QStringList SignalGroupItem::mimeTypes() const
{
  TRACE;
  QStringList types;
  types << "geopsy/x-groups";
  return types;
}

Qt::DropActions SignalGroupItem::supportedDropActions() const
{
  TRACE;
  return Qt::MoveAction;
}

QMimeData * SignalGroupItem::mimeData(const QModelIndexList& indexes) const
{
  TRACE;
  QMimeData *mimeData=new QMimeData();
  QByteArray encodedData;
  QDataStream stream(&encodedData, QIODevice::WriteOnly);

  foreach(QModelIndex index, indexes) {
    if (index.isValid()) {
      AbstractSignalGroup * g=group(index);
      if(!g->readOnly()) {
        stream << reinterpret_cast<quintptr>(g);
      }
    }
  }
  mimeData->setData("geopsy/x-groups", encodedData);
  return mimeData;
}

bool SignalGroupItem::dropMimeData(const QMimeData * data, Qt::DropAction action,
                                   int row, int column, const QModelIndex &parent)
{
  TRACE;
  Q_UNUSED(column)
  if(action==Qt::MoveAction) {
    QByteArray encodedData=data->data("geopsy/x-groups");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    while(!stream.atEnd()) {
      quintptr tmp;  // Direct assignement of g through casting generates a warning:
                     //   "Dereferencing type-punned pointer will break strict-aliasing rules"
      stream >> tmp;
      AbstractSignalGroup * g=reinterpret_cast<AbstractSignalGroup *>(tmp);
      g->setParent(group(parent));
      int r=g->rank();
      if(row>-1 && r!=row) { // Occurs when moving to new parent
        beginMoveRows(parent, r, r, parent, row);
        g->setRank(row);
        endMoveRows();
      }
    }
  }
  return true;
}

void SignalGroupItem::getSelection(SubSignalPool& sel, const QItemSelectionModel& sm,
                                   const QModelIndex& parent)
{
  TRACE;
  int n=rowCount(parent);
  for(int i=0;i < n;i++ ) {
    QModelIndex mi=index(i, 0, parent);
    if(sm.isSelected(mi)) { // No need to go through tree because addGroup will do it for us
      sel.addGroup(group(mi));
    } else {
      getSelection(sel, sm, mi);
    }
  }
}

void SignalGroupItem::getSelection(QList<AbstractSignalGroup *>& sel, const QItemSelectionModel& sm,
                                   const QModelIndex& parent)
{
  TRACE;
  int n=rowCount(parent);
  for(int i=0; i<n; i++) {
    QModelIndex mi=index(i, 0, parent);
    if(sm.isSelected(mi)) {
      sel.append(group(mi));
    }
    getSelection(sel, sm, mi);
  }
}

void SignalGroupItem::dataChanged(AbstractSignalGroup * g)
{
  TRACE;
  QModelIndex ig=index(g);
  //printf("Data changed %s\n", g->pathName().toLatin1().data());
  emit QAbstractItemModel::dataChanged(ig, ig);
}

void SignalGroupItem::parentAboutToBeChanged(AbstractSignalGroup * g, AbstractSignalGroup * newParent)
{
  TRACE;
  //printf("parent about to be changed %s\n", g->pathName().toLatin1().data());
  AbstractSignalGroup * oldParent=g->parent();
  if(oldParent==newParent) return;
  if(oldParent) {
    int row=oldParent->rank(g);
    if(newParent) {
      // Disregard returned value, it should always be true due to tests in AbstractSignalGroup
      ASSERT(beginMoveRows(index(oldParent), row, row, index(newParent), newParent->childrenCount()));
    } else {
      beginRemoveRows(index(oldParent), row, row);
    }
  } else {
    int row=newParent->childrenCount();
    beginInsertRows(index(newParent), row, row);
  }
}

void SignalGroupItem::parentChanged(AbstractSignalGroup * g, AbstractSignalGroup * oldParent)
{
  TRACE;
  //printf("parent changed %s\n", g->pathName().toLatin1().data());
  AbstractSignalGroup * newParent=g->parent();
  if(oldParent==newParent) return;
  if(oldParent) {
    if(newParent) {
      endMoveRows();
    } else {
      endRemoveRows();
    }
  } else {
    endInsertRows();
  }
}

} // namespace GeopsyGui
