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

// File birth has been deprecated for a while now, so I'm just going to not include it anymore.
#if MAX_VERSION_MAJOR < 15

#include "resource.h"

#include "PFlow Operators\MaxKrakatoaPFChannelInterface.h"
#include "PFlow Operators\MaxKrakatoaPFFileUpdate.h"

#include <frantic/channels/channel_accessor.hpp>
#include <frantic/files/files.hpp>

#include <autolink_openexr.hpp>
#include <frantic/graphics/vector3f.hpp>
#include <frantic/logging/logging_level.hpp>

#include <frantic/particles/particle_file_stream_factory.hpp>
#include <frantic/particles/streams/fractional_particle_istream.hpp>
#include <frantic/particles/streams/transformed_particle_istream.hpp>

#include <frantic/max3d/geopipe/get_inodes.hpp>
#include <frantic/max3d/particles/particle_stream_factory.hpp>

#include <frantic/files/files.hpp>
#include <frantic/max3d/maxscript/maxscript.hpp>

void* KrakatoaPFFileUpdateDesc::Create( BOOL ) { return new KrakatoaPFFileUpdate; }

INT_PTR KrakatoaPFFileUpdateDesc::Execute( int cmd, ULONG_PTR arg1, ULONG_PTR arg2, ULONG_PTR /*arg3*/ ) {
    TCHAR** res = NULL;
    bool* isPublic;
    HBITMAP* depotIcon;
    HBITMAP* depotMask;
    switch( cmd ) {
    case kPF_GetActionDescription:
        if( arg1 == NULL )
            return 0;
        res = (TCHAR**)arg1;
        *res =
            _T("Loads position and velocity data from a sequence of particle files."); /*GetString(IDS_KrakatoaPFFileUpdate_DESCRIPTION)*/
        ;
        break;
    case kPF_PViewPublic:
        if( arg1 == NULL )
            return 0;
        isPublic = (bool*)arg1;
        *isPublic = false;
        break;
    case kPF_PViewCategory:
        if( arg1 == NULL )
            return 0;
        res = (TCHAR**)arg1;
        *res = _T("Operator");
        break;
    case kPF_PViewDepotIcon:
        if( arg1 == NULL )
            return 0;
        depotIcon = (HBITMAP*)arg1;
        if( arg2 == NULL )
            return 0;
        depotMask = (HBITMAP*)arg2;
        if( m_depotIcon == NULL )
            m_depotIcon = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_FILEUPDATE_ACTIVE_DEPOT ) );
        if( m_depotMask == NULL )
            m_depotMask = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_FILEUPDATE_MASK_DEPOT ) );
        *depotIcon = m_depotIcon;
        *depotMask = m_depotMask;
        break;
    default:
        return 0;
    }
    return 1;
}

HBITMAP KrakatoaPFFileUpdateDesc::m_depotIcon = NULL;
HBITMAP KrakatoaPFFileUpdateDesc::m_depotMask = NULL;

ClassDesc2* GetKrakatoaPFFileUpdateDesc() {
    static KrakatoaPFFileUpdateDesc theKrakatoaPFFileUpdateDesc;
    return &theKrakatoaPFFileUpdateDesc;
}
INT_PTR KrakatoaPFFileUpdateParamProc::DlgProc( TimeValue /*t*/, IParamMap2* map, HWND hWnd, UINT msg, WPARAM wParam,
                                                LPARAM lParam ) {

    switch( msg ) {
    case WM_INITDIALOG:
        map->GetParamBlock()->GetParamDef( kKrakatoaPFFileUpdate_filepattern ).init_file =
            const_cast<MCHAR*>( map->GetParamBlock()->GetStr( kKrakatoaPFFileUpdate_filepattern ) );

        // Send the message to notify the initialization of dialog
        map->GetParamBlock()->NotifyDependents( FOREVER, (PartID)map, kKrakatoaPFFileUpdate_message_init );
        break;
    case WM_COMMAND:
        switch( LOWORD( wParam ) ) {
        case kKrakatoaPFFileUpdate_message_filename:
            if( !lParam )
                break;
            SetDlgItemText( hWnd, IDC_FILEUPDATE_FILEPATTERN,
                            _T( frantic::files::filename_from_path( reinterpret_cast<char*>( lParam ) ).c_str() ) );
            map->GetParamBlock()->GetParamDef( kKrakatoaPFFileUpdate_filepattern ).init_file =
                const_cast<char*>( map->GetParamBlock()->GetStr( kKrakatoaPFFileUpdate_filepattern ) );

            break;
        case IDC_FILEUPDATE_CHANNEL_SETUP_BUTTON:
            // The only dependant of this param block should be the file update operator it belongs to, so
            // only that one thing should get notified.
            map->GetParamBlock()->NotifyDependents( FOREVER, 0, IDC_FILEUPDATE_CHANNEL_SETUP_BUTTON );
            break;
        }
        break;
    }
    return FALSE;
}

class channel_name_link : public PBAccessor {
    TSTR GetLocalName( ReferenceMaker* owner, ParamID /*id*/, int tabIndex ) {
        KrakatoaPFFileUpdate* pFU = (KrakatoaPFFileUpdate*)owner;
        if( pFU && pFU->GetParamBlock( 0 ) ) {
            return pFU->GetParamBlock( 0 )->GetStr( kKrakatoaPFFileUpdate_blend_channel_names, 0, tabIndex );
        } else {
            return "";
        }
    }
};
static channel_name_link channel_name_link_object;

