/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 CERN
 * @author Maciej Suminski <maciej.suminski@cern.ch>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file vbo_container.h
 * @brief Class to store VBO_ITEMs.
 */

#ifndef VBO_CONTAINER_H_
#define VBO_CONTAINER_H_

#include <GL/gl.h>
#include <gal/opengl/glm/glm.hpp>
#include <gal/opengl/vbo_item.h>
#include <gal/color4d.h>
#include <map>
#include <boost/unordered_map.hpp>
#include <wx/log.h>

namespace KiGfx
{
class VBO_ITEM;
typedef struct VBO_VERTEX VBO_VERTEX;

class VBO_CONTAINER
{
public:
    VBO_CONTAINER( unsigned int aSize = defaultInitSize );
    ~VBO_CONTAINER();

    ///< Maps size of free memory chunks to their offsets
    typedef std::pair<const unsigned int, unsigned int> Chunk;
    typedef std::multimap<const unsigned int, unsigned int> FreeChunkMap;

    ///< Maps VBO_ITEMs to reserved memory chunks offsets & sizes
    typedef std::pair<VBO_ITEM* const, Chunk> ReservedChunk;
    typedef boost::unordered_map<VBO_ITEM* const, Chunk> ReservedChunkMap;

    /**
     * Function StartItem()
     * Starts an unknown sized item. After calling the function it is possible to add vertices
     * using function Add().
     * @param aVboItem is the item that is going to store vertices in the container.
     */
    void StartItem( VBO_ITEM* aVboItem );

    /**
     * Function EndItem()
     * Marks current item as finished and returns unused memory to the pool. StartItem() function
     * has to be called first.
     */
    void EndItem();

    /**
     * Function Add()
     * Stores given number of vertices in the container for the specific VBO_ITEM.
     * @param aVboItem is the owner of the vertices.
     * @param aVertex are vertices data to be stored.
     * @param aSize is the number of vertices to be added.
     */
    void Add( VBO_ITEM* aVboItem, const VBO_VERTEX* aVertex, unsigned int aSize = 1 );

    /**
     * Function Free()
     * Frees the chunk reserved by the aVboItem.
     * @param aVboItem is the owner of the chunk to be freed.
     */
    inline void Free( VBO_ITEM* aVboItem )
    {
        ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem );
        freeChunk( it );

        // Dynamic memory freeing, there is no point in holding
        // a large amount of memory when there is no use for it
        if( m_freeSpace > ( m_currentSize / 2 ) )
        {
            resizeContainer( m_currentSize / 2 );
        }
    }

    /**
     * Function Clear()
     * Removes all the data stored in the container.
     */
    void Clear();

    /**
     * Function GetAllVertices()
     * Returns all vertices stored in the container. It is especially useful for transferring
     * data to the GPU memory.
     */
    VBO_VERTEX* GetAllVertices() const;

    /**
     * Function GetVertices()
     * Returns vertices stored by the specific item.
     * @aVboItem is the specific item.
     */
    VBO_VERTEX* GetVertices( const VBO_ITEM* aVboItem ) const;

    /**
     * Function GetVertices()
     * Returns vertices stored at the specific offset.
     * @aOffset is the specific offset.
     */
    inline VBO_VERTEX* GetVertices( unsigned int aOffset ) const
    {
        return &m_vertices[aOffset];
    }

    /**
     * Function GetSize()
     * Returns amount of vertices currently stored in the container.
     */
    inline int GetSize() const
    {
        return m_currentSize;
    }

    /**
     * Function SetTransformMatrix()
     * Sets transformation matrix for vertices that are added to VBO_ITEM. If you do not want to
     * transform vertices at all, pass NULL as the argument.
     * @param aMatrix is the new transform matrix or NULL if you do not want to use transformation
     * matrix.
     */
    inline void SetTransformMatrix( const glm::mat4* aMatrix )
    {
        m_transform = aMatrix;
    }

    /**
     * Function UseColor()
     * Sets color used for all added vertices.
     * @param aColor is the color used for added vertices.
     */
    inline void UseColor( const COLOR4D& aColor )
    {
        m_color[0] = aColor.r * 255;
        m_color[1] = aColor.g * 255;
        m_color[2] = aColor.b * 255;
        m_color[3] = aColor.a * 255;
    }

    /**
     * Function UseColor()
     * Sets color used for all added vertices.
     * @param aColor is the color used for added vertices.
     */
    inline void UseColor( const GLfloat aColor[VBO_ITEM::ColorStride] )
    {
        for( unsigned int i = 0; i < VBO_ITEM::ColorStride; ++i )
        {
            m_color[i] = aColor[i] * 255;
        }
    }

    /**
     * Function UseColor()
     * Sets color used for all added vertices.
     * @param aR is the red component of the color.
     * @param aG is the green component of the color.
     * @param aB is the blue component of the color.
     * @param aA is the alpha component of the color.
     */
    inline void UseColor( GLfloat aR, GLfloat aG, GLfloat aB, GLfloat aA )
    {
        m_color[0] = aR * 255;
        m_color[1] = aG * 255;
        m_color[2] = aB * 255;
        m_color[3] = aA * 255;
    }

