#include "multiverseitem.h"
#include "multiversemodel.h"
#include "uuid/uuid.h"
#include "qsacnnode.h"

#include <QMetaEnum>

/**
 * @brief MultiverseModel::MultiverseModel
 * @param parent
 * @param node
 */
MultiverseModel::MultiverseModel(QObject *parent, QSacnNode *node)
  : QAbstractItemModel(parent)
  , node_(node)
  , rootItem_(new MultiverseItem())
{
  QMetaEnum e = QMetaEnum::fromType<MultiverseModel::Catagory>();
  for (int k = 0; k < e.keyCount(); k++)
    {
      auto item = new MultiverseItem(rootItem_);
      item->setOverrideData(QString(e.key(k)));
      auto idx = MultiverseModel::index(k, 0, QModelIndex());
      categoryIndexes.insert(static_cast<MultiverseModel::Catagory>
                              (e.value(k)), idx);
    }

  connect(node, &QSacnNode::discoveryUpdates,
          this, &MultiverseModel::doDiscovery);
  connect(node, &QSacnNode::subscribing,
          this, &MultiverseModel::doSubscription);
  connect(node, &QSacnNode::unsubscribing,
          this, &MultiverseModel::doUnsubscribe);
  connect(node, &QSacnNode::creating,
          this, &MultiverseModel::doCreation);
  connect(node, &QSacnNode::terminating,
          this, &MultiverseModel::doTerminate);
}


/**
 * @brief MultiverseModel::~MultiverseModel
 */
MultiverseModel::~MultiverseModel()
{
  delete rootItem_;
}


/**
 * @brief MultiverseModel::root
 * @return
 */
MultiverseItem * MultiverseModel::root()
{
  return rootItem_;
}


/**
 * @brief MultiverseModel::getItem
 * @param index
 * @return
 */
MultiverseItem * MultiverseModel::getItem(const QModelIndex &index) const
{
  if (index.isValid())
    {
      auto item = static_cast<MultiverseItem*>(index.internalPointer());
      if (item)
          return item;
  }
  return rootItem_;
}


/**
 * @brief MultiverseModel::headerData
 * @param section
 * @param orientation
 * @param role
 * @return
 */
QVariant MultiverseModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
  if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
      switch (static_cast<Column>(section)) {
        case Universe:
          return tr("Universe");
        case Priority:
          return tr("Priority");
        case Channels:
          return tr("Channels");
        case Description:
          return tr("Description");
        }
    }
  return QVariant();
}


/**
 * @brief MultiverseModel::index
 * @param row
 * @param column
 * @param parent
 * @return
 */
QModelIndex MultiverseModel::index(int row, int column,
                                   const QModelIndex &parent) const
{
  if (parent.isValid() && parent.column() != 0)
    return QModelIndex();

  MultiverseItem *parentItem = getItem(parent);
  if (!parentItem)
    return QModelIndex();

  auto childItem = parentItem->child(row);
  if (childItem)
    return createIndex(row, column, childItem);

  return QModelIndex();
}


/**
 * @brief MultiverseModel::parent
 * @param index
 * @return
 */
QModelIndex MultiverseModel::parent(const QModelIndex &index) const
{
  if (!checkIndex(index, CheckIndexOption::IndexIsValid |
                         CheckIndexOption::DoNotUseParent))
    return QModelIndex();

  auto childItem = getItem(index);
  auto parentItem = childItem ? childItem->parentItem() : nullptr;

  if (parentItem == rootItem_ || !parentItem)
    return QModelIndex();

  return createIndex(parentItem->row(), 0, parentItem);
}


/**
 * @brief MultiverseModel::rowCount
 * @param parent
 * @return
 */
int MultiverseModel::rowCount(const QModelIndex &parent) const
{
  auto parentItem = getItem(parent);
  return parentItem ? parentItem->childCount() : 0;
}


/**
 * @brief MultiverseModel::columnCount
 * @param parent
 * @return
 */
int MultiverseModel::columnCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return QMetaEnum::fromType<Column>().keyCount();
}


/**
 * @brief MultiverseModel::data
 * @param index
 * @param role
 * @return
 */
QVariant MultiverseModel::data(const QModelIndex &index, int role) const
{
  if (!checkIndex(index, CheckIndexOption::IndexIsValid))
    return QVariant();

  return getItem(index)->data(index.column(), role);
}


/**
 * @brief MultiverseModel::flags
 * @param index
 * @return
 */
