// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "stdafx.h"

#include "frantic/magma/maya/nodes/maya_magma_input_geometry_node.hpp"

#include <frantic/magma/nodes/magma_node_impl.hpp>
#include <frantic/maya/convert.hpp>
#include <frantic/maya/selection.hpp>

#include <maya/MDistance.h>
#include <maya/MFnTransform.h>
#include <maya/MTransformationMatrix.h>

#include <frantic/geometry/dcel_construction.hpp>

#include <boost/config.hpp>

namespace frantic {
namespace magma {
namespace maya {
namespace nodes {

namespace geom_property_visitor {
class get_property_visitor : public boost::static_visitor<bool>, boost::noncopyable {
    void* m_outValue;
    const std::type_info& m_outType;

  public:
    get_property_visitor( const std::type_info& outType, void* outValue )
        : m_outType( outType )
        , m_outValue( outValue ) {}

    bool operator()( boost::blank ) { return false; }

    template <class T>
    bool operator()( T val ) {
        if( m_outType != typeid( T ) )
            return false;

        *reinterpret_cast<T*>( m_outValue ) = val;

        return true;
    }
};
} // namespace geom_property_visitor

MAGMA_DEFINE_TYPE( "InputGeometry", "Input", maya_magma_input_geometry_node )
MAGMA_EXPOSE_PROPERTY( geometryNames, std::vector<frantic::tstring> )
MAGMA_OUTPUT_NAMES( "Geometry", "Object Count", "Objects" )
MAGMA_DESCRIPTION( "Exposes the geometry of a node for other operators." )
MAGMA_DEFINE_TYPE_END

std::size_t maya_magma_input_geometry_node::size() const { return m_meshData.size(); }

magma_geometry_ptr maya_magma_input_geometry_node::get_geometry( std::size_t index ) const { return m_meshData[index]; }

static inline void get_mesh_and_transform_matrix( MFnDependencyNode& depNode, MObject& outMesh, MObject& outTransform,
                                                  bool& outFoundMesh, bool& outFoundTransform ) {
    MStatus status;
    MObject depObject = depNode.object();

    if( depObject.hasFn( MFn::kTransform ) ) {
        outTransform = depNode.object(); // MFnTransform
        outFoundTransform = true;

        // TODO: NOT IMPLEMENTED YET
        // outMesh = depNode.attribute( "geometry", &status );
        MPlug depPlug = depNode.findPlug( "geometry", &status );
        outMesh = depPlug.elementByLogicalIndex( 1 ).asMObject();
        outFoundMesh = ( status == MStatus::kSuccess );
    } else if( depObject.hasFn( MFn::kMesh ) || depObject.hasFn( MFn::kMeshGeom ) ) {
        outMesh = depNode.object(); // MFnMesh
        outFoundMesh = true;

        MPlug depPlug = depNode.findPlug( "worldMatrix", &status );
        outTransform = depPlug.elementByLogicalIndex( 0 ).asMObject(); // MFnMatrixData
        outFoundTransform = ( status == MStatus::kSuccess );
    }
}

void maya_magma_input_geometry_node::compile_as_extension_type( frantic::magma::magma_compiler_interface& compiler ) {
    if( m_geometryNames.empty() )
        // No input geometry
        throw magma_exception()
            << magma_exception::node_id( get_id() ) << magma_exception::property_name( _T( "geometryNames" ) )
            << magma_exception::error_name(
                   _T("maya_magma_input_geometry_node::compile_as_extension_type error: No mesh given.") );

    // TODO: update this to make use of Libraries/FranticMayaLibrary/frantic/maya/geometry/mesh.hpp if needed

    if( m_meshData.size() == 0 ) {

        std::set<frantic::tstring> added;

        for( std::vector<frantic::tstring>::const_iterator iter = m_geometryNames.begin();
             iter != m_geometryNames.end(); ++iter ) {
            // Maya 2014:
            //   mesh object of type kMeshGeom, but contains kMesh
            //   transform object of type kMeshData, but contains kMatrixData
            // Maya all others:
            //   mesh object of type kMesh
            //   transform object of type kMatrixData

            // Ignore duplicates
            if( added.count( *iter ) > 0 ) {
                continue;
            }
            added.insert( *iter );

            // Get the object by name
            MString objectName( iter->c_str() );
            // GET_DEPENDENCY_NODE_FROM_MSTRING( depNode, objectName );
            MSelectionList currentGeo;
            currentGeo.add( objectName );

            if( currentGeo.length() != 1 )
                throw magma_exception()
                    << magma_exception::node_id( get_id() ) << magma_exception::property_name( _T( "geometryNames" ) )
                    << magma_exception::error_name(
                           _T("maya_magma_input_geometry_node::compile_as_extension_type error: Object \"") + *iter +
                           _T("\" not found.") );

            MObject foundGeoObject;
            currentGeo.getDependNode( 0, foundGeoObject );
            MFnDependencyNode depNode( foundGeoObject );

            MObject selectedObject, transform;
            bool foundMesh, foundTrans;
            get_mesh_and_transform_matrix( depNode, selectedObject, transform, foundMesh, foundTrans );

            m_objects.push_back( foundGeoObject );

            MStatus status;

            MFnMesh mesh( selectedObject, &status );
            if( status != MStatus::kSuccess ) {
                if( !foundMesh || !( selectedObject.hasFn( MFn::kMesh ) || selectedObject.hasFn( MFn::kMeshGeom ) ) )
                    throw magma_exception()
                        << magma_exception::node_id( get_id() )
                        << magma_exception::property_name( _T( "geometryNames" ) )
                        << magma_exception::error_name(
                               _T("maya_magma_input_geometry_node::compile_as_extension_type error: Object \"") +
                               *iter + _T(" does not contain a mesh.") );
                else
                    throw magma_exception()
                        << magma_exception::node_id( get_id() )
                        << magma_exception::property_name( _T( "geometryNames" ) )
                        << magma_exception::error_name( _T("maya_magma_input_geometry_node::compile_as_extension_type ")
                                                        _T("error: Unable to read mesh from \"") +
                                                        *iter + _T(".") );
            }

            MMatrix trans;
            if( foundTrans ) {
                if( transform.hasFn( MFn::kTransform ) ) {
                    MFnTransform mtrans( transform, &status );
                    MTransformationMatrix mmtrans = mtrans.transformation( &status );
                    trans = mmtrans.asMatrix();
                } else if( transform.hasFn( MFn::kMatrixData ) ) {
                    MFnMatrixData mtrans( transform, &status );
                    trans = mtrans.matrix();
                } else { // transform.hasFn( MFn::kMeshData )
                    MFnMatrixData mtrans( transform, &status );
                    if( status == MStatus::kSuccess )
                        trans = mtrans.matrix();
                }
            }

            frantic::magma::magma_geometry_ptr meshptr = frantic::magma::magma_geometry_interface::create_instance(
                boost::shared_ptr<maya_magma_mesh>( new maya_magma_mesh( mesh ) ),
                frantic::maya::from_maya_t( trans ) );
            m_meshData.push_back( meshptr );
        }
    }

    if( m_objects.empty() ) {
        throw magma_exception()
            << magma_exception::node_id( get_id() ) << magma_exception::property_name( _T( "geometryNames" ) )
            << magma_exception::error_name(
                   _T("maya_magma_input_geometry_node::compile_as_extension_type error: No objects at source.") );
    }

    compiler.register_interface( get_id(),
                                 static_cast<frantic::magma::nodes::magma_input_geometry_interface*>( this ) );
    compiler.compile_constant( get_id(), (int)m_meshData.size() );
    compiler.register_interface( get_id(), static_cast<frantic::magma::nodes::magma_input_objects_interface*>( this ) );
}

bool maya_magma_input_geometry_node::get_property_internal( std::size_t index, const frantic::tstring& propName,
                                                            const std::type_info& typeInfo, void* outValue ) {
    MObject obj = m_objects[index];
    if( obj.isNull() ) {
        return false;
    }

    if( propName == _T( "worldMatrix" ) && typeid( frantic::graphics::transform4f ) == typeInfo ) {
        frantic::graphics::transform4f xform;
        bool ok = false;
        get_transform( index, xform, ok );
        if( ok ) {
            *reinterpret_cast<frantic::graphics::transform4f*>( outValue ) = xform;
            return true;
        } else {
            throw magma_exception() << magma_exception::node_id( get_id() )
                                    << magma_exception::property_name( _T( "geometryNames" ) )
                                    << magma_exception::error_name(
                                           _T("maya_magma_input_geometry_node::get_property_internal error: Could not ")
                                           _T("find transform matrix for object.") );
        }
    }

    variant_t result;
    this->get_property( index, propName, result );

    geom_property_visitor::get_property_visitor visitor( typeInfo, outValue );

    return boost::apply_visitor( visitor, result );
}

void maya_magma_input_geometry_node::get_property( std::size_t index, const frantic::tstring& propName,
                                                   variant_t& outValue ) {
    MStatus status;
    if( index >= m_objects.size() ) {
        return;
    }

    if( m_objects[index].isNull() ) {
        return;
    }

    MObject source = m_objects[index];
    MFnDependencyNode object( source, &status );
    if( status != MStatus::kSuccess ) {
        return;
    }

    MPlug prop = object.findPlug( frantic::maya::to_maya_t( propName ), &status );
    if( status != MStatus::kSuccess ) {
        throw magma_exception() << magma_exception::node_id( get_id() )
                                << magma_exception::property_name( _T( "objectName" ) )
                                << magma_exception::error_name(
                                       _T("maya_magma_input_object_node::get_property error: Attribute \"") + propName +
                                       _T("\" not found.") );
    }

    bool ok = false;
    MObject propType = prop.attribute();

    if( propType.hasFn( MFn::kNumericAttribute ) ) {
        MFnNumericAttribute propSubType( propType );

        // Boolean
        if( propSubType.unitType() == MFnNumericData::kBoolean ) {
            bool value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = value;
                ok = true;
            }

            // Int
        } else if( propSubType.unitType() == MFnNumericData::kByte ) {
            int value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = value;
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::kInt || propSubType.unitType() == MFnNumericData::kLong ) {
            int value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = value;
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::kShort ) {
            short value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (int)value;
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::kChar ) {
            char value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (int)value;
                ok = true;
            }

            // Float
        } else if( propSubType.unitType() == MFnNumericData::kFloat ) {
            float value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = value;
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::kDouble ) {
            float value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (float)value;
                ok = true;
            }

