#ifndef _gpuCacheSample_h_
#define _gpuCacheSample_h_
#include <maya/MBoundingBox.h>
#include <maya/MMatrix.h>
#include <maya/MHWGeometry.h>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/functional/hash.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <Alembic/Util/Digest.h>
#include <tbb/mutex.h>
#include <vector>
#include "gpuCacheConfig.h"
#include "gpuCacheMakeSharedHelper.h"
namespace GPUCache {
class ArrayBase
{
public:
    typedef Alembic::Util::Digest Digest;
    
    
    
    
    struct Key 
    {
        Key(const size_t  bytes,
            const Digest& digest
        ) : fBytes(bytes),
            fDigest(digest)
        {}
        const size_t fBytes;
        const Digest fDigest;
    };
    
    struct KeyHash
        : std::unary_function<Key, std::size_t>
    {
        std::size_t operator()(Key const& key) const
        {
            std::size_t seed = 0;
            boost::hash_combine(seed, key.fBytes);
            boost::hash_combine(seed, key.fDigest.words[0]);
            boost::hash_combine(seed, key.fDigest.words[1]);
            return seed;
        }
    };
    
    struct KeyEqualTo
        : std::binary_function<Key, Key, bool>
    {
        bool operator()(Key const& x,
                        Key const& y) const
        {
            return (x.fBytes  == y.fBytes &&
                    x.fDigest == y.fDigest);
        }
    };
    
    typedef void (*Callback)(const Key& array);
    
    
    static void registerCreationCallback(Callback callback);
    
    static void unregisterCreationCallback(Callback callback);
    
    
    static void registerDestructionCallback(Callback callback);
    
    static void unregisterDestructionCallback(Callback callback);
    
    virtual ~ArrayBase();
    
    size_t bytes() const    { return fKey.fBytes; }
    
    
    
    Digest digest() const   { return fKey.fDigest; }
    
    
    
    const Key& key() const  { return fKey; }
    
    
    
    bool isReadable() const { return fIsReadable; }
protected:
    
    ArrayBase(size_t bytes, const Digest& digest, bool isReadable);
    
private:
    
    
    
    ArrayBase(const ArrayBase&);
    const ArrayBase& operator= (const ArrayBase&);
    
    
    const Key fKey;
    
    
    const bool fIsReadable;
};
template<typename T>
class ArrayReadInterface
{
public:
    virtual const T* get() const = 0;
    virtual ~ArrayReadInterface() {}
};
template <class T> class ReadableArray;
template <class T>
class Array : public ArrayBase
{
public:
    
    
    
    
    
    virtual ~Array();
    
    
    
    
    
    
    virtual boost::shared_ptr<const ArrayReadInterface<T> > getReadable() const = 0;
    virtual boost::shared_ptr<ReadableArray<T> > getReadableArray() const = 0;
    
    size_t size() const     { return bytes() / sizeof(T); }
protected:
    
    Array(size_t size, const Digest& digest, bool isReadable)
        : ArrayBase(size * sizeof(T), digest, isReadable)
    {}
};
template <typename T>
class ReadableArray : public Array<T>, public ArrayReadInterface<T>, public boost::enable_shared_from_this<ReadableArray<T> >
{
public:
    typedef typename Array<T>::Digest Digest;
    virtual ~ReadableArray() {}
    
    
    
    
    virtual boost::shared_ptr<const ArrayReadInterface<T> > getReadable() const
    {
        
        
        return this->shared_from_this();
    }
    virtual boost::shared_ptr<ReadableArray<T> > getReadableArray() const
    {
        
        return const_cast<ReadableArray<T> *>(this)->shared_from_this();
    }
protected:
    ReadableArray(size_t size, const Digest& digest)
        : Array<T>(size, digest, true)
    {}
};
template <typename T>
class ArrayRegistry
{
public:
    typedef Alembic::Util::Digest Digest;
    
    
    static tbb::mutex& mutex();
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    static boost::shared_ptr<Array<T> > lookup(
        const Digest& digest,
        size_t size
    );
    static boost::shared_ptr<Array<T> > lookupNonReadable(
        const Digest& digest,
        size_t size
    );
    static boost::shared_ptr<ReadableArray<T> > lookupReadable(
        const Digest& digest,
        size_t size
    );
    
    
    
    
    
    
    
    
    static void insert(boost::shared_ptr<Array<T> > array);
};
template <class T>
class SharedArray : public ReadableArray<T>
{
public:
    typedef typename Array<T>::Digest Digest;
    
    
    
    
    static boost::shared_ptr<ReadableArray<T> > create(
        const boost::shared_array<T>& data, size_t size);
    