KrakatoaPFFileUpdateParamProc* KrakatoaPFFileUpdateDesc::dialog_proc = new KrakatoaPFFileUpdateParamProc();

//----------------------------------------------------
// Param Block info
//----------------------------------------------------
ParamBlockDesc2 KrakatoaPFFileUpdateDesc::pblock_desc(
    // required block spec
    kKrakatoaPFFileUpdate_pblock_index, _T("Parameters"), IDS_PARAMS, GetKrakatoaPFFileUpdateDesc(),
    P_AUTO_CONSTRUCT + P_AUTO_UI,
    // auto constuct block refno : none
    0,
    // auto ui parammap specs : none
    IDD_FILEUPDATE_PARAMETERS, IDS_PARAMS, 0,
    0, // open/closed
    KrakatoaPFFileUpdateDesc::dialog_proc,
    // required param specs

    // These are for channel-wise blending
    kKrakatoaPFFileUpdate_blend_channel_names, _T("BlendChannelNames"),
    TYPE_STRING_TAB, // type
    0,               // size
    P_READ_ONLY,     // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_NAMES, end, kKrakatoaPFFileUpdate_blend_channel_alphas, _T("BlendChannelAlphas"),
    TYPE_FLOAT_TAB,                                 // type
    0,                                              // size
    P_ANIMATABLE + P_TV_SHOW_ALL + P_COMPUTED_NAME, // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_ALPHAS,            // name
    p_accessor, &channel_name_link_object,          // optional tags
    end,

    // These are for saving the channel data params
    kKrakatoaPFFileUpdate_blend_channel_arity, _T(""),
    TYPE_INT_TAB, // type
    0,            // size
    P_INVISIBLE,  // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_ARITY, end, kKrakatoaPFFileUpdate_blend_channel_use, _T(""),
    TYPE_INT_TAB, // type
    0,            // size
    P_INVISIBLE,  // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_USE, end, kKrakatoaPFFileUpdate_blend_channel_data_type, _T(""),
    TYPE_INT_TAB, // type
    0,            // size
    P_INVISIBLE,  // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_DATATYPE, end, kKrakatoaPFFileUpdate_blend_channel_blend_type, _T(""),
    TYPE_STRING_TAB, // type
    0,               // size
    P_INVISIBLE,     // flags
    IDS_FILEUPDATE_BLEND_CHANNEL_BLENDTYPE, end, kKrakatoaPFFileUpdate_target_channel_names, _T(""),
    TYPE_STRING_TAB, // type
    0,               // size
    P_INVISIBLE,     // flags
    IDS_FILEUPDATE_TARGET_CHANNEL_NAMES, end,

    // The rest of the options
    kKrakatoaPFFileUpdate_filepattern, _T("InputFileSequence"), TYPE_FILENAME, 0, IDS_FILEUPDATE_FILENAME, p_default,
    "", p_caption, IDS_FILEUPDATE_CAPTION, p_file_types, IDS_FILETYPES, p_ui, TYPE_FILEOPENBUTTON,
    IDC_FILEUPDATE_FILEBUTTON, end,

    kKrakatoaPFFileUpdate_frame_offset, _T("FrameOffset"), TYPE_INT, P_RESET_DEFAULT, IDS_FILEUPDATE_FRAMEOFFSET,
    // optional tagged param specs
    p_default, 0, p_ms_default, 0, p_range, -0x7FFF000, 0x7FFF000, p_ui, TYPE_SPINNER, EDITTYPE_INT,
    IDC_FILEUPDATE_FRAME_OFFSET, IDC_FILEUPDATE_FRAME_OFFSET_SPIN, 1.0f, end,

    kKrakatoaPFFileUpdate_single_frame_only, _T("SingleFrameOnly"), TYPE_BOOL, P_RESET_DEFAULT,
    IDS_FILEUPDATE_SINGLEFRAMEONLY,
    // optional tagged param specs
    p_default, FALSE, p_ms_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_FILEUPDATE_SINGLE_FRAME_ONLY, end,

    kKrakatoaPFFileUpdate_limit_to_custom_range, _T("LimitToCustomRange"), TYPE_BOOL, P_RESET_DEFAULT,
    IDS_FILEUPDATE_LIMITTOCUSTOMRANGE,
    // optional tagged param specs
    p_default, FALSE, p_ms_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_FILEUPDATE_LIMIT_TO_CUSTOM_RANGE,
    p_enable_ctrls, 2, kKrakatoaPFFileUpdate_custom_range_start, kKrakatoaPFFileUpdate_custom_range_end, end,

    kKrakatoaPFFileUpdate_custom_range_start, _T("CustomRangeStart"), TYPE_INT, P_RESET_DEFAULT,
    IDS_FILEUPDATE_CUSTOMRANGESTART,
    // optional tagged param specs
    p_default, 0, p_ms_default, 0, p_range, -0x7FFF000, 0x7FFF000, p_ui, TYPE_SPINNER, EDITTYPE_INT,
    IDC_FILEUPDATE_CUSTOM_RANGE_START, IDC_FILEUPDATE_CUSTOM_RANGE_START_SPIN, 1.0f, end,

    kKrakatoaPFFileUpdate_custom_range_end, _T("CustomRangeEnd"), TYPE_INT, P_RESET_DEFAULT,
    IDS_FILEUPDATE_CUSTOMRANGEEND,
    // optional tagged param specs
    p_default, 100, p_ms_default, 100, p_range, -0x7FFF000, 0x7FFF000, p_ui, TYPE_SPINNER, EDITTYPE_INT,
    IDC_FILEUPDATE_CUSTOM_RANGE_END, IDC_FILEUPDATE_CUSTOM_RANGE_END_SPIN, 1.0f, end,

    kKrakatoaPFFileUpdate_linktoobjecttm, _T("LinkToObjectTM"), TYPE_BOOL, P_RESET_DEFAULT,
    IDS_FILEUPDATE_LINKTOOBJECTTM,
    // optional tagged param specs
    p_default, FALSE, p_ms_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_FILEUPDATE_LINKTOOBJECTTM,
    // p_enable_ctrls, 1, kKrakatoaPFFileUpdate_objectfortm,
    end,

    end );
