#include "virtualtreemodel.h"

#include "internalnode.h"

#include "virtualtreemodelprivate.h"

#include "../virtualmodeladapter/virtualmodeladapter.h"

VirtualTreeModel::VirtualTreeModel(VirtualModelAdapter *adapter,
                                   QObject *parent)
    : QAbstractItemModel(parent), m_adapter(adapter), m_updating(0),
      m_syncing(false) {
  m_root = new InternalNode(nullptr, nullptr, 0);
  m_model = new VirtualTreeModelPrivate(*this);
  if (m_adapter)
    adapter->setModel(m_model);
  syncTree();
}

VirtualTreeModel::~VirtualTreeModel() {
  delete m_root;
  delete m_model;
}

void VirtualTreeModel::syncNodeList(InternalNode &node, void *parent) {
  QModelIndex modelIndex = getIndex(node);
  InternalNode::InternalChildren &nestedNodes = node.children;
  int nestedCount = int(nestedNodes.size());
  int currentIndex = -1;
  bool complete;
  do {
    // variable initialization
    InternalNode *currentNestedNode;
    int cachedNestedNodeIndex;

    complete = (++currentIndex >= nestedCount);
    if (complete) {
      currentNestedNode = nullptr;
      cachedNestedNodeIndex = 0;
    } else {
      currentNestedNode = nestedNodes[currentIndex].get();
      // index of "item" with "parent" from "destinationStart"
      cachedNestedNodeIndex =
          m_adapter->indexOfItem(parent, currentNestedNode->item, currentIndex);
    }
    // if doesn't exist - remove
    if (cachedNestedNodeIndex < 0) {
      beginRemoveRows(modelIndex, currentIndex, currentIndex);
      node.eraseChildren(nestedNodes.begin() + currentIndex,
                         nestedNodes.begin() + currentIndex + 1 /*to end*/);
      endRemoveRows();
      complete = (currentIndex >= --nestedCount);
    } else if (currentNestedNode != nullptr &&
               currentNestedNode->isInitialized(m_adapter))
      syncNodeList(*currentNestedNode, currentNestedNode->item);
  } while (!complete);

  int distance = m_adapter->getItemsCount(parent) - currentIndex;
  if (distance > 0) {
    beginInsertRows(modelIndex, currentIndex,
                    currentIndex + distance - 1 /*to index*/);
    for (int i = 0; i < distance; ++i, ++currentIndex) {
      void *cachedItem = m_adapter->getItem(parent, currentIndex);
      auto newNode = new InternalNode(&node, cachedItem, currentIndex);
      nestedNodes.emplace(nestedNodes.begin() + currentIndex, newNode);
    }
    node.insertedChildren(currentIndex);
    endInsertRows();
  }

  node.childInitialized = true;
}

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

  if (m_updating > 0 || m_adapter == nullptr)
    return QVariant();

  void *item = getNode(index).item;
  return m_adapter->data(item, role);
}

QModelIndex VirtualTreeModel::index(int row, int column,
                                    const QModelIndex &parent) const {
  InternalNode &parentItem = getNode(parent);
  if (row < static_cast<int>(parentItem.children.size())) {
    InternalNode *childItem = parentItem.children.at(row).get();
    return getIndex(*childItem, column);
  } else
    return QModelIndex();
}

QModelIndex VirtualTreeModel::parent(const QModelIndex &index) const {
  if (!index.isValid() || !m_adapter)
    return QModelIndex();

  InternalNode &childItem = getNode(index);
  InternalNode *parentItem = childItem.parent;
  if (parentItem == nullptr || parentItem == m_root)
    return QModelIndex();
  return getIndex(*parentItem);
}

int VirtualTreeModel::rowCount(const QModelIndex &parent) const {
  if (parent.isValid() && !this->parent(parent).isValid())
    parent.internalId();
  InternalNode &parentItem = getNode(parent);
  if (m_syncing)
    return static_cast<int>(parentItem.children.size());
  else if (m_adapter)
    // lazy children loading
    return parentItem.childCount(m_adapter);
  else
    return 0;
}

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

VirtualTreeModel::InternalNode &
VirtualTreeModel::getNode(const QModelIndex &index) const {
  if (index.isValid())
    return *(InternalNode *)index.internalPointer();
  else
    return *m_root;
}

VirtualTreeModel::InternalNode *
VirtualTreeModel::getItemNode(void *item) const {
  if (m_adapter == nullptr)
    return nullptr;
  void *parentItem = m_adapter->getItemParent(item);
  if (parentItem == item)
    return nullptr;
  if (parentItem == nullptr)
    return m_root;
  else {
    auto parentNode = getItemNode(parentItem);
    int index = m_adapter->indexOfItem(parentItem, item);
    if (index >= 0) {
      parentNode->loadChildren(m_adapter);
      return parentNode->children[index].get();
    } else {
      return nullptr;
    }
  }
}

QModelIndex VirtualTreeModel::getIndex(const InternalNode &node,
                                       int column) const {
  if (&node == m_root)
    return QModelIndex();
  else
    return createIndex(static_cast<int>(node.parentIndex), column,
                       quintptr(&node));
}

void VirtualTreeModel::doQueuedUpdate() { endUpdate(); }

bool VirtualTreeModel::hasChildren(const QModelIndex &parent) const {
  if (!parent.isValid())
    return true;
  InternalNode &item = getNode(parent);
  if (item.childInitialized) {
    return item.children.size() > 0;
  } else if (m_adapter) {
    item.hasChildrenQueryed = true;
    bool has = m_adapter->hasItems(item.item);
    item.hasChildren = has;
    return has;
  } else
    return false;
}

void *VirtualTreeModel::getItem(const QModelIndex &index) const {
  return getNode(index).item;
}

QModelIndex VirtualTreeModel::getItemModelIndex(void *item) const {
  auto node = getItemNode(item);
  if (node)
    return getIndex(*node);
  else
    return QModelIndex();
}

VirtualModelAdapter *
VirtualTreeModel::setModelAdapter(VirtualModelAdapter *adapter) {
  VirtualModelAdapter *oldAdapter = m_adapter;
  beginUpdate();
  if (m_adapter)
    m_adapter->setModel(nullptr);
  m_adapter = adapter;
  beginResetModel();
  m_root->children.clear();
  endResetModel();
  if (m_adapter)
    m_adapter->setModel(m_model);
  endUpdate();
  return oldAdapter;
}

VirtualModelAdapter *VirtualTreeModel::getModelAdapter() const {
  return m_adapter;
}

void VirtualTreeModel::beginUpdate() { ++m_updating; }

void VirtualTreeModel::endUpdate() {
  if (m_updating == 1)
    syncTree();
  --m_updating;
  // force tree to repaint all nestedNodes
  if (m_updating == 0)
    emit dataChanged(QModelIndex(), QModelIndex());
}

bool VirtualTreeModel::isUpdating() const {
  return m_updating > 0 || m_syncing;
}

void VirtualTreeModel::queuedUpdate() {
  if (m_updating == 0) {
    beginUpdate();
    QMetaObject::invokeMethod(this, "doQueuedUpdate", Qt::QueuedConnection);
  }
}

void VirtualTreeModel::syncTree() {
  if (m_adapter) {
    m_syncing = true;
    syncNodeList(*m_root, nullptr);
    m_syncing = false;
  }
}
