//
//   Copyright 2021 Pixar
//
//   Licensed under the terms set forth in the LICENSE.txt file available at
//   https://opensubdiv.org/license.
//

#include "../bfr/surface.h"
#include "../bfr/surfaceData.h"
#include "../bfr/pointOperations.h"
#include "../bfr/patchTree.h"
#include "../far/patchParam.h"
#include "../far/patchDescriptor.h"
#include "../far/patchBasis.h"

#include <algorithm>
#include <cassert>
#include <cstdio>

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {

namespace Bfr {

//
//  Constructor for the Surface -- defers to the constructor for its full
//  set of member variables, but marks the precision as double when needed
//  (as a specialization here):
//
template <typename REAL>
Surface<REAL>::Surface() : _data() {

    //  Surface<> should not be adding members outside its SurfaceData:
    assert(sizeof(*this) == sizeof(internal::SurfaceData));
}

template <>
Surface<double>::Surface() : _data() {

    _data.setDouble(true);
}


//
//  Simple internal utilities:
//
template <typename REAL>
inline internal::IrregularPatchType const &
Surface<REAL>::getIrregPatch() const {

    return _data.getIrregPatch();
}

template <typename REAL>
int
Surface<REAL>::GetNumPatchPoints() const {

    if (IsRegular()) {
        return GetNumControlPoints();
    } else if (IsLinear()) {
        return 2 * GetNumControlPoints() + 1;
    } else {
        return getIrregPatch().GetNumPointsTotal();
    }
}

template <typename REAL>
int
Surface<REAL>::GetControlPointIndices(Index cvs[]) const {

    std::memcpy(cvs, _data.getCVIndices(), _data.getNumCVs() * sizeof(Index));
    return _data.getNumCVs();
}


//
//  Methods for gathering and computing control and patch points:
//
template <typename REAL>
template <typename REAL_MESH>
void
Surface<REAL>::GatherControlPoints(
        REAL_MESH const meshPoints[], PointDescriptor const & meshDesc,
        REAL controlPoints[], PointDescriptor const & controlDesc) const {

    //
    //  Assemble parameters of the point copy operation and apply:
    //
    typedef points::CopyConsecutive<REAL,REAL_MESH> PointCopier;

    typename PointCopier::Parameters copyParams;
    copyParams.pointData    = meshPoints;
    copyParams.pointSize    = meshDesc.size;
    copyParams.pointStride  = meshDesc.stride;

    copyParams.srcCount   = GetNumControlPoints();
    copyParams.srcIndices = _data.getCVIndices();

    copyParams.resultData   = controlPoints;
    copyParams.resultStride = controlDesc.stride;

    PointCopier::Apply(copyParams);
}

template <typename REAL>
void
Surface<REAL>::computeLinearPatchPoints(REAL pointData[],
        PointDescriptor const & pointDesc) const {

    //
    //  The initial control points of the N-sided face will be followed
    //  by the midpoint of the face and the midpoint of the N edges.
    //
    //  Assemble parameters of the face splitting operation and apply:
    //
    int N = GetNumControlPoints();

    typedef points::SplitFace<REAL> PointSplitter;

    typename PointSplitter::Parameters splitParams;
    splitParams.pointData   = pointData;
    splitParams.pointSize   = pointDesc.size;
    splitParams.pointStride = pointDesc.stride;

    splitParams.srcCount = N;

    splitParams.resultData = pointData + pointDesc.stride * N;

    PointSplitter::Apply(splitParams);
}

template <typename REAL>
void
Surface<REAL>::computeIrregularPatchPoints(REAL pointData[],
        PointDescriptor const & pointDesc) const {

    //
    //  An "irregular patch" may be represented by a regular patch in
    //  rare cases, so be sure there are patch points to compute:
    //
    internal::IrregularPatchType const & irregPatch = getIrregPatch();

    int numControlPoints = GetNumControlPoints();
    int numPatchPoints   = irregPatch.GetNumPointsTotal();

    if (numPatchPoints == numControlPoints) return;

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    typedef points::CombineConsecutive<REAL> PointCombiner;

    typename PointCombiner::Parameters combParams;
    combParams.pointData   = pointData;
    combParams.pointSize   = pointDesc.size;
    combParams.pointStride = pointDesc.stride;

    combParams.srcCount = numControlPoints;

    combParams.resultCount = numPatchPoints - numControlPoints;
    combParams.resultData  = pointData + pointDesc.stride * numControlPoints;
    combParams.weightData  = irregPatch.GetStencilMatrix<REAL>();

    PointCombiner::Apply(combParams);
}

//
//  Methods for computing the extent of the control points:
//
template <typename REAL>
void
Surface<REAL>::BoundControlPoints(
        REAL const controlPoints[], PointDescriptor const & pointDesc,
        REAL boundMin[], REAL boundMax[]) const {

    int numPoints = GetNumControlPoints();
    int pointSize = pointDesc.size;

    REAL const * p = controlPoints;
    std::memcpy(boundMin, p, pointSize * sizeof(REAL));
    std::memcpy(boundMax, p, pointSize * sizeof(REAL));

    for (int i = 1; i < numPoints; ++i) {
        p += pointDesc.stride;
        for (int j = 0; j < pointSize; ++j) {
            boundMin[j] = std::min(boundMin[j], p[j]);
            boundMax[j] = std::max(boundMax[j], p[j]);
        }
    }
}

template <typename REAL>
void
Surface<REAL>::BoundControlPointsFromMesh(
        REAL const meshPoints[], PointDescriptor const & pointDesc,
        REAL boundMin[], REAL boundMax[]) const {

    int numPoints = GetNumControlPoints();
    int pointSize = pointDesc.size;

    int const * meshIndices = _data.getCVIndices();

    REAL const * p = meshPoints + pointDesc.stride * meshIndices[0];
    std::memcpy(boundMin, p, pointSize * sizeof(REAL));
    std::memcpy(boundMax, p, pointSize * sizeof(REAL));

    for (int i = 1; i < numPoints; ++i) {
        p = meshPoints + pointDesc.stride * meshIndices[i];
        for (int j = 0; j < pointSize; ++j) {
            boundMin[j] = std::min(boundMin[j], p[j]);
            boundMax[j] = std::max(boundMax[j], p[j]);
        }
    }
}


//
//  Internal helper for evaluation:
//
namespace {
    template <typename REAL>
    inline int
    assignWeightsPerDeriv(REAL * const deriv[6], int wSize, REAL wBuffer[],
                          REAL * wDeriv[6]) {

        std::memset(wDeriv, 0, 6 * sizeof(REAL*));

        wDeriv[0] = wBuffer;
        if (deriv[1] && deriv[2]) {
            wDeriv[1] = wDeriv[0] + wSize;
            wDeriv[2] = wDeriv[1] + wSize;
            if (deriv[3] && deriv[4] && deriv[5]) {
                wDeriv[3] = wDeriv[2] + wSize;
                wDeriv[4] = wDeriv[3] + wSize;
                wDeriv[5] = wDeriv[4] + wSize;
                return 6;
            }
            return 3;
        }
        return 1;
    }
}


//
//  Evaluation methods accessing the local data for a simple regular patch:
//
template <typename REAL>
void
Surface<REAL>::evalRegularBasis(REAL const uv[2], REAL * wDeriv[]) const {

    Far::PatchParam patchParam;
    patchParam.Set(0, 0, 0, 0, 0, getRegPatchMask(), 0, true);

    Far::internal::EvaluatePatchBasisNormalized(
        getRegPatchType(), patchParam, uv[0], uv[1],
        wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);
}

template <typename REAL>
int
Surface<REAL>::evalRegularStencils(REAL const uv[2], REAL * sDeriv[]) const {

    //
    //  The control points of a regular patch are always the full set
    //  of points required by a patch, i.e. phantom points will have an
    //  entry of some kind (a duplicate).  For example, for an isolated
    //  quad, its regular patch still has 16 control points.  So we can
    //  return the basis weights as stencil weights for all cases.
    //
    Far::PatchParam patchParam;
    patchParam.Set(0, 0, 0, 0, 0, getRegPatchMask(), 0, true);

    Far::internal::EvaluatePatchBasisNormalized(
        getRegPatchType(), patchParam, uv[0], uv[1],
        sDeriv[0], sDeriv[1], sDeriv[2], sDeriv[3], sDeriv[4], sDeriv[5]);

    return GetNumControlPoints();
}

template <typename REAL>
void
Surface<REAL>::evalRegularDerivs(REAL const uv[2],
        REAL const patchPoints[], PointDescriptor const & pointDesc,
        REAL * deriv[]) const {

    //
    //  Regular basis evaluation simply returns weights for use with
    //  the entire set of patch control points.
    //
    //  Assign weights for requested derivatives and evaluate:
    //
    REAL   wBuffer[6 * 20];
    REAL * wDeriv[6];

    int numDerivs = assignWeightsPerDeriv(deriv, 20, wBuffer, wDeriv);

    evalRegularBasis(uv, wDeriv);

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    points::CommonCombinationParameters<REAL> combineParams;
    combineParams.pointData   = patchPoints;
    combineParams.pointSize   = pointDesc.size;
    combineParams.pointStride = pointDesc.stride;

    combineParams.srcCount   = GetNumControlPoints();
    combineParams.srcIndices = 0;

    combineParams.resultCount = numDerivs;
    combineParams.resultArray = deriv;
    combineParams.weightArray = wDeriv;

    if (numDerivs == 1) {
        points::Combine1<REAL>::Apply(combineParams);
    } else if (numDerivs == 3) {
        points::Combine3<REAL>::Apply(combineParams);
    } else {
        points::CombineMultiple<REAL>::Apply(combineParams);
    }
}

//
//  Evaluation methods accessing the PatchTree for irregular patches:
//
template <typename REAL>
typename Surface<REAL>::IndexArray
Surface<REAL>::evalIrregularBasis(REAL const UV[2], REAL * wDeriv[]) const {

    Parameterization param = GetParameterization();
    REAL uv[2] = { UV[0], UV[1] };
    int subFace = param.HasSubFaces() ?
                  param.ConvertCoordToNormalizedSubFace(uv, uv) : 0;

    internal::IrregularPatchType const & irregPatch = getIrregPatch();
    int subPatchIndex = irregPatch.FindSubPatch(uv[0], uv[1], subFace);
    assert(subPatchIndex >= 0);

    irregPatch.EvalSubPatchBasis(subPatchIndex, uv[0], uv[1],
            wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);

    return irregPatch.GetSubPatchPoints(subPatchIndex);
}

template <typename REAL>
int
Surface<REAL>::evalIrregularStencils(REAL const UV[2], REAL * sDeriv[]) const {

    Parameterization param = GetParameterization();
    REAL uv[2] = { UV[0], UV[1] };
    int subFace = param.HasSubFaces() ?
                  param.ConvertCoordToNormalizedSubFace(uv, uv) : 0;

    internal::IrregularPatchType const & irregPatch = getIrregPatch();
    int subPatchIndex = irregPatch.FindSubPatch(uv[0], uv[1], subFace);
    assert(subPatchIndex >= 0);

    return irregPatch.EvalSubPatchStencils(
            subPatchIndex, uv[0], uv[1],
            sDeriv[0], sDeriv[1], sDeriv[2], sDeriv[3], sDeriv[4], sDeriv[5]);
}

template <typename REAL>
void
Surface<REAL>::evalIrregularDerivs(REAL const uv[2],
        REAL const patchPoints[], PointDescriptor const & pointDesc,
        REAL * deriv[]) const {

    //
    //  Non-linear irregular basis evaluation returns both the weights
    //  and the corresponding points of a sub-patch defined by a subset
    //  of the given patch points.
    //
    //  Assign weights for requested derivatives and evaluate:
    //
    REAL   wBuffer[6 * 20];
    REAL * wDeriv[6];

    int numDerivs = assignWeightsPerDeriv(deriv, 20, wBuffer, wDeriv);

    IndexArray indices = evalIrregularBasis(uv, wDeriv);

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    points::CommonCombinationParameters<REAL> combineParams;
    combineParams.pointData   = patchPoints;
    combineParams.pointSize   = pointDesc.size;
    combineParams.pointStride = pointDesc.stride;

    combineParams.srcCount   = indices.size();
    combineParams.srcIndices = &indices[0];

    combineParams.resultCount = numDerivs;
    combineParams.resultArray = deriv;
    combineParams.weightArray = wDeriv;

    if (numDerivs == 1) {
        points::Combine1<REAL>::Apply(combineParams);
    } else if (numDerivs == 3) {
        points::Combine3<REAL>::Apply(combineParams);
    } else {
        points::CombineMultiple<REAL>::Apply(combineParams);
    }
}

//
//  Supporting methods for the N-sided quadrangulated linear patch:
//
namespace {
    //
    //  For stencils, there are four unique weights derived from the
    //  four bilinear weights of the sub-face.  Given these weights as
    //  input for a sub-face with origin at base point P, the resulting
    //  weights are associated with the N base points as follows:
    //
    //      w[0] = the point at the origin (P)
    //      w[1] = the point following P
    //      w[2] = the N-3 points not adjacent to P (contributing to center)
    //      w[3] = the point preceding P
    //
    template <typename REAL>
    inline void
    transformLinearQuadWeightsToStencil(REAL w[4], int N) {

        REAL wOrigin = w[0];
        REAL wNext   = w[1] * 0.5f;
        REAL wCenter = w[2] / (REAL)N;
        REAL wPrev   = w[3] * 0.5f;

        w[0] = wCenter + wNext + wPrev + wOrigin;
        w[1] = wCenter + wNext;
        w[2] = wCenter;
        w[3] = wCenter + wPrev;
    }

    template <typename REAL>
    inline void
    scaleWeights4(REAL w[4], REAL derivScale) {
        if (w) {
            w[0] *= derivScale;
            w[1] *= derivScale;
            w[2] *= derivScale;
            w[3] *= derivScale;
        }
    }
}

template <typename REAL>
int
Surface<REAL>::evalMultiLinearBasis(REAL const UV[2], REAL *wDeriv[]) const {

    Parameterization param = GetParameterization();
    assert(param.GetType() == Parameterization::QUAD_SUBFACES);

    REAL uv[2];
    int subFace = param.ConvertCoordToNormalizedSubFace(UV, uv);

    //  WIP - Prefer to eval Linear basis directly, i.e.:
    //
    //      Far::internal::EvalBasisLinear(u, v, wP, wDu, wDv);
    //
    //  but this internal Far function is sometimes optimized out, causing
    //  link errors.  Need to fix in Far with explicit instantiation...
    Far::internal::EvaluatePatchBasisNormalized(Far::PatchDescriptor::QUADS,
            Far::PatchParam(), uv[0], uv[1],
            wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);

    //  Scale weights for derivatives (only mixed partial of 2nd is non-zero):
    scaleWeights4<REAL>(wDeriv[1], 2.0f);
    scaleWeights4<REAL>(wDeriv[2], 2.0f);

    scaleWeights4<REAL>(wDeriv[4], 4.0f);

    return subFace;
}

template <typename REAL>
int
Surface<REAL>::evalMultiLinearStencils(REAL const uv[2], REAL *sDeriv[]) const {

    //
    //  Linear evaluation of irregular N-sided faces evaluates one of N
    //  locally subdivided quad faces and identifies that sub-face -- also
    //  the origin vertex of the quad. The basis weights are subsequently
    //  transformed into the four unique values that are then assigned to
    //  the N vertices of the face.
    //
    //  Assign weights for requested stencils and evaluate:
    //
    REAL   wBuffer[6 * 4];
    REAL * wDeriv[6];

    int numDerivs = assignWeightsPerDeriv(sDeriv, 4, wBuffer, wDeriv);

    int iOrigin = evalMultiLinearBasis(uv, wDeriv);

    //
    //  Transform the four linear weights to four unique stencil weights:
    //
    int numControlPoints = GetNumControlPoints();

    transformLinearQuadWeightsToStencil(wDeriv[0], numControlPoints);
    if (numDerivs > 1) {
        transformLinearQuadWeightsToStencil(wDeriv[1], numControlPoints);
        transformLinearQuadWeightsToStencil(wDeriv[2], numControlPoints);
        if (numDerivs > 3) {
            transformLinearQuadWeightsToStencil(wDeriv[4], numControlPoints);
        }
    }

    //
    //  Assign the N stencil weights from the four unique values:
    //
    int iNext = (iOrigin + 1) % numControlPoints;
    int iPrev = (iOrigin + numControlPoints - 1) % numControlPoints;

    for (int i = 0; i < numControlPoints; ++i) {
        int wIndex = 2;
        if (i == iOrigin) {
            wIndex = 0;
        } else if (i == iNext) {
            wIndex = 1;
        } else if (i == iPrev) {
            wIndex = 3;
        }

        sDeriv[0][i] = wDeriv[0][wIndex];
        if (numDerivs > 1) {
            sDeriv[1][i] = wDeriv[1][wIndex];
            sDeriv[2][i] = wDeriv[2][wIndex];
            if (numDerivs > 3) {
                sDeriv[3][i] = 0.0f;
                sDeriv[4][i] = wDeriv[4][wIndex];
                sDeriv[5][i] = 0.0f;
            }
        }
    }
    return numControlPoints;
}

template <typename REAL>
void
Surface<REAL>::evalMultiLinearDerivs(REAL const uv[],
        REAL const patchPoints[], PointDescriptor const & pointDesc,
        REAL * deriv[]) const {

    //
    //  Linear evaluation of irregular N-sided faces evaluates one of N
    //  locally subdivided quad faces and identifies that sub-face.
    //
    //  Assign weights for requested derivatives and evaluate:
    //
    REAL   wBuffer[6 * 4];
    REAL * wDeriv[6];

    int numDerivs = assignWeightsPerDeriv(deriv, 4, wBuffer, wDeriv);

    int subQuad = evalMultiLinearBasis(uv, wDeriv);

    //
    //  Identify the patch points for the sub-face and interpolate:
    //
    int N = GetNumControlPoints();

    int quadIndices[4];
    quadIndices[0] = subQuad;
    quadIndices[1] = N + 1 + subQuad;
    quadIndices[2] = N;
    quadIndices[3] = N + 1 + (subQuad + N - 1) % N;

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    points::CommonCombinationParameters<REAL> combineParams;
    combineParams.pointData   = patchPoints;
    combineParams.pointSize   = pointDesc.size;
    combineParams.pointStride = pointDesc.stride;

    combineParams.srcCount   = 4;
    combineParams.srcIndices = quadIndices;

    combineParams.resultCount = numDerivs;
    combineParams.resultArray = deriv;
    combineParams.weightArray = wDeriv;

    if (numDerivs == 1) {
        points::Combine1<REAL>::Apply(combineParams);
    } else if (numDerivs == 3) {
        points::Combine3<REAL>::Apply(combineParams);
    } else {
        points::CombineMultiple<REAL>::Apply(combineParams);
    }
}


//
//  Public methods to apply stencils:
//
template <typename REAL>
void
Surface<REAL>::ApplyStencilFromMesh(REAL const stencil[],
        REAL const meshPoints[], PointDescriptor const & pointDesc,
        REAL result[]) const {

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    typedef points::Combine1<REAL> PointCombiner;

    typename PointCombiner::Parameters combParams;
    combParams.pointData   = meshPoints;
    combParams.pointSize   = pointDesc.size;
    combParams.pointStride = pointDesc.stride;

    combParams.srcCount   = GetNumControlPoints();
    combParams.srcIndices = _data.getCVIndices();

    combParams.resultCount = 1;
    combParams.resultArray = &result;
    combParams.weightArray = &stencil;

    PointCombiner::Apply(combParams);
}

template <typename REAL>
void
Surface<REAL>::ApplyStencil(REAL const stencil[],
        REAL const controlPoints[], PointDescriptor const & pointDesc,
        REAL result[]) const {

    //
    //  Assemble parameters of the point combination operation and apply:
    //
    typedef points::Combine1<REAL> PointCombiner;

    typename PointCombiner::Parameters combParams;
    combParams.pointData   = controlPoints;
    combParams.pointSize   = pointDesc.size;
    combParams.pointStride = pointDesc.stride;

    combParams.srcCount   = GetNumControlPoints();
    combParams.srcIndices = 0;

    combParams.resultCount = 1;
    combParams.resultArray = &result;
    combParams.weightArray = &stencil;

    PointCombiner::Apply(combParams);
}


//
//  Explicitly instantiate Surface<> implementations for float and double:
//
template class Surface<float>;
template class Surface<double>;

//
//  Explicitly instantiate template methods for converting precision:
//
template void Surface<float>::GatherControlPoints(
                      float const [], PointDescriptor const &,
                      float       [], PointDescriptor const &) const;
template void Surface<float>::GatherControlPoints(
                      double const [], PointDescriptor const &,
                      float        [], PointDescriptor const &) const;

template void Surface<double>::GatherControlPoints(
                      double const [], PointDescriptor const &,
                      double       [], PointDescriptor const &) const;
template void Surface<double>::GatherControlPoints(
                      float const [], PointDescriptor const &,
                      double      [], PointDescriptor const &) const;

} // end namespace Bfr

} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv
