#ifndef SQLTREEMODEL_HPP
#define SQLTREEMODEL_HPP

#include "sqltreemodel.h"

#include "../sqlscript/sqlscript.h"

#include <map>

#include <QSqlQuery>

#include <QDebug>

#define SQLTREEMODEL_TEMPLATE                                                  \
  template <unsigned idColumnIndex, unsigned parentIdColumnIndex,              \
            unsigned dataColumnIndex, typename DataType>

#define SQLTREEMODEL_CLASS                                                     \
  SqlTreeModel<idColumnIndex, parentIdColumnIndex, dataColumnIndex, DataType>

SQLTREEMODEL_TEMPLATE
inline SQLTREEMODEL_CLASS::SqlTreeModel(QObject *parent)
    : VirtualTreeModel(nullptr, parent), m_adapter(m_root) {
  ASSERT_INDEX(idColumnIndex);
  ASSERT_INDEX(parentIdColumnIndex);
  ASSERT_INDEX(dataColumnIndex);
  // initialize VirtualTreeModel's adapter after VirtualNodeAdapter
  setModelAdapter(&m_adapter);
}

SQLTREEMODEL_TEMPLATE
inline SQLTREEMODEL_CLASS::SqlTreeModel(QString tableName, QObject *parent)
    : SqlTreeModel(parent) {
  selectFromTable(tableName);
}

SQLTREEMODEL_TEMPLATE
inline SQLTREEMODEL_CLASS::~SqlTreeModel() {}

SQLTREEMODEL_TEMPLATE
inline typename SQLTREEMODEL_CLASS::SqlTreeNode *
SQLTREEMODEL_CLASS::getNode(const QModelIndex &index) {
  return m_adapter.getValue(getItem(index));
}

SQLTREEMODEL_TEMPLATE
inline typename SQLTREEMODEL_CLASS::SqlTreeNode *
SQLTREEMODEL_CLASS::getNode(IdType productTypeId) {
  const auto searchResult = m_nodes.find(productTypeId);
  return searchResult == m_nodes.end() ? nullptr : searchResult->second;
}

SQLTREEMODEL_TEMPLATE
inline void SQLTREEMODEL_CLASS::selectFromTable(const QString &tableName) {
  m_tableName = tableName;

  std::vector columnNumbers{parentIdColumnIndex + 1, idColumnIndex + 1};
  auto select = SqlScript::selectOrderBy<unsigned>(tableName, columnNumbers);
  if (select.isActive()) {
    queuedUpdate();
    std::for_each(m_root.nestedNodes.begin(), m_root.nestedNodes.end(),
                  [this](const auto &value) { m_root.remove(value.get()); });

    m_nodes.clear();
    beginUpdate();
    while (select.next()) {
      int parentId = select.value(parentIdColumnIndex).toInt();
      if (parentId > idColumnIndex)
        break;
      auto id = select.value(idColumnIndex).value<IdType>();
      auto *newNode = m_root.createFront(
          id, select.value(dataColumnIndex).value<DataType>());
      m_nodes.insert({id, newNode});
    }

    if (select.isValid())
      do {
        int id = select.value(idColumnIndex).toInt();
        int parentId = select.value(parentIdColumnIndex).toInt();
        auto *newNode = m_nodes[parentId]->createFront(
            id, select.value(dataColumnIndex).value<DataType>());
        m_nodes.insert({id, newNode});
      } while (select.next());
    endUpdate();
  }
}

SQLTREEMODEL_TEMPLATE
QModelIndex SQLTREEMODEL_CLASS::insertProductType(int productTypeId,
                                                  const QString &newTypeName) {
  int lastInsertId = SqlScript::insertProductType(productTypeId, newTypeName);
  SqlTreeNode *node = getNode(productTypeId);
  beginUpdate();
  SqlTreeNode *newNode = node->createFront(lastInsertId, newTypeName);
  m_nodes.insert({lastInsertId, newNode});
  endUpdate();
  return getItemModelIndex(newNode);
}

SQLTREEMODEL_TEMPLATE
void SQLTREEMODEL_CLASS::specifyProductType(int productTypeId,
                                            const QString &newTypeName) {
  int lastInsertId = SqlScript::specifyProductType(productTypeId, newTypeName);
  SqlTreeNode *node = getNode(productTypeId);
  beginUpdate();
  SqlTreeNode *newNode = node->createBack(lastInsertId, newTypeName);
  m_nodes.insert({lastInsertId, newNode});
  endUpdate();
}

SQLTREEMODEL_TEMPLATE
void SQLTREEMODEL_CLASS::renameProductType(int productTypeId,
                                           const QString &newTypeName) {
  SqlScript::renameProductType(productTypeId, newTypeName);
  SqlTreeNode *node = getNode(productTypeId);
  beginUpdate();
  node->data = newTypeName;
  endUpdate();
}

#endif // SQLTREEMODEL_HPP