    static boost::shared_ptr<ReadableArray<T> > create(
        const boost::shared_array<T>& data, Digest digest, size_t size);
    
    virtual ~SharedArray();
    virtual const T* get() const;
private:
    
    
    
    GPUCACHE_DECLARE_MAKE_SHARED_FRIEND_3;
 
    SharedArray(
        const boost::shared_array<T>& data,
        size_t size,
        const Digest& digest
    ) : ReadableArray<T>(size, digest),
        fData(data)
    {}
 
    
    const boost::shared_array<T> fData;
};
class IndexBuffer
{
public:
    typedef Alembic::Util::Digest Digest;
    typedef unsigned int index_t;
    typedef boost::shared_ptr<const ReadableArray<index_t> > ReadableArrayPtr;
    typedef boost::shared_ptr<const ArrayReadInterface<index_t> > ReadInterfacePtr;
    
    
    
    struct Key 
    {
        Key(
            const boost::shared_ptr<Array<index_t> >& array,
            const size_t beginIdx,
            const size_t endIdx
        ) : fArrayKey(array->key()),
            fBeginIdx(beginIdx),
            fEndIdx(endIdx)
        {}
        const ArrayBase::Key                        fArrayKey;
        const size_t                                fBeginIdx;
        const size_t                                fEndIdx;
    };
    
    struct KeyHash
        : std::unary_function<Key, std::size_t>
    {
        std::size_t operator()(Key const& key) const
        {
            std::size_t seed = 0;
            boost::hash_combine(seed, ArrayBase::KeyHash()(key.fArrayKey));
            boost::hash_combine(seed, key.fBeginIdx);
            boost::hash_combine(seed, key.fEndIdx);
            return seed;
        }
    };
    
    struct KeyEqualTo
        : std::binary_function<Key, Key, bool>
    {
        bool operator()(Key const& x,
                        Key const& y) const
        {
            return (ArrayBase::KeyEqualTo()(x.fArrayKey, y.fArrayKey) &&
                    x.fBeginIdx == y.fBeginIdx &&
                    x.fEndIdx   == y.fEndIdx);
        }
    };
    
    static boost::shared_ptr<IndexBuffer> create(
        const boost::shared_ptr<Array<index_t> >& array
    ) 
    { return create( array, 0, array->size() ); }
    
    
    
    static boost::shared_ptr<IndexBuffer> create(
        const boost::shared_ptr<Array<index_t> >& array,
        const size_t beginIdx,
        const size_t endIdx
    );
    
    
    static size_t nbAllocated();
    
    
    
    static size_t nbAllocatedBytes();
    
    ~IndexBuffer();
    ReadInterfacePtr readableInterface() const { return fArray->getReadable(); }
    size_t numIndices() const       { return fEndIdx - fBeginIdx; }
    size_t bytes() const            { return numIndices() * sizeof(index_t); }
    const boost::shared_ptr<Array<index_t> >& array() const
    { return fArray; }
    
    const size_t beginIdx() const
    { return fBeginIdx; }
    
    const size_t endIdx() const
    { return fEndIdx; }
    
    
    
    
    
    void ReplaceArrayInstance(boost::shared_ptr<Array<index_t> >& newArray) const;
private:
    
    
    
    GPUCACHE_DECLARE_MAKE_SHARED_FRIEND_3;
    IndexBuffer(
        const boost::shared_ptr<Array<index_t> >& array,
        const size_t beginIdx,
        const size_t endIdx
    ) : fArray(array),
        fBeginIdx(beginIdx),
        fEndIdx(endIdx)
    {}
    
    
    const   boost::shared_ptr<Array<index_t> >          fArray;
    const   size_t                                      fBeginIdx;
    const   size_t                                      fEndIdx;
};
class VertexBuffer
{
public:
    typedef Alembic::Util::Digest Digest;
    typedef boost::shared_ptr<const ReadableArray<float> > ReadableArrayPtr;
    typedef boost::shared_ptr<const ArrayReadInterface<float> > ReadInterfacePtr;
    
    
    
    
    struct Key 
    {
        Key(
            const boost::shared_ptr<Array<float> >&     array,
        ) : fArrayKey(array->key()),
        {}
        const ArrayBase::Key fArrayKey;
        std::string fName;
        int fDimension;
        int fOffset;
        int fStride;
    };
    
