// SPDX-License-Identifier: BSD-3-Clause
// Copyright Contributors to the OpenColorIO Project.


#include "OpenColorIO_AE.h"

#include "OpenColorIO_AE_Context.h"
#include "OpenColorIO_AE_Dialogs.h"

#include "AEGP_SuiteHandler.h"

#include <mutex>

#include <assert.h>


// this lives in OpenColorIO_AE_UI.cpp
std::string GetProjectDir(PF_InData *in_data);


static PF_Err About(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    PF_SPRINTF( out_data->return_msg,
                "OpenColorIO\r\r"
                "opencolorio.org\r"
                "version %s",
                OCIO::GetVersion() );
    
    return PF_Err_NONE;
}


static PF_Err GlobalSetup(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    out_data->my_version    =   PF_VERSION( MAJOR_VERSION,
                                            MINOR_VERSION,
                                            BUG_VERSION,
                                            STAGE_VERSION,
                                            BUILD_VERSION);

    out_data->out_flags     =   PF_OutFlag_DEEP_COLOR_AWARE     |
                                PF_OutFlag_PIX_INDEPENDENT      |
                                PF_OutFlag_CUSTOM_UI            |
                                PF_OutFlag_USE_OUTPUT_EXTENT    |
                                PF_OutFlag_I_HAVE_EXTERNAL_DEPENDENCIES;

    out_data->out_flags2    =   PF_OutFlag2_PARAM_GROUP_START_COLLAPSED_FLAG |
                                PF_OutFlag2_SUPPORTS_SMART_RENDER   |
                                PF_OutFlag2_FLOAT_COLOR_AWARE       |
                                PF_OutFlag2_PPRO_DO_NOT_CLONE_SEQUENCE_DATA_FOR_RENDER |
                                PF_OutFlag2_SUPPORTS_GET_FLATTENED_SEQUENCE_DATA |
                                PF_OutFlag2_SUPPORTS_THREADED_RENDERING |
                                PF_OutFlag2_MUTABLE_RENDER_SEQUENCE_DATA_SLOWER;
    
    
    GlobalSetup_GL();
    
    
    if(in_data->appl_id == 'PrMr')
    {
        PF_PixelFormatSuite1 *pfS = NULL;
        
        in_data->pica_basicP->AcquireSuite(kPFPixelFormatSuite,
                                            kPFPixelFormatSuiteVersion1,
                                            (const void **)&pfS);
        
        if(pfS)
        {
            pfS->ClearSupportedPixelFormats(in_data->effect_ref);
            
            pfS->AddSupportedPixelFormat(in_data->effect_ref,
                                            PrPixelFormat_BGRA_4444_32f);
            
            in_data->pica_basicP->ReleaseSuite(kPFPixelFormatSuite,
                                                kPFPixelFormatSuiteVersion1);
        }
    }
    
    return PF_Err_NONE;
}


static PF_Err GlobalSetdown(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    GlobalSetdown_GL();
    
    return PF_Err_NONE;
}


static PF_Err ParamsSetup(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output)
{
    PF_Err          err = PF_Err_NONE;
    
    PF_ParamDef     def;

    // readout
    AEFX_CLR_STRUCT(def);
    
    ArbNewDefault(in_data, out_data, NULL, &def.u.arb_d.dephault);
    
    PF_ADD_ARBITRARY2("OCIO",
                        UI_CONTROL_WIDTH,
                        UI_CONTROL_HEIGHT,
                        PF_ParamFlag_SUPERVISE | PF_ParamFlag_CANNOT_TIME_VARY,
                        PF_PUI_CONTROL,
                        def.u.arb_d.dephault,
                        OCIO_DATA,
                        NULL);
    
    
    AEFX_CLR_STRUCT(def);
    PF_ADD_CHECKBOX("",
                    "Use GPU",
                    FALSE,
                    0,
                    OCIO_GPU_ID);
    

    out_data->num_params = OCIO_NUM_PARAMS;

    // register custom UI
    if (!err)
    {
        PF_CustomUIInfo         ci;

        AEFX_CLR_STRUCT(ci);
        
        ci.events               = PF_CustomEFlag_EFFECT;
        
        ci.comp_ui_width        = ci.comp_ui_height = 0;
        ci.comp_ui_alignment    = PF_UIAlignment_NONE;
        
        ci.layer_ui_width       = 0;
        ci.layer_ui_height      = 0;
        ci.layer_ui_alignment   = PF_UIAlignment_NONE;
        
        ci.preview_ui_width     = 0;
        ci.preview_ui_height    = 0;
        ci.layer_ui_alignment   = PF_UIAlignment_NONE;

        err = (*(in_data->inter.register_ui))(in_data->effect_ref, &ci);
    }


    return err;
}


