#ifndef CSM_WOLRD_REFIDADAPTERIMP_H
#define CSM_WOLRD_REFIDADAPTERIMP_H

#include <algorithm>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include <QVariant>

#include <components/esm/defs.hpp>
#include <components/esm/esmcommon.hpp>
#include <components/esm3/aipackage.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadappa.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadcont.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loaddoor.hpp>
#include <components/esm3/loadingr.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadligh.hpp>
#include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/esm3/transport.hpp>

#include "columnbase.hpp"
#include "nestedtablewrapper.hpp"
#include "record.hpp"
#include "refidadapter.hpp"
#include "refiddata.hpp"
#include "universalid.hpp"

namespace CSMWorld
{
    class RefIdColumn;
    struct BaseColumns
    {
        const RefIdColumn* mId;
        const RefIdColumn* mModified;
        const RefIdColumn* mType;
        const RefIdColumn* mBlocked;

        BaseColumns()
            : mId(nullptr)
            , mModified(nullptr)
            , mType(nullptr)
            , mBlocked(nullptr)
        {
        }
    };

    /// \brief Base adapter for all refereceable record types
    /// Adapters that can handle nested tables, needs to return valid qvariant for parent columns
    template <typename RecordT>
    class BaseRefIdAdapter : public RefIdAdapter
    {
        UniversalId::Type mType;
        BaseColumns mBase;

    public:
        BaseRefIdAdapter(UniversalId::Type type, const BaseColumns& base);

        ESM::RefId getId(const RecordBase& record) const override;

        void setId(RecordBase& record, const std::string& id) override;

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.

        UniversalId::Type getType() const;
    };

    template <typename RecordT>
    BaseRefIdAdapter<RecordT>::BaseRefIdAdapter(UniversalId::Type type, const BaseColumns& base)
        : mType(type)
        , mBase(base)
    {
    }

    template <typename RecordT>
    void BaseRefIdAdapter<RecordT>::setId(RecordBase& record, const std::string& id)
    {
        (dynamic_cast<Record<RecordT>&>(record).get().mId) = ESM::RefId::stringRefId(id);
    }

    template <typename RecordT>
    ESM::RefId BaseRefIdAdapter<RecordT>::getId(const RecordBase& record) const
    {
        return dynamic_cast<const Record<RecordT>&>(record).get().mId;
    }

    template <typename RecordT>
    QVariant BaseRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record
            = static_cast<const Record<RecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

        if (column == mBase.mId)
            return QString::fromStdString(record.get().mId.toString());

        if (column == mBase.mModified)
        {
            if (record.mState == Record<RecordT>::State_Erased)
                return static_cast<int>(Record<RecordT>::State_Deleted);

            return static_cast<int>(record.mState);
        }

        if (column == mBase.mType)
            return static_cast<int>(mType);

        if (column == mBase.mBlocked)
            return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0;