    /**
     * Function UseShader()
     * Sets shader and its parameters used for all added vertices.
     * @param aShader is the array that contains shader number followed by its parameters.
     */
    inline void UseShader( const GLfloat aShader[VBO_ITEM::ShaderStride] )
    {
        for( unsigned int i = 0; i < VBO_ITEM::ShaderStride; ++i )
        {
            m_shader[i] = aShader[i];
        }
    }

private:
    ///< Stores size & offset of free chunks.
    FreeChunkMap        m_freeChunks;
    ///< Stores owners (VBO_ITEM*) of reserved chunks and their size & offset.
    ReservedChunkMap    m_reservedChunks;

    /**
    * Function allocate()
    * Finds an offset where the number of vertices can be stored in a continous space. If there is
    * no such chunk, appropriate amount of memory is allocated first.
    * @param aVboItem is the owner of vertices to be stored.
    * @param aSize is the number of vertices to be stored.
    */
    unsigned int allocate( VBO_ITEM* aVboItem, unsigned int aSize );

    /**
     * Function getChunkSize()
     * Returns size of the given chunk (works both for reserved and free chunks).
     * @param aChunk is the chunk.
     */
    inline int getChunkSize( const Chunk& aChunk ) const
    {
        return aChunk.first;
    }

    inline int getChunkSize( const ReservedChunk& aChunk ) const
    {
        return aChunk.second.first;
    }

    /**
     * Function getChunkOffset()
     * Returns offset of the given chunk (works both for reserved and free chunks).
     * @param aChunk is the chunk.
     */
    inline unsigned int getChunkOffset( const Chunk& aChunk ) const
    {
        return aChunk.second;
    }

    inline unsigned int getChunkOffset( const ReservedChunk& aChunk ) const
    {
        return aChunk.second.second;
    }

    /**
     * Function getChunkOffset()
     * Upadtes offset of the given chunk (works both for reserved and free chunks).
     * !! IMPORTANT: it does not reallocate the chunk, it just changes its properties.
     * @param aChunk is the chunk.
     */
    inline void setChunkOffset( Chunk& aChunk, unsigned int aOffset ) const
    {
        aChunk.second = aOffset;
    }

    inline void setChunkOffset( ReservedChunk& aChunk, unsigned int aOffset ) const
    {
        aChunk.second.second = aOffset;
    }

    /**
     * Function getChunkVboItem()
     * Returns owner of the given reserved chunk.
     * @param aChunk is the chunk.
     */
    inline VBO_ITEM* getChunkVboItem( const ReservedChunk& aChunk ) const
    {
        return aChunk.first;
    }

    /**
     * Function defragment()
     * Removes empty spaces between chunks, so after that there is a long continous space
     * for storing vertices at the and of the container.
     * @return false in case of failure (eg. memory shortage)
     */
    bool defragment( VBO_VERTEX* aTarget = NULL );

    /**
     * Function resizeChunk()
     * Changes size of the chunk that stores vertices of aVboItem.
     * @param aVboItem is the item for which reserved space size should be changed.
     * @param aNewSize is the new size for the aVboItem, expressed in vertices number.
     */
    void resizeChunk( VBO_ITEM* aVboItem, int aNewSize );

    /**
     * Function resizeContainer()
     * Prepares a bigger container of a given size.
     * @param aNewSize is the new size of container, expressed in vertices
     * @return false in case of failure (eg. memory shortage)
     */
    bool resizeContainer( unsigned int aNewSize );

    /**
     * Function freeChunk()
     * Frees the space described in aChunk and returns it to the free space pool.
     * @param aChunk is a space to be freed.
     */
    void freeChunk( const ReservedChunkMap::iterator& aChunk );

    ///< How many vertices we can store in the container
    unsigned int    m_freeSpace;

    ///< How big is the current container, expressed in vertices
    unsigned int    m_currentSize;

    ///< Actual storage memory
    VBO_VERTEX*     m_vertices;

    ///< A flag saying if there is the item with an unknown size being added
    bool            itemStarted;

    ///< Variables holding the state of the item currently being added
    unsigned int    itemSize;
    unsigned int    itemChunkSize;
    VBO_ITEM*       item;

    ///< Color used for new vertices pushed.
    GLubyte         m_color[VBO_ITEM::ColorStride];

    ///< Shader and its parameters used for new vertices pushed
    GLfloat         m_shader[VBO_ITEM::ShaderStride];

    ///< Current transform matrix applied for every new vertex pushed.
    const glm::mat4*    m_transform;

    ///< Failure flag
    bool m_failed;

    /**
     * Function getPowerOf2()
     * Returns the nearest power of 2, bigger than aNumber.
     * @param aNumber is the number for which we look for a bigger power of 2.
     */
    unsigned int getPowerOf2( unsigned int aNumber ) const
    {
        unsigned int power = 1;

        while( power < aNumber && power )
            power <<= 1;

        return power;
    }

    ///< Default initial size of a container (expressed in vertices)
    static const unsigned int defaultInitSize = 1048576;
};
} // namespace KiGfx

#endif /* VBO_CONTAINER_H_ */