static void UpdateContext(
    PF_InData       *in_data,
    ArbitraryData   *arb_data,
    SequenceData    *seq_data)
{
    assert(seq_data != NULL && arb_data != NULL);

    seq_data->status = STATUS_OK;

    std::string dir = GetProjectDir(in_data);

    // must always verify that our context lines up with the parameters
    // things like undo can change them without notice
    if(seq_data->context != NULL)
    {
        bool verified = seq_data->context->Verify(arb_data, dir);
        
        if(!verified)
        {
            delete seq_data->context;
            
            seq_data->status = STATUS_UNKNOWN;
            seq_data->context = NULL;
        }
    }


    if(arb_data->action == OCIO_ACTION_NONE)
    {
        seq_data->status = STATUS_NO_FILE;
    }
    else
    {
        const bool refreshSeqData = (seq_data->source != arb_data->source ||
                                    0 != strcmp(seq_data->path, arb_data->path) ||
                                    0 != strcmp(seq_data->relative_path, arb_data->relative_path));
        
        if(seq_data->context == NULL || refreshSeqData)
        {
            seq_data->source = arb_data->source;
            
            if(arb_data->source == OCIO_SOURCE_ENVIRONMENT)
            {
                std::string env;
                OpenColorIO_AE_Context::getenvOCIO(env);
                
                if(env.empty())
                {
                    seq_data->status = STATUS_FILE_MISSING;
                }
                else
                {
                    nt_strncpy(seq_data->path, "", ARB_PATH_LEN+1);
                    nt_strncpy(seq_data->relative_path, "", ARB_PATH_LEN+1);
                }
            }
            else if(arb_data->source == OCIO_SOURCE_STANDARD)
            {
                std::string path = GetStdConfigPath(arb_data->path);
                
                if( path.empty() )
                {
                    seq_data->status = STATUS_FILE_MISSING;
                }
                else
                {
                    nt_strncpy(seq_data->path, arb_data->path, ARB_PATH_LEN+1);
                    nt_strncpy(seq_data->relative_path, arb_data->relative_path, ARB_PATH_LEN+1);
                }
            }
            else if(arb_data->source == OCIO_SOURCE_CUSTOM)
            {
                Path absolute_path(arb_data->path, dir);
                Path relative_path(arb_data->relative_path, dir);
                Path seq_absolute_path(seq_data->path, dir);
                Path seq_relative_path(seq_data->relative_path, dir);
                
                if( absolute_path.exists() )
                {
                    seq_data->status = STATUS_USING_ABSOLUTE;
                    
                    nt_strncpy(seq_data->path, absolute_path.full_path().c_str(), ARB_PATH_LEN+1);
                    nt_strncpy(seq_data->relative_path, absolute_path.relative_path(false).c_str(), ARB_PATH_LEN+1);
                }
                else if( relative_path.exists() )
                {
                    seq_data->status = STATUS_USING_RELATIVE;
                    
                    nt_strncpy(seq_data->path, relative_path.full_path().c_str(), ARB_PATH_LEN+1);
                    nt_strncpy(seq_data->relative_path, relative_path.relative_path(false).c_str(), ARB_PATH_LEN+1);
                }
                else if( seq_absolute_path.exists() )
                {
                    // In some cases, we may have a good path in sequence options but not in
                    // the arbitrary parameter.  An alert will not be provided because it is the
                    // sequence data that gets checked.  Therefore, we have to use the sequence
                    // options as a last resort.  We copy the path back to arb data, but the change
                    // should not stick.
                    seq_data->status = STATUS_USING_ABSOLUTE;
                    
                    nt_strncpy(arb_data->path, seq_absolute_path.full_path().c_str(), ARB_PATH_LEN+1);
                    nt_strncpy(arb_data->relative_path, seq_absolute_path.relative_path(false).c_str(), ARB_PATH_LEN+1);
                }
                else if( seq_relative_path.exists() )
                {
                    seq_data->status = STATUS_USING_RELATIVE;
                    
                    nt_strncpy(arb_data->path, seq_relative_path.full_path().c_str(), ARB_PATH_LEN+1);
                    nt_strncpy(arb_data->relative_path, seq_relative_path.relative_path(false).c_str(), ARB_PATH_LEN+1);
                }
                else
                    seq_data->status = STATUS_FILE_MISSING;
            }
            
            
            if(seq_data->status != STATUS_FILE_MISSING)
            {
                seq_data->context = new OpenColorIO_AE_Context(arb_data, dir);
            }
        }
    }
}


static PF_Err UserChangedParam(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output,
    PF_UserChangedParamExtra *extra)
{
    PF_Err          err = PF_Err_NONE;
    
    if(extra->param_index == OCIO_DATA)
    {
        assert(params[OCIO_DATA]->u.arb_d.value != NULL && in_data->sequence_data != NULL);
    
        ArbitraryData *arb_data = (ArbitraryData *)PF_LOCK_HANDLE(params[OCIO_DATA]->u.arb_d.value);
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        try
        {
            UpdateContext(in_data, arb_data, seq_data);
        }
        catch(...)
        {
            seq_data->status = STATUS_OCIO_ERROR;
            
            assert(FALSE);
        }
        
        PF_UNLOCK_HANDLE(params[OCIO_DATA]->u.arb_d.value);
        PF_UNLOCK_HANDLE(in_data->sequence_data);
    }
    else
        assert(FALSE);
    
    return err;
}