            // Vec3
        } else if( propSubType.unitType() == MFnNumericData::k3Float ) {
            MStatus s1, s2, s3;
            float x, y, z;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );
            s3 = prop.child( 2 ).getValue( z );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess && s3 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( x, y, z );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k3Double ) {
            MStatus s1, s2, s3;
            double x, y, z;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );
            s3 = prop.child( 2 ).getValue( z );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess && s3 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, (float)z );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k3Int ||
                   propSubType.unitType() == MFnNumericData::k3Long ) {
            MStatus s1, s2, s3;
            int x, y, z;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );
            s3 = prop.child( 2 ).getValue( z );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess && s3 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, (float)z );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k3Short ) {
            MStatus s1, s2, s3;
            short x, y, z;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );
            s3 = prop.child( 2 ).getValue( z );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess && s3 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, (float)z );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k2Float ) {
            MStatus s1, s2;
            float x, y;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( x, y, 0 );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k2Double ) {
            MStatus s1, s2;
            double x, y;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, 0 );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k2Int ||
                   propSubType.unitType() == MFnNumericData::k2Long ) {
            MStatus s1, s2;
            int x, y;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, 0 );
                ok = true;
            }
        } else if( propSubType.unitType() == MFnNumericData::k2Short ) {
            MStatus s1, s2;
            short x, y;
            s1 = prop.child( 0 ).getValue( x );
            s2 = prop.child( 1 ).getValue( y );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess ) {
                outValue = frantic::magma::vec3( (float)x, (float)y, 0 );
                ok = true;
            }

            // Quat
        } else if( propSubType.unitType() == MFnNumericData::k4Double ) {
            // TODO: does this make sense?
            MStatus s1, s2, s3, s4;
            double w, x, y, z;
            s1 = prop.child( 0 ).getValue( w );
            s2 = prop.child( 1 ).getValue( x );
            s3 = prop.child( 2 ).getValue( y );
            s4 = prop.child( 3 ).getValue( z );

            if( s1 == MS::kSuccess && s2 == MS::kSuccess && s3 == MS::kSuccess && s4 == MS::kSuccess ) {
                outValue = frantic::magma::quat( (float)w, (float)x, (float)y, (float)z );
                ok = true;
            }
        }
    } else if( propType.hasFn( MFn::kUnitAttribute ) ) {
        MFnUnitAttribute propSubType( propType );

        if( propSubType.unitType() == MFnUnitAttribute::kDistance ) {
            MDistance value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (float)value.asCentimeters();
                ok = true;
            }
        } else if( propSubType.unitType() == MFnUnitAttribute::kAngle ) {
            MAngle value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (float)value.asRadians();
                ok = true;
            }
        } else if( propSubType.unitType() == MFnUnitAttribute::kTime ) {
            MTime value;
            status = prop.getValue( value );
            if( status == MS::kSuccess ) {
                outValue = (float)value.as( MTime::kSeconds );
                ok = true;
            }
        }
    }

    if( !ok ) {
        throw magma_exception() << magma_exception::node_id( get_id() )
                                << magma_exception::property_name( _T( "objectName" ) )
                                << magma_exception::error_name(
                                       _T("maya_magma_input_object_node::get_property error: Attribute \"") + propName +
                                       _T("\" is neither a bool, int, float, vec3, or quat.") );
    }
}