    struct KeyHash
        : std::unary_function<Key, std::size_t>
    {
        std::size_t operator()(Key const& key) const
        {
            std::size_t seed = 0;
            boost::hash_combine(seed, ArrayBase::KeyHash()(key.fArrayKey));
            boost::hash_combine(seed, key.fName);
            boost::hash_combine(seed, key.fSemantic);
            boost::hash_combine(seed, key.fDataType);
            boost::hash_combine(seed, key.fDimension);
            boost::hash_combine(seed, key.fOffset);
            boost::hash_combine(seed, key.fStride);
            return seed;
        }
    };
    
    struct KeyEqualTo
        : std::binary_function<Key, Key, bool>
    {
        bool operator()(Key const& x,
                        Key const& y) const
        {
            return (ArrayBase::KeyEqualTo()(x.fArrayKey, y.fArrayKey) &&
                    x.fName      == y.fName &&
                    x.fSemantic  == y.fSemantic &&
                    x.fDataType  == y.fDataType &&
                    x.fDimension == y.fDimension &&
                    x.fOffset    == y.fOffset &&
                    x.fStride    == y.fStride);
        }
    };
    
    
    
    static boost::shared_ptr<VertexBuffer> createPositions(
        const boost::shared_ptr<Array<float> >& array);
    
    static boost::shared_ptr<VertexBuffer> createNormals(
        const boost::shared_ptr<Array<float> >& array);
    
    static boost::shared_ptr<VertexBuffer> createUVs(
        const boost::shared_ptr<Array<float> >& array);
    
    
    
    static size_t nbAllocated();
    
    
    
    static size_t nbAllocatedBytes();
    
    ~VertexBuffer();
    ReadInterfacePtr readableInterface() const { return fArray->getReadable(); }
    size_t numVerts() const
    { return fArray->size() / fDescriptor.dimension(); }
    size_t bytes() const
    { return fArray->bytes(); }
    
    const boost::shared_ptr<Array<float> >& array() const
    { return fArray; }
    
    { return fDescriptor; }
    
    
    
    
    
    
    
    void ReplaceArrayInstance(boost::shared_ptr<Array<float> >& newArray) const;
    
private:
    
    
    GPUCACHE_DECLARE_MAKE_SHARED_FRIEND_2;
    
    
    static boost::shared_ptr<VertexBuffer> create(
        const boost::shared_ptr<Array<float> >&     array,
    );
    
    
    VertexBuffer(
        const boost::shared_ptr<Array<float> >&     array,
    ) 
        : fArray(array),
          fDescriptor(desc)
    {}
    
    
    const   boost::shared_ptr<Array<float> >            fArray;
};
class ShapeSample
{
public:
    
    static boost::shared_ptr<ShapeSample> create(
        double timeInSeconds,
        size_t numWires,
        size_t numVerts,
        const boost::shared_ptr<IndexBuffer>&  wireVertIndices,
        const boost::shared_ptr<IndexBuffer>&  triangleVertIndices,
        const boost::shared_ptr<VertexBuffer>& positions,
        bool                visibility)
    {
        return boost::make_shared<ShapeSample>(
            timeInSeconds,
            numWires, numVerts,
            wireVertIndices, triangleVertIndices,
            positions, boundingBox, diffuseColor, visibility);
    }
    static boost::shared_ptr<ShapeSample> create(
        double timeInSeconds,
        size_t numWires,
        size_t numVerts,
        const boost::shared_ptr<IndexBuffer>&  wireVertIndices,
        const std::vector<boost::shared_ptr<IndexBuffer> >&  triangleVertIndices,
        const boost::shared_ptr<VertexBuffer>& positions,
        bool                visibility)
    {
        return boost::make_shared<ShapeSample>(
            timeInSeconds,
            numWires, numVerts,
            wireVertIndices, triangleVertIndices,
            positions, boundingBox, diffuseColor, visibility);
    }
    static boost::shared_ptr<ShapeSample> createEmptySample( 
        double timeInSeconds)
    {
        return ShapeSample::create(
            timeInSeconds,
            0,
            0,
            boost::shared_ptr<IndexBuffer>(),
            boost::shared_ptr<IndexBuffer>(),
            boost::shared_ptr<VertexBuffer>(),
            GPUCache::Config::kDefaultGrayColor,
            false);
    }
    static boost::shared_ptr<ShapeSample> createBoundingBoxPlaceHolderSample(
        double timeInSeconds, 
const MBoundingBox& bbox, 
bool visibility)
 