static PF_Err SequenceSetup(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    PF_Err err = PF_Err_NONE;
    
    // set up sequence data
    assert(in_data->sequence_data == NULL);
    assert(sizeof(SequenceData) == 528);
    
    out_data->sequence_data = PF_NEW_HANDLE( sizeof(SequenceData) );
    
    SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(out_data->sequence_data);

    seq_data->status = STATUS_UNKNOWN;
    seq_data->gpu_err = GPU_ERR_NONE;
    seq_data->prem_status = PREMIERE_UNKNOWN;
    memset(seq_data->reserved, 0, 4);
    seq_data->context = NULL;
    
    seq_data->source = OCIO_SOURCE_NONE;
    nt_strncpy(seq_data->path, "", ARB_PATH_LEN+1);
    nt_strncpy(seq_data->relative_path, "", ARB_PATH_LEN+1);

    PF_UNLOCK_HANDLE(out_data->sequence_data);
    
    return err;
}


static PF_Err SequenceResetup(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    PF_Err err = PF_Err_NONE;
    
    // reset pre-existing sequence data
    if(in_data->sequence_data != NULL)
    {
        SequenceData *seq_data_old = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        out_data->sequence_data = PF_NEW_HANDLE( sizeof(SequenceData) );
        
        assert(PF_GET_HANDLE_SIZE(in_data->sequence_data) == PF_GET_HANDLE_SIZE(out_data->sequence_data));
        
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(out_data->sequence_data);
        
        seq_data->status = STATUS_UNKNOWN;
        seq_data->gpu_err = GPU_ERR_NONE;
        seq_data->prem_status = PREMIERE_UNKNOWN;
        memset(seq_data->reserved, 0, 4);
        assert(seq_data_old->context == NULL);
        seq_data->context = NULL;
        
        seq_data->source = seq_data_old->source;
        nt_strncpy(seq_data->path, seq_data_old->path, ARB_PATH_LEN+1);
        nt_strncpy(seq_data->relative_path, seq_data_old->relative_path, ARB_PATH_LEN+1);
        
        PF_UNLOCK_HANDLE(out_data->sequence_data);
        PF_UNLOCK_HANDLE(in_data->sequence_data);
    }
    else
        err = SequenceSetup(in_data, out_data, params, output); // getting this after pasting the effect

    return err;
}


static PF_Err SequenceSetdown(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    PF_Err err = PF_Err_NONE;
    
    if(in_data->sequence_data)
    {
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        if(seq_data->context != NULL)
        {
            delete seq_data->context;
            
            seq_data->context = NULL;
        }
        
        PF_UNLOCK_HANDLE(in_data->sequence_data);
        
        PF_DISPOSE_HANDLE(in_data->sequence_data);
    }
    else
        assert(FALSE);

    return err;
}


static PF_Err SequenceFlatten(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    PF_Err err = PF_Err_NONE;

    if(in_data->sequence_data != NULL)
    {
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        out_data->sequence_data = PF_NEW_HANDLE( sizeof(SequenceData) );
        
        assert(PF_GET_HANDLE_SIZE(in_data->sequence_data) == PF_GET_HANDLE_SIZE(out_data->sequence_data));
        
        SequenceData *flat_seq_data = (SequenceData *)PF_LOCK_HANDLE(out_data->sequence_data);
        
        if(seq_data->context != NULL)
        {
            delete seq_data->context;
            
            seq_data->context = NULL;
        }
        
        flat_seq_data->status = STATUS_UNKNOWN;
        flat_seq_data->gpu_err = GPU_ERR_NONE;
        flat_seq_data->prem_status = PREMIERE_UNKNOWN;
        memset(flat_seq_data->reserved, 0, 4);
        flat_seq_data->context = NULL;
        
        flat_seq_data->source = seq_data->source;
        nt_strncpy(flat_seq_data->path, seq_data->path, ARB_PATH_LEN+1);
        nt_strncpy(flat_seq_data->relative_path, seq_data->relative_path, ARB_PATH_LEN+1);
        
        PF_UNLOCK_HANDLE(out_data->sequence_data);
        PF_UNLOCK_HANDLE(in_data->sequence_data);
        
        PF_DISPOSE_HANDLE(in_data->sequence_data);
    }
    else
        assert(FALSE);

    return err;
}


static PF_Err
GetFlattenedSequenceData (
    PF_InData        *in_data,
    PF_OutData        *out_data,
    PF_ParamDef        *params[],
    PF_LayerDef        *output )
{
    PF_Err err = PF_Err_NONE;
    
    if(in_data->sequence_data != NULL)
    {
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        out_data->sequence_data = PF_NEW_HANDLE( sizeof(SequenceData) );
        
        assert(PF_GET_HANDLE_SIZE(in_data->sequence_data) == PF_GET_HANDLE_SIZE(out_data->sequence_data));
        
        SequenceData *flat_seq_data = (SequenceData *)PF_LOCK_HANDLE(out_data->sequence_data);
        
        flat_seq_data->status = STATUS_UNKNOWN;
        flat_seq_data->gpu_err = GPU_ERR_NONE;
        flat_seq_data->prem_status = PREMIERE_UNKNOWN;
        memset(flat_seq_data->reserved, 0, 4);
        flat_seq_data->context = NULL;
        
        flat_seq_data->source = seq_data->source;
        nt_strncpy(flat_seq_data->path, seq_data->path, ARB_PATH_LEN+1);
        nt_strncpy(flat_seq_data->relative_path, seq_data->relative_path, ARB_PATH_LEN+1);
        
        PF_UNLOCK_HANDLE(out_data->sequence_data);
        PF_UNLOCK_HANDLE(in_data->sequence_data);
    }
    else
        assert(FALSE);

    return PF_Err_NONE;
}


