/*
  metaobjecttreemodel.cpp

  This file is part of GammaRay, the Qt application inspection and
  manipulation tool.

  Copyright (C) 2012-2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  Author: Kevin Funk <kevin.funk@kdab.com>

  This program 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 2 of the License, or
  (at your option) any later version.

  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "metaobjecttreemodel.h"

#include "probe.h"
#include "readorwritelocker.h"

#include <QDebug>
#include <QThread>

using namespace GammaRay;

#define IF_DEBUG(x)

namespace GammaRay {
/**
 * Open QObject for access to protected data members
 */
class UnprotectedQObject : public QObject
{
public:
  inline QObjectData* data() const { return d_ptr.data(); }
};
}

/**
 * Return true in case the object has a dynamic meta object
 *
 * If you look at the code generated by moc you'll see this:
 * @code
 * const QMetaObject *GammaRay::MessageModel::metaObject() const
 * {
 *     return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
 * }
 * @endcode
 *
 * QtQuick uses dynamic meta objects (subclasses of QAbstractDynamicMetaObject, defined in qobject_h.p)
 * for QML types. It's possible that these meta objects get destroyed
 * at runtime, so we need to protect against this.
 *
 * @note We cannot say if a specific QMetaObject* is dynamic or not
 * (QMetaObject is non-polymorphic, so we cannot dynamic_cast to QAbstractDynamicMetaObject*),
 * we can just judge by looking at QObjectData of QObject*
 * -- hence the QObject* parameter in hasDynamicMetaObject.
 *
 * @return Return true in case metaObject() does not point to staticMetaObject.
 */
static inline bool hasDynamicMetaObject(const QObject* object)
{
  return reinterpret_cast<const UnprotectedQObject*>(object)->data()->metaObject != 0;
}

MetaObjectTreeModel::MetaObjectTreeModel(QObject *parent)
  : QAbstractItemModel(parent)
{
}

QVariant MetaObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
  if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
    switch (section) {
      case ObjectColumn:
        return tr("Meta Object Class Hierarchy");
      default:
        return QVariant();
    }
  }

  return QAbstractItemModel::headerData(section, orientation, role);
}

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

  const int column = index.column();
  const QMetaObject *object = metaObjectForIndex(index);
  if (role == Qt::DisplayRole) {
    switch(column) {
    case ObjectColumn:
      return object->className();
    default:
      break;
    }
  } else if (role == MetaObjectRole) {
    return QVariant::fromValue<const QMetaObject*>(object);
  }
  return QVariant();
}

int MetaObjectTreeModel::columnCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return 1;
}

int MetaObjectTreeModel::rowCount(const QModelIndex &parent) const
{
  const QMetaObject *metaObject = metaObjectForIndex(parent);
  return m_parentChildMap.value(metaObject).size();
}

QModelIndex MetaObjectTreeModel::parent(const QModelIndex &child) const
{
  if (!child.isValid()) {
    return QModelIndex();
  }

  const QMetaObject *object = metaObjectForIndex(child);
  Q_ASSERT(object);
  const QMetaObject *parentObject = object->superClass();
  return indexForMetaObject(parentObject);
}

QModelIndex MetaObjectTreeModel::index(int row, int column, const QModelIndex &parent) const
{
  const QMetaObject *parentObject = metaObjectForIndex(parent);
  const QVector<const QMetaObject*> children = m_parentChildMap.value(parentObject);
  if (row < 0 || column < 0 || row >= children.size()  || column >= columnCount()) {
    return QModelIndex();
  }

  const QMetaObject *object = children.at(row);
  return createIndex(row, column, const_cast<QMetaObject*>(object));
}

void MetaObjectTreeModel::objectAdded(QObject *obj)
{
  // slot, hence should always land in main thread due to auto connection
  Q_ASSERT(thread() == QThread::currentThread());

  ReadOrWriteLocker objectLock(Probe::instance()->objectLock());
  if (!Probe::instance()->isValidObject(obj)) {
    return;
  }
  Q_ASSERT(!obj->parent() || Probe::instance()->isValidObject(obj->parent()));

  if (hasDynamicMetaObject(obj)) {
    // Encountered dynamic meta object, ignoring for now
    return;
  }

  const QMetaObject *metaObject = obj->metaObject();
  addMetaObject(metaObject);
}

void MetaObjectTreeModel::addMetaObject(const QMetaObject *metaObject)
{
  if (indexForMetaObject(metaObject).isValid()) {
    return;
  }

  const QMetaObject *parentMetaObject = metaObject->superClass();
  if (parentMetaObject) {
    const QModelIndex parentIndex = indexForMetaObject(parentMetaObject);
    if (!parentIndex.isValid()) {
      // add parent first
      addMetaObject(metaObject->superClass());
    }
  }

  const QModelIndex parentIndex = indexForMetaObject(parentMetaObject);
  // either we get a proper parent and hence valid index or there is no parent
  Q_ASSERT(parentIndex.isValid() || !parentMetaObject);

  QVector<const QMetaObject*> &children = m_parentChildMap[ parentMetaObject ];

  beginInsertRows(parentIndex, children.size(), children.size());
  children.push_back(metaObject);
  m_childParentMap.insert(metaObject, parentMetaObject);

  endInsertRows();
}

void MetaObjectTreeModel::removeMetaObject(const QMetaObject *metaObject)
{
  Q_UNUSED(metaObject);
  // TODO: Meta objects may be deleted, find a way to detect this
}

void MetaObjectTreeModel::objectRemoved(QObject *obj)
{
  Q_UNUSED(obj);
  // TODO
}

QModelIndex MetaObjectTreeModel::indexForMetaObject(const QMetaObject *metaObject) const
{
  if (!metaObject) {
    return QModelIndex();
  }

  const QMetaObject *parentObject = m_childParentMap.value(metaObject);
  const QModelIndex parentIndex = indexForMetaObject(parentObject);
  if (!parentIndex.isValid() && parentObject) {
    return QModelIndex();
  }

  const int row = m_parentChildMap[parentObject].indexOf(metaObject);
  if (row < 0) {
    return QModelIndex();
  }

  return index(row, 0, parentIndex);
}

const QMetaObject *MetaObjectTreeModel::metaObjectForIndex(const QModelIndex &index) const
{
  if (!index.isValid()) {
    return 0;
  }

  void *internalPointer = index.internalPointer();
  const QMetaObject* metaObject = reinterpret_cast<QMetaObject*>(internalPointer);
  return metaObject;
}

