#ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP
#define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP

#include <vector>

#include "niffile.hpp"
#include "nifstream.hpp"

namespace Nif
{

    /** A reference to another record. It is read as an index from the
        NIF, and later looked up in the index table to get an actual
        pointer.
    */
    template <class X>
    class RecordPtrT
    {
        union
        {
            intptr_t index;
            X* mPtr;
        };

    public:
        RecordPtrT()
            : index(-2)
        {
        }

        RecordPtrT(X* ptr)
            : mPtr(ptr)
        {
        }

        /// Read the index from the nif
        void read(NIFStream* nif)
        {
            // Can only read the index once
            assert(index == -2);

            // Store the index for later
            index = nif->get<int32_t>();
            assert(index >= -1);
        }

        /// Resolve index to pointer
        void post(Reader& nif)
        {
            if (index < 0)
                mPtr = nullptr;
            else
            {
                Record* r = nif.getRecord(index);
                // And cast it
                mPtr = dynamic_cast<X*>(r);
                assert(mPtr != nullptr);
            }
        }

        /// Look up the actual object from the index
        const X* getPtr() const
        {
            assert(mPtr != nullptr);
            return mPtr;
        }
        X* getPtr()
        {
            assert(mPtr != nullptr);
            return mPtr;
        }

        const X& get() const { return *getPtr(); }
        X& get() { return *getPtr(); }

        /// Syntactic sugar
        const X* operator->() const { return getPtr(); }
        X* operator->() { return getPtr(); }

        /// Pointers are allowed to be empty
        bool empty() const { return mPtr == nullptr; }
    };

    /** A list of references to other records. These are read as a list,
        and later converted to pointers as needed. Not an optimized
        implementation.
     */
    template <class X>
    using RecordListT = std::vector<RecordPtrT<X>>;

    template <class T>
    void readRecordList(NIFStream* nif, RecordListT<T>& list)
    {
        const std::uint32_t length = nif->get<std::uint32_t>();

        // No reasonable list can hit this generous limit
        if (length >= (1 << 24))
            throw std::runtime_error("Record list too long: " + std::to_string(length));

        list.resize(length);

        for (auto& value : list)
            value.read(nif);
    }

    template <class T>
    void postRecordList(Reader& nif, RecordListT<T>& list)
    {
        for (auto& value : list)
            value.post(nif);
    }

    struct NiAVObject;
    struct Extra;
    struct NiProperty;
    struct NiUVData;
    struct NiPosData;
    struct NiVisData;
    struct NiTimeController;
    struct NiObjectNET;
    struct NiSkinData;
    struct NiFloatData;
    struct NiMorphData;
    struct NiPixelData;
    struct NiColorData;
    struct NiKeyframeData;
    struct NiTriStripsData;
    struct NiSkinInstance;
    struct NiSourceTexture;
    struct NiPalette;
    struct NiParticleModifier;
    struct BSMasterParticleSystem;
    struct NiParticleSystem;
    struct NiPSysCollider;
    struct NiPSysColliderManager;
    struct NiPSysEmitterCtlrData;
    struct NiPSysModifier;
    struct NiPSysSpawnModifier;
    struct NiBoolData;
    struct NiBSplineData;
    struct NiBSplineBasisData;
    struct NiSkinPartition;
    struct BSShaderTextureSet;
    struct NiTriBasedGeom;
    struct NiGeometryData;
    struct BSShaderProperty;
    struct NiAlphaProperty;
    struct NiCollisionObject;
    struct bhkSystem;
    struct bhkWorldObject;
    struct bhkShape;
    struct bhkSerializable;
    struct bhkEntity;
    struct bhkConvexShape;
    struct bhkRigidBody;
    struct hkPackedNiTriStripsData;
    struct NiAccumulator;
    struct NiInterpolator;
    struct NiStringPalette;
    struct NiControllerManager;
    struct NiBlendInterpolator;
    struct NiDefaultAVObjectPalette;
    struct NiControllerSequence;
    struct bhkCompressedMeshShapeData;
    struct BSMultiBound;
    struct BSMultiBoundData;
    struct BSSkinBoneData;
    struct BSAnimNote;
    struct BSAnimNotes;