static PF_Boolean IsEmptyRect(const PF_LRect *r){
    return (r->left >= r->right) || (r->top >= r->bottom);
}

#ifndef mmin
    #define mmin(a,b) ((a) < (b) ? (a) : (b))
    #define mmax(a,b) ((a) > (b) ? (a) : (b))
#endif


static void UnionLRect(const PF_LRect *src, PF_LRect *dst)
{
    if (IsEmptyRect(dst)) {
        *dst = *src;
    } else if (!IsEmptyRect(src)) {
        dst->left   = mmin(dst->left, src->left);
        dst->top    = mmin(dst->top, src->top);
        dst->right  = mmax(dst->right, src->right);
        dst->bottom = mmax(dst->bottom, src->bottom);
    }
}


static PF_Err PreRender(
    PF_InData               *in_data,
    PF_OutData              *out_data,
    PF_PreRenderExtra       *extra)
{
    PF_Err err = PF_Err_NONE;
    PF_RenderRequest req = extra->input->output_request;
    PF_CheckoutResult in_result;
    
    req.preserve_rgb_of_zero_alpha = TRUE;

    ERR(extra->cb->checkout_layer(  in_data->effect_ref,
                                    OCIO_INPUT,
                                    OCIO_INPUT,
                                    &req,
                                    in_data->current_time,
                                    in_data->time_step,
                                    in_data->time_scale,
                                    &in_result));


    UnionLRect(&in_result.result_rect,      &extra->output->result_rect);
    UnionLRect(&in_result.max_result_rect,  &extra->output->max_result_rect);
    
    return err;
}

#pragma mark-


template <typename InFormat, typename OutFormat>
static inline OutFormat Convert(InFormat in);

template <>
static inline float Convert<A_u_char, float>(A_u_char in)
{
    return (float)in / (float)PF_MAX_CHAN8;
}

template <>
static inline float Convert<A_u_short, float>(A_u_short in)
{
    return (float)in / (float)PF_MAX_CHAN16;
}

template <>
static inline float Convert<float, float>(float in)
{
    return in;
}

static inline float Clamp(float in)
{
    return (in > 1.f ? 1.f : in < 0.f ? 0.f : in);
}

template <>
static inline A_u_char Convert<float, A_u_char>(float in)
{
    return ( Clamp(in) * (float)PF_MAX_CHAN8 ) + 0.5f;
}

template <>
static inline A_u_short Convert<float, A_u_short>(float in)
{
    return ( Clamp(in) * (float)PF_MAX_CHAN16 ) + 0.5f;
}



typedef struct {
    PF_InData *in_data;
    void *in_buffer;
    A_long in_rowbytes;
    void *out_buffer;
    A_long out_rowbytes;
    int width;
} IterateData;

template <typename InFormat, typename OutFormat>
static PF_Err CopyWorld_Iterate(
    void *refconPV,
    A_long thread_indexL,
    A_long i,
    A_long iterationsL)
{
    PF_Err err = PF_Err_NONE;
    
    IterateData *i_data = (IterateData *)refconPV;
    PF_InData *in_data = i_data->in_data;
    
    InFormat *in_pix = (InFormat *)((char *)i_data->in_buffer + (i * i_data->in_rowbytes));
    OutFormat *out_pix = (OutFormat *)((char *)i_data->out_buffer + (i * i_data->out_rowbytes));
    
#ifdef NDEBUG
    if(thread_indexL == 0)
        err = PF_ABORT(in_data);
#endif

    for(int x=0; x < i_data->width; x++)
    {
        *out_pix++ = Convert<InFormat, OutFormat>( *in_pix++ );
    }
    
    return err;
}


typedef struct {
    PF_InData *in_data;
    void *in_buffer;
    A_long in_rowbytes;
    int width;
} SwapData;

static PF_Err Swap_Iterate(
    void *refconPV,
    A_long thread_indexL,
    A_long i,
    A_long iterationsL)
{
    PF_Err err = PF_Err_NONE;
    
    SwapData *i_data = (SwapData *)refconPV;
    PF_InData *in_data = i_data->in_data;
    
    PF_PixelFloat *pix = (PF_PixelFloat *)((char *)i_data->in_buffer + (i * i_data->in_rowbytes));
    
#ifdef NDEBUG
    if(thread_indexL == 0)
        err = PF_ABORT(in_data);
#endif

    for(int x=0; x < i_data->width; x++)
    {
        float temp;
        
        // BGRA -> ARGB
        temp       = pix->alpha; // BGRA temp B
        pix->alpha = pix->blue;  // AGRA temp B
        pix->blue  = temp;       // AGRB temp B
        temp       = pix->red;   // AGRB temp G
        pix->red   = pix->green; // ARRB temp G
        pix->green = temp;       // ARGB temp G
        
        pix++;
    }
    
    return err;
}