//----------------------------------------------------
// End of Param Block info
//----------------------------------------------------

// I don't like disabling warning 4239 (doing so promotes non cross-platform compatible code), but the Particle Flow SDK
// basically forces my hand here.
#pragma warning( disable : 4239 )

using namespace std;
using namespace boost;
using namespace frantic;
// using namespace frantic::graphics;
using namespace frantic::particles;
using namespace frantic::particles::streams;
using namespace frantic::channels;
using namespace frantic::max3d::particles;
using namespace frantic::max3d;

//----------------------------------------------------
// Simple useless functions
//----------------------------------------------------
KrakatoaPFFileUpdate::KrakatoaPFFileUpdate() {
    InitializeFPDescriptor();
    GetClassDesc()->MakeAutoParamBlocks( this );
}

KrakatoaPFFileUpdate::~KrakatoaPFFileUpdate() {}

Class_ID KrakatoaPFFileUpdate::ClassID() { return GetClassDesc()->ClassID(); }

ClassDesc* KrakatoaPFFileUpdate::GetClassDesc() const { return GetKrakatoaPFFileUpdateDesc(); }

void KrakatoaPFFileUpdate::BeginEditParams( IObjParam* ip, ULONG flags, Animatable* prev ) {
    GetClassDesc()->BeginEditParams( ip, this, flags, prev );
}

void KrakatoaPFFileUpdate::EndEditParams( IObjParam* ip, ULONG flags, Animatable* next ) {
    GetClassDesc()->EndEditParams( ip, this, flags, next );
}

const ParticleChannelMask& KrakatoaPFFileUpdate::ChannelsUsed( const Interval& /*time*/ ) const {
    static ParticleChannelMask mask( PCU_Amount | PCU_Position | PCU_Speed, PCU_Speed );
    return mask;
}

const Interval KrakatoaPFFileUpdate::ActivityInterval() const {
    //	return Interval(pblock()->GetInt(kKrakatoaPFFileUpdate_start), TIME_PosInfinity);
    if( pblock()->GetInt( kKrakatoaPFFileUpdate_limit_to_custom_range ) )
        return Interval(
            GetTicksPerFrame() * pblock()->GetInt( kKrakatoaPFFileUpdate_custom_range_start ) - GetTicksPerFrame() / 2,
            GetTicksPerFrame() * pblock()->GetInt( kKrakatoaPFFileUpdate_custom_range_end ) + GetTicksPerFrame() / 2 );
    else {
        Interval interval;
        interval = GetCOREInterface()->GetAnimRange();
        return interval;
    }
}

RefTargetHandle KrakatoaPFFileUpdate::Clone( RemapDir& remap ) {
    KrakatoaPFFileUpdate* newOp = new KrakatoaPFFileUpdate();
    newOp->ReplaceReference( 0, remap.CloneRef( GetReference( 0 ) ) );
    BaseClone( this, newOp, remap );
    return newOp;
}

#if MAX_VERSION_MAJOR >= 15
const
#endif
    TCHAR*
    KrakatoaPFFileUpdate::GetObjectName() {
    return _T("Krakatoa File Update ");
}

void KrakatoaPFFileUpdate::PassMessage( int message, LPARAM param ) {
    if( pblock() == NULL )
        return;

    IParamMap2* map = pblock()->GetMap();
    if( ( map == NULL ) && ( NumPViewParamMaps() == 0 ) )
        return;

    HWND hWnd;
    if( map != NULL ) {
        hWnd = map->GetHWnd();
        if( hWnd ) {
            SendMessage( hWnd, WM_COMMAND, message, param );
        }
    }

    for( int i = 0; i < NumPViewParamMaps(); i++ ) {
        hWnd = GetPViewParamMap( i )->GetHWnd();
        if( hWnd ) {
            SendMessage( hWnd, WM_COMMAND, message, param );
        }
    }
}

