/**
 * Application specific vertex objects.
 */
#pragma once

#include "common.hpp"
#include <glm/glm.hpp>
#include <string.h>
#include <vector>
#include <iosfwd>

namespace render {

/// Vertex properties
struct VertexData {
    VertexData(const glm::vec3 &position, glm::u16 material, const glm::vec3 &normal):
        Position(position), Material(material), Normal(normal) { }
    glm::vec3 Position;
    glm::u16 Material;
    glm::vec3 Normal;
};

/// Quantized vertex properties
struct PackedVertexData {
    PackedVertexData(glm::u16 x, glm::u16 y, glm::u16 z, glm::u16 mat, glm::u16 nx, glm::u16 ny, glm::u16 nz, glm::u16 nw = 0) {
        PosMat[0] = x;
        PosMat[1] = y;
        PosMat[2] = z;
        PosMat[3] = mat;
        Nrm[0] = nx;
        Nrm[1] = ny;
        Nrm[2] = nz;
        Nrm[3] = nw;
    }
    glm::u16 PosMat[4];
    glm::u16 Nrm[4]; //NOTE: Triplanar does not handle 10_10_10_2_REV
    bool operator<(const PackedVertexData that) const {
        return memcmp((void *)this, (void *)&that, sizeof(PackedVertexData)) > 0;
    };
};

/// Position only buffer
class Shape {
public:
    virtual ~Shape() { }

    static const std::vector<glm::vec3> SKY_CUBE;
    static const std::vector<glm::vec3> LINE_CUBE;
    static const std::vector<glm::vec3> LINE_SPHERE;
};

/// Color lines model
class ColoredShape {
public:
    virtual ~ColoredShape() { }

    static const std::pair<std::vector<glm::vec3>, std::vector<glm::vec4>> LINE_CUBE;
};

/// VertexData model with index
class Model {
public:
    virtual ~Model() { }

    /// Preindexed buffer data
    struct Data {
        using indices_t = std::vector<glm::u16>;
        indices_t indices;
        std::vector<PackedVertexData> vertices;

        Data() { }
        Data(const std::vector<PackedVertexData> &vertices, const indices_t &indices);
        Data(const std::vector<PackedVertexData> &vertices) { index(vertices); }
        Data(std::istream &in);

        void index(const std::vector<PackedVertexData> &vertices);
        bool empty() const {
            return indices.empty();
        }
        void clear() {
            indices.clear();
            vertices.clear();
        }

        void serialize(std::ostream &out);
    };

    static _FORCE_INLINE_ std::unique_ptr<Model> Create(const Data& data) {
        assert(createFunc != nullptr && "Uninitialized renderer");
        return createFunc(data);
    }

protected:
    static std::unique_ptr<Model> (*createFunc)(const Data&);
};

/// VertexData model with Level Of Detail indices
class LodModel {
public:
    virtual ~LodModel() { }

    using LodData = std::pair<Model::Data, std::vector<size_t>>;

    static _FORCE_INLINE_ std::unique_ptr<LodModel> Create(const LodData& data) {
        assert(createFunc != nullptr && "Uninitialized renderer");
        return createFunc(data);
    }

    void inline setLevel(size_t l) { level = l; }

protected:
    size_t level = 0;

    size_t indexSize;
    std::vector<size_t> offsets;
    constexpr inline size_t getOffset(size_t level) const {
        return level <= 0 ? 0 : (level-1 < offsets.size() ? offsets[level-1] : indexSize);
    }

    static std::unique_ptr<LodModel> (*createFunc)(const LodData&);
};
}