typedef struct {
    PF_InData               *in_data;
    void                    *buffer;
    A_long                  rowbytes;
    int                     width;
    OpenColorIO_AE_Context  *context;
} ProcessData;

static PF_Err Process_Iterate(
    void *refconPV,
    A_long thread_indexL,
    A_long i,
    A_long iterationsL)
{
    PF_Err err = PF_Err_NONE;
    
    ProcessData *i_data = (ProcessData *)refconPV;
    PF_InData *in_data = i_data->in_data;
    
    PF_PixelFloat *pix = (PF_PixelFloat *)((char *)i_data->buffer + (i * i_data->rowbytes));
    
#ifdef NDEBUG
    if(thread_indexL == 0)
        err = PF_ABORT(in_data);
#endif

    try
    {
        float *rOut = &pix->red;

        OCIO::PackedImageDesc img(rOut, i_data->width, 1, 4);
        
        i_data->context->cpu_processor()->apply(img);
    }
    catch(...)
    {
        err = PF_Err_INTERNAL_STRUCT_DAMAGED;
    }
    
    
    return err;
}


// two functions below to get Premiere to run my functions multi-threaded
// because they couldn't bother to give me PF_Iterate8Suite1->iterate_generic

typedef PF_Err (*GenericIterator)(void *refconPV,
                                    A_long thread_indexL,
                                    A_long i,
                                    A_long iterationsL);

typedef struct {
    PF_InData       *in_data;
    GenericIterator fn_func;
    void            *refconPV;
    A_long          height;
} FakeData;

static PF_Err MyFakeIterator(
    void *refcon,
    A_long x,
    A_long y,
    PF_Pixel *in,
    PF_Pixel *out)
{
    PF_Err err = PF_Err_NONE;
    
    FakeData *i_data = (FakeData *)refcon;
    PF_InData *in_data = i_data->in_data;
    
    err = i_data->fn_func(i_data->refconPV, 1, y, i_data->height);
    
    return err;
}

typedef PF_Err (*GenericIterateFunc)(
        A_long          iterationsL,
        void            *refconPV,
        GenericIterator fn_func);

static PF_Err MyGenericIterateFunc(
        A_long          iterationsL,
        void            *refconPV,
        GenericIterator fn_func)
{
    PF_Err err = PF_Err_NONE;
    
    PF_InData **in_dataH = (PF_InData **)refconPV; // always put PF_InData first
    PF_InData *in_data = *in_dataH;
    
    PF_Iterate8Suite1 *i8sP = NULL;
    in_data->pica_basicP->AcquireSuite(kPFIterate8Suite, kPFIterate8SuiteVersion1, (const void **)&i8sP);

    if(i8sP && i8sP->iterate)
    {
        PF_EffectWorld fake_world;
        PF_NEW_WORLD(1, iterationsL, PF_NewWorldFlag_NONE, &fake_world);
        
        
        FakeData i_data = { in_data, fn_func, refconPV, iterationsL };
        
        err = i8sP->iterate(in_data, 0, iterationsL, &fake_world, NULL,
                            (void *)&i_data, MyFakeIterator, &fake_world);
        
        
        PF_DISPOSE_WORLD(&fake_world);
        
        in_data->pica_basicP->ReleaseSuite(kPFIterate8Suite, kPFIterate8SuiteVersion1);
    }
    else
    {
        for(int i=0; i < iterationsL && !err; i++)
        {
            err = fn_func(refconPV, 0, i, iterationsL);
        }
    }
    
    return err;
}


static std::mutex gpu_mutex;