//----------------------------------------------------
// Meaty Important Functions
//----------------------------------------------------
RefResult KrakatoaPFFileUpdate::NotifyRefChanged( const Interval& changeInt, RefTargetHandle hTarget,
                                                  PartID& /*partID*/, RefMessage message, BOOL /*propagate*/ ) {

    switch( message ) {
    case REFMSG_CHANGE:
        if( hTarget == pblock() ) {
            int lastUpdateID = pblock()->LastNotifyParamID();
            switch( lastUpdateID ) {
            case kKrakatoaPFFileUpdate_filepattern: {
                // query the file for a particle channel map when it gets changed and stick it in
                // the member variable
                std::string filename = pblock()->GetStr( kKrakatoaPFFileUpdate_filepattern );
                shared_ptr<particle_istream> pin = frantic::particles::particle_file_istream_factory( filename );
                frantic::channels::channel_map channelMap = pin->get_channel_map();

                if( !channelMap.has_channel( "ID" ) ) {
                    std::string msg( "WARNING:  The selected particle file (" + filename +
                                     ") has no ID channel.  Particles will be assigned IDs based upon the order that "
                                     "they appear in the file." );
                    mprintf( "%s\n", msg.c_str() );
                    LogSys* log = GetCOREInterface()->Log();
                    log->LogEntry( SYSLOG_ERROR, DISPLAY_DIALOG, _T("KrakatoaPFFileUpdate WARNING"), _T("%s"),
                                   msg.c_str() );
                }
                // reset the channelData structures and include any default information
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_names, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_target_channel_names, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_alphas, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_arity, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_data_type, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_use, (int)channelMap.channel_count() );
                pblock()->SetCount( kKrakatoaPFFileUpdate_blend_channel_blend_type, (int)channelMap.channel_count() );
                for( unsigned int i = 0; i < channelMap.channel_count(); ++i ) {

                    pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_names, 0,
                                        (TCHAR*)channelMap[i].name().c_str(), i );
                    pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_arity, 0, (int)channelMap[i].arity(), i );
                    pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_data_type, 0, channelMap[i].data_type(),
                                        i );

                    // default position and ID to "on"
                    if( channelMap[i].name().compare( "Position" ) == 0 || channelMap[i].name().compare( "ID" ) == 0 )
                        pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_use, 0, 1, i );
                    else
                        pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_use, 0, 0, i );

                    pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_blend_type, 0, (TCHAR*)"Blend", i );
                    pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_alphas, 0, 100.f, (int)i );
                    pblock()->SetValue( kKrakatoaPFFileUpdate_target_channel_names, 0,
                                        (TCHAR*)channelMap[i].name().c_str(), i );

                    // default density to go to MXSFloat
                    if( channelMap[i].name().compare( "Density" ) == 0 )
                        pblock()->SetValue( kKrakatoaPFFileUpdate_target_channel_names, 0, (TCHAR*)"MXSFloat", i );
                }

                PassMessage( kKrakatoaPFFileUpdate_message_filename,
                             (LPARAM)pblock()->GetStr( kKrakatoaPFFileUpdate_filepattern ) );
            }
            case kKrakatoaPFFileUpdate_frame_offset:
            case kKrakatoaPFFileUpdate_single_frame_only:
            case kKrakatoaPFFileUpdate_limit_to_custom_range:
            case kKrakatoaPFFileUpdate_custom_range_start:
            case kKrakatoaPFFileUpdate_custom_range_end:
            case kKrakatoaPFFileUpdate_linktoobjecttm:
                NotifyDependents( FOREVER, 0, kPFMSG_InvalidateParticles, NOTIFY_ALL, TRUE );
                break;
            }
            UpdatePViewUI( lastUpdateID );
        }
        break;
    // Initialization of Dialog
    case kKrakatoaPFFileUpdate_message_init:
        PassMessage( kKrakatoaPFFileUpdate_message_filename,
                     (LPARAM)pblock()->GetStr( kKrakatoaPFFileUpdate_filepattern ) );
        return REF_STOP;
    case IDC_FILEUPDATE_CHANNEL_SETUP_BUTTON:
        try {

            // make sure we have some data to pass first.  ie, that a file has been selected.
            std::string file = pblock()->GetStr( kKrakatoaPFFileUpdate_filepattern );
            if( file.empty() )
                break;

            std::string scriptString =
                "Krakatoa_PFlowOperator_Functions.OpenChannelSelectorDialog KrakatoaPFFileUpdateObject";
            frantic::max3d::mxs::expression( scriptString )
                .bind( "KrakatoaPFFileUpdateObject", this )
                .at_time( changeInt.Start() )
                .evaluate<Value*>();

            break;

        } catch( const std::exception& e ) {
            mprintf( "Error in KrakatoaPFFileUpdate::NotifyRefChanged()\n\t%s\n", e.what() );
            LogSys* log = GetCOREInterface()->Log();
            log->LogEntry( SYSLOG_ERROR, DISPLAY_DIALOG, _T("KrakatoaPFFileUpdate Error"), _T("%s"), e.what() );
            if( GetCOREInterface()->IsNetworkRenderServer() )
                throw MAXException( const_cast<char*>( e.what() ) );
        }
    }
    return REF_SUCCEED;
}

//+--------------------------------------------------------------------------+
//|							From IPViewItem
//|
//+--------------------------------------------------------------------------+
HBITMAP KrakatoaPFFileUpdate::GetActivePViewIcon() {
    if( activeIcon() == NULL )
        _activeIcon() = (HBITMAP)LoadImage( hInstance, MAKEINTRESOURCE( IDB_FILEUPDATE_ACTIVE ), IMAGE_BITMAP,
                                            kActionImageWidth, kActionImageHeight, LR_SHARED );
    return activeIcon();
}