    using NiAVObjectPtr = RecordPtrT<NiAVObject>;
    using ExtraPtr = RecordPtrT<Extra>;
    using NiUVDataPtr = RecordPtrT<NiUVData>;
    using NiPosDataPtr = RecordPtrT<NiPosData>;
    using NiVisDataPtr = RecordPtrT<NiVisData>;
    using NiTimeControllerPtr = RecordPtrT<NiTimeController>;
    using NiObjectNETPtr = RecordPtrT<NiObjectNET>;
    using NiSkinDataPtr = RecordPtrT<NiSkinData>;
    using NiMorphDataPtr = RecordPtrT<NiMorphData>;
    using NiPixelDataPtr = RecordPtrT<NiPixelData>;
    using NiFloatDataPtr = RecordPtrT<NiFloatData>;
    using NiColorDataPtr = RecordPtrT<NiColorData>;
    using NiKeyframeDataPtr = RecordPtrT<NiKeyframeData>;
    using NiSkinInstancePtr = RecordPtrT<NiSkinInstance>;
    using NiSourceTexturePtr = RecordPtrT<NiSourceTexture>;
    using NiPalettePtr = RecordPtrT<NiPalette>;
    using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
    using BSMasterParticleSystemPtr = RecordPtrT<BSMasterParticleSystem>;
    using NiParticleSystemPtr = RecordPtrT<NiParticleSystem>;
    using NiPSysColliderPtr = RecordPtrT<NiPSysCollider>;
    using NiPSysColliderManagerPtr = RecordPtrT<NiPSysColliderManager>;
    using NiPSysEmitterCtlrDataPtr = RecordPtrT<NiPSysEmitterCtlrData>;
    using NiPSysModifierPtr = RecordPtrT<NiPSysModifier>;
    using NiPSysSpawnModifierPtr = RecordPtrT<NiPSysSpawnModifier>;
    using NiBoolDataPtr = RecordPtrT<NiBoolData>;
    using NiBSplineDataPtr = RecordPtrT<NiBSplineData>;
    using NiBSplineBasisDataPtr = RecordPtrT<NiBSplineBasisData>;
    using NiSkinPartitionPtr = RecordPtrT<NiSkinPartition>;
    using BSShaderTextureSetPtr = RecordPtrT<BSShaderTextureSet>;
    using NiTriBasedGeomPtr = RecordPtrT<NiTriBasedGeom>;
    using NiGeometryDataPtr = RecordPtrT<NiGeometryData>;
    using BSShaderPropertyPtr = RecordPtrT<BSShaderProperty>;
    using NiAlphaPropertyPtr = RecordPtrT<NiAlphaProperty>;
    using NiCollisionObjectPtr = RecordPtrT<NiCollisionObject>;
    using bhkSystemPtr = RecordPtrT<bhkSystem>;
    using bhkWorldObjectPtr = RecordPtrT<bhkWorldObject>;
    using bhkShapePtr = RecordPtrT<bhkShape>;
    using bhkEntityPtr = RecordPtrT<bhkEntity>;
    using bhkConvexShapePtr = RecordPtrT<bhkConvexShape>;
    using bhkRigidBodyPtr = RecordPtrT<bhkRigidBody>;
    using hkPackedNiTriStripsDataPtr = RecordPtrT<hkPackedNiTriStripsData>;
    using NiAccumulatorPtr = RecordPtrT<NiAccumulator>;
    using NiInterpolatorPtr = RecordPtrT<NiInterpolator>;
    using NiStringPalettePtr = RecordPtrT<NiStringPalette>;
    using NiControllerManagerPtr = RecordPtrT<NiControllerManager>;
    using NiBlendInterpolatorPtr = RecordPtrT<NiBlendInterpolator>;
    using NiDefaultAVObjectPalettePtr = RecordPtrT<NiDefaultAVObjectPalette>;
    using bhkCompressedMeshShapeDataPtr = RecordPtrT<bhkCompressedMeshShapeData>;
    using BSMultiBoundPtr = RecordPtrT<BSMultiBound>;
    using BSMultiBoundDataPtr = RecordPtrT<BSMultiBoundData>;
    using BSSkinBoneDataPtr = RecordPtrT<BSSkinBoneData>;

    using NiAVObjectList = RecordListT<NiAVObject>;
    using NiPropertyList = RecordListT<NiProperty>;
    using ExtraList = RecordListT<Extra>;
    using NiSourceTextureList = RecordListT<NiSourceTexture>;
    using NiInterpolatorList = RecordListT<NiInterpolator>;
    using NiTriStripsDataList = RecordListT<NiTriStripsData>;
    using bhkShapeList = RecordListT<bhkShape>;
    using bhkSerializableList = RecordListT<bhkSerializable>;
    using bhkEntityList = RecordListT<bhkEntity>;
    using bhkRigidBodyList = RecordListT<bhkEntity>;
    using NiControllerSequenceList = RecordListT<NiControllerSequence>;
    using NiPSysModifierList = RecordListT<NiPSysModifier>;
    using NiTriBasedGeomList = RecordListT<NiTriBasedGeom>;
    using BSAnimNoteList = RecordListT<BSAnimNote>;
    using BSAnimNotesList = RecordListT<BSAnimNotes>;

} // Namespace
#endif