static PF_Err DoRender(
    PF_InData       *in_data,
    PF_EffectWorld  *input,
    PF_ParamDef     *OCIO_data,
    PF_ParamDef     *OCIO_gpu,
    PF_OutData      *out_data,
    PF_EffectWorld  *output)
{
    PF_Err              err     = PF_Err_NONE;

    AEGP_SuiteHandler suites(in_data->pica_basicP);

    PF_PixelFormatSuite1 *pfS = NULL;
    PF_WorldSuite2 *wsP = NULL;
    
    err = in_data->pica_basicP->AcquireSuite(kPFPixelFormatSuite, kPFPixelFormatSuiteVersion1, (const void **)&pfS);
    err = in_data->pica_basicP->AcquireSuite(kPFWorldSuite, kPFWorldSuiteVersion2, (const void **)&wsP);

    if(!err)
    {
        ArbitraryData *arb_data = (ArbitraryData *)PF_LOCK_HANDLE(OCIO_data->u.arb_d.value);
        SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
        
        try
        {
            UpdateContext(in_data, arb_data, seq_data);
        }
        catch(...)
        {
            seq_data->status = STATUS_OCIO_ERROR;
        }
        
        
        if(seq_data->status == STATUS_FILE_MISSING || seq_data->status == STATUS_OCIO_ERROR)
        {
            err = PF_Err_INTERNAL_STRUCT_DAMAGED;
        }

        
        if(!err)
        {
            if(seq_data->context == NULL || seq_data->context->processor()->isNoOp())
            {
                err = PF_COPY(input, output, NULL, NULL);
            }
            else
            {
                GenericIterateFunc iterate_generic = suites.Iterate8Suite1()->iterate_generic;
                
                if(iterate_generic == NULL)
                    iterate_generic = MyGenericIterateFunc; // thanks a lot, Premiere
            
                // OpenColorIO only does float worlds
                // might have to create one
                PF_EffectWorld *float_world = NULL;
                
                PF_EffectWorld temp_world_data;
                PF_EffectWorld *temp_world = NULL;
                PF_Handle temp_worldH = NULL;
                
                
                PF_PixelFormat format;
                wsP->PF_GetPixelFormat(output, &format);
                
                if(in_data->appl_id == 'PrMr' && pfS)
                {
                    // the regular world suite function will give a bogus value for Premiere
                    pfS->GetPixelFormat(output, (PrPixelFormat *)&format);
                    
                    seq_data->prem_status = (format == PrPixelFormat_BGRA_4444_32f_Linear ?
                                                PREMIERE_LINEAR : PREMIERE_NON_LINEAR);
                }
                
                
                A_Boolean use_gpu = OCIO_gpu->u.bd.value;
                seq_data->gpu_err = GPU_ERR_NONE;
                A_long non_padded_rowbytes = sizeof(PF_PixelFloat) * output->width;
                

                if(format == PF_PixelFormat_ARGB128 &&
                    (!use_gpu || output->rowbytes == non_padded_rowbytes)) // GPU doesn't do padding
                {
                    err = PF_COPY(input, output, NULL, NULL);
                    
                    float_world = output;
                }
                else
                {
                    temp_worldH = PF_NEW_HANDLE(non_padded_rowbytes * (output->height + 1)); // little extra because we go over by a channel
                    
                    if(temp_worldH)
                    {
                        temp_world_data.data = (PF_PixelPtr)PF_LOCK_HANDLE(temp_worldH);
                        
                        temp_world_data.width = output->width;
                        temp_world_data.height = output->height;
                        temp_world_data.rowbytes = non_padded_rowbytes;
                        
                        float_world = temp_world = &temp_world_data;

                        // convert to new temp float world
                        IterateData i_data = { in_data, input->data, input->rowbytes,
                                                float_world->data, float_world->rowbytes,
                                                float_world->width * 4 };
                        
                        if(format == PF_PixelFormat_ARGB32 || format == PrPixelFormat_BGRA_4444_8u)
                        {
                            err = iterate_generic(float_world->height, &i_data,
                                                    CopyWorld_Iterate<A_u_char, float>);
                        }
                        else if(format == PF_PixelFormat_ARGB64)
                        {
                            err = iterate_generic(float_world->height, &i_data,
                                                    CopyWorld_Iterate<A_u_short, float>);
                        }
                        else if(format == PF_PixelFormat_ARGB128 ||
                                format == PrPixelFormat_BGRA_4444_32f ||
                                format == PrPixelFormat_BGRA_4444_32f_Linear)
                        {
                            err = iterate_generic(float_world->height, &i_data,
                                                    CopyWorld_Iterate<float, float>);
                        }
                        
                        // switch BGRA to ARGB for premiere
                        if(!err &&
                            (format == PrPixelFormat_BGRA_4444_8u ||
                            format == PrPixelFormat_BGRA_4444_32f_Linear ||
                            format == PrPixelFormat_BGRA_4444_32f))
                        {
                            SwapData s_data = { in_data, float_world->data,
                                            float_world->rowbytes, float_world->width };
                            
                            err = iterate_generic(float_world->height, &s_data,
                                                    Swap_Iterate);
                        }
                    }
                    else
                        err = PF_Err_OUT_OF_MEMORY;
                }
                
                
                if(!err)
                {
                    bool gpu_rendered = false;

                    // OpenColorIO processing
                    if(use_gpu)
                    {
                        if( HaveOpenGL() )
                        {
                            gpu_mutex.lock();
                        
                            gpu_rendered = seq_data->context->ProcessWorldGL(float_world);
                            
                            gpu_mutex.unlock();
                            
                            if(!gpu_rendered)
                                seq_data->gpu_err = GPU_ERR_RENDER_ERR;
                        }
                        else
                            seq_data->gpu_err = GPU_ERR_INSUFFICIENT;
                    }
                    
                    if(!gpu_rendered)
                    {
                        ProcessData p_data = { in_data,
                                                float_world->data,
                                                float_world->rowbytes,
                                                float_world->width,
                                                seq_data->context };

                        err = iterate_generic(float_world->height, &p_data, Process_Iterate);
                    }
                }
                
                
                // copy back to non-float world and dispose
                if(temp_world)
                {
                    if(!err &&
                        (format == PrPixelFormat_BGRA_4444_8u ||
                        format == PrPixelFormat_BGRA_4444_32f_Linear ||
                        format == PrPixelFormat_BGRA_4444_32f))
                    {
                        SwapData s_data = { in_data, float_world->data,
                                            float_world->rowbytes, float_world->width };
                        
                        err = iterate_generic(float_world->height, &s_data, Swap_Iterate);
                    }
                    
                    if(!err)
                    {
                        IterateData i_data = { in_data, float_world->data,
                                                float_world->rowbytes, output->data,
                                                output->rowbytes, output->width * 4 };
                        
                        if(format == PF_PixelFormat_ARGB32 || format == PrPixelFormat_BGRA_4444_8u)
                        {
                            err = iterate_generic(output->height, &i_data,
                                                    CopyWorld_Iterate<float, A_u_char>);
                        }
                        else if(format == PF_PixelFormat_ARGB64)
                        {
                            err = iterate_generic(output->height, &i_data,
                                                    CopyWorld_Iterate<float, A_u_short>);
                        }
                        else if(format == PF_PixelFormat_ARGB128 ||
                                format == PrPixelFormat_BGRA_4444_32f ||
                                format == PrPixelFormat_BGRA_4444_32f_Linear)
                        {
                            err = iterate_generic(output->height, &i_data,
                                                    CopyWorld_Iterate<float, float>);
                        }
                        
                    }

                    PF_DISPOSE_HANDLE(temp_worldH);
                }
                
                
                PF_UNLOCK_HANDLE(OCIO_data->u.arb_d.value);
                PF_UNLOCK_HANDLE(in_data->sequence_data);


                if(seq_data->gpu_err == GPU_ERR_INSUFFICIENT)
                {
                    suites.AdvAppSuite2()->PF_AppendInfoText("OpenColorIO: GPU Insufficient");
                }
                else if(seq_data->gpu_err == GPU_ERR_RENDER_ERR)
                {
                    suites.AdvAppSuite2()->PF_AppendInfoText("OpenColorIO: GPU Render Error");
                }
            }
        }
    }

    if(pfS)
        in_data->pica_basicP->ReleaseSuite(kPFPixelFormatSuite, kPFPixelFormatSuiteVersion1);
    
    if(wsP)
        in_data->pica_basicP->ReleaseSuite(kPFWorldSuite, kPFWorldSuiteVersion2);
    
    
    
    return err;
}


