//
//   Copyright 2013 Pixar
//
//   Licensed under the Apache License, Version 2.0 (the "Apache License")
//   with the following modification; you may not use this file except in
//   compliance with the Apache License and the following modification to it:
//   Section 6. Trademarks. is deleted and replaced with:
//
//   6. Trademarks. This License does not grant permission to use the trade
//      names, trademarks, service marks, or product names of the Licensor
//      and its affiliates, except as required to comply with Section 4(c) of
//      the License and to reproduce the content of the NOTICE file.
//
//   You may obtain a copy of the Apache License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the Apache License with the above modification is
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//   KIND, either express or implied. See the Apache License for the specific
//   language governing permissions and limitations under the Apache License.
//

#ifndef OPENSUBDIV3_OSD_MESH_H
#define OPENSUBDIV3_OSD_MESH_H

#include "../version.h"

#include <bitset>
#include <cassert>
#include <cstring>
#include <vector>

#include "../far/topologyRefiner.h"
#include "../far/patchTableFactory.h"
#include "../far/stencilTable.h"
#include "../far/stencilTableFactory.h"

#include "../osd/bufferDescriptor.h"

struct ID3D11DeviceContext;

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {

namespace Osd {

enum MeshBits {
    MeshAdaptive             = 0,
    MeshInterleaveVarying    = 1,
    MeshFVarData             = 2,
    MeshUseSingleCreasePatch = 3,
    MeshEndCapBSplineBasis   = 4,  // exclusive
    MeshEndCapGregoryBasis   = 5,  // exclusive
    MeshEndCapLegacyGregory  = 6,  // exclusive
    NUM_MESH_BITS            = 7,
};
typedef std::bitset<NUM_MESH_BITS> MeshBitset;

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

template <class PATCH_TABLE>
class MeshInterface {
public:
    typedef PATCH_TABLE PatchTable;
    typedef typename PatchTable::VertexBufferBinding VertexBufferBinding;

public:
    MeshInterface() { }

    virtual ~MeshInterface() { }

    virtual int GetNumVertices() const = 0;

    virtual int GetMaxValence() const = 0;

    virtual void UpdateVertexBuffer(float const *vertexData,
                                    int startVertex, int numVerts) = 0;

    virtual void UpdateVaryingBuffer(float const *varyingData,
                                     int startVertex, int numVerts) = 0;

    virtual void Refine() = 0;

    virtual void Synchronize() = 0;

    virtual PatchTable * GetPatchTable() const = 0;

    virtual Far::PatchTable const *GetFarPatchTable() const = 0;

    virtual VertexBufferBinding BindVertexBuffer() = 0;

    virtual VertexBufferBinding BindVaryingBuffer() = 0;

protected:
    static inline void refineMesh(Far::TopologyRefiner & refiner,
                                  int level, bool adaptive,
                                  bool singleCreasePatch) {
        if (adaptive) {
            Far::TopologyRefiner::AdaptiveOptions options(level);
            options.useSingleCreasePatch = singleCreasePatch;
            refiner.RefineAdaptive(options);
        } else {
            //  This dependency on FVar channels should not be necessary
            bool fullTopologyInLastLevel = refiner.GetNumFVarChannels()>0;

            Far::TopologyRefiner::UniformOptions options(level);
            options.fullTopologyInLastLevel = fullTopologyInLastLevel;
            refiner.RefineUniform(options);
        }
    }
};

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

template <typename STENCIL_TABLE, typename SRC_STENCIL_TABLE,
          typename DEVICE_CONTEXT>
STENCIL_TABLE const *
convertToCompatibleStencilTable(
    SRC_STENCIL_TABLE const *table, DEVICE_CONTEXT *context) {
    if (not table) return NULL;
    return STENCIL_TABLE::Create(table, context);
}

template <>
inline Far::StencilTable const *
convertToCompatibleStencilTable<Far::StencilTable, Far::StencilTable, void>(
    Far::StencilTable const *table, void *  /*context*/) {
    // no need for conversion
    // XXX: We don't want to even copy.
    if (not table) return NULL;
    return new Far::StencilTable(*table);
}

template <>
inline Far::LimitStencilTable const *
convertToCompatibleStencilTable<Far::LimitStencilTable, Far::LimitStencilTable, void>(
    Far::LimitStencilTable const *table, void *  /*context*/) {
    // no need for conversion
    // XXX: We don't want to even copy.
    if (not table) return NULL;
    return new Far::LimitStencilTable(*table);
}

template <>
inline Far::StencilTable const *
convertToCompatibleStencilTable<Far::StencilTable, Far::StencilTable, ID3D11DeviceContext>(
    Far::StencilTable const *table, ID3D11DeviceContext *  /*context*/) {
    // no need for conversion
    // XXX: We don't want to even copy.
    if (not table) return NULL;
    return new Far::StencilTable(*table);
}

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

// Osd evaluator cache: for the GPU backends require compiled instance
//   (GLXFB, GLCompue, CL)
//
// note: this is just an example usage and client applications are supposed
//       to implement their own structure for Evaluator instance.
//
template <typename EVALUATOR>
class EvaluatorCacheT {
public:
    ~EvaluatorCacheT() {
        for(typename Evaluators::iterator it = _evaluators.begin();
            it != _evaluators.end(); ++it) {
            delete it->evaluator;
        }
    }

    // XXX: FIXME, linear search
    struct Entry {
        Entry(BufferDescriptor const &srcDescArg,
              BufferDescriptor const &dstDescArg,
              BufferDescriptor const &duDescArg,
              BufferDescriptor const &dvDescArg,
              EVALUATOR *evalArg) : srcDesc(srcDescArg), dstDesc(dstDescArg),
                              duDesc(duDescArg), dvDesc(dvDescArg), evaluator(evalArg) {}
        BufferDescriptor srcDesc, dstDesc, duDesc, dvDesc;
        EVALUATOR *evaluator;
    };
    typedef std::vector<Entry> Evaluators;

    template <typename DEVICE_CONTEXT>
    EVALUATOR *GetEvaluator(BufferDescriptor const &srcDesc,
                            BufferDescriptor const &dstDesc,
                            DEVICE_CONTEXT *deviceContext) {
        return GetEvaluator(srcDesc, dstDesc,
                            BufferDescriptor(),
                            BufferDescriptor(),
                            deviceContext);
    }

    template <typename DEVICE_CONTEXT>
    EVALUATOR *GetEvaluator(BufferDescriptor const &srcDesc,
                            BufferDescriptor const &dstDesc,
                            BufferDescriptor const &duDesc,
                            BufferDescriptor const &dvDesc,
                            DEVICE_CONTEXT *deviceContext) {

        for(typename Evaluators::iterator it = _evaluators.begin();
            it != _evaluators.end(); ++it) {
            if (isEqual(srcDesc, it->srcDesc) &&
                isEqual(dstDesc, it->dstDesc) &&
                isEqual(duDesc, it->duDesc) &&
                isEqual(dvDesc, it->dvDesc)) {
                return it->evaluator;
            }
        }
        EVALUATOR *e = EVALUATOR::Create(srcDesc, dstDesc,
                                         duDesc, dvDesc,
                                         deviceContext);
        _evaluators.push_back(Entry(srcDesc, dstDesc, duDesc, dvDesc, e));
        return e;
    }

private:
    static bool isEqual(BufferDescriptor const &a,
                        BufferDescriptor const &b) {
        int offsetA = a.stride ? (a.offset % a.stride) : 0;
        int offsetB = b.stride ? (b.offset % b.stride) : 0;

        // Note: XFB kernel needs to be configured with the local offset
        // of the dstDesc to skip preceding primvars.
        return (offsetA == offsetB &&
                a.length == b.length &&
                a.stride == b.stride);
    }

    Evaluators _evaluators;
};

/// @cond INTERNAL

// template helpers to see if the evaluator is instantiatable or not.
template <typename EVALUATOR>
struct instantiatable
{
    typedef char yes[1];
    typedef char no[2];
    template <typename C> static yes &chk(typename C::Instantiatable *t=0);
    template <typename C> static no  &chk(...);
    static bool const value = sizeof(chk<EVALUATOR>(0)) == sizeof(yes);
};
template <bool C, typename T=void>
struct enable_if { typedef T type; };
template <typename T>
struct enable_if<false, T> { };

/// @endcond

// extract a kernel from cache if available
template <typename EVALUATOR, typename DEVICE_CONTEXT>
static EVALUATOR *GetEvaluator(
    EvaluatorCacheT<EVALUATOR> *cache,
    BufferDescriptor const &srcDesc,
    BufferDescriptor const &dstDesc,
    BufferDescriptor const &duDesc,
    BufferDescriptor const &dvDesc,
    DEVICE_CONTEXT deviceContext,
    typename enable_if<instantiatable<EVALUATOR>::value, void>::type*t=0) {
    (void)t;
    if (cache == NULL) return NULL;
    return cache->GetEvaluator(srcDesc, dstDesc, duDesc, dvDesc, deviceContext);
}

template <typename EVALUATOR, typename DEVICE_CONTEXT>
static EVALUATOR *GetEvaluator(
    EvaluatorCacheT<EVALUATOR> *cache,
    BufferDescriptor const &srcDesc,
    BufferDescriptor const &dstDesc,
    DEVICE_CONTEXT deviceContext,
    typename enable_if<instantiatable<EVALUATOR>::value, void>::type*t=0) {
    (void)t;
    if (cache == NULL) return NULL;
    return cache->GetEvaluator(srcDesc, dstDesc,
                               BufferDescriptor(),
                               BufferDescriptor(),
                               deviceContext);
}

// fallback
template <typename EVALUATOR, typename DEVICE_CONTEXT>
static EVALUATOR *GetEvaluator(
    EvaluatorCacheT<EVALUATOR> *,
    BufferDescriptor const &,
    BufferDescriptor const &,
    BufferDescriptor const &,
    BufferDescriptor const &,
    DEVICE_CONTEXT,
    typename enable_if<!instantiatable<EVALUATOR>::value, void>::type*t=0) {
    (void)t;
    return NULL;
}

template <typename EVALUATOR, typename DEVICE_CONTEXT>
static EVALUATOR *GetEvaluator(
    EvaluatorCacheT<EVALUATOR> *,
    BufferDescriptor const &,
    BufferDescriptor const &,
    DEVICE_CONTEXT,
    typename enable_if<!instantiatable<EVALUATOR>::value, void>::type*t=0) {
    (void)t;
    return NULL;
}

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

template <typename VERTEX_BUFFER,
          typename STENCIL_TABLE,
          typename EVALUATOR,
          typename PATCH_TABLE,
          typename DEVICE_CONTEXT = void>
class Mesh : public MeshInterface<PATCH_TABLE> {
public:
    typedef VERTEX_BUFFER VertexBuffer;
    typedef EVALUATOR Evaluator;
    typedef STENCIL_TABLE StencilTable;
    typedef PATCH_TABLE PatchTable;
    typedef DEVICE_CONTEXT DeviceContext;
    typedef EvaluatorCacheT<Evaluator> EvaluatorCache;
    typedef typename PatchTable::VertexBufferBinding VertexBufferBinding;

    Mesh(Far::TopologyRefiner * refiner,
         int numVertexElements,
         int numVaryingElements,
         int level,
         MeshBitset bits = MeshBitset(),
         EvaluatorCache * evaluatorCache = NULL,
         DeviceContext * deviceContext = NULL) :

            _refiner(refiner),
            _farPatchTable(NULL),
            _numVertices(0),
            _maxValence(0),
            _vertexBuffer(NULL),
            _varyingBuffer(NULL),
            _vertexStencilTable(NULL),
            _varyingStencilTable(NULL),
            _evaluatorCache(evaluatorCache),
            _patchTable(NULL),
            _deviceContext(deviceContext) {

        assert(_refiner);

        MeshInterface<PATCH_TABLE>::refineMesh(
            *_refiner, level,
            bits.test(MeshAdaptive),
            bits.test(MeshUseSingleCreasePatch));

        int vertexBufferStride = numVertexElements +
            (bits.test(MeshInterleaveVarying) ? numVaryingElements : 0);
        int varyingBufferStride =
            (bits.test(MeshInterleaveVarying) ? 0 : numVaryingElements);

        initializeContext(numVertexElements,
                          numVaryingElements,
                          level, bits);

        initializeVertexBuffers(_numVertices,
                                vertexBufferStride,
                                varyingBufferStride);

        // configure vertex buffer descriptor
        _vertexDesc =
            BufferDescriptor(0, numVertexElements, vertexBufferStride);
        if (bits.test(MeshInterleaveVarying)) {
            _varyingDesc = BufferDescriptor(
                numVertexElements, numVaryingElements, vertexBufferStride);
        } else {
            _varyingDesc = BufferDescriptor(
                0, numVaryingElements, varyingBufferStride);
        }
    }

    virtual ~Mesh() {
        delete _refiner;
        delete _farPatchTable;
        delete _vertexBuffer;
        delete _varyingBuffer;
        delete _vertexStencilTable;
        delete _varyingStencilTable;
        delete _patchTable;
        // deviceContext and evaluatorCache are not owned by this class.
    }

    virtual void UpdateVertexBuffer(float const *vertexData,
                                    int startVertex, int numVerts) {
        _vertexBuffer->UpdateData(vertexData, startVertex, numVerts,
                                  _deviceContext);
    }

    virtual void UpdateVaryingBuffer(float const *varyingData,
                                     int startVertex, int numVerts) {
        _varyingBuffer->UpdateData(varyingData, startVertex, numVerts,
                                   _deviceContext);
    }

    virtual void Refine() {

        int numControlVertices = _refiner->GetLevel(0).GetNumVertices();

        BufferDescriptor srcDesc = _vertexDesc;
        BufferDescriptor dstDesc(srcDesc);
        dstDesc.offset += numControlVertices * dstDesc.stride;

        // note that the _evaluatorCache can be NULL and thus
        // the evaluatorInstance can be NULL
        //  (for uninstantiatable kernels CPU,TBB etc)
        Evaluator const *instance = GetEvaluator<Evaluator>(
            _evaluatorCache, srcDesc, dstDesc,
            _deviceContext);

        Evaluator::EvalStencils(_vertexBuffer, srcDesc,
                                _vertexBuffer, dstDesc,
                                _vertexStencilTable,
                                instance, _deviceContext);

        if (_varyingDesc.length > 0) {
            BufferDescriptor vSrcDesc = _varyingDesc;
            BufferDescriptor vDstDesc(vSrcDesc);
            vDstDesc.offset += numControlVertices * vDstDesc.stride;

            instance = GetEvaluator<Evaluator>(
                _evaluatorCache, vSrcDesc, vDstDesc,
                _deviceContext);

            if (_varyingBuffer) {
                // non-interleaved
                Evaluator::EvalStencils(_varyingBuffer, vSrcDesc,
                                        _varyingBuffer, vDstDesc,
                                        _varyingStencilTable,
                                        instance, _deviceContext);
            } else {
                // interleaved
                Evaluator::EvalStencils(_vertexBuffer, vSrcDesc,
                                        _vertexBuffer, vDstDesc,
                                        _varyingStencilTable,
                                        instance, _deviceContext);
            }
        }
    }

    virtual void Synchronize() {
        Evaluator::Synchronize(_deviceContext);
    }

    virtual PatchTable * GetPatchTable() const {
        return _patchTable;
    }

    virtual Far::PatchTable const *GetFarPatchTable() const {
        return _farPatchTable;
    }

    virtual int GetNumVertices() const { return _numVertices; }

    virtual int GetMaxValence() const { return _maxValence; }

    virtual VertexBufferBinding BindVertexBuffer() {
        return _vertexBuffer->BindVBO(_deviceContext);
    }

    virtual VertexBufferBinding BindVaryingBuffer() {
        return _varyingBuffer->BindVBO(_deviceContext);
    }

    virtual VertexBuffer * GetVertexBuffer() {
        return _vertexBuffer;
    }

    virtual VertexBuffer * GetVaryingBuffer() {
        return _varyingBuffer;
    }

    virtual Far::TopologyRefiner const * GetTopologyRefiner() const {
        return _refiner;
    }

private:
    void initializeContext(int numVertexElements,
                           int numVaryingElements,
                           int level, MeshBitset bits) {
        assert(_refiner);

        Far::StencilTableFactory::Options options;
        options.generateOffsets = true;
        options.generateIntermediateLevels =
            _refiner->IsUniform() ? false : true;

        Far::StencilTable const * vertexStencils = NULL;
        Far::StencilTable const * varyingStencils = NULL;

        if (numVertexElements>0) {

            vertexStencils = Far::StencilTableFactory::Create(*_refiner,
                                                              options);
        }

        if (numVaryingElements>0) {

            options.interpolationMode =
                Far::StencilTableFactory::INTERPOLATE_VARYING;

            varyingStencils = Far::StencilTableFactory::Create(*_refiner,
                                                               options);
        }

        Far::PatchTableFactory::Options poptions(level);
        poptions.generateFVarTables = bits.test(MeshFVarData);
        poptions.useSingleCreasePatch = bits.test(MeshUseSingleCreasePatch);

        if (bits.test(MeshEndCapBSplineBasis)) {
            poptions.SetEndCapType(
                Far::PatchTableFactory::Options::ENDCAP_BSPLINE_BASIS);
        } else if (bits.test(MeshEndCapGregoryBasis)) {
            poptions.SetEndCapType(
                Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS);
            // points on gregory basis endcap boundary can be shared among
            // adjacent patches to save some stencils.
            poptions.shareEndCapPatchPoints = true;
        } else if (bits.test(MeshEndCapLegacyGregory)) {
            poptions.SetEndCapType(
                Far::PatchTableFactory::Options::ENDCAP_LEGACY_GREGORY);
        }

        _farPatchTable = Far::PatchTableFactory::Create(*_refiner, poptions);

        // if there's endcap stencils, merge it into regular stencils.
        if (_farPatchTable->GetLocalPointStencilTable()) {
            // append stencils
            if (Far::StencilTable const *vertexStencilsWithLocalPoints =
                Far::StencilTableFactory::AppendLocalPointStencilTable(
                    *_refiner,
                    vertexStencils,
                    _farPatchTable->GetLocalPointStencilTable())) {
                delete vertexStencils;
                vertexStencils = vertexStencilsWithLocalPoints;
            }
            if (varyingStencils) {
                if (Far::StencilTable const *varyingStencilsWithLocalPoints =
                    Far::StencilTableFactory::AppendLocalPointStencilTable(
                        *_refiner,
                        varyingStencils,
                        _farPatchTable->GetLocalPointVaryingStencilTable())) {
                    delete varyingStencils;
                    varyingStencils = varyingStencilsWithLocalPoints;
                }
            }
        }

        _maxValence = _farPatchTable->GetMaxValence();
        _patchTable = PatchTable::Create(_farPatchTable, _deviceContext);

        // numvertices = coarse verts + refined verts + gregory basis verts
        _numVertices = vertexStencils->GetNumControlVertices()
            + vertexStencils->GetNumStencils();

        // convert to device stenciltable if necessary.
        _vertexStencilTable =
            convertToCompatibleStencilTable<StencilTable>(
            vertexStencils, _deviceContext);
        _varyingStencilTable =
            convertToCompatibleStencilTable<StencilTable>(
            varyingStencils, _deviceContext);

        // FIXME: we do extra copyings for Far::Stencils.
        delete vertexStencils;
        delete varyingStencils;
    }

    void initializeVertexBuffers(int numVertices,
                                 int numVertexElements,
                                 int numVaryingElements) {

        if (numVertexElements) {
            _vertexBuffer = VertexBuffer::Create(numVertexElements,
                                                 numVertices, _deviceContext);
        }

        if (numVaryingElements) {
            _varyingBuffer = VertexBuffer::Create(numVaryingElements,
                                                  numVertices, _deviceContext);
        }
    }

    Far::TopologyRefiner * _refiner;
    Far::PatchTable * _farPatchTable;

    int _numVertices;
    int _maxValence;

    VertexBuffer * _vertexBuffer;
    VertexBuffer * _varyingBuffer;

    BufferDescriptor _vertexDesc;
    BufferDescriptor _varyingDesc;

    StencilTable const * _vertexStencilTable;
    StencilTable const * _varyingStencilTable;
    EvaluatorCache * _evaluatorCache;

    PatchTable *_patchTable;
    DeviceContext *_deviceContext;
};

} // end namespace Osd

} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;

}  // end namespace OpenSubdiv

#endif  // OPENSUBDIV3_OSD_MESH_H