void maya_magma_input_geometry_node::get_transform( std::size_t index, frantic::graphics::transform4f& outTransform,
                                                    bool& foundOutTransform ) {
    if( index >= 0 && index < m_geometryNames.size() ) {
        MString objectName( frantic::strings::to_string( m_geometryNames[index] ).c_str() );
        MSelectionList currentGeo;
        currentGeo.add( objectName );

        if( currentGeo.length() != 1 ) {
            foundOutTransform = false;
            throw magma_exception()
                << magma_exception::node_id( get_id() ) << magma_exception::property_name( _T( "geometryNames" ) )
                << magma_exception::error_name(
                       _T("maya_magma_input_geometry_node::compile_as_extension_type error: Object \"") +
                       m_geometryNames[index] + _T("\" not found.") );
        }

        MObject foundGeoObject;
        currentGeo.getDependNode( 0, foundGeoObject );
        MFnDependencyNode depNode( foundGeoObject );

        MObject selectedObject, transform;
        bool foundMesh;
        get_mesh_and_transform_matrix( depNode, selectedObject, transform, foundMesh, foundOutTransform );

        MStatus status;
        MMatrix trans;
        if( foundOutTransform ) {
            if( transform.hasFn( MFn::kTransform ) ) {
                MFnTransform mtrans( transform, &status );
                MTransformationMatrix mmtrans = mtrans.transformation( &status );
                trans = mmtrans.asMatrix();
            } else if( transform.hasFn( MFn::kMatrixData ) ) {
                MFnMatrixData mtrans( transform, &status );
                trans = mtrans.matrix();
            } else { // transform.hasFn( MFn::kMeshData )
                MFnMatrixData mtrans( transform, &status );
                if( status == MStatus::kSuccess )
                    trans = mtrans.matrix();
            }
            outTransform = frantic::maya::from_maya_t( trans );
        }

    } else {
        foundOutTransform = false;
        throw magma_exception() << magma_exception::node_id( get_id() )
                                << magma_exception::property_name( _T( "geometryNames" ) )
                                << magma_exception::error_name(
                                       _T("maya_magma_input_geometry_node::compile_as_extension_type error: Index \"") +
                                       boost::lexical_cast<frantic::tstring>( index ) + _T("\" out of range.") );
    }
}

} // namespace nodes
} // namespace maya
} // namespace magma
} // namespace frantic

