/************************************************************************
 *
 * Copyright (C) 2017-2020 IRCAD France
 * Copyright (C) 2017-2020 IHU Strasbourg
 *
 * This file is part of Sight.
 *
 * Sight is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sight is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
 *
 ***********************************************************************/

#pragma once

#include "fwRenderOgre/config.hpp"
#include "fwRenderOgre/Material.hpp"

#include <fwData/Mesh.hpp>
#include <fwData/PointList.hpp>

#include <OGRE/OgreMesh.h>

namespace fwRenderOgre
{

class R2VBRenderable;

/**
 * @brief   Manages a generic mesh
 */
class FWRENDEROGRE_CLASS_API Mesh
{
public:
    typedef std::shared_ptr<Mesh> sptr;
    typedef std::weak_ptr<Mesh> wptr;

    enum BufferBinding
    {
        POSITION_NORMAL = 0,
        COLOUR          = 1,
        TEXCOORD        = 2,
        NUM_BINDINGS
    };

    /// Constructor
    FWRENDEROGRE_API Mesh(const std::string& _name);

    /// Destructor
    FWRENDEROGRE_API ~Mesh();

    /**
     * @brief Adds the given buffer semantic if it does not exist, and allocate it if necessary.
     *
     * The buffer must contain a data type and must not be interleaved.
     *
     * @param _mesh used to find buffer data.
     * @param _binding layer binfing.
     * @param _semantic semantic of the buffer.
     * @param _type data type in the buffer.
     */
    void bindLayer(const ::fwData::Mesh::csptr& _mesh, BufferBinding _binding, ::Ogre::VertexElementSemantic _semantic,
                   ::Ogre::VertexElementType _type);

    /// Set meshes vertex buffer to dynamic state (only has effect if called before service starting/update)
    FWRENDEROGRE_API void setDynamicVertices(bool _isDynamic);
    /// Set meshes and indices buffers to dynamic state (only has effect if called before service starting/update)
    FWRENDEROGRE_API void setDynamic(bool _isDynamic);

    FWRENDEROGRE_API void setVisible(bool _visible);
    FWRENDEROGRE_API void updateMesh(const ::fwData::Mesh::sptr& _mesh, bool _pointsOnly = false);
    FWRENDEROGRE_API void updateMesh(const ::fwData::PointList::csptr& _pointList);
    FWRENDEROGRE_API std::pair<bool, std::vector<R2VBRenderable*> > updateR2VB(const ::fwData::Mesh::sptr& _mesh,
                                                                               ::Ogre::SceneManager& _sceneMgr,
                                                                               const std::string& _materialName,
                                                                               bool _hasTexture);

    /// Updates the vertices position
    FWRENDEROGRE_API void updateVertices(const ::fwData::Mesh::csptr& _mesh);
    /// Updates the vertices position
    FWRENDEROGRE_API void updateVertices(const ::fwData::PointList::csptr& mesh);
    /// Updates the vertices colors.
    FWRENDEROGRE_API void updateColors(const ::fwData::Mesh::csptr& _mesh);
    /// Updates the vertices texture coordinates.
    FWRENDEROGRE_API void updateTexCoords(const ::fwData::Mesh::csptr& _mesh);
    /// Erase the mesh data, called when the configuration change (new layer, etc...), to simplify modifications.
    FWRENDEROGRE_API void clearMesh(::Ogre::SceneManager& _sceneMgr);

    FWRENDEROGRE_API void updateMaterial(::fwRenderOgre::Material* _material, bool _isR2VB) const;

    FWRENDEROGRE_API bool hasColorLayerChanged(const ::fwData::Mesh::csptr& _mesh);

    FWRENDEROGRE_API ::Ogre::Entity* createEntity(::Ogre::SceneManager& _sceneMgr);

    FWRENDEROGRE_API void invalidateR2VB();

private:

    /// Returns true if the bounding box of a ogre mesh is valid (not NaN or infinite values)
    static bool areBoundsValid(const ::Ogre::MeshPtr& _ogreMesh);

    /// Maximum size of a texture (TODO: get this from hardware instead)
    static const unsigned int s_maxTextureSize = 2048;

    /// Actual mesh data
    ::Ogre::MeshPtr m_ogreMesh;

    /// Binding for each layer
    unsigned short m_binding[NUM_BINDINGS];

    /// Number of primitives types that are handled by ::fwData::Mesh
    static const unsigned int s_numPrimitiveTypes = static_cast<std::uint8_t>(::fwData::Mesh::CellType::TETRA) + 1;

    /// Pointers on submeshes needed for reallocation check.
    /// For QUADS and TETRAS primitives, they point to r2vb submeshes.
    std::array< ::Ogre::SubMesh*, s_numPrimitiveTypes> m_subMeshes;

    /// Texture used to store per-primitive color
    ::Ogre::TexturePtr m_perPrimitiveColorTexture;
    /// Name of the texture used to store per-primitive color
    std::string m_perPrimitiveColorTextureName;

    /// Node containing inputs for the r2vb objects - it will never be inserted in the scene
    ::Ogre::Entity* m_r2vbEntity { nullptr };
    /// Mesh data for r2vb input - contains only line lists with adjacency information primitives
    ::Ogre::MeshPtr m_r2vbMesh;
    /// List of r2vb objects - these objects triggers the r2vb process and render the output data
    std::map< ::fwData::Mesh::CellType, ::fwRenderOgre::R2VBRenderable*> m_r2vbObject;

    /// Defines if there is a normal layer
    bool m_hasNormal { false };
    /// Defines if there is a vertex color layer
    bool m_hasVertexColor { false };
    /// Defines if there is a primitive color layer
    bool m_hasPrimitiveColor { false };
    /// defines if the mesh has UV coordinates, defined in m_configuration
    bool m_hasUV { false };
    /// defines if the mesh changes dynamically, defined in m_configuration
    bool m_isDynamic { false };
    /// defines if the vertices change dynamically, defined in m_configuration
    bool m_isDynamicVertices { false };
};

//------------------------------------------------------------------------------

inline void Mesh::setDynamic(bool _isDynamic)
{
    m_isDynamic = _isDynamic;
}

//------------------------------------------------------------------------------

inline void Mesh::setDynamicVertices(bool _isDynamic)
{
    m_isDynamicVertices = _isDynamic;
}

//------------------------------------------------------------------------------

} // namespace fwRenderOgre