static PF_Err SmartRender(
    PF_InData               *in_data,
    PF_OutData              *out_data,
    PF_SmartRenderExtra     *extra)

{
    PF_Err          err     = PF_Err_NONE,
                    err2    = PF_Err_NONE;
    
    PF_EffectWorld *input, *output;
    
    PF_ParamDef OCIO_data, OCIO_gpu;

    // zero-out parameters
    AEFX_CLR_STRUCT(OCIO_data);
    AEFX_CLR_STRUCT(OCIO_gpu);
    
    
    // checkout input & output buffers.
    ERR(    extra->cb->checkout_layer_pixels( in_data->effect_ref, OCIO_INPUT, &input)  );
    ERR(    extra->cb->checkout_output( in_data->effect_ref, &output)   );


    // bail before param checkout
    if(err)
        return err;

#define PF_CHECKOUT_PARAM_NOW( PARAM, DEST ) \
        PF_CHECKOUT_PARAM(  in_data, (PARAM), in_data->current_time,\
                            in_data->time_step, in_data->time_scale, DEST )

    // checkout the required params
    ERR(    PF_CHECKOUT_PARAM_NOW( OCIO_DATA,   &OCIO_data )    );
    ERR(    PF_CHECKOUT_PARAM_NOW( OCIO_GPU,    &OCIO_gpu  )    );

    ERR(DoRender(   in_data,
                    input,
                    &OCIO_data,
                    &OCIO_gpu,
                    out_data,
                    output));

    // Always check in, no matter what the error condition!
    ERR2(   PF_CHECKIN_PARAM(in_data, &OCIO_data )  );
    ERR2(   PF_CHECKIN_PARAM(in_data, &OCIO_gpu  )  );


    return err;
  
}


static PF_Err Render(
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output )
{
    return DoRender(in_data,
                    &params[OCIO_INPUT]->u.ld,
                    params[OCIO_DATA],
                    params[OCIO_GPU],
                    out_data,
                    output);
}


