/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

/// @file
///     @author Don Gagne <don@thegagnes.com>

#include "QmlObjectListModel.h"

#include <QDebug>
#include <QQmlEngine>

const int QmlObjectListModel::ObjectRole = Qt::UserRole;
const int QmlObjectListModel::TextRole = Qt::UserRole + 1;

QmlObjectListModel::QmlObjectListModel(QObject *parent)
    : QAbstractListModel(parent), _dirty(false), _skipDirtyFirstItem(false) {}

QmlObjectListModel::~QmlObjectListModel() {}

int QmlObjectListModel::rowCount(const QModelIndex &parent) const {
  Q_UNUSED(parent);

  return _objectList.count();
}

QVariant QmlObjectListModel::data(const QModelIndex &index, int role) const {
  if (!index.isValid()) {
    return QVariant();
  }

  if (index.row() < 0 || index.row() >= _objectList.count()) {
    return QVariant();
  }

  if (role == ObjectRole) {
    return QVariant::fromValue(_objectList[index.row()]);
  } else if (role == TextRole) {
    return QVariant::fromValue(_objectList[index.row()]->objectName());
  } else {
    return QVariant();
  }
}

QHash<int, QByteArray> QmlObjectListModel::roleNames(void) const {
  QHash<int, QByteArray> hash;

  hash[ObjectRole] = "object";
  hash[TextRole] = "text";

  return hash;
}

bool QmlObjectListModel::setData(const QModelIndex &index,
                                 const QVariant &value, int role) {
  if (index.isValid() && role == ObjectRole) {
    _objectList.replace(index.row(), value.value<QObject *>());
    emit dataChanged(index, index);
    return true;
  }

  return false;
}

bool QmlObjectListModel::insertRows(int position, int rows,
                                    const QModelIndex &parent) {
  Q_UNUSED(parent);

  if (position < 0 || position > _objectList.count() + 1) {
    qWarning() << "Invalid position position:count" << position
               << _objectList.count();
  }

  beginInsertRows(QModelIndex(), position, position + rows - 1);
  endInsertRows();

  emit countChanged(count());

  return true;
}

bool QmlObjectListModel::removeRows(int position, int rows,
                                    const QModelIndex &parent) {
  Q_UNUSED(parent);

  if (position < 0 || position >= _objectList.count()) {
    qWarning() << "Invalid position position:count" << position
               << _objectList.count();
  } else if (position + rows > _objectList.count()) {
    qWarning() << "Invalid rows position:rows:count" << position << rows
               << _objectList.count();
  }

  beginRemoveRows(QModelIndex(), position, position + rows - 1);
  for (int row = 0; row < rows; row++) {
    _objectList.removeAt(position);
  }
  endRemoveRows();

  emit countChanged(count());

  return true;
}

QObject *QmlObjectListModel::operator[](int index) {
  if (index < 0 || index >= _objectList.count()) {
    return NULL;
  }
  return _objectList[index];
}

const QObject *QmlObjectListModel::operator[](int index) const {
  if (index < 0 || index >= _objectList.count()) {
    return NULL;
  }
  return _objectList[index];
}

bool QmlObjectListModel::operator==(const QmlObjectListModel &other) const {
  if (this->count() == other.count()) {
    for (int i = 0; i < this->count(); ++i) {
      if (this->get(i) != other.get(i)) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

bool QmlObjectListModel::operator!=(const QmlObjectListModel &other) const {
  return !this->operator==(other);
}

void QmlObjectListModel::clear() {
  while (rowCount()) {
    removeAt(0);
  }
}

QObject *QmlObjectListModel::removeAt(int i) {
  QObject *removedObject = _objectList[i];
  if (removedObject) {
    // Look for a dirtyChanged signal on the object
    if (_objectList[i]->metaObject()->indexOfSignal(
            QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) {
      if (!_skipDirtyFirstItem || i != 0) {
        QObject::disconnect(_objectList[i], SIGNAL(dirtyChanged(bool)), this,
                            SLOT(_childDirtyChanged(bool)));
      }
    }
  }
  removeRows(i, 1);
  setDirty(true);
  return removedObject;
}

void QmlObjectListModel::insert(int i, QObject *object) {
  if (i < 0 || i > _objectList.count()) {
    qWarning() << "Invalid index index:count" << i << _objectList.count();
  }

  QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);

  // Look for a dirtyChanged signal on the object
  if (object->metaObject()->indexOfSignal(
          QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) {
    if (!_skipDirtyFirstItem || i != 0) {
      QObject::connect(object, SIGNAL(dirtyChanged(bool)), this,
                       SLOT(_childDirtyChanged(bool)));
    }
  }

  _objectList.insert(i, object);
  insertRows(i, 1);

  setDirty(true);
}

void QmlObjectListModel::insert(int i, QList<QObject *> objects) {
  if (i < 0 || i > _objectList.count()) {
    qWarning() << "Invalid index index:count" << i << _objectList.count();
  }

  int j = i;
  for (QObject *object : objects) {
    QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);

    // Look for a dirtyChanged signal on the object
    if (object->metaObject()->indexOfSignal(
            QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) {
      if (!_skipDirtyFirstItem || j != 0) {
        QObject::connect(object, SIGNAL(dirtyChanged(bool)), this,
                         SLOT(_childDirtyChanged(bool)));
      }
    }
    j++;

    _objectList.insert(j, object);
  }

  insertRows(i, objects.count());

  setDirty(true);
}

void QmlObjectListModel::append(QObject *object) {
  insert(_objectList.count(), object);
}

void QmlObjectListModel::append(QList<QObject *> objects) {
  insert(_objectList.count(), objects);
}

QObjectList QmlObjectListModel::swapObjectList(const QObjectList &newlist) {
  QObjectList oldlist(_objectList);
  beginResetModel();
  _objectList = newlist;
  endResetModel();
  emit countChanged(count());
  return oldlist;
}

int QmlObjectListModel::count() const { return rowCount(); }

void QmlObjectListModel::setDirty(bool dirty) {
  if (_dirty != dirty) {
    _dirty = dirty;
    if (!dirty) {
      // Need to clear dirty from all children
      for (QObject *object : _objectList) {
        if (object->property("dirty").isValid()) {
          object->setProperty("dirty", false);
        }
      }
    }
    emit dirtyChanged(_dirty);
  }
}

void QmlObjectListModel::_childDirtyChanged(bool dirty) {
  _dirty |= dirty;
  // We want to emit dirtyChanged even if the actual value of _dirty didn't
  // change. It can be a useful signal to know when a child has changed dirty
  // state
  emit dirtyChanged(_dirty);
}

void QmlObjectListModel::deleteListAndContents() {
  for (int i = 0; i < _objectList.count(); i++) {
    _objectList[i]->deleteLater();
  }
  deleteLater();
}

void QmlObjectListModel::clearAndDeleteContents() {
  beginResetModel();
  for (int i = 0; i < _objectList.count(); i++) {
    _objectList[i]->deleteLater();
  }
  clear();
  endResetModel();
}

void swap(QmlObjectListModel &list1, QmlObjectListModel &list2) {
  using std::swap;

  swap(list1._objectList, list2._objectList);
  swap(list1._dirty, list2._dirty);
  swap(list1._skipDirtyFirstItem, list2._skipDirtyFirstItem);
}