//+--------------------------------------------------------------------------+
//|							From IPViewItem
//|
//+--------------------------------------------------------------------------+
HBITMAP KrakatoaPFFileUpdate::GetInactivePViewIcon() {
    if( inactiveIcon() == NULL )
        _inactiveIcon() = (HBITMAP)LoadImage( hInstance, MAKEINTRESOURCE( IDB_FILEUPDATE_INACTIVE ), IMAGE_BITMAP,
                                              kActionImageWidth, kActionImageHeight, LR_SHARED );
    return inactiveIcon();
}

std::string KrakatoaPFFileUpdate::get_channel_data() {

    std::string channelDataString;
    int channelCount = pblock()->Count( kKrakatoaPFFileUpdate_blend_channel_names );
    for( int i = 0; i < channelCount; ++i ) {
        channelDataString.append(
            std::string( pblock()->GetStr( kKrakatoaPFFileUpdate_blend_channel_names, 0, i ) ) + "," +
            channel_data_type_str( (frantic::channels::data_type_t)pblock()->GetInt(
                kKrakatoaPFFileUpdate_blend_channel_data_type, 0, i ) ) +
            "," +
            boost::lexical_cast<std::string>( pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_arity, 0, i ) ) +
            "," +
            boost::lexical_cast<std::string>( pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_use, 0, i ) ) +
            "," + std::string( pblock()->GetStr( kKrakatoaPFFileUpdate_blend_channel_blend_type, 0, i ) ) + "," +
            std::string( pblock()->GetStr( kKrakatoaPFFileUpdate_target_channel_names, 0, i ) ) + "," );
    }
    channelDataString.erase( channelDataString.size() - 1 );
    // mprintf("get_channel_data(): channelDataString: %s\n", channelDataString.c_str());
    // ofstream //fout("C:\\maxtests\\particleflow\\krakatoa\\debug.txt", std::ios_base::app);
    // fout << "get_channel_data(): channelDataString: " << channelDataString << std::endl;
    return channelDataString;
}