namespace frantic {
namespace magma {
namespace maya {
namespace nodes {

#pragma region maya_magma_mesh
maya_magma_mesh::maya_magma_mesh( MFnMesh& mesh ) {
    m_EnableCache = true;
    m_mesh_object = mesh.object();

    // Set up the mapping between points/polygon IDs/vertex IDs/Triangles
    // This complex mapping is needed for the following reasons:
    //   Code elsewhere assumes all faces are triangles (see below TODO)
    //   Properties such as color and texture coordinates are not just vertex specific in Maya, but polygon specific
    //   Properties accessors only pass in one index value
    MIntArray triangleCount;
    MIntArray triangleVerts;
    mesh.getTriangles( triangleCount, triangleVerts );

    int ourVertexID = 0;
    for( unsigned int polygonID = 0; polygonID < triangleCount.length(); polygonID++ ) {

        MIntArray perPolygonVerts;
        mesh.getPolygonVertices( polygonID, perPolygonVerts );

        for( unsigned int triangleID = 0; triangleID < (unsigned int)triangleCount[polygonID]; triangleID++ ) {
            int triangleVerts[3];
            mesh.getPolygonTriangleVertices( polygonID, triangleID, triangleVerts );

            int triangleIDs[3] = { -1, -1, -1 };
            // Figure out which triangle vertex refers to which polygon vertex
            // Assuming there are no duplicates
            for( unsigned int perPolygonVertexID = 0; perPolygonVertexID < perPolygonVerts.length();
                 perPolygonVertexID++ ) {
                if( triangleVerts[0] == perPolygonVerts[perPolygonVertexID] )
                    triangleIDs[0] = perPolygonVertexID;
                if( triangleVerts[1] == perPolygonVerts[perPolygonVertexID] )
                    triangleIDs[1] = perPolygonVertexID;
                if( triangleVerts[2] == perPolygonVerts[perPolygonVertexID] )
                    triangleIDs[2] = perPolygonVertexID;
                if( triangleIDs[0] >= 0 && triangleIDs[1] >= 0 && triangleIDs[2] >= 0 )
                    break;
            }

            maya_magma_mesh::polygon_map_data data;
            m_FaceIDToPolygonData.push_back( data );
            m_FaceIDToPolygonData.back().polygonID = polygonID;
            m_FaceIDToPolygonData.back().triangleID = triangleID;
            m_FaceIDToPolygonData.back().triangleVertex[0] = triangleVerts[0];
            m_FaceIDToPolygonData.back().triangleVertex[1] = triangleVerts[1];
            m_FaceIDToPolygonData.back().triangleVertex[2] = triangleVerts[2];
            m_FaceIDToPolygonData.back().polygonVertex[0] = triangleIDs[0];
            m_FaceIDToPolygonData.back().polygonVertex[1] = triangleIDs[1];
            m_FaceIDToPolygonData.back().polygonVertex[2] = triangleIDs[2];

            m_FaceIDToPolygonData.back().ourVertexID[0] = ourVertexID + 0;
            m_FaceIDToPolygonData.back().ourVertexID[1] = ourVertexID + 1;
            m_FaceIDToPolygonData.back().ourVertexID[2] = ourVertexID + 2;

            m_IDToPolygonVertex.push_back( std::make_pair( polygonID, triangleIDs[0] ) );
            m_IDToVertex.push_back( triangleVerts[0] );
            m_IDToPolygonVertex.push_back( std::make_pair( polygonID, triangleIDs[1] ) );
            m_IDToVertex.push_back( triangleVerts[1] );
            m_IDToPolygonVertex.push_back( std::make_pair( polygonID, triangleIDs[2] ) );
            m_IDToVertex.push_back( triangleVerts[2] );

            ourVertexID += 3;
        }
    }

    initialize_default_channels();

    if( m_EnableCache ) {
        mesh.getPoints( m_points );
    }
}

bool maya_magma_mesh::is_valid() const { return true; }

void maya_magma_mesh::initialize_default_channels() {
    std::unique_ptr<maya_magma_mesh_vertex_position_accessor> pos(
        new maya_magma_mesh_vertex_position_accessor( this ) );
    this->append_vertex_channel( std::move( pos ) );
}

bool maya_magma_mesh::populate_channel( const frantic::tstring& channelName, bool vertexChannel ) {
    if( vertexChannel ) {
        if( this->get_vertex_channels().get_channel( channelName ) != NULL )
            return true;

        if( channelName == _T("Position") ) {
            std::unique_ptr<maya_magma_mesh_vertex_position_accessor> pos(
                new maya_magma_mesh_vertex_position_accessor( this ) );
            this->append_vertex_channel( std::move( pos ) );
            return true;

        } else if( channelName == _T("Normal") ) {
            std::unique_ptr<maya_magma_mesh_vertex_normal_accessor> norm(
                new maya_magma_mesh_vertex_normal_accessor( false, this ) );
            this->append_vertex_channel( std::move( norm ) );
            return true;

        } else if( channelName == _T("SmoothNormal") ) {
            std::unique_ptr<maya_magma_mesh_vertex_normal_accessor> norm(
                new maya_magma_mesh_vertex_normal_accessor( true, this ) );
            this->append_vertex_channel( std::move( norm ) );
            return true;

        } else if( channelName == _T("Color") ) {
            std::unique_ptr<maya_magma_mesh_vertex_color_accessor> col(
                new maya_magma_mesh_vertex_color_accessor( this ) );
            this->append_vertex_channel( std::move( col ) );
            return true;

        } else if( channelName == _T("TextureCoord") ) {
            std::unique_ptr<maya_magma_mesh_vertex_texture_accessor> txt(
                new maya_magma_mesh_vertex_texture_accessor( this ) );
            this->append_vertex_channel( std::move( txt ) );
            return true;

        } else {
            // Unknown name could refer to a Maya Color Set or Texture Set
            MFnMesh mesh( m_mesh_object );

            MString mayaChannel = frantic::maya::to_maya_t( channelName );
            MStringArray values;

            // Color Set
            values.clear();
            mesh.getColorSetNames( values );
            for( unsigned int i = 0; i < values.length(); i++ ) {
                if( mayaChannel == values[i] ) {
                    std::unique_ptr<maya_magma_mesh_vertex_color_accessor> custcol(
                        new maya_magma_mesh_vertex_color_accessor( channelName, this ) );
                    this->append_vertex_channel( std::move( custcol ) );
                    return true;
                }
            }

            // Texture Set
            values.clear();
            mesh.getUVSetNames( values );
            for( unsigned int i = 0; i < values.length(); i++ ) {
                if( mayaChannel == values[i] ) {
                    std::unique_ptr<maya_magma_mesh_vertex_texture_accessor> custcol(
                        new maya_magma_mesh_vertex_texture_accessor( channelName, this ) );
                    this->append_vertex_channel( std::move( custcol ) );
                    return true;
                }
            }
        }
    } else {
        if( this->get_face_channels().get_channel( channelName ) != NULL )
            return true;

        if( channelName == _T("FaceNormal") ) {
            std::unique_ptr<maya_magma_mesh_face_normal_accessor> accessor(
                new maya_magma_mesh_face_normal_accessor( this ) );
            this->append_face_channel( std::move( accessor ) );
            return true;
        }
    }
    return false;
}

bool maya_magma_mesh::request_channel( const frantic::tstring& channelName, bool vertexChannel, bool forOutput,
                                       bool throwOnError ) {
    const frantic::geometry::mesh_channel* ch;

    // Check if the channel is already there
    if( vertexChannel )
        ch = this->get_vertex_channels().get_channel( channelName );
    else
        ch = this->get_face_channels().get_channel( channelName );
    if( ch != NULL ) {
        if( !forOutput )
            return true;
        if( ch->is_writeable() )
            return true;
        if( throwOnError )
            throw std::runtime_error( "maya_magma_mesh::request_channel(): Failed to request channel: \"" +
                                      frantic::strings::to_string( channelName ) + "\" is readonly" );
        return false;
    }

    // If not, try to add it and check again
    if( populate_channel( channelName, vertexChannel ) ) {
        if( vertexChannel )
            ch = this->get_vertex_channels().get_channel( channelName );
        else
            ch = this->get_face_channels().get_channel( channelName );

        if( ch != NULL ) {
            if( !forOutput )
                return true;
            if( ch->is_writeable() )
                return true;
            if( throwOnError )
                throw std::runtime_error( "maya_magma_mesh::request_channel(): Failed to request channel: \"" +
                                          frantic::strings::to_string( channelName ) + "\" is readonly" );
            return false;
        }
    }

    if( throwOnError )
        throw std::runtime_error( "maya_magma_mesh::request_channel(): Failed to request channel: \"" +
                                  frantic::strings::to_string( channelName ) + "\" does not exist" );
    return false;
}

std::size_t maya_magma_mesh::get_num_verts() const {
    std::size_t count;
    if( m_EnableCache ) {
        count = m_points.length();
    } else {
        MFnMesh mesh( m_mesh_object );
        count = (std::size_t)mesh.numVertices();
    }
    return count;
}

void maya_magma_mesh::get_vert( std::size_t index, float ( &outValues )[3] ) const {
    MPoint position;
    if( m_EnableCache ) {
        position = m_points[(int)index];
    } else {
        MFnMesh mesh( m_mesh_object );
        mesh.getPoint( (int)index, position );
    }
    outValues[0] = (float)position.x;
    outValues[1] = (float)position.y;
    outValues[2] = (float)position.z;
}

std::size_t maya_magma_mesh::get_num_faces() const { return m_FaceIDToPolygonData.size(); }

std::size_t maya_magma_mesh::get_num_face_verts( std::size_t faceIndex ) const {
    // TODO: WARNING: MagmaProject\src\magma_geometry_interface.cpp IS ASSUMING TRIANGLES
    return 3;
}

std::size_t maya_magma_mesh::get_face_vert_index( std::size_t faceIndex, std::size_t fvertIndex ) const {
    const polygon_map_data& data = m_FaceIDToPolygonData[faceIndex];
    return data.triangleVertex[fvertIndex];
}

std::size_t maya_magma_mesh::get_face_vert_id( std::size_t faceIndex, std::size_t fvertIndex ) const {
    const polygon_map_data& data = m_FaceIDToPolygonData[faceIndex];
    return data.ourVertexID[fvertIndex];
}

bool maya_magma_mesh::get_maya_face_vert_from_id( std::size_t id, std::size_t& outPolygonIndex,
                                                  std::size_t& outFVertIndex ) const {
    if( id < m_IDToPolygonVertex.size() ) {
        const std::pair<int, int>& faceAndVert = m_IDToPolygonVertex[id];
        outPolygonIndex = faceAndVert.first;
        outFVertIndex = faceAndVert.second;
        return true;
    }
    outPolygonIndex = 0;
    outFVertIndex = 0;
    return false;
}

std::size_t maya_magma_mesh::get_maya_vertex_from_id( std::size_t id ) const { return m_IDToVertex[id]; }

std::size_t maya_magma_mesh::get_maya_polygon_from_face_id( std::size_t faceIndex ) const {
    const polygon_map_data& data = m_FaceIDToPolygonData[faceIndex];
    return data.polygonID;
}

void maya_magma_mesh::get_face_vert_indices( std::size_t faceIndex, std::size_t outValues[] ) const {
    for( std::size_t i = 0; i < get_num_face_verts( faceIndex ); i++ ) {
        outValues[i] = get_face_vert_index( faceIndex, i );
    }
}

void maya_magma_mesh::get_face_verts( std::size_t faceIndex, float outValues[][3] ) const {
    for( std::size_t i = 0; i < get_num_face_verts( faceIndex ); i++ ) {
        std::size_t j = get_face_vert_index( faceIndex, i );
        get_vert( j, outValues[i] );
    }
}

std::size_t maya_magma_mesh::get_num_elements() const { return 1; }

std::size_t maya_magma_mesh::get_face_element_index( std::size_t faceIndex ) const { return 0; }

MObject maya_magma_mesh::get_maya_mesh_object() const { return m_mesh_object; }

void maya_magma_mesh::init_adjacency() {
    if( !has_adjacency() ) {
#if( defined( __linux__ ) || defined( __APPLE__ ) ) && !defined( BOOST_NO_CXX11_HDR_SMART_PTR )
        // For the version of Boost we use, Boost.Move doesn't seem to work correctly in C++11
        frantic::geometry::dcel* adjacency = new frantic::geometry::dcel();
        frantic::geometry::mesh_interface_to_dcel( this, *adjacency );
        m_adjacencyDelegate.reset( new frantic::geometry::dcel_mesh_interface( adjacency, true ) );
#else
        frantic::geometry::dcel adjacency;
        frantic::geometry::mesh_interface_to_dcel( this, adjacency );
        m_adjacencyDelegate.reset( new frantic::geometry::dcel_mesh_interface( boost::move( adjacency ) ) );
#endif
    }
}

bool maya_magma_mesh::has_adjacency() const { return m_adjacencyDelegate.get() != NULL; }

bool maya_magma_mesh::init_vertex_iterator( frantic::geometry::vertex_iterator& vIt, std::size_t vertexIndex ) const {
    assert( has_adjacency() && "init_adjacency() must be called before maya_magma_mesh::init_vertex_iterator()" );
    return m_adjacencyDelegate->init_vertex_iterator( vIt, vertexIndex );
}

bool maya_magma_mesh::advance_vertex_iterator( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->advance_vertex_iterator( vIt );
}

std::size_t maya_magma_mesh::get_edge_endpoint( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_edge_endpoint( vIt );
}

std::size_t maya_magma_mesh::get_edge_left_face( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_edge_left_face( vIt );
}

std::size_t maya_magma_mesh::get_edge_right_face( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_edge_right_face( vIt );
}

bool maya_magma_mesh::is_edge_visible( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->is_edge_visible( vIt );
}

bool maya_magma_mesh::is_edge_boundary( frantic::geometry::vertex_iterator& vIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->is_edge_boundary( vIt );
}

void maya_magma_mesh::init_face_iterator( frantic::geometry::face_iterator& fIt, std::size_t faceIndex ) const {
    assert( has_adjacency() && "init_adjacency() must be called before maya_magma_mesh::init_face_iterator()" );
    m_adjacencyDelegate->init_face_iterator( fIt, faceIndex );
}

bool maya_magma_mesh::advance_face_iterator( frantic::geometry::face_iterator& fIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->advance_face_iterator( fIt );
}

std::size_t maya_magma_mesh::get_face_neighbor( frantic::geometry::face_iterator& fIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_face_neighbor( fIt );
}

std::size_t maya_magma_mesh::get_face_next_vertex( frantic::geometry::face_iterator& fIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_face_next_vertex( fIt );
}

std::size_t maya_magma_mesh::get_face_prev_vertex( frantic::geometry::face_iterator& fIt ) const {
    assert( has_adjacency() );
    return m_adjacencyDelegate->get_face_prev_vertex( fIt );
}

#pragma endregion

#pragma region Geometry Accessors

void maya_magma_mesh_vertex_position_accessor::get_value( std::size_t index, void* outValue ) const {
    std::size_t mayaVertIndex = m_mesh->get_maya_vertex_from_id( index );

    float positions[3];
    m_mesh->get_vert( mayaVertIndex, positions );
    float* outVal = reinterpret_cast<float*>( outValue );
    outVal[0] = positions[0];
    outVal[1] = positions[1];
    outVal[2] = positions[2];
}

void maya_magma_mesh_vertex_normal_accessor::get_value( std::size_t index, void* outValue ) const {
    MObject meshObj = m_mesh->get_maya_mesh_object();
    std::size_t mayaVertIndex = m_mesh->get_maya_vertex_from_id( index );
    MFnMesh mesh( meshObj );

    MVector norm;
    mesh.getVertexNormal( (int)mayaVertIndex, true, norm );
    float* outVal = reinterpret_cast<float*>( outValue );
    outVal[0] = (float)norm.x;
    outVal[1] = (float)norm.y;
    outVal[2] = (float)norm.z;
}

void maya_magma_mesh_vertex_color_accessor::get_value( std::size_t index, void* outValue ) const {
    MObject meshObj = m_mesh->get_maya_mesh_object();
    std::size_t face, faceVert;
    m_mesh->get_maya_face_vert_from_id( index, face, faceVert );
    MFnMesh mesh( meshObj );

    int colorid;
    if( m_set_name.length() > 0 ) {
        MString mayaSetName = frantic::maya::to_maya_t( m_set_name );
        mesh.getColorIndex( (int)face, (int)faceVert, colorid, &mayaSetName );
    } else {
        mesh.getColorIndex( (int)face, (int)faceVert, colorid );
    }
    MColor color;
    mesh.getColor( colorid, color );

    float* outVal = reinterpret_cast<float*>( outValue );
    outVal[0] = color.r;
    outVal[1] = color.g;
    outVal[2] = color.b;
    // outVal[3] = color.a;
}

void maya_magma_mesh_vertex_texture_accessor::get_value( std::size_t index, void* outValue ) const {
    MObject meshObj = m_mesh->get_maya_mesh_object();
    std::size_t face, faceVert;
    m_mesh->get_maya_face_vert_from_id( index, face, faceVert );
    MFnMesh mesh( meshObj );

    float u, v;
    if( m_set_name.length() > 0 ) {
        MString mayaSetName = frantic::maya::to_maya_t( m_set_name );
        mesh.getPolygonUV( (int)face, (int)faceVert, u, v, &mayaSetName );
    } else {
        mesh.getPolygonUV( (int)face, (int)faceVert, u, v );
    }

    float* outVal = reinterpret_cast<float*>( outValue );
    outVal[0] = u;
    outVal[1] = v;
    outVal[2] = 0;
}

void maya_magma_mesh_face_normal_accessor::get_value( std::size_t index, void* outValue ) const {
    MObject meshObj = m_mesh->get_maya_mesh_object();
    std::size_t mayaPolygon = m_mesh->get_maya_polygon_from_face_id( index );
    MFnMesh mesh( meshObj );

    MVector norm;
    mesh.getPolygonNormal( (int)mayaPolygon, norm );
    float* outVal = reinterpret_cast<float*>( outValue );
    outVal[0] = (float)norm.x;
    outVal[1] = (float)norm.y;
    outVal[2] = (float)norm.z;
}

#pragma endregion

} // namespace nodes
} // namespace maya
} // namespace magma
} // namespace frantic