QModelIndex
SourceFileModel::indexFromSourceFile(SourceFile *sourceFile)
  const {
  if (!sourceFile)
    return QModelIndex{};

  return indexFromSourceFile(reinterpret_cast<quint64>(sourceFile), QModelIndex{});
}
bool
SourceFileModel::dropSourceFiles(QMimeData const *data,
                                 Qt::DropAction action,
                                 int row,
                                 QModelIndex const &parent) {
  if (action != Qt::MoveAction)
    return QAbstractItemModel::dropMimeData(data, action, row, 0, parent);

  auto encoded = data->data(mtx::gui::MimeTypes::MergeSourceFileModelItem);
  QDataStream stream{&encoded, QIODevice::ReadOnly};

  while (!stream.atEnd()) {
    quint64 value;
    stream >> value;
    auto sourceFile = m_sourceFileMap[value];
    auto sourceIdx  = indexFromSourceFile(sourceFile.get());

    if (!sourceIdx.isValid())
      continue;

    auto sourceParent     = sourceIdx.parent();
    auto sourceParentItem = sourceParent.isValid() ? itemFromIndex(sourceParent) : invisibleRootItem();
    auto rowItems         = sourceParentItem->takeRow(sourceIdx.row());

    if (!parent.isValid()) {
      if ((sourceParent == parent) && (sourceIdx.row() < row))
        --row;

      invisibleRootItem()->insertRow(row, rowItems);
      ++row;

    } else {
      auto parentFile = fromIndex(parent);
      Q_ASSERT(parentFile);

      if (sourceFile->isAdditionalPart())
        row = std::min(row, parentFile->m_additionalParts.size());
      else
        row = std::max(row, parentFile->m_additionalParts.size());

      if ((sourceParent == parent) && (sourceIdx.row() < row))
        --row;

      itemFromIndex(parent)->insertRow(row, rowItems);
      ++row;
    }

    updateSourceFileLists();
  }

  return false;
}
void
SourceFileModel::sourceFileUpdated(SourceFile *sourceFile) {
  auto idx = indexFromSourceFile(sourceFile);
  if (!idx.isValid())
    return;

  auto items = QList<QStandardItem *>{};

  for (auto column = 0, numColumns = columnCount(); column < numColumns; ++column)
    items << itemFromIndex(idx.sibling(idx.row(), column));

  setItemsFromSourceFile(items, sourceFile);
}
QModelIndex
SourceFileModel::indexFromSourceFile(quint64 value,
                                     QModelIndex const &parent)
  const {
  auto currentValue = storageValueFromIndex(parent);
  if (currentValue == value)
    return parent;

  auto invalidIdx = QModelIndex{};

  for (auto row = 0, numRows = rowCount(parent); row < numRows; ++row) {
    auto idx = indexFromSourceFile(value, index(row, 0, parent));
    if (idx != invalidIdx)
      return idx;
  }

  return invalidIdx;
}
void
SourceFileModel::sortSourceFiles(QList<SourceFile *> &files,
                                 bool reverse) {
  auto rows = QHash<SourceFile *, int>{};

  for (auto const &file : files)
    rows[file] = indexFromSourceFile(file).row();

  auto totalNumFiles = m_sourceFiles->count();
  for (auto const &file : *m_sourceFiles)
    totalNumFiles += file->m_appendedFiles.count() + file->m_additionalParts.count();

  std::sort(files.begin(), files.end(), [&rows](SourceFile *a, SourceFile *b) -> bool {
    auto rowA = rows[a];
    auto rowB = rows[b];

    if ( a->isRegular() &&  b->isRegular())
      return rowA < rowB;

    if ( a->isRegular() && !b->isRegular())
      return true;

    if (!a->isRegular() &&  b->isRegular())
      return false;

    auto parentA = rows[a->m_appendedTo];
    auto parentB = rows[b->m_appendedTo];

    return (parentA < parentB)
        || ((parentA == parentB) && (rowA < rowB));
  });

  if (reverse) {
    std::reverse(files.begin(), files.end());
    std::stable_partition(files.begin(), files.end(), [](SourceFile *file) { return file->isRegular(); });
  }
}
bool
SourceFileModel::dropSourceFiles(QMimeData const *data,
                                 Qt::DropAction action,
                                 int row,
                                 QModelIndex const &parent) {
  if (action != Qt::MoveAction)
    return QAbstractItemModel::dropMimeData(data, action, row, 0, parent);

  mxinfo(boost::format("dropMimeData row %1% parent %2%/%3%\n") % row % parent.row() % parent.column());

  auto encoded = data->data(MIME_TYPE);
  QDataStream stream{&encoded, QIODevice::ReadOnly};

  while (!stream.atEnd()) {
    quint64 value;
    stream >> value;
    auto sourceFile = m_sourceFileMap[value];
    auto sourceIdx  = indexFromSourceFile(sourceFile.get());

    if (!sourceIdx.isValid())
      continue;

    mxinfo(boost::format("  val %|1$08x| ptr %2% idx %3%/%4%\n") % value % sourceFile.get() % sourceIdx.row() % sourceIdx.column());

    auto sourceParent     = sourceIdx.parent();
    auto sourceParentItem = sourceParent.isValid() ? itemFromIndex(sourceParent) : invisibleRootItem();
    auto priorRow         = row;
    auto rowItems         = sourceParentItem->takeRow(sourceIdx.row());

    if (!parent.isValid()) {
      if ((sourceParent == parent) && (sourceIdx.row() < row))
        --row;

      mxinfo(boost::format("tried moving case 1 from %1% row %2% to new parent %3% row %4% new row %5%\n") % dumpIdx(sourceIdx.parent()) % sourceIdx.row() % dumpIdx(parent) % priorRow % (row + 1));

      invisibleRootItem()->insertRow(row, rowItems);
      ++row;

    } else {
      auto parentFile = fromIndex(parent);
      Q_ASSERT(parentFile);

      if (sourceFile->isAdditionalPart())
        row = std::min(row, parentFile->m_additionalParts.size());
      else
        row = std::max(row, parentFile->m_additionalParts.size());

      if ((sourceParent == parent) && (sourceIdx.row() < row))
        --row;

      mxinfo(boost::format("tried moving case 2 from %1% row %2% to new parent %3% row %4% new row %5%\n") % dumpIdx(sourceIdx.parent()) % sourceIdx.row() % dumpIdx(parent) % priorRow % (row + 1));

      itemFromIndex(parent)->insertRow(row, rowItems);
      ++row;
    }

    updateSourceFileLists();
  }

  return false;
}
void
SourceFileModel::moveSourceFilesUpOrDown(QList<SourceFile *> files,
                                         bool up) {
  sortSourceFiles(files, !up);

  // qDebug() << "move up?" << up << "files" << files;

  auto couldNotBeMoved = QHash<SourceFile *, bool>{};
  auto isSelected      = QHash<SourceFile *, bool>{};
  auto const direction = up ? -1 : +1;
  auto const topRows   = rowCount();

  for (auto const &file : files) {
    isSelected[file] = true;

    if (!file->isRegular() && isSelected[file->m_appendedTo])
      continue;

    auto idx = indexFromSourceFile(file);
    Q_ASSERT(idx.isValid());

    auto targetRow = idx.row() + direction;
    if (couldNotBeMoved[fromIndex(idx.sibling(targetRow, 0)).get()]) {
      couldNotBeMoved[file] = true;
      continue;
    }

    if (file->isRegular()) {
      if (!((0 <= targetRow) && (targetRow < topRows))) {
        couldNotBeMoved[file] = true;
        continue;
      }

      // qDebug() << "top level: would like to move" << idx.row() << "to" << targetRow;

      insertRow(targetRow, takeRow(idx.row()));

      continue;
    }

    auto parentItem                   = itemFromIndex(idx.parent());
    auto const appendedAdditionalRows = countAppendedAndAdditionalParts(parentItem);
    auto const additionalPartsRows    = appendedAdditionalRows.first;
    auto const appendedRows           = appendedAdditionalRows.second;
    auto const lowerLimit             = (file->isAdditionalPart() ? 0 : additionalPartsRows);
    auto const upperLimit             = (file->isAdditionalPart() ? 0 : appendedRows) +  additionalPartsRows;

    if ((lowerLimit <= targetRow) && (targetRow < upperLimit)) {
      // qDebug() << "appended level normal: would like to move" << idx.row() << "to" << targetRow;

      parentItem->insertRow(targetRow, parentItem->takeRow(idx.row()));
      continue;
    }

    auto parentIdx = parentItem->index();
    Q_ASSERT(parentIdx.isValid());

    auto newParentRow = parentIdx.row() + direction;
    if ((0 > newParentRow) || (rowCount() <= newParentRow)) {
      // qDebug() << "appended, cannot move further";
      couldNotBeMoved[file] = true;
      continue;
    }

    auto newParent        = fromIndex(index(newParentRow, 0));
    auto newParentItem    = itemFromIndex(index(newParentRow, 0));
    auto rowItems         = parentItem->takeRow(idx.row());
    auto newParentNumbers = countAppendedAndAdditionalParts(newParentItem);
    targetRow             = up  && file->isAdditionalPart() ? newParentNumbers.first
                          : up                              ? newParentNumbers.first + newParentNumbers.second
                          : !up && file->isAdditionalPart() ? 0
                          :                                   newParentNumbers.first;

    Q_ASSERT(!!newParent);

    // qDebug() << "appended level cross: would like to move" << idx.row() << "from" << file->m_appendedTo << "to" << newParent.get() << "as" << targetRow;

    newParentItem->insertRow(targetRow, rowItems);
    file->m_appendedTo = newParent.get();
  }

  updateSourceFileLists();
}