        return QVariant();
    }

    template <typename RecordT>
    void BaseRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

        if (column == mBase.mModified)
            record.mState = static_cast<RecordBase::State>(value.toInt());
        else if (column == mBase.mBlocked)
        {
            RecordT record2 = record.get();

            if (value.toInt() != 0)
                record2.mRecordFlags |= ESM::FLAG_Blocked;
            else
                record2.mRecordFlags &= ~ESM::FLAG_Blocked;

            record.setModified(record2);
        }
    }

    template <typename RecordT>
    UniversalId::Type BaseRefIdAdapter<RecordT>::getType() const
    {
        return mType;
    }

    // NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects
    //       table at the moment).
    //
    //       Spellmaking - not persistent - currently not part of objects table
    //       Enchanting  - not persistent - currently not part of objects table
    //
    //       Leveled Creature - no model, so not persistent
    //       Leveled Item     - no model, so not persistent

    struct ModelColumns : public BaseColumns
    {
        const RefIdColumn* mModel;
        const RefIdColumn* mPersistence;

        ModelColumns(const BaseColumns& base)
            : BaseColumns(base)
            , mModel(nullptr)
            , mPersistence(nullptr)
        {
        }
    };

    /// \brief Adapter for IDs with models (all but levelled lists)
    template <typename RecordT>
    class ModelRefIdAdapter : public BaseRefIdAdapter<RecordT>
    {
        ModelColumns mModel;

    public:
        ModelRefIdAdapter(UniversalId::Type type, const ModelColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    ModelRefIdAdapter<RecordT>::ModelRefIdAdapter(UniversalId::Type type, const ModelColumns& columns)
        : BaseRefIdAdapter<RecordT>(type, columns)
        , mModel(columns)
    {
    }

    template <typename RecordT>
    QVariant ModelRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mModel.mModel)
            return QString::fromUtf8(record.get().mModel.c_str());

        if (column == mModel.mPersistence)
            return (record.get().mRecordFlags & ESM::FLAG_Persistent) != 0;

        return BaseRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void ModelRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mModel.mModel)
            record2.mModel = value.toString().toUtf8().constData();
        else if (column == mModel.mPersistence)
        {
            if (value.toInt() != 0)
                record2.mRecordFlags |= ESM::FLAG_Persistent;
            else
                record2.mRecordFlags &= ~ESM::FLAG_Persistent;
        }
        else
        {
            BaseRefIdAdapter<RecordT>::setData(column, data, index, value);
            return;
        }

        record.setModified(record2);
    }

    struct NameColumns : public ModelColumns
    {
        const RefIdColumn* mName;
        const RefIdColumn* mScript;

        NameColumns(const ModelColumns& base)
            : ModelColumns(base)
            , mName(nullptr)
            , mScript(nullptr)
        {
        }
    };

    /// \brief Adapter for IDs with names (all but levelled lists and statics)
    template <typename RecordT>
    class NameRefIdAdapter : public ModelRefIdAdapter<RecordT>
    {
        NameColumns mName;

    public:
        NameRefIdAdapter(UniversalId::Type type, const NameColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    NameRefIdAdapter<RecordT>::NameRefIdAdapter(UniversalId::Type type, const NameColumns& columns)
        : ModelRefIdAdapter<RecordT>(type, columns)
        , mName(columns)
    {
    }

    template <typename RecordT>
    QVariant NameRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mName.mName)
            return QString::fromUtf8(record.get().mName.c_str());

        if (column == mName.mScript)
            return QString::fromUtf8(record.get().mScript.getRefIdString().c_str());

        return ModelRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void NameRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mName.mName)
            record2.mName = value.toString().toUtf8().constData();
        else if (column == mName.mScript)
            record2.mScript = ESM::RefId::stringRefId(value.toString().toUtf8().constData());
        else
        {
            ModelRefIdAdapter<RecordT>::setData(column, data, index, value);
            return;
        }

        record.setModified(record2);
    }

    struct InventoryColumns : public NameColumns
    {
        const RefIdColumn* mIcon;
        const RefIdColumn* mWeight;
        const RefIdColumn* mValue;

        InventoryColumns(const NameColumns& base)
            : NameColumns(base)
            , mIcon(nullptr)
            , mWeight(nullptr)
            , mValue(nullptr)
        {
        }
    };

    /// \brief Adapter for IDs that can go into an inventory
    template <typename RecordT>
    class InventoryRefIdAdapter : public NameRefIdAdapter<RecordT>
    {
        InventoryColumns mInventory;

    public:
        InventoryRefIdAdapter(UniversalId::Type type, const InventoryColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    InventoryRefIdAdapter<RecordT>::InventoryRefIdAdapter(UniversalId::Type type, const InventoryColumns& columns)
        : NameRefIdAdapter<RecordT>(type, columns)
        , mInventory(columns)
    {
    }

    template <typename RecordT>
    QVariant InventoryRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mInventory.mIcon)
            return QString::fromUtf8(record.get().mIcon.c_str());

        if (column == mInventory.mWeight)
            return record.get().mData.mWeight;

        if (column == mInventory.mValue)
            return record.get().mData.mValue;

        return NameRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void InventoryRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mInventory.mIcon)
            record2.mIcon = value.toString().toUtf8().constData();
        else if (column == mInventory.mWeight)
            record2.mData.mWeight = value.toFloat();
        else if (column == mInventory.mValue)
            record2.mData.mValue = value.toInt();
        else
        {
            NameRefIdAdapter<RecordT>::setData(column, data, index, value);
            return;
        }

        record.setModified(record2);
    }

    struct PotionColumns : public InventoryColumns
    {
        const RefIdColumn* mEffects;

        PotionColumns(const InventoryColumns& columns);
    };

    class PotionRefIdAdapter : public InventoryRefIdAdapter<ESM::Potion>
    {
        PotionColumns mColumns;
        const RefIdColumn* mAutoCalc;

    public:
        PotionRefIdAdapter(const PotionColumns& columns, const RefIdColumn* autoCalc);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    struct IngredientColumns : public InventoryColumns
    {
        const RefIdColumn* mEffects;

        IngredientColumns(const InventoryColumns& columns);
    };

    class IngredientRefIdAdapter : public InventoryRefIdAdapter<ESM::Ingredient>
    {
        IngredientColumns mColumns;

    public:
        IngredientRefIdAdapter(const IngredientColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

    public:
        IngredEffectRefIdAdapter();
        IngredEffectRefIdAdapter(const IngredEffectRefIdAdapter&) = delete;
        IngredEffectRefIdAdapter& operator=(const IngredEffectRefIdAdapter&) = delete;
        ~IngredEffectRefIdAdapter() override = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    struct EnchantableColumns : public InventoryColumns
    {
        const RefIdColumn* mEnchantment;
        const RefIdColumn* mEnchantmentPoints;

        EnchantableColumns(const InventoryColumns& base)
            : InventoryColumns(base)
            , mEnchantment(nullptr)
            , mEnchantmentPoints(nullptr)
        {
        }
    };

    /// \brief Adapter for enchantable IDs
    template <typename RecordT>
    class EnchantableRefIdAdapter : public InventoryRefIdAdapter<RecordT>
    {
        EnchantableColumns mEnchantable;

    public:
        EnchantableRefIdAdapter(UniversalId::Type type, const EnchantableColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    EnchantableRefIdAdapter<RecordT>::EnchantableRefIdAdapter(UniversalId::Type type, const EnchantableColumns& columns)
        : InventoryRefIdAdapter<RecordT>(type, columns)
        , mEnchantable(columns)
    {
    }

    template <typename RecordT>
    QVariant EnchantableRefIdAdapter<RecordT>::getData(
        const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mEnchantable.mEnchantment)
            return QString::fromUtf8(record.get().mEnchant.getRefIdString().c_str());

        if (column == mEnchantable.mEnchantmentPoints)
            return static_cast<int>(record.get().mData.mEnchant);

        return InventoryRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void EnchantableRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mEnchantable.mEnchantment)
            record2.mEnchant = ESM::RefId::stringRefId(value.toString().toUtf8().constData());
        else if (column == mEnchantable.mEnchantmentPoints)
            record2.mData.mEnchant = value.toInt();
        else
        {
            InventoryRefIdAdapter<RecordT>::setData(column, data, index, value);
            return;
        }

        record.setModified(record2);
    }

    struct ToolColumns : public InventoryColumns
    {
        const RefIdColumn* mQuality;
        const RefIdColumn* mUses;

        ToolColumns(const InventoryColumns& base)
            : InventoryColumns(base)
            , mQuality(nullptr)
            , mUses(nullptr)
        {
        }
    };

    /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
    template <typename RecordT>
    class ToolRefIdAdapter : public InventoryRefIdAdapter<RecordT>
    {
        ToolColumns mTools;

    public:
        ToolRefIdAdapter(UniversalId::Type type, const ToolColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    ToolRefIdAdapter<RecordT>::ToolRefIdAdapter(UniversalId::Type type, const ToolColumns& columns)
        : InventoryRefIdAdapter<RecordT>(type, columns)
        , mTools(columns)
    {
    }

    template <typename RecordT>
    QVariant ToolRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mTools.mQuality)
            return record.get().mData.mQuality;

        if (column == mTools.mUses)
            return record.get().mData.mUses;

        return InventoryRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void ToolRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mTools.mQuality)
            record2.mData.mQuality = value.toFloat();
        else if (column == mTools.mUses)
            record2.mData.mUses = value.toInt();
        else
        {
            InventoryRefIdAdapter<RecordT>::setData(column, data, index, value);
            return;
        }

        record.setModified(record2);
    }

    struct ActorColumns : public NameColumns
    {
        const RefIdColumn* mHello;
        const RefIdColumn* mFlee;
        const RefIdColumn* mFight;
        const RefIdColumn* mAlarm;
        const RefIdColumn* mInventory;
        const RefIdColumn* mSpells;
        const RefIdColumn* mDestinations;
        const RefIdColumn* mAiPackages;
        std::map<const RefIdColumn*, unsigned int> mServices;

        ActorColumns(const NameColumns& base)
            : NameColumns(base)
            , mHello(nullptr)
            , mFlee(nullptr)
            , mFight(nullptr)
            , mAlarm(nullptr)
            , mInventory(nullptr)
            , mSpells(nullptr)
            , mDestinations(nullptr)
            , mAiPackages(nullptr)
        {
        }
    };

    /// \brief Adapter for actor IDs (handles common AI functionality)
    template <typename RecordT>
    class ActorRefIdAdapter : public NameRefIdAdapter<RecordT>
    {
        ActorColumns mActors;

    public:
        ActorRefIdAdapter(UniversalId::Type type, const ActorColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    ActorRefIdAdapter<RecordT>::ActorRefIdAdapter(UniversalId::Type type, const ActorColumns& columns)
        : NameRefIdAdapter<RecordT>(type, columns)
        , mActors(columns)
    {
    }

    template <typename RecordT>
    QVariant ActorRefIdAdapter<RecordT>::getData(const RefIdColumn* column, const RefIdData& data, int index) const
    {
        const Record<RecordT>& record = static_cast<const Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        if (column == mActors.mHello)
            return record.get().mAiData.mHello;

        if (column == mActors.mFlee)
            return record.get().mAiData.mFlee;

        if (column == mActors.mFight)
            return record.get().mAiData.mFight;

        if (column == mActors.mAlarm)
            return record.get().mAiData.mAlarm;

        if (column == mActors.mInventory)
            return QVariant::fromValue(ColumnBase::TableEdit_Full);

        if (column == mActors.mSpells)
            return QVariant::fromValue(ColumnBase::TableEdit_Full);

        if (column == mActors.mDestinations)
            return QVariant::fromValue(ColumnBase::TableEdit_Full);

        if (column == mActors.mAiPackages)
            return QVariant::fromValue(ColumnBase::TableEdit_Full);

        std::map<const RefIdColumn*, unsigned int>::const_iterator iter = mActors.mServices.find(column);

        if (iter != mActors.mServices.end())
            return (record.get().mAiData.mServices & iter->second) != 0;

        return NameRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void ActorRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        Record<RecordT>& record = static_cast<Record<RecordT>&>(
            data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter<RecordT>::getType())));

        RecordT record2 = record.get();
        if (column == mActors.mHello)
            record2.mAiData.mHello = value.toInt();
        else if (column == mActors.mFlee) // Flee, Fight and Alarm ratings are probabilities.
            record2.mAiData.mFlee = std::min(100, value.toInt());
        else if (column == mActors.mFight)
            record2.mAiData.mFight = std::min(100, value.toInt());
        else if (column == mActors.mAlarm)
            record2.mAiData.mAlarm = std::min(100, value.toInt());
        else
        {
            typename std::map<const RefIdColumn*, unsigned int>::const_iterator iter = mActors.mServices.find(column);
            if (iter != mActors.mServices.end())
            {
                if (value.toInt() != 0)
                    record2.mAiData.mServices |= iter->second;
                else
                    record2.mAiData.mServices &= ~iter->second;
            }
            else
            {
                NameRefIdAdapter<RecordT>::setData(column, data, index, value);
                return;
            }
        }

        record.setModified(record2);
    }

    class ApparatusRefIdAdapter : public InventoryRefIdAdapter<ESM::Apparatus>
    {
        const RefIdColumn* mType;
        const RefIdColumn* mQuality;

    public:
        ApparatusRefIdAdapter(const InventoryColumns& columns, const RefIdColumn* type, const RefIdColumn* quality);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class ArmorRefIdAdapter : public EnchantableRefIdAdapter<ESM::Armor>
    {
        const RefIdColumn* mType;
        const RefIdColumn* mHealth;
        const RefIdColumn* mArmor;
        const RefIdColumn* mPartRef;

    public:
        ArmorRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* type, const RefIdColumn* health,
            const RefIdColumn* armor, const RefIdColumn* partRef);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class BookRefIdAdapter : public EnchantableRefIdAdapter<ESM::Book>
    {
        const RefIdColumn* mBookType;
        const RefIdColumn* mSkill;
        const RefIdColumn* mText;

    public:
        BookRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* bookType, const RefIdColumn* skill,
            const RefIdColumn* text);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class ClothingRefIdAdapter : public EnchantableRefIdAdapter<ESM::Clothing>
    {
        const RefIdColumn* mType;
        const RefIdColumn* mPartRef;

    public:
        ClothingRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* type, const RefIdColumn* partRef);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class ContainerRefIdAdapter : public NameRefIdAdapter<ESM::Container>
    {
        const RefIdColumn* mWeight;
        const RefIdColumn* mOrganic;
        const RefIdColumn* mRespawn;
        const RefIdColumn* mContent;

    public:
        ContainerRefIdAdapter(const NameColumns& columns, const RefIdColumn* weight, const RefIdColumn* organic,
            const RefIdColumn* respawn, const RefIdColumn* content);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    struct CreatureColumns : public ActorColumns
    {
        std::map<const RefIdColumn*, unsigned int> mFlags;
        const RefIdColumn* mType;
        const RefIdColumn* mScale;
        const RefIdColumn* mOriginal;
        const RefIdColumn* mAttributes;
        const RefIdColumn* mAttacks;
        const RefIdColumn* mMisc;
        const RefIdColumn* mBloodType;

        CreatureColumns(const ActorColumns& actorColumns);
    };

    class CreatureRefIdAdapter : public ActorRefIdAdapter<ESM::Creature>
    {
        CreatureColumns mColumns;

    public:
        CreatureRefIdAdapter(const CreatureColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class DoorRefIdAdapter : public NameRefIdAdapter<ESM::Door>
    {
        const RefIdColumn* mOpenSound;
        const RefIdColumn* mCloseSound;

    public:
        DoorRefIdAdapter(const NameColumns& columns, const RefIdColumn* openSound, const RefIdColumn* closeSound);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    struct LightColumns : public InventoryColumns
    {
        const RefIdColumn* mTime;
        const RefIdColumn* mRadius;
        const RefIdColumn* mColor;
        const RefIdColumn* mSound;
        const RefIdColumn* mEmitterType;
        std::map<const RefIdColumn*, unsigned int> mFlags;

        LightColumns(const InventoryColumns& columns);
    };

    class LightRefIdAdapter : public InventoryRefIdAdapter<ESM::Light>
    {
        LightColumns mColumns;

    public:
        LightRefIdAdapter(const LightColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class MiscRefIdAdapter : public InventoryRefIdAdapter<ESM::Miscellaneous>
    {
        const RefIdColumn* mKey;

    public:
        MiscRefIdAdapter(const InventoryColumns& columns, const RefIdColumn* key);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    struct NpcColumns : public ActorColumns
    {
        std::map<const RefIdColumn*, unsigned int> mFlags;
        const RefIdColumn* mRace;
        const RefIdColumn* mClass;
        const RefIdColumn* mFaction;
        const RefIdColumn* mHair;
        const RefIdColumn* mHead;
        const RefIdColumn* mAttributes; // depends on npc type
        const RefIdColumn* mSkills; // depends on npc type
        const RefIdColumn* mMisc; // may depend on npc type, e.g. FactionID
        const RefIdColumn* mBloodType;
        const RefIdColumn* mGender;

        NpcColumns(const ActorColumns& actorColumns);
    };

    class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC>
    {
        NpcColumns mColumns;

    public:
        NpcRefIdAdapter(const NpcColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    struct WeaponColumns : public EnchantableColumns
    {
        const RefIdColumn* mType;
        const RefIdColumn* mHealth;
        const RefIdColumn* mSpeed;
        const RefIdColumn* mReach;
        const RefIdColumn* mChop[2];
        const RefIdColumn* mSlash[2];
        const RefIdColumn* mThrust[2];
        std::map<const RefIdColumn*, unsigned int> mFlags;

        WeaponColumns(const EnchantableColumns& columns);
    };

    class WeaponRefIdAdapter : public EnchantableRefIdAdapter<ESM::Weapon>
    {
        WeaponColumns mColumns;

    public:
        WeaponRefIdAdapter(const WeaponColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        NpcAttributesRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        NpcSkillsRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        NpcMiscRefIdAdapter() = default;
        NpcMiscRefIdAdapter(const NpcMiscRefIdAdapter&) = delete;
        NpcMiscRefIdAdapter& operator=(const NpcMiscRefIdAdapter&) = delete;
        ~NpcMiscRefIdAdapter() override = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        CreatureAttributesRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        CreatureAttackRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase
    {
    public:
        CreatureMiscRefIdAdapter() = default;
        CreatureMiscRefIdAdapter(const CreatureMiscRefIdAdapter&) = delete;
        CreatureMiscRefIdAdapter& operator=(const CreatureMiscRefIdAdapter&) = delete;
        ~CreatureMiscRefIdAdapter() override = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override;

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override;

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override;

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override;

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override;

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override;

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override;

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override;
    };

    template <typename ESXRecordT>
    class EffectsListAdapter;

    template <typename ESXRecordT>
    class EffectsRefIdAdapter : public EffectsListAdapter<ESXRecordT>, public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        EffectsRefIdAdapter(const EffectsRefIdAdapter&);
        EffectsRefIdAdapter& operator=(const EffectsRefIdAdapter&);

    public:
        EffectsRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~EffectsRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            EffectsListAdapter<ESXRecordT>::addRow(record, position);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            EffectsListAdapter<ESXRecordT>::removeRow(record, rowToRemove);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            EffectsListAdapter<ESXRecordT>::setTable(record, nestedTable);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            return EffectsListAdapter<ESXRecordT>::table(record);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            return EffectsListAdapter<ESXRecordT>::getData(record, subRowIndex, subColIndex);
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            EffectsListAdapter<ESXRecordT>::setData(record, value, subRowIndex, subColIndex);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override
        {
            const Record<ESXRecordT> record; // not used, just a dummy
            return EffectsListAdapter<ESXRecordT>::getColumnsCount(record);
        }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            return EffectsListAdapter<ESXRecordT>::getRowsCount(record);
        }
    };

    template <typename ESXRecordT>
    class NestedInventoryRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        NestedInventoryRefIdAdapter(const NestedInventoryRefIdAdapter&);
        NestedInventoryRefIdAdapter& operator=(const NestedInventoryRefIdAdapter&);

    public:
        NestedInventoryRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~NestedInventoryRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT container = record.get();

            std::vector<ESM::ContItem>& list = container.mInventory.mList;

            ESM::ContItem newRow = ESM::ContItem();

            if (position >= (int)list.size())
                list.push_back(newRow);
            else
                list.insert(list.begin() + position, newRow);

            record.setModified(container);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT container = record.get();

            std::vector<ESM::ContItem>& list = container.mInventory.mList;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(container);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT container = record.get();

            container.mInventory.mList
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::ContItem>>&>(nestedTable).mNestedTable;

            record.setModified(container);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::ContItem>>(record.get().mInventory.mList);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::ContItem>& list = record.get().mInventory.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::ContItem& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0:
                    return QString::fromUtf8(content.mItem.getRefIdString().c_str());
                case 1:
                    return content.mCount;
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT container = record.get();
            std::vector<ESM::ContItem>& list = container.mInventory.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            switch (subColIndex)
            {
                case 0:
                    list.at(subRowIndex).mItem = ESM::RefId::stringRefId(value.toString().toUtf8().constData());
                    break;

                case 1:
                    list.at(subRowIndex).mCount = value.toInt();
                    break;

                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }

            record.setModified(container);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 2; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mInventory.mList.size());
        }
    };

    template <typename ESXRecordT>
    class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        NestedSpellRefIdAdapter(const NestedSpellRefIdAdapter&);
        NestedSpellRefIdAdapter& operator=(const NestedSpellRefIdAdapter&);

    public:
        NestedSpellRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~NestedSpellRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT caster = record.get();

            std::vector<ESM::RefId>& list = caster.mSpells.mList;

            ESM::RefId newString;

            if (position >= (int)list.size())
                list.push_back(newString);
            else
                list.insert(list.begin() + position, newString);

            record.setModified(caster);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT caster = record.get();

            std::vector<ESM::RefId>& list = caster.mSpells.mList;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(caster);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT caster = record.get();

            caster.mSpells.mList
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::RefId>>&>(nestedTable).mNestedTable;

            record.setModified(caster);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::RefId>>(record.get().mSpells.mList);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::RefId>& list = record.get().mSpells.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::RefId& content = list.at(subRowIndex);

            if (subColIndex == 0)
                return QString::fromUtf8(content.getRefIdString().c_str());
            else
                throw std::runtime_error("Trying to access non-existing column in the nested table!");
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT caster = record.get();
            std::vector<ESM::RefId>& list = caster.mSpells.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            if (subColIndex == 0)
                list.at(subRowIndex) = ESM::RefId::stringRefId(value.toString().toUtf8().constData());
            else
                throw std::runtime_error("Trying to access non-existing column in the nested table!");

            record.setModified(caster);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 1; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mSpells.mList.size());
        }
    };

    template <typename ESXRecordT>
    class NestedTravelRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        NestedTravelRefIdAdapter(const NestedTravelRefIdAdapter&);
        NestedTravelRefIdAdapter& operator=(const NestedTravelRefIdAdapter&);

    public:
        NestedTravelRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~NestedTravelRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT traveller = record.get();

            std::vector<ESM::Transport::Dest>& list = traveller.mTransport.mList;

            ESM::Position newPos;
            for (unsigned i = 0; i < 3; ++i)
            {
                newPos.pos[i] = 0;
                newPos.rot[i] = 0;
            }

            ESM::Transport::Dest newRow;
            newRow.mPos = newPos;
            newRow.mCellName.clear();

            if (position >= (int)list.size())
                list.push_back(newRow);
            else
                list.insert(list.begin() + position, newRow);

            record.setModified(traveller);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT traveller = record.get();

            std::vector<ESM::Transport::Dest>& list = traveller.mTransport.mList;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(traveller);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT traveller = record.get();

            traveller.mTransport.mList
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::Transport::Dest>>&>(nestedTable)
                      .mNestedTable;

            record.setModified(traveller);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::Transport::Dest>>(record.get().mTransport.mList);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::Transport::Dest>& list = record.get().mTransport.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::Transport::Dest& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0:
                    return QString::fromUtf8(content.mCellName.c_str());
                case 1:
                    return content.mPos.pos[0];
                case 2:
                    return content.mPos.pos[1];
                case 3:
                    return content.mPos.pos[2];
                case 4:
                    return content.mPos.rot[0];
                case 5:
                    return content.mPos.rot[1];
                case 6:
                    return content.mPos.rot[2];
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT traveller = record.get();
            std::vector<ESM::Transport::Dest>& list = traveller.mTransport.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            switch (subColIndex)
            {
                case 0:
                    list.at(subRowIndex).mCellName = value.toString().toUtf8().constData();
                    break;
                case 1:
                    list.at(subRowIndex).mPos.pos[0] = value.toFloat();
                    break;
                case 2:
                    list.at(subRowIndex).mPos.pos[1] = value.toFloat();
                    break;
                case 3:
                    list.at(subRowIndex).mPos.pos[2] = value.toFloat();
                    break;
                case 4:
                    list.at(subRowIndex).mPos.rot[0] = value.toFloat();
                    break;
                case 5:
                    list.at(subRowIndex).mPos.rot[1] = value.toFloat();
                    break;
                case 6:
                    list.at(subRowIndex).mPos.rot[2] = value.toFloat();
                    break;
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }

            record.setModified(traveller);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 7; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mTransport.mList.size());
        }
    };

    template <typename ESXRecordT>
    class ActorAiRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        ActorAiRefIdAdapter(const ActorAiRefIdAdapter&);
        ActorAiRefIdAdapter& operator=(const ActorAiRefIdAdapter&);

    public:
        ActorAiRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~ActorAiRefIdAdapter() = default;

        // FIXME: should check if the AI package type is already in the list and use a default
        //        that wasn't used already (in extreme case do not add anything at all?
        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT actor = record.get();

            std::vector<ESM::AIPackage>& list = actor.mAiPackage.mList;

            ESM::AIPackage newRow;
            newRow.mType = ESM::AI_Wander;
            newRow.mWander.mDistance = 0;
            newRow.mWander.mDuration = 0;
            newRow.mWander.mTimeOfDay = 0;
            for (int i = 0; i < 8; ++i)
                newRow.mWander.mIdle[i] = 0;
            newRow.mWander.mShouldRepeat = 1;
            newRow.mCellName.clear();

            if (position >= (int)list.size())
                list.push_back(newRow);
            else
                list.insert(list.begin() + position, newRow);

            record.setModified(actor);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT actor = record.get();

            std::vector<ESM::AIPackage>& list = actor.mAiPackage.mList;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(actor);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT actor = record.get();

            actor.mAiPackage.mList
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::AIPackage>>&>(nestedTable)
                      .mNestedTable;

            record.setModified(actor);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::AIPackage>>(record.get().mAiPackage.mList);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::AIPackage>& list = record.get().mAiPackage.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::AIPackage& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0:
                    // FIXME: should more than one AI package type be allowed?  Check vanilla
                    switch (content.mType)
                    {
                        case ESM::AI_Wander:
                            return 0;
                        case ESM::AI_Travel:
                            return 1;
                        case ESM::AI_Follow:
                            return 2;
                        case ESM::AI_Escort:
                            return 3;
                        case ESM::AI_Activate:
                            return 4;
                        default:
                            return QVariant();
                    }
                case 1: // wander dist
                    if (content.mType == ESM::AI_Wander)
                        return content.mWander.mDistance;
                    else
                        return QVariant();
                case 2: // wander/follow dur
                    if (content.mType == ESM::AI_Wander)
                        return content.mWander.mDuration;
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return content.mTarget.mDuration;
                    else
                        return QVariant();
                case 3: // wander ToD
                    if (content.mType == ESM::AI_Wander)
                        return content.mWander.mTimeOfDay; // FIXME: not sure of the format
                    else
                        return QVariant();
                case 4: // wander idle
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                case 10:
                case 11:
                    if (content.mType == ESM::AI_Wander)
                        return static_cast<int>(content.mWander.mIdle[subColIndex - 4]);
                    else
                        return QVariant();
                case 12: // repeat
                    if (content.mType == ESM::AI_Wander)
                        return content.mWander.mShouldRepeat != 0;
                    else if (content.mType == ESM::AI_Travel)
                        return content.mTravel.mShouldRepeat != 0;
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return content.mTarget.mShouldRepeat != 0;
                    else if (content.mType == ESM::AI_Activate)
                        return content.mActivate.mShouldRepeat != 0;
                    else
                        return QVariant();
                case 13: // activate name
                    if (content.mType == ESM::AI_Activate)
                        return QString(content.mActivate.mName.toString().c_str());
                    else
                        return QVariant();
                case 14: // target id
                    if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return QString(content.mTarget.mId.toString().c_str());
                    else
                        return QVariant();
                case 15: // target cell
                    if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return QString::fromUtf8(content.mCellName.c_str());
                    else
                        return QVariant();
                case 16:
                    if (content.mType == ESM::AI_Travel)
                        return content.mTravel.mX;
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return content.mTarget.mX;
                    else
                        return QVariant();
                case 17:
                    if (content.mType == ESM::AI_Travel)
                        return content.mTravel.mY;
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return content.mTarget.mY;
                    else
                        return QVariant();
                case 18:
                    if (content.mType == ESM::AI_Travel)
                        return content.mTravel.mZ;
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        return content.mTarget.mZ;
                    else
                        return QVariant();
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT actor = record.get();
            std::vector<ESM::AIPackage>& list = actor.mAiPackage.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            ESM::AIPackage& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0: // ai package type
                    switch (value.toInt())
                    {
                        case 0:
                            content.mType = ESM::AI_Wander;
                            break;
                        case 1:
                            content.mType = ESM::AI_Travel;
                            break;
                        case 2:
                            content.mType = ESM::AI_Follow;
                            break;
                        case 3:
                            content.mType = ESM::AI_Escort;
                            break;
                        case 4:
                            content.mType = ESM::AI_Activate;
                            break;
                        default:
                            return; // return without saving
                    }
                    break; // always save

                case 1:
                    if (content.mType == ESM::AI_Wander)
                        content.mWander.mDistance = static_cast<short>(value.toInt());
                    else
                        return; // return without saving

                    break; // always save
                case 2:
                    if (content.mType == ESM::AI_Wander)
                        content.mWander.mDuration = static_cast<short>(value.toInt());
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mTarget.mDuration = static_cast<short>(value.toInt());
                    else
                        return; // return without saving
                    break;
                case 3:
                    if (content.mType == ESM::AI_Wander)
                        content.mWander.mTimeOfDay = static_cast<unsigned char>(value.toInt());
                    else
                        return; // return without saving

                    break; // always save
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                case 10:
                case 11:
                    if (content.mType == ESM::AI_Wander)
                        content.mWander.mIdle[subColIndex - 4] = static_cast<unsigned char>(value.toInt());
                    else
                        return; // return without saving

                    break; // always save
                case 12:
                    if (content.mType == ESM::AI_Wander)
                        content.mWander.mShouldRepeat = static_cast<unsigned char>(value.toInt());
                    else if (content.mType == ESM::AI_Travel)
                        content.mTravel.mShouldRepeat = static_cast<unsigned char>(value.toInt());
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mTarget.mShouldRepeat = static_cast<unsigned char>(value.toInt());
                    else if (content.mType == ESM::AI_Activate)
                        content.mActivate.mShouldRepeat = static_cast<unsigned char>(value.toInt());
                    else
                        return; // return without saving

                    break; // always save
                case 13: // NAME32
                    if (content.mType == ESM::AI_Activate)
                    {
                        const QByteArray name = value.toString().toUtf8();
                        content.mActivate.mName.assign(std::string_view(name.constData(), name.size()));
                    }
                    else
                        return; // return without saving

                    break; // always save
                case 14: // NAME32
                    if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                    {
                        const QByteArray id = value.toString().toUtf8();
                        content.mTarget.mId.assign(std::string_view(id.constData(), id.size()));
                    }
                    else
                        return; // return without saving

                    break; // always save
                case 15:
                    if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mCellName = std::string(value.toString().toUtf8().constData());
                    else
                        return; // return without saving

                    break; // always save
                case 16:
                    if (content.mType == ESM::AI_Travel)
                        content.mTravel.mX = value.toFloat();
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mTarget.mX = value.toFloat();
                    else
                        return; // return without saving

                    break; // always save
                case 17:
                    if (content.mType == ESM::AI_Travel)
                        content.mTravel.mY = value.toFloat();
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mTarget.mY = value.toFloat();
                    else
                        return; // return without saving

                    break; // always save
                case 18:
                    if (content.mType == ESM::AI_Travel)
                        content.mTravel.mZ = value.toFloat();
                    else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
                        content.mTarget.mZ = value.toFloat();
                    else
                        return; // return without saving

                    break; // always save
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }

            record.setModified(actor);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 19; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mAiPackage.mList.size());
        }
    };

    template <typename ESXRecordT>
    class BodyPartRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        BodyPartRefIdAdapter(const BodyPartRefIdAdapter&);
        BodyPartRefIdAdapter& operator=(const BodyPartRefIdAdapter&);

    public:
        BodyPartRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~BodyPartRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT apparel = record.get();

            std::vector<ESM::PartReference>& list = apparel.mParts.mParts;

            ESM::PartReference newPart;
            newPart.mPart = 0; // 0 == head
            newPart.mMale = ESM::RefId();
            newPart.mFemale = ESM::RefId();

            if (position >= (int)list.size())
                list.push_back(newPart);
            else
                list.insert(list.begin() + position, newPart);

            record.setModified(apparel);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT apparel = record.get();

            std::vector<ESM::PartReference>& list = apparel.mParts.mParts;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(apparel);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT apparel = record.get();

            apparel.mParts.mParts
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::PartReference>>&>(nestedTable)
                      .mNestedTable;

            record.setModified(apparel);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::PartReference>>(record.get().mParts.mParts);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::PartReference>& list = record.get().mParts.mParts;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::PartReference& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0:
                {
                    if (content.mPart < ESM::PRT_Count)
                        return content.mPart;
                    else
                        throw std::runtime_error("Part Reference Type unexpected value");
                }
                case 1:
                    return QString(content.mMale.getRefIdString().c_str());
                case 2:
                    return QString(content.mFemale.getRefIdString().c_str());
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT apparel = record.get();
            std::vector<ESM::PartReference>& list = apparel.mParts.mParts;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            switch (subColIndex)
            {
                case 0:
                    list.at(subRowIndex).mPart = static_cast<unsigned char>(value.toInt());
                    break;
                case 1:
                    list.at(subRowIndex).mMale = ESM::RefId::stringRefId(value.toString().toStdString());
                    break;
                case 2:
                    list.at(subRowIndex).mFemale = ESM::RefId::stringRefId(value.toString().toStdString());
                    break;
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }

            record.setModified(apparel);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 3; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mParts.mParts.size());
        }
    };

    struct LevListColumns : public BaseColumns
    {
        const RefIdColumn* mLevList;
        const RefIdColumn* mNestedListLevList;

        LevListColumns(const BaseColumns& base)
            : BaseColumns(base)
            , mLevList(nullptr)
            , mNestedListLevList(nullptr)
        {
        }
    };

    template <typename RecordT>
    class LevelledListRefIdAdapter : public BaseRefIdAdapter<RecordT>
    {
        LevListColumns mLevList;

    public:
        LevelledListRefIdAdapter(UniversalId::Type type, const LevListColumns& columns);

        QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override;

        void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override;
        ///< If the data type does not match an exception is thrown.
    };

    template <typename RecordT>
    LevelledListRefIdAdapter<RecordT>::LevelledListRefIdAdapter(UniversalId::Type type, const LevListColumns& columns)
        : BaseRefIdAdapter<RecordT>(type, columns)
        , mLevList(columns)
    {
    }

    template <typename RecordT>
    QVariant LevelledListRefIdAdapter<RecordT>::getData(
        const RefIdColumn* column, const RefIdData& data, int index) const
    {
        if (column == mLevList.mLevList || column == mLevList.mNestedListLevList)
            return QVariant::fromValue(ColumnBase::TableEdit_Full);

        return BaseRefIdAdapter<RecordT>::getData(column, data, index);
    }

    template <typename RecordT>
    void LevelledListRefIdAdapter<RecordT>::setData(
        const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const
    {
        BaseRefIdAdapter<RecordT>::setData(column, data, index, value);
        return;
    }

    // for non-tables
    template <typename ESXRecordT>
    class NestedListLevListRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        NestedListLevListRefIdAdapter(const NestedListLevListRefIdAdapter&);
        NestedListLevListRefIdAdapter& operator=(const NestedListLevListRefIdAdapter&);

    public:
        NestedListLevListRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~NestedListLevListRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            throw std::logic_error("cannot add a row to a fixed table");
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            throw std::logic_error("cannot remove a row to a fixed table");
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            throw std::logic_error("table operation not supported");
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            throw std::logic_error("table operation not supported");
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            if (mType == UniversalId::Type_CreatureLevelledList)
            {
                switch (subColIndex)
                {
                    case 0:
                        return QVariant(); // disable the checkbox editor
                    case 1:
                        return record.get().mFlags & ESM::CreatureLevList::AllLevels;
                    case 2:
                        return static_cast<int>(record.get().mChanceNone);
                    default:
                        throw std::runtime_error("Trying to access non-existing column in levelled creatues!");
                }
            }
            else
            {
                switch (subColIndex)
                {
                    case 0:
                        return record.get().mFlags & ESM::ItemLevList::Each;
                    case 1:
                        return record.get().mFlags & ESM::ItemLevList::AllLevels;
                    case 2:
                        return static_cast<int>(record.get().mChanceNone);
                    default:
                        throw std::runtime_error("Trying to access non-existing column in levelled items!");
                }
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT leveled = record.get();

            if (mType == UniversalId::Type_CreatureLevelledList)
            {
                switch (subColIndex)
                {
                    case 0:
                        return; // return without saving
                    case 1:
                    {
                        if (value.toBool())
                        {
                            leveled.mFlags |= ESM::CreatureLevList::AllLevels;
                            break;
                        }
                        else
                        {
                            leveled.mFlags &= ~ESM::CreatureLevList::AllLevels;
                            break;
                        }
                    }
                    case 2:
                        leveled.mChanceNone = static_cast<unsigned char>(value.toInt());
                        break;
                    default:
                        throw std::runtime_error("Trying to set non-existing column in levelled creatures!");
                }
            }
            else
            {
                switch (subColIndex)
                {
                    case 0:
                    {
                        if (value.toBool())
                        {
                            leveled.mFlags |= ESM::ItemLevList::Each;
                            break;
                        }
                        else
                        {
                            leveled.mFlags &= ~ESM::ItemLevList::Each;
                            break;
                        }
                    }
                    case 1:
                    {
                        if (value.toBool())
                        {
                            leveled.mFlags |= ESM::ItemLevList::AllLevels;
                            break;
                        }
                        else
                        {
                            leveled.mFlags &= ~ESM::ItemLevList::AllLevels;
                            break;
                        }
                    }
                    case 2:
                        leveled.mChanceNone = static_cast<unsigned char>(value.toInt());
                        break;
                    default:
                        throw std::runtime_error("Trying to set non-existing column in levelled items!");
                }
            }

            record.setModified(leveled);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 3; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            return 1; // fixed at size 1
        }
    };

    // for tables
    template <typename ESXRecordT>
    class NestedLevListRefIdAdapter : public NestedRefIdAdapterBase
    {
        UniversalId::Type mType;

        // not implemented
        NestedLevListRefIdAdapter(const NestedLevListRefIdAdapter&);
        NestedLevListRefIdAdapter& operator=(const NestedLevListRefIdAdapter&);

    public:
        NestedLevListRefIdAdapter(UniversalId::Type type)
            : mType(type)
        {
        }

        virtual ~NestedLevListRefIdAdapter() = default;

        void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT leveled = record.get();

            std::vector<ESM::LevelledListBase::LevelItem>& list = leveled.mList;

            ESM::LevelledListBase::LevelItem newItem;
            newItem.mId = ESM::RefId();
            newItem.mLevel = 0;

            if (position >= (int)list.size())
                list.push_back(newItem);
            else
                list.insert(list.begin() + position, newItem);

            record.setModified(leveled);
        }

        void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT leveled = record.get();

            std::vector<ESM::LevelledListBase::LevelItem>& list = leveled.mList;

            if (rowToRemove < 0 || rowToRemove >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            list.erase(list.begin() + rowToRemove);

            record.setModified(leveled);
        }

        void setNestedTable(const RefIdColumn* column, RefIdData& data, int index,
            const NestedTableWrapperBase& nestedTable) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));
            ESXRecordT leveled = record.get();

            leveled.mList
                = static_cast<const NestedTableWrapper<std::vector<typename ESM::LevelledListBase::LevelItem>>&>(
                    nestedTable)
                      .mNestedTable;

            record.setModified(leveled);
        }

        NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            // deleted by dtor of NestedTableStoring
            return new NestedTableWrapper<std::vector<typename ESM::LevelledListBase::LevelItem>>(record.get().mList);
        }

        QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex,
            int subColIndex) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            const std::vector<ESM::LevelledListBase::LevelItem>& list = record.get().mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            const ESM::LevelledListBase::LevelItem& content = list.at(subRowIndex);

            switch (subColIndex)
            {
                case 0:
                    return QString(content.mId.getRefIdString().c_str());
                case 1:
                    return content.mLevel;
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }
        }

        void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex,
            int subColIndex) const override
        {
            Record<ESXRecordT>& record
                = static_cast<Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(row, mType)));
            ESXRecordT leveled = record.get();
            std::vector<ESM::LevelledListBase::LevelItem>& list = leveled.mList;

            if (subRowIndex < 0 || subRowIndex >= static_cast<int>(list.size()))
                throw std::runtime_error("index out of range");

            switch (subColIndex)
            {
                case 0:
                    list.at(subRowIndex).mId = ESM::RefId::stringRefId(value.toString().toStdString());
                    break;
                case 1:
                    list.at(subRowIndex).mLevel = static_cast<short>(value.toInt());
                    break;
                default:
                    throw std::runtime_error("Trying to access non-existing column in the nested table!");
            }

            record.setModified(leveled);
        }

        int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 2; }

        int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override
        {
            const Record<ESXRecordT>& record
                = static_cast<const Record<ESXRecordT>&>(data.getRecord(RefIdData::LocalIndex(index, mType)));

            return static_cast<int>(record.get().mList.size());
        }
    };
}

#endif
