#include "BlenderCustomData.h"
#include "BlenderDNA.h"
#include <array>
#include <functional>

namespace Assimp {
namespace Blender {
/**
        *   @brief  read/convert of Structure array to memory
        */
template <typename T>
bool read(const Structure &s, T *p, const size_t cnt, const FileDatabase &db) {
    for (size_t i = 0; i < cnt; ++i) {
        T read;
        s.Convert(read, db);
        *p = read;
        p++;
    }
    return true;
}

/**
        *   @brief  pointer to function read memory for n CustomData types
        */
typedef bool (*PRead)(ElemBase *pOut, const size_t cnt, const FileDatabase &db);
typedef ElemBase *(*PCreate)(const size_t cnt);
typedef void (*PDestroy)(ElemBase *);

#define IMPL_STRUCT_READ(ty)                                               \
    bool read##ty(ElemBase *v, const size_t cnt, const FileDatabase &db) { \
        ty *ptr = dynamic_cast<ty *>(v);                                   \
        if (nullptr == ptr) {                                              \
            return false;                                                  \
        }                                                                  \
        return read<ty>(db.dna[#ty], ptr, cnt, db);                        \
    }

#define IMPL_STRUCT_CREATE(ty)               \
    ElemBase *create##ty(const size_t cnt) { \
        return new ty[cnt];                  \
    }

#define IMPL_STRUCT_DESTROY(ty)         \
    void destroy##ty(ElemBase *pE) {    \
        ty *p = dynamic_cast<ty *>(pE); \
        delete[] p;                     \
    }

/**
        *   @brief  helper macro to define Structure functions
        */
#define IMPL_STRUCT(ty)    \
    IMPL_STRUCT_READ(ty)   \
    IMPL_STRUCT_CREATE(ty) \
    IMPL_STRUCT_DESTROY(ty)

// supported structures for CustomData
IMPL_STRUCT(MVert)
IMPL_STRUCT(MEdge)
IMPL_STRUCT(MFace)
IMPL_STRUCT(MTFace)
IMPL_STRUCT(MTexPoly)
IMPL_STRUCT(MLoopUV)
IMPL_STRUCT(MLoopCol)
IMPL_STRUCT(MPoly)
IMPL_STRUCT(MLoop)

/**
        *   @brief  describes the size of data and the read function to be used for single CustomerData.type
        */
struct CustomDataTypeDescription {
    PRead Read; ///< function to read one CustomData type element
    PCreate Create; ///< function to allocate n type elements
    PDestroy Destroy;

    CustomDataTypeDescription(PRead read, PCreate create, PDestroy destroy) :
            Read(read), Create(create), Destroy(destroy) {}
};

/**
        *   @brief  helper macro to define Structure type specific CustomDataTypeDescription
        *   @note   IMPL_STRUCT_READ for same ty must be used earlier to implement the typespecific read function
        */
#define DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(ty) \
    CustomDataTypeDescription { &read##ty, &create##ty, &destroy##ty }

/**
        *   @brief  helper macro to define CustomDataTypeDescription for UNSUPPORTED type
        */
#define DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION \
    CustomDataTypeDescription { nullptr, nullptr, nullptr }

/**
        *   @brief  descriptors for data pointed to from CustomDataLayer.data
        *   @note   some of the CustomData uses already well defined Structures
        *           other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures
        *           use a special readfunction for that cases
        */
std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { { DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert),
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MFace),
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTFace),
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,

        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTexPoly),
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopUV),
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopCol),
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,

        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MPoly),
        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoop),
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,

        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,

        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
        DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION } };

bool isValidCustomDataType(const int cdtype) {
    return cdtype >= 0 && cdtype < CD_NUMTYPES;
}

bool readCustomData(std::shared_ptr<ElemBase> &out, const int cdtype, const size_t cnt, const FileDatabase &db) {
    if (!isValidCustomDataType(cdtype)) {
        throw Error((Formatter::format(), "CustomData.type ", cdtype, " out of index"));
    }

    const CustomDataTypeDescription cdtd = customDataTypeDescriptions[cdtype];
    if (cdtd.Read && cdtd.Create && cdtd.Destroy && cnt > 0) {
        // allocate cnt elements and parse them from file
        out.reset(cdtd.Create(cnt), cdtd.Destroy);
        return cdtd.Read(out.get(), cnt, db);
    }
    return false;
}

std::shared_ptr<CustomDataLayer> getCustomDataLayer(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) {
    for (auto it = customdata.layers.begin(); it != customdata.layers.end(); ++it) {
        if (it->get()->type == cdtype && name == it->get()->name) {
            return *it;
        }
    }
    return nullptr;
}

const ElemBase *getCustomDataLayerData(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) {
    const std::shared_ptr<CustomDataLayer> pLayer = getCustomDataLayer(customdata, cdtype, name);
    if (pLayer && pLayer->data) {
        return pLayer->data.get();
    }
    return nullptr;
}
} // namespace Blender
} // namespace Assimp