static PF_Err GetExternalDependencies(
    PF_InData                   *in_data,
    PF_OutData                  *out_data,
    PF_ExtDependenciesExtra     *extra)
{
    PF_Err err = PF_Err_NONE;
    
    SequenceData *seq_data = (SequenceData *)PF_LOCK_HANDLE(in_data->sequence_data);
    
    if(seq_data == NULL)
        return PF_Err_BAD_CALLBACK_PARAM;
    

    std::string dependency;
    
    if(seq_data->source == OCIO_SOURCE_ENVIRONMENT)
    {
        if(extra->check_type == PF_DepCheckType_ALL_DEPENDENCIES)
        {
            dependency = "$OCIO environment variable";
        }
        else if(extra->check_type == PF_DepCheckType_MISSING_DEPENDENCIES)
        {
            std::string env;
            OpenColorIO_AE_Context::getenvOCIO(env);
            
            if(env.empty())
                dependency = "$OCIO environment variable";
        }
    }
    else if(seq_data->source == OCIO_SOURCE_STANDARD)
    {
        if(extra->check_type == PF_DepCheckType_ALL_DEPENDENCIES)
        {
            dependency = "OCIO configuration " + std::string(seq_data->path);
        }
        else if(extra->check_type == PF_DepCheckType_MISSING_DEPENDENCIES)
        {
            std::string path = GetStdConfigPath(seq_data->path);
            
            if( path.empty() )
                dependency = "OCIO configuration " + std::string(seq_data->path);
        }
    }
    else if(seq_data->source == OCIO_SOURCE_CUSTOM && seq_data->path[0] != '\0')
    {
        std::string dir = GetProjectDir(in_data);
        
        Path absolute_path(seq_data->path, "");
        Path relative_path(seq_data->relative_path, dir);
    
        if(extra->check_type == PF_DepCheckType_ALL_DEPENDENCIES)
        {
            if( !absolute_path.exists() && relative_path.exists() )
            {
                dependency = relative_path.full_path();
            }
            else
                dependency = absolute_path.full_path();
        }
        else if(extra->check_type == PF_DepCheckType_MISSING_DEPENDENCIES &&
                    !absolute_path.exists() && !relative_path.exists() )
        {
            dependency = absolute_path.full_path();
        }
    }
    
    
    if( !dependency.empty() )
    {
        const size_t len = sizeof(char) * (dependency.size() + 1);
        
        extra->dependencies_strH = PF_NEW_HANDLE(len);
        
        char *p = (char *)PF_LOCK_HANDLE(extra->dependencies_strH);
        
        nt_strncpy(p, dependency.c_str(), len);
        
        PF_UNLOCK_HANDLE(extra->dependencies_strH);
        
        // After Effects introduced a bug in CC 2018 where the missing dependencies
        // are no longer being reported.  Until the bug is fixed, I have to put up
        // my own alert.
        // Hey Plabt, check out DVAAE-4210509.
        if(extra->check_type == PF_DepCheckType_MISSING_DEPENDENCIES &&
            in_data->version.major == PF_AE150_PLUG_IN_VERSION &&
            in_data->version.minor >= PF_AE150_PLUG_IN_SUBVERS)
        {
            PF_SPRINTF(out_data->return_msg, "OpenColorIO missing dependency:\r%s", dependency.c_str());
        }
    }
    
    PF_UNLOCK_HANDLE(in_data->sequence_data);
    
    return err;
}


DllExport PF_Err PluginMain(
    PF_Cmd          cmd,
    PF_InData       *in_data,
    PF_OutData      *out_data,
    PF_ParamDef     *params[],
    PF_LayerDef     *output,
    void            *extra)
{
    PF_Err      err = PF_Err_NONE;
    
    try {
        switch(cmd) {
            case PF_Cmd_ABOUT:
                err = About(in_data,out_data,params,output);
                break;
            case PF_Cmd_GLOBAL_SETUP:
                err = GlobalSetup(in_data,out_data,params,output);
                break;
            case PF_Cmd_GLOBAL_SETDOWN:
                err = GlobalSetdown(in_data,out_data,params,output);
                break;
            case PF_Cmd_PARAMS_SETUP:
                err = ParamsSetup(in_data,out_data,params,output);
                break;
            case PF_Cmd_USER_CHANGED_PARAM:
                err = UserChangedParam(in_data, out_data, params, output, (PF_UserChangedParamExtra *)extra);
                break;
            case PF_Cmd_SEQUENCE_SETUP:
                err = SequenceSetup(in_data, out_data, params, output);
                break;
            case PF_Cmd_SEQUENCE_RESETUP:
                err = SequenceResetup(in_data, out_data, params, output);
                break;
            case PF_Cmd_SEQUENCE_FLATTEN:
                err = SequenceFlatten(in_data, out_data, params, output);
                break;
            case PF_Cmd_SEQUENCE_SETDOWN:
                err = SequenceSetdown(in_data, out_data, params, output);
                break;
            case PF_Cmd_GET_FLATTENED_SEQUENCE_DATA:
                err = GetFlattenedSequenceData(in_data, out_data, params, output);
                break;
            case PF_Cmd_SMART_PRE_RENDER:
                err = PreRender(in_data, out_data, (PF_PreRenderExtra*)extra);
                break;
            case PF_Cmd_SMART_RENDER:
                err = SmartRender(in_data, out_data, (PF_SmartRenderExtra*)extra);
                break;
            case PF_Cmd_RENDER:
                err = Render(in_data, out_data, params, output);
                break;
            case PF_Cmd_EVENT:
                err = HandleEvent(in_data, out_data, params, output, (PF_EventExtra *)extra);
                break;
            case PF_Cmd_ARBITRARY_CALLBACK:
                err = HandleArbitrary(in_data, out_data, params, output, (PF_ArbParamsExtra *)extra);
                break;
            case PF_Cmd_GET_EXTERNAL_DEPENDENCIES:
                err = GetExternalDependencies(in_data, out_data, (PF_ExtDependenciesExtra *)extra);
                assert(params == NULL); // would love to check my ArbData, but...
                break;
        }
    }
    catch(PF_Err &thrown_err) { err = thrown_err; }
    catch(...) { err = PF_Err_INTERNAL_STRUCT_DAMAGED; }
    
    return err;
}