Qt::ItemFlags MultiverseModel::flags(const QModelIndex &index) const
{
  if (!checkIndex(index, CheckIndexOption::IndexIsValid))
    return Qt::NoItemFlags;

  auto item = getItem(index);

  if (!item)
    return Qt::NoItemFlags;

  return item->flags(index.column(), QAbstractItemModel::flags(index));
}


/**
 * @brief MultiverseModel::insert
 * @param universe
 * @param parent
 * @return
 */
void MultiverseModel::insert(const QModelIndex &parent,
                             QSacnUniverse* universe,
                             discoveredUniverse *discovery)
{
  auto item = getItem(parent);
  beginInsertRows(parent, item->childCount(), item->childCount());
  auto child = new MultiverseItem(item, universe, discovery);
  endInsertRows();
  auto index = QPersistentModelIndex(createIndex(item->childCount() - 1, 0,
                                                 child));

  auto refreshRow = [this] (QPersistentModelIndex index) {
        auto begin = index.sibling(index.row(), 0);
        auto end = begin.siblingAtColumn(columnCount() - 1);
        emit dataChanged(begin, end);
      };

  connect(universe, &QSacnUniverse::changed,
          this, [refreshRow, index](){ refreshRow(index); });

  connect(universe, &QSacnUniverse::sourceListChanged,
          this, [this, universe, child, index, refreshRow]() {
      // remove the old children
      beginRemoveRows(index, 0, child->childCount() - 1);
      child->removeChildren();
      endRemoveRows();

      // add the new children
      beginInsertRows(index, 0, universe->sources().size() - 1);
      child->createChildren();
      endInsertRows();

      // refresh row of new child when data arrives
      for (int i = 0; i < child->childCount(); i++)
        {
          auto grandchild = child->child(i);
          auto index = QPersistentModelIndex(createIndex(i, 0, grandchild));
          auto data = grandchild->data(Column::Universe, Qt::EditRole);
          if (data.metaType().id() != qMetaTypeId<QSacnUniverse*>())
            return;
          auto universe = data.value<QSacnUniverse*>();
          connect(universe, &QSacnUniverse::changed,
                  this, [refreshRow, index](){ refreshRow(index); });
        }
    });
}


/**
 * @brief MultiverseModel::remove
 * @param parent
 * @param data
 */
void MultiverseModel::remove(const QModelIndex &parent, const QVariant &data)
{
  auto item = static_cast<MultiverseItem*>(parent.internalPointer());
  auto row = item->childRow(Column::Universe, data);
  if (row < 0)
    return;
  auto child = item->child(row);
  if (!child)
    return;
  beginRemoveRows(parent, row, row);
  item->removeChild(child);
  endRemoveRows();
}


void MultiverseModel::doDiscovery()
{
  auto parentIndex = categoryIndexes.value(MultiverseModel::Discovery);
  auto parent = static_cast<MultiverseItem*>(parentIndex.internalPointer());

  beginRemoveRows(parentIndex, 0, parent->childCount() - 1);
  parent->removeChildren();
  endRemoveRows();

  beginInsertRows(parentIndex, 0, node_->discovered.size() - 1);
  for (auto& discovery : node_->discovered)
    new MultiverseItem(parent, nullptr, discovery.get());
  endInsertRows();
}


/**
 * @brief MultiverseModel::doSubscription
 * @param universe
 */
void MultiverseModel::doSubscription(QSacnUniverse* universe)
{
  auto parentIndex = categoryIndexes.value(MultiverseModel::Receiver);
  insert(parentIndex, universe);
}


/**
 * @brief MultiverseModel::doUnsubscribe
 * @param universe
 */
void MultiverseModel::doUnsubscribe(QSacnUniverse* universe)
{
  auto parentIndex = categoryIndexes.value(MultiverseModel::Receiver);
  remove(parentIndex, QVariant::fromValue(universe));
}


/**
 * @brief MultiverseModel::doCreation
 * @param universe
 */
void MultiverseModel::doCreation(QSacnUniverse* universe)
{
  auto parentIndex = categoryIndexes.value(MultiverseModel::Source);
  insert(parentIndex, universe);
}


/**
 * @brief MultiverseModel::doTerminate
 * @param universe
 */
void MultiverseModel::doTerminate(QSacnUniverse *universe)
{
  auto parentIndex = categoryIndexes.value(MultiverseModel::Source);
  remove(parentIndex, QVariant::fromValue(universe));
}