void KrakatoaPFFileUpdate::set_channel_data( std::string channelDataString ) {
    // mprintf("set_channel_data(): channelDataString: %s\n", channelDataString.c_str());
    // ofstream //fout("C:\\maxtests\\particleflow\\krakatoa\\debug.txt", std::ios_base::app);
    // fout << "set_channel_data(): channelDataString: " << channelDataString << std::endl;
    try {

        vector<string> tokens;
        frantic::strings::split( channelDataString, tokens, ',' );

        size_t tokenCount = 0;
        int channelCount = 0;

        while( tokenCount < tokens.size() ) {

            pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_names, 0, (TCHAR*)tokens[tokenCount++].c_str(),
                                channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error(
                    "KrakatoaPFFileUpdate::SetChannelData: Could not read channel type.  Token did not exist." );
            frantic::channels::data_type_t dataType = channel_data_type_from_string( tokens[tokenCount++] );
            pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_data_type, 0, dataType, channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error(
                    "KrakatoaPFFileUpdate::SetChannelData: Could not read channel arity.  Token did not exist." );
            int arity = boost::lexical_cast<int, string>( tokens[tokenCount++] );
            if( arity < 1 )
                throw std::runtime_error(
                    "KrakatoaPFFileUpdate::SetChannelData: Bad channel arity token.  Value converts to 0 or less." );

            pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_arity, 0, arity, channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error(
                    "KrakatoaPFFileUpdate::SetChannelData: Could not read use channel flag.  Token did not exist." );
            int useChannel = boost::lexical_cast<int, string>( tokens[tokenCount++] );

            pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_use, 0, useChannel, channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error(
                    "KrakatoaPFFileUpdate::SetChannelData: Could not read blend type.  Token did not exist." );

            pblock()->SetValue( kKrakatoaPFFileUpdate_blend_channel_blend_type, 0, (TCHAR*)tokens[tokenCount++].c_str(),
                                channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error( "KrakatoaPFFileUpdate::SetChannelData: Could not read particle flow channel "
                                          "name.  Token did not exist." );

            pblock()->SetValue( kKrakatoaPFFileUpdate_target_channel_names, 0, (TCHAR*)tokens[tokenCount++].c_str(),
                                channelCount );

            if( tokenCount > tokens.size() )
                throw std::runtime_error( "KrakatoaPFFileUpdate::SetChannelData: Could not read particle channel name. "
                                          " Token did not exist." );

            channelCount++;
        }

    } catch( const std::exception& e ) {
        mprintf( "Error in KrakatoaPFFileUpdate::SetChannelData()\n\t%s\n", e.what() );
        LogSys* log = GetCOREInterface()->Log();
        log->LogEntry( SYSLOG_ERROR, DISPLAY_DIALOG, _T("KrakatoaPFFileUpdate Error"), _T("%s"), e.what() );
        if( GetCOREInterface()->IsNetworkRenderServer() )
            throw MAXException( const_cast<char*>( e.what() ) );
    }
}

bool KrakatoaPFFileUpdate::Proceed( IObject* pCont, PreciseTimeValue timeStart, PreciseTimeValue& timeEnd,
                                    Object* pSystem, INode* pNode, INode* /*actionNode*/,
                                    IPFIntegrator* /*integrator*/ ) {
    try {
        if( pblock() == NULL ) {
            if( this->GetParamBlock( 0 ) != NULL )
                throw std::runtime_error( "KrakatoaPFFileUpdate's pblock was NULL, but Ref0 was not" );
            throw std::runtime_error( "KrakatoaPFFileUpdate's pblock was NULL" );
        }

        // ofstream fout("C:\\maxtests\\particleflow\\krakatoa\\debug.txt");
        // mprintf("\n\nExecuting KrakatoaPFFileUpdate::Proceed\n");
        // fout << "KrakatoaPFFileUpdate::Proceed\n" << std::endl;

        IParticleObjectExt* poExt = GetParticleObjectExtInterface( GetPFObject( pSystem ) );
        if( poExt == NULL ) {
            throw std::runtime_error( "KrakatoaPFFileUpdate: Could not set up the interface to ParticleObjectExt" );
        } // no handle for ParticleObjectExt interface

        IPFSystem* pfSys = GetPFSystemInterface( pSystem );
        if( pfSys == NULL ) {
            throw std::runtime_error( "KrakatoaPFFileUpdate: Could not set up the interface to PFSystem" );
        } // no handle for PFSystem interface

        TimeValue evalTime = timeEnd;
        int frameNumber = (int)ceil( double( evalTime ) / (double)GetTicksPerFrame() );
        // int frameNumber = (evalTime + GetTicksPerFrame()/2) / GetTicksPerFrame();

        // mprintf("KrakatoaPFFileUpdate: evalTime %d  frameNumber %d\n", evalTime, frameNumber);
        // fout << "KrakatoaPFFileUpdate: evalTime " << evalTime << " frameNumber " << frameNumber << std::endl;
        // fout << "timestart " << timeStart.TimeValue() << "  timeEnd " << timeEnd.TimeValue() << std::endl;
        if( pblock()->GetInt( kKrakatoaPFFileUpdate_limit_to_custom_range ) ) {
            TimeValue customRangeStart = pblock()->GetInt( kKrakatoaPFFileUpdate_custom_range_start );
            TimeValue customRangeEnd = pblock()->GetInt( kKrakatoaPFFileUpdate_custom_range_end );

            // Return success if it's outside the range
            if( frameNumber < customRangeStart || frameNumber > customRangeEnd )
                return true;
        }

        std::string targetFile = pblock()->GetStr( kKrakatoaPFFileUpdate_filepattern );
        if( targetFile.empty() )
            return true;

        // Substitute the frame number
        bool singleFrameOnly = ( pblock()->GetInt( kKrakatoaPFFileUpdate_single_frame_only ) != FALSE );
        if( !singleFrameOnly ) {
            frameNumber += pblock()->GetInt( kKrakatoaPFFileUpdate_frame_offset );
            targetFile = frantic::files::replace_sequence_number( targetFile, frameNumber );
        }

        if( !frantic::files::file_exists( targetFile ) ) {
            // throw std::runtime_error("PFOperatorKrakatoaPFFileUpdate: The input file: " + targetFile + " does not
            // exist.");
            return false;
        }

        // Define the source and destination channel maps.  We're only allowing a shuffling of channels
        // of the same type/arity at the moment, so I basically just have to switch the names of the channels.
        // I should be enforcing this with a check though, really.
        channel_map filePcm, pflowPcm;
        for( int i = 0; i < pblock()->Count( kKrakatoaPFFileUpdate_blend_channel_names ); ++i ) {
            if( pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_use, 0, i ) == 1 ) {
                filePcm.define_channel(
                    pblock()->GetStr( kKrakatoaPFFileUpdate_blend_channel_names, 0, i ),
                    pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_arity, 0, i ),
                    (data_type_t)pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_data_type, 0, i ) );
                pflowPcm.define_channel(
                    pblock()->GetStr( kKrakatoaPFFileUpdate_target_channel_names, 0, i ),
                    pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_arity, 0, i ),
                    (data_type_t)pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_data_type, 0, i ) );
            }
        }
        filePcm.end_channel_definition( 1, true );
        pflowPcm.end_channel_definition( 1, true );

        if( filePcm.channel_count() == 0 )
            return true;

        // fout << "done pcm definitions " << std::endl;
        // for (size_t i = 0; i < filePcm.channel_count(); ++i) {
        // fout << filePcm[i].name() << ",";
        //}
        // fout << std::endl;
        // for (size_t i = 0; i < pflowPcm.channel_count(); ++i){
        // fout << pflowPcm[i].name() << ",";
        //}
        // fout << std::endl;

        // Set up the blending and try to acquire the channels we need (will toss an exception if we can't)
        std::vector<string> blendChannelNames;
        std::vector<int> blendTypes;
        std::vector<float> blendAlphas;
        float expFraction = float( timeEnd.TimeValue() - timeStart.TimeValue() ) / float( GetTicksPerFrame() );

        for( int i = 0; i < pblock()->Count( kKrakatoaPFFileUpdate_blend_channel_names ); ++i ) {
            if( pblock()->GetInt( kKrakatoaPFFileUpdate_blend_channel_use, timeStart, i ) ) {
                float alpha = pblock()->GetFloat( kKrakatoaPFFileUpdate_blend_channel_alphas, timeStart, i ) / 100.f;
                blendChannelNames.push_back(
                    pblock()->GetStr( kKrakatoaPFFileUpdate_target_channel_names, timeStart, i ) );
                // fout << pblock()->GetStr(kKrakatoaPFFileUpdate_blend_channel_names,timeStart,i);
                // fout << "   " << pblock()->GetStr(kKrakatoaPFFileUpdate_blend_channel_blend_type,timeStart,i) << "
                // alpha: " << alpha << std::endl;
                if( !string( "Blend" ).compare(
                        pblock()->GetStr( kKrakatoaPFFileUpdate_blend_channel_blend_type, timeStart, i ) ) ) {
                    blendTypes.push_back( 0 ); // regular blend
                    blendAlphas.push_back( 1.f - pow( 1.f - alpha, expFraction ) );
                } else {
                    blendTypes.push_back( 1 ); // "additive" blend
                    blendAlphas.push_back( alpha * float( timeEnd - timeStart ) / GetTicksPerFrame() );
                }
            }
        }
        // fout << "done blending map data" << std::endl;

        particle_flow_channel_interface pfci( pCont );
        pfci.initialize_channel_interface( pflowPcm, blendChannelNames, blendTypes, blendAlphas );
        // fout << "done channel initialization " << std::endl;

        // mprintf( "Getting particles from \"%s\"\n", targetFile.c_str() );
        // fout << "Geting particles from " << targetFile << std::endl;

        // Set up the stream for reading
        shared_ptr<particle_istream> pin = frantic::particles::particle_file_istream_factory( targetFile, filePcm );

        // TODO:  Come up with some sort of hashing scheme to allow the fractional loading to work correctly.
        // Right now, you have to load everything if you want it to work correctly.  Fractionally loading does
        // not guarantee (on the contrary, it's highly unlikely) that you'll get consistent IDs for the
        // birthing/updating, unless particles IDs are completely static throughout the sequence.

        // force a particle multiplier of 100% for file operations
        float particleCountMultiplier = 1.0f; // pfSys->GetMultiplier(timeStart);
        int particlesLimit = pfSys->GetBornAllowance() - poExt->NumParticlesGenerated();
        bool evenlyDistributeLoading = true;

        if( pin->particle_count() > 0 )
            pin = apply_fractional_particle_istream( pin, particleCountMultiplier, particlesLimit,
                                                     evenlyDistributeLoading );

        if( pin->particle_count() == 0 ) {
            return true;
        }
        // fout << "num particles " << pin->particle_count() << std::endl;

        bool useNodeTransform = ( pblock()->GetInt( kKrakatoaPFFileUpdate_linktoobjecttm ) != FALSE );
        if( useNodeTransform && pNode )
            pin = frantic::max3d::particles::transform_stream_with_inode( pNode, timeStart,
                                                                          ( timeEnd - timeStart ).TimeValue(), pin );

        // Load the particles

        size_t particleSize = filePcm.structure_size();  // Size of the particle structure
        vector<char> buffer;                             // Temp storage
        unsigned counter;                                // Counts the number of particles in a reading batch
        int fileCount = 0;                               // Counts total number of particles read from file
        int pflowCount = pfci.get_particle_flow_count(); // Number of current pflow particles

        vector<char> particles;         // Local storage of particles from file
        vector<idPair> fileID;          // Indices and IDs of file particles
        vector<idPair> pflowID;         // Indices and IDs of pflow particles
        channel_general_accessor idAcc; // accessor for particle's ID info in the file
        bool hasID = false;
        if( filePcm.has_channel( "ID" ) ) {
            idAcc = filePcm.get_general_accessor( "ID" );
            hasID = true;
        }

        // fout << "Particle size: " << particleSize << " buffer size: " << buffer.size() << std::endl;
        //  We don't know how many particles we are reading...do batches?
        if( pin->particle_count() < 0 ) {
            buffer.resize( NUM_BUFFERED_PARTICLES * particleSize );
            do {
                char* pParticle = &buffer[0];
                for( counter = 0; counter < NUM_BUFFERED_PARTICLES; ++counter ) {
                    if( !pin->get_particle( pParticle ) )
                        break;
                    pParticle += particleSize;
                }

                if( counter == 0 )
                    break;

                // build id info
                fileID.resize( fileID.size() + counter );

                for( unsigned int i = 0; i < counter; i++ ) {
                    fileID[fileCount + i].index = fileCount + i;
                    if( hasID )
                        fileID[fileCount + i].id =
                            *( (int*)idAcc.get_channel_data_pointer( &buffer[i * particleSize] ) );
                    else
                        fileID[fileCount + i].id = fileCount + i;
                }

                // temp store of particle info
                particles.resize( particles.size() + counter * particleSize );
                memcpy( &particles[fileCount * particleSize], &buffer[0], counter * particleSize );
                fileCount += counter;

            } while( counter == NUM_BUFFERED_PARTICLES ); // Keep writing particles until we find a non-full batch
        }
        // Positive particle count, we know how many.
        else {

            fileCount = (int)pin->particle_count();
            particles.resize( fileCount * particleSize );
            fileID.resize( fileCount );
            for( int i = 0; i < fileCount; i++ ) {

                pin->get_particle( &particles[i * particleSize] );

                // build id info
                fileID[i].index = i;
                if( hasID )
                    fileID[i].id = *( (int*)idAcc.get_channel_data_pointer( &particles[i * particleSize] ) );
                else
                    fileID[i].id = i;
            }
        }

        // fout << "Done fetching particles from file" << std::endl;

        // Grab all the pflow particle indices/ids
        pflowID.resize( pflowCount );
        for( int i = 0; i < pflowCount; i++ ) {
            pflowID[i].index = i;
            pflowID[i].id = pfci.get_particle_file_id( i );
        }

        // fout << "Done fetching pflow ids" << std::endl;

        // sort the file index/ID pairs by ID
        if( hasID ) // only needs to be sorted if we had to load IDs from the file
            sort( fileID.begin(), fileID.end(), &KrakatoaPFFileUpdate::sortFunction );
        sort( pflowID.begin(), pflowID.end(), &KrakatoaPFFileUpdate::sortFunction );

        /*
                        mprintf("\nKrakatoaPFFileUpdate Particles\n");
                        mprintf("Interval: %d %d\n", timeStart.tick, timeEnd.tick);
                        mprintf("BEFORE\n");
                        mprintf("File particles (but with the pflow map):\n");
                        for(unsigned int i = 0;i < fileID.size(); i++) {
                                mprintf("FID: %d\t", fileID[i].id);
                                if ( pflowPcm.has_channel("Position") ) {
                                        channel_cvt_accessor<frantic::graphics::vector3f> pa =
           pflowPcm.get_cvt_accessor<frantic::graphics::vector3f>("Position"); frantic::graphics::vector3f pos =
           pa.get(&particles[i*particleSize]); mprintf("pos: %s\t", pos.str().c_str());
                                }
                                if ( pflowPcm.has_channel("Velocity") ) {
                                        channel_cvt_accessor<frantic::graphics::vector3f> va =
           pflowPcm.get_cvt_accessor<frantic::graphics::vector3f>("Velocity"); frantic::graphics::vector3f vel =
           va.get(&particles[i*particleSize]); mprintf("vel: %s\t", vel.str().c_str());
                                }
                                mprintf("\n");
                        }
                        mprintf("Pflow particles BEFORE:\n");
                        for(unsigned int i = 0; i < pflowID.size(); i++)  {
                                mprintf("FID: %d\t", pflowID[i].id);
                                if (pfci.m_channels.positionR){
                                        frantic::graphics::vector3f pos = pfci.m_channels.positionR->GetValue(i);
                                        mprintf("pos: %s  blend:%f\t", pos.str().c_str(),
           pfci.m_channels.positionBlendAlpha);
                                }
                                if (pfci.m_channels.velocityR) {
                                        frantic::graphics::vector3f vel = pfci.m_channels.velocityR->GetValue(i);
                                        mprintf("vel: %s  blend:%f\t", vel.str().c_str(),
           pfci.m_channels.velocityBlendAlpha);
                                }
                                mprintf("\n");
                        }
        */

        // fout << "Done sorting ids" << std::endl;
        size_t fileCounter = 0, pflowCounter = 0;
        bool done = false;
        while( !done ) {
            if( fileCounter >= fileID.size() || pflowCounter >= pflowID.size() ) {
                // we hit the end of one of the ID arrays
                done = true;
            } else if( pflowID[pflowCounter].id == fileID[fileCounter].id ) {
                pfci.copy_particle( particles[fileID[fileCounter].index * particleSize], pflowID[pflowCounter].index,
                                    timeEnd, pfci.is_particle_new( pflowID[pflowCounter].index ) );
                pflowCounter++;
                fileCounter++;
            } else if( pflowID[pflowCounter].id < fileID[fileCounter].id ) {
                // the pflow particle doesnt have a match in the file, keep going
                pfci.set_particle_file_id( pflowID[pflowCounter].index, -1 );
                pflowCounter++;
            } else if( fileID[fileCounter].id < pflowID[pflowCounter].id ) {
                // the file ID doesn't exist in the pflow ID list, keep going
                fileCounter++;
            }
        }
        // since you might have just hit the end of one of the arrays, loop through the remaining
        // particles in pflow and flag them for deletion
        while( pflowCounter < pflowID.size() ) {
            pfci.set_particle_file_id( pflowID[pflowCounter].index, -1 );
            pflowCounter++;
        }

        /*
                        mprintf("Pflow particles AFTER:\n");
                        for(unsigned int i = 0; i < pflowID.size(); i++)  {
                                mprintf("FID: %d\t", pflowID[i].id);
                                if (pfci.m_channels.positionR){
                                        frantic::graphics::vector3f pos = pfci.m_channels.positionR->GetValue(i);
                                        mprintf("pos: %s\t", pos.str().c_str());
                                }
                                if (pfci.m_channels.velocityR) {
                                        frantic::graphics::vector3f vel = pfci.m_channels.velocityR->GetValue(i);
                                        mprintf("vel: %s\t", vel.str().c_str());
                                }
                                mprintf("\n");
                        }
        */
    } catch( const std::exception& e ) {
        mprintf( "Error in KrakatoaPFFileUpdate::Proceed()\n\t%s\n", e.what() );
        // LogSys* log = GetCOREInterface()->Log();
        // log->LogEntry( SYSLOG_ERROR, DISPLAY_DIALOG, _T("KrakatoaPFFileUpdate Error"), _T("%s"), e.what() );
        if( GetCOREInterface()->IsNetworkRenderServer() )
            throw MAXException( const_cast<char*>( e.what() ) );
        return false;
    }

    return true;
}

void KrakatoaPFFileUpdate::InitializeFPDescriptor() {
    FFCreateDescriptor c( this, KrakatoaPFFileUpdate_Interface_ID, "KrakatoaPFFileUpdateInterface",
                          GetKrakatoaPFFileUpdateDesc() );

    c.add_function( &KrakatoaPFFileUpdate::get_channel_data, "GetChannelData" );
    c.add_function( &KrakatoaPFFileUpdate::set_channel_data, "SetChannelData", "ChannelDataString" );
}
#endif