    {
        boost::shared_ptr<ShapeSample> sample = ShapeSample::create(
            timeInSeconds,
            0,
            0,
            boost::shared_ptr<IndexBuffer>(),
            boost::shared_ptr<IndexBuffer>(),
            boost::shared_ptr<VertexBuffer>(),
            bbox,
            GPUCache::Config::kDefaultGrayColor,
            visibility);
        sample->setBoundingBoxPlaceHolder();
        return sample;
    }
    
    ~ShapeSample();
    void setNormals(const boost::shared_ptr<VertexBuffer>& normals);
    void setUVs(const boost::shared_ptr<VertexBuffer>& uvs);
    double timeInSeconds() const    { return fTimeInSeconds; }
    bool visibility() const { return fVisibility; }
    size_t numWires() const         { return fNumWires; }
    size_t numTriangles(size_t groupId) const 
    { return fTriangleVertIndices[groupId] ? fTriangleVertIndices[groupId]->numIndices()/3 : 0; }
    size_t numTriangles() const;
    size_t numVerts() const         { return fNumVerts; }
    
    const boost::shared_ptr<IndexBuffer>& wireVertIndices() const
    { return fWireVertIndices; }
    const boost::shared_ptr<IndexBuffer>& triangleVertIndices(size_t groupId) const
    { return fTriangleVertIndices[groupId]; }
    const std::vector<boost::shared_ptr<IndexBuffer> >& triangleVertexIndexGroups() const
    { return fTriangleVertIndices; }
    size_t numIndexGroups() const
    { return fTriangleVertIndices.size(); }
    const boost::shared_ptr<VertexBuffer>& positions() const
    { return fPositions; }
    { return fBoundingBox; }
    const MColor& diffuseColor()
 const 
    { return fDiffuseColor; }
    const boost::shared_ptr<VertexBuffer>& normals() const
    { return fNormals; }
    const boost::shared_ptr<VertexBuffer>& uvs() const
    { return fUVs; }
    bool isBoundingBoxPlaceHolder() const
    { return fBoundingBoxPlaceHolder; }
    void setBoundingBoxPlaceHolder()
    { fBoundingBoxPlaceHolder = true; }
    
private:
    
    
    
    GPUCACHE_DECLARE_MAKE_SHARED_FRIEND_9;
    
    ShapeSample(
        double timeInSeconds,
        size_t numWires,
        size_t numVerts,
        const boost::shared_ptr<IndexBuffer>&  wireVertIndices,
        const boost::shared_ptr<IndexBuffer>&  triangleVertIndices,
        const boost::shared_ptr<VertexBuffer>& positions,
        bool                visibility
    );
    ShapeSample(
        double timeInSeconds,
        size_t numWires,
        size_t numVerts,
        const boost::shared_ptr<IndexBuffer>&  wireVertIndices,
        const std::vector<boost::shared_ptr<IndexBuffer> >&  triangleVertIndices,
        const boost::shared_ptr<VertexBuffer>& positions,
        bool                visibility
    );
    
    
    const double  fTimeInSeconds;
    const size_t fNumWires;
    const size_t fNumVerts;
    
    const boost::shared_ptr<IndexBuffer>                fWireVertIndices;
    const std::vector<boost::shared_ptr<IndexBuffer> >  fTriangleVertIndices;
    const boost::shared_ptr<VertexBuffer>               fPositions;
    const bool                                          fVisibility;
    
    boost::shared_ptr<VertexBuffer>                     fNormals;
    boost::shared_ptr<VertexBuffer>                     fUVs;
    
    bool                                                fBoundingBoxPlaceHolder;
};
class XformSample
{
public:
    
    static boost::shared_ptr<XformSample> create(
        double              timeInSeconds,
        bool                visibility)
    {
        return boost::make_shared<XformSample>(
            timeInSeconds, xform, boundingBox, visibility);
    }
    
    ~XformSample() {}
    double timeInSeconds() const            { return fTimeInSeconds; }
    const MMatrix& xform()
 const            { 
return fXform; }
 
    bool isReflection() const               { return fIsReflection; }
    const MBoundingBox& boundingBox()
 const { 
return fBoundingBox; }
 
    bool visibility() const                 { return fVisibility; }
private:
    
    
    
    GPUCACHE_DECLARE_MAKE_SHARED_FRIEND_4;
    XformSample(double                 timeInSeconds,
                bool                   visibility)
        : fTimeInSeconds(timeInSeconds),
          fXform(xform),
          fIsReflection(xform.
det3x3() < 0.0f),
          fBoundingBox(boundingBox),
          fVisibility(visibility)
    {}
    
    const double        fTimeInSeconds;
    const bool          fIsReflection;
    const bool          fVisibility;
};
} 
#endif