// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "stdafx.h"
#include <magma/base_node.hpp>
#include <magma/magma_mxs_parser.hpp>
#include <magma/parser_interface.hpp>

#include <frantic/channels/channel_operation_nodes.hpp>

namespace magma {

class geometry_provider_impl : public geometry_provider {
    std::map<std::string, geometry_provider::value_type> m_geometryMap;

  public:
    virtual void clear() { m_geometryMap.clear(); }

    virtual bool has_geometry( const std::string& name ) { return m_geometryMap.find( name ) != m_geometryMap.end(); }

    virtual value_type& get_geometry( const std::string& name ) {
        std::map<std::string, value_type>::iterator it = m_geometryMap.find( name );
        if( it == m_geometryMap.end() )
            throw std::runtime_error( "No geometry with name: \"" + name + "\"" );
        return it->second;
    }

    virtual void set_geometry( const std::string& name, mesh_ptr_type pMesh ) {
        kdtree_ptr_type pTree( new kdtree_type( *pMesh ) );
        m_geometryMap.insert( std::make_pair( name, value_type( pMesh, pTree ) ) );
    }
};

class magma_mxs_parser_impl : public magma_mxs_parser {
    geometry_provider_ptr m_geomProvider;
    krakatoa::scene_context_ptr m_sceneContext;

    INode* m_pBoundNode;
    ReferenceTarget* m_pMagmaHolder;

  private:
    void parse_input( const base_node& node, std::vector<frantic::channels::channel_op_node*>& outExpr );

    void parse_output( const base_node& node, std::vector<frantic::channels::channel_op_node*>& outExpr );

    void parse_operator( const base_node& node, std::vector<frantic::channels::channel_op_node*>& outExpr );

  public:
    magma_mxs_parser_impl()
        : m_pMagmaHolder( NULL )
        , m_pBoundNode( NULL )
        , m_geomProvider( new geometry_provider_impl ) {}

    virtual ~magma_mxs_parser_impl() {}

    virtual void set_max_data( ReferenceTarget* pMagmaHolder );

    virtual void set_bound_inode( INode* pNode );

    virtual void set_scene_context( krakatoa::scene_context_ptr pSceneContext );

    virtual void set_geometry_provider( geometry_provider_ptr pGeomProvider );

    virtual INode* get_bound_inode();

    virtual ReferenceTarget* get_max_data();

    virtual geometry_provider& get_geometry_provider();

    virtual krakatoa::scene_context& get_scene_context();

    virtual krakatoa::scene_context_ptr get_scene_context_ptr();

    virtual void create_expression( std::vector<frantic::channels::channel_op_node*>& outExpr );
};

magma_mxs_parser* create_mxs_parser() { return new magma_mxs_parser_impl; }

void magma_mxs_parser_impl::set_max_data( ReferenceTarget* pMagmaHolder ) { m_pMagmaHolder = pMagmaHolder; }

void magma_mxs_parser_impl::set_bound_inode( INode* pNode ) { m_pBoundNode = pNode; }

void magma_mxs_parser_impl::set_scene_context( krakatoa::scene_context_ptr pSceneContext ) {
    m_sceneContext = pSceneContext;
}

void magma_mxs_parser_impl::set_geometry_provider( geometry_provider_ptr pGeomProvider ) {
    m_geomProvider = pGeomProvider;
}

INode* magma_mxs_parser_impl::get_bound_inode() { return m_pBoundNode; }

ReferenceTarget* magma_mxs_parser_impl::get_max_data() { return m_pMagmaHolder; }

geometry_provider& magma_mxs_parser_impl::get_geometry_provider() { return *m_geomProvider; }

krakatoa::scene_context& magma_mxs_parser_impl::get_scene_context() { return *m_sceneContext; }

krakatoa::scene_context_ptr magma_mxs_parser_impl::get_scene_context_ptr() { return m_sceneContext; }

void magma_mxs_parser_impl::create_expression( std::vector<frantic::channels::channel_op_node*>& outExpr ) {
    using namespace frantic::channels;

    if( !m_pMagmaHolder )
        return;

    try {
        base_node parsedNode;

        Value* pNodeArrayVal = frantic::max3d::mxs::expression( _T("execute theMod.flow") )
                                   .bind( _T("theMod"), m_pMagmaHolder )
                                   .evaluate<Value*>();

        if( !pNodeArrayVal || !is_array( pNodeArrayVal ) )
            throw channel_compiler_error( -1, "Invalid .flow property in modifier" );

        Array* pNodeArray = static_cast<Array*>( pNodeArrayVal );

        for( int i = 0; i < pNodeArray->size; ++i ) {
            parsedNode.reset( pNodeArray->get( 1 + i ), i );

            if( parsedNode.disabled ) {
                int passThroughIndex = -1;
                if( parsedNode.connection_count() > 0 ) {
                    passThroughIndex = parsedNode.get_connection( 0 );
                    if( passThroughIndex >= pNodeArray->size )
                        passThroughIndex = -1;
                }

                outExpr.push_back( new disabled_op_node( i, passThroughIndex ) );
            } else {
                parser_interface::parse_node( parsedNode, *this, outExpr );
            }
        }

        // TODO: Verify the structure of the created expression.
    } catch( MAXScriptException& e ) {
        frantic::max3d::mxs::frame<1> f;
        frantic::max3d::mxs::local<StringStream> stream( f );

        stream = new StringStream();
        e.sprin1( stream );

        throw std::runtime_error( frantic::strings::to_string( stream->to_string() ) );
    }
}

} // namespace magma