/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>



/*
 * Process Parts
 * John E. Allain
 * Apply a user specified function to each part of a group.
 * assemble groups with the same structure as the input.
 */

#include <string.h>
#include <dx/dx.h>


#define  PartsDebugChar         "P"
#define  ErrorGotoPlus1(e,s,a)  {DXSetError(e,s,a); goto error;}
#define  MessageGotoPlus1(s,a)  {DXAddMessage(s,a); goto error;}

/*
 * Running debate:
 *    The structure of _recurse is convolved because the convention 
 *    'copy' passes fields uncopied to the process_part worker.
 *    If it were copied first, and the worker modified components in
 *    place, then it could be cleaner.  This would however require the 
 *    worker to clean out beaucoup components when it really wants to 
 *    build a new field.
 *
 * Note significant changes:
 *    DXEmptyField check is no longer performed.
 *    The DXEndField call is now expected of the worker routine.
 *
 * Function naming convention:
 *
 *    Mixed case lettering, leading underscore:
 *            public (extern) routine, undocumented.
 *    Mixed case lettering, no underscores:
 *            public (extern) routine, documented.
 *    Lower case lettering, with underscores:
 *            private (static) routine, undocumented.
 * 
 * Cleanup convention:
 *   out_object is to be considered deletable at all times.
 */



/*-----------------------------------*\
| Repackage of the input for AddTask
\*-----------------------------------*/
typedef struct 
{
    /* User supplied items */
    Field       (*process_part) ( Field, char *, int );
    Object      self;
    Pointer     args;
    int         size;

    /* internal states */
    int         parallel;
    int         copy;
    int         preserve;

    Object      parent;
    char        *member_name;
    int         group_member_position;
    float       series_FP_value;
}
part_arg_type;

static part_arg_type part_arg_initializer
    = { NULL, NULL, NULL, 0,   1, 1, 0,   NULL, NULL, 0, 0.0 };


static
/*
 * Set a child for an existing parent as found in arg->parent.
 *   Specifically, arg->parent will refer to child, using the
 *     referencing mechanism appropriate to that object.
 *   Note that on first call with a given parent/child combination
 *     that the child is is not so much replaced as placed.
 *
 * The caller is expected to set 'child' to null following this call,
 *   for the purposes of not deleting it twice during error cleanup.
 */
Error _replace_child ( part_arg_type *arg, Object child )
{
    DXASSERTGOTO ( arg         != NULL );
    DXASSERTGOTO ( child       != NULL );
    DXASSERTGOTO ( arg->copy   == 1    );
    DXASSERTGOTO ( arg->parent != NULL );

    /* Trace */
    if ( DXQueryDebug ( PartsDebugChar ) )
        if ( ( DXGetObjectClass ( child ) == CLASS_FIELD )
             &&
             DXEmptyField ( (Field) child ) )
            DXDebug ( PartsDebugChar, "_replace_child with DXEmptyField" );
        else
            DXDebug ( PartsDebugChar, "_replace_child" );

    switch ( DXGetObjectClass ( arg->parent ) )
    {
        case CLASS_GROUP:
            switch ( DXGetGroupClass ( (Group)arg->parent ) )
            {
                case CLASS_SERIES:
                    if ( !DXSetSeriesMember
                              ( (Series)arg->parent,
                                arg->group_member_position,
                                arg->series_FP_value,
                                child ) )
                        goto error;
                    break;

                default:
                    /* member name association can preserve order */
                    if ( arg->preserve )
                    {
                        if ( arg->member_name == NULL )
                        {
                            if ( !DXSetEnumeratedMember
                                     ( (Group)arg->parent,
                                        arg->group_member_position,
                                        child ) )
                                goto error;
                        }
                        else
                        {
                            if ( !DXSetMember
                                     ( (Group)arg->parent,
                                        arg->member_name,
                                        child ) )
                                goto error;
                        }
                    }
                    else
                        if ( !DXSetMember
                                 ( (Group)arg->parent,
                                    arg->member_name,
                                    child ) )
                            goto error;
            }
            break;

        case CLASS_XFORM:
            if ( !DXSetXformObject ( (Xform)arg->parent, child ) )
                goto error;
            break;

        case CLASS_SCREEN:
            if ( !DXSetScreenObject ( (Screen)arg->parent, child ) )
                goto error;
            break;

        case CLASS_CLIPPED:
            if ( !DXSetClippedObjects ( (Clipped)arg->parent, child, NULL ) )
                goto error;
            break;

        default:
           DXErrorGoto
               ( ERROR_ASSERTION,
                 "#11530" /* ProcessParts: Impossible parent */ );
    }

    return OK;

    error:
        ASSERT ( DXGetError() != ERROR_NONE );

        return ERROR;
}


/*-----------------------------------------*\
| The Leaf Processor called by AddTask
\*-----------------------------------------*/
static
/* 
 * Call the process_part 'worker'.
 *     Do so in a way acceptable to DXAddTask.
 *     Check the return states.
 */
Error _call_process_part ( Pointer p )
{
    part_arg_type  *arg       = (part_arg_type *) p;
    Object         out_object = NULL;

    DXASSERTGOTO ( arg != NULL );

    out_object = (Object)
                 arg->process_part
                     ( (Field)arg->self, (char *)arg->args, arg->size );

    /*
     * Check error states and trivial return cases.
     */
    if ( out_object == NULL )
    {
        if ( DXGetError() != ERROR_NONE )
            goto error;

        else 
        {
            /* 
             * If preserve case or series groups, DXEmptyField's have already 
             * been put in as placeholders.
             * Else, no need to use result.  Either way OK
             */ 
            arg->self = NULL;

            /* 
             * But...
             * Ensure that DXEmptyField is returned if a single-level hierarchy.
             * (if parent is not NULL then a placeholder is there if need be)
             */
            if ( ( arg->copy ) && ( arg->parent == NULL ) )
            {
                if ( NULL == ( arg->self = (Object) DXNewField() ) )
                    goto error;
            }

            return OK;
        }
    }
    else
        if ( DXGetError() != ERROR_NONE )
            MessageGotoPlus1
                ( "#8000",
                  /* %s set the error code, but did not return error */
                  "'process_part' worker" )

    /*
     * Let's see if a valid object.
     *     And if so, use it.
     */
    if ( arg->copy )
    {
        if ( arg->self == out_object )
            DXErrorGoto
                ( ERROR_ASSERTION, "#11500"
       /* ProcessParts: worker output can't equal input when called with copy */
                );

        if ( arg->parent == NULL )
            arg->self = out_object;
        else
        {
            if ( !_replace_child ( arg, out_object ) )
                goto error;

            arg->self  = out_object;
            out_object = NULL;
        }
    }
    else
        if ( arg->self != out_object )
           DXErrorGoto
               ( ERROR_ASSERTION, "#11510"
     /* ProcessParts: worker output can't equal input when called with nocopy */
               );

    return OK;

    error:
        if ( ( out_object != NULL ) && ( out_object != arg->self ) )
            DXDelete ( out_object );

        ASSERT ( DXGetError() != ERROR_NONE );

        return ERROR;
}


/*--------------------------------*\
| The Group Processor [Recursive] 
\*--------------------------------*/
static
Error _recurse ( part_arg_type *arg, Field placeholder  )
{
    part_arg_type  call_arg;
    Object         out_object = NULL;
    Class          class;

    /* 
     * Determine if a Field or Non-Field, and act accordingly.
     */
    if ( ( class = DXGetObjectClass ( arg->self ) ) == CLASS_FIELD )
    {
        if ( arg->parallel == 0 )
        {
            if ( !_call_process_part ( (Pointer)arg ) ) goto error;

            out_object = arg->self;
        }
        else
        {
            /*
             * (insure that there is a place to put result during parallelism) 
             */
            DXASSERTGOTO ( arg->parent != NULL );

            /*
             * Recall that DXAddTask will copy its input when
             * it is passed in as non-zero length.
             */
            if ( !DXAddTask ( _call_process_part,
                            (Pointer)arg,
                            sizeof( part_arg_type ),
                            (double)1.0 ) )
                goto error;

            /*
             * Install a place-holder if called for; And that is:
             *     copy and preserve mode
             *     or copy mode with a series group parent
             *   The actual output will be placed after ExectueTaskGroup() 
             *     is called.
             */
            if ( arg->copy )
                if ( arg->preserve 
                     ||
                     ( ( DXGetObjectClass ( arg->parent )
                             == CLASS_GROUP )
                       &&
                       ( DXGetGroupClass ( (Group)arg->parent )
                             == CLASS_SERIES ) ) )
                    if ( !_replace_child ( arg, (Object)placeholder ) )
                        goto error;
        }
    }
    else /* class != CLASS_FIELD */
    {
        call_arg              = part_arg_initializer;
        call_arg.size         = arg->size;
        call_arg.args         = arg->args;
        call_arg.process_part = arg->process_part;
        call_arg.parallel     = arg->parallel;
        call_arg.copy         = arg->copy;
        call_arg.preserve     = arg->preserve;
    
        if ( arg->copy == 0 ) 
            call_arg.parent = arg->self;
        else
        {
            /*
             * XXX
             * It is not entirely known how this DXCopy will behave across
             *   the different object types.
             */
            if ( !( out_object = DXCopy ( arg->self, COPY_ATTRIBUTES ) ) )
                goto error;

            call_arg.parent = out_object;
        }

        switch ( class )
        {
            case CLASS_GROUP:

                switch ( class = DXGetGroupClass ( (Group) arg->self ) )
                {
                    case CLASS_SERIES:
                        call_arg.member_name = NULL;
                        for ( call_arg.group_member_position = 0;
                              call_arg.self
                                  = DXGetSeriesMember
                                        ( (Series)arg->self,
                                          call_arg.group_member_position,
                                          &call_arg.series_FP_value );
                              call_arg.group_member_position++ )
                            if ( !_recurse ( &call_arg, placeholder ) )
                                goto error;

                        break;

                    default:
                        call_arg.series_FP_value = 0.0;
                        for ( call_arg.group_member_position = 0;
                              call_arg.self
                                  = DXGetEnumeratedMember
                                        ( (Group)arg->self,
                                          call_arg.group_member_position,
                                          &call_arg.member_name );
                              call_arg.group_member_position++ )
                            if ( ! _recurse ( &call_arg, placeholder ) )
                                goto error;

                        /* XXX above */
                }
                break;

            case CLASS_XFORM:
                if ( !DXGetXformInfo ( (Xform)arg->self, &call_arg.self, NULL )
                     ||
                     !_recurse ( &call_arg, placeholder ) )
                    goto error;
                break;

            case CLASS_SCREEN:
                if ( !DXGetScreenInfo
                         ( (Screen)arg->self, &call_arg.self, NULL,NULL )
                     ||
                     !_recurse ( &call_arg, placeholder ) )
                    goto error;
                break;

            case CLASS_CLIPPED:
                if ( !DXGetClippedInfo
                          ( (Clipped)arg->self, &call_arg.self, NULL )
                     ||
                     !_recurse ( &call_arg, placeholder ) )
                    goto error;
                break;

            default:
                DXASSERTGOTO ( class != CLASS_FIELD );
                DXDebug ( PartsDebugChar, "funny class: %d", class );
        }

        /* 
         * Place the computed output in the new hierarchy;
         */
        if ( arg->copy )
        {
            if ( arg->parent == NULL )
                arg->self = out_object;
            else
            {
                if ( !_replace_child ( arg, out_object ) )
                    goto error;

                arg->self  = out_object;
                out_object = NULL;
            }
        }
    }

    return OK;

    error:
        if ( ( out_object != NULL ) && ( out_object != arg->self ) )
            DXDelete ( out_object );

        ASSERT ( DXGetError() != ERROR_NONE );

        return ERROR;
}


static int _protected_parallel_switch = 1;

extern
Object _dxfProcessPartsNP ( Object  object,
                        Field   (*process_part) ( Field, char *, int ),
                        Pointer args,
                        int     size,
                        int     copy,
                        int     preserve )
{
    Object out_object;

    _protected_parallel_switch = 0;

    out_object = DXProcessParts
                     ( object, process_part, args, size, copy, preserve );

    _protected_parallel_switch = 1;

    return out_object;

}


extern
Object DXProcessParts ( Object  object,
                      Field   (*process_part) ( Field, char *, int ),
                      Pointer args,
                      int     size,
                      int     copy,
                      int     preserve )
{
    /** prototype new argument **/
    int           parallel         = _protected_parallel_switch;
    /**/

    part_arg_type call_arg;
    Pointer       args_copy_global = NULL;
    Field         placeholder      = NULL;

    call_arg = part_arg_initializer;

    if (object == NULL) {
        DXSetError (ERROR_MISSING_DATA, "#10000", "Object");
	return NULL;
    }

    call_arg.self         = object;
    call_arg.args         = args;
    call_arg.size         = size;
    call_arg.process_part = process_part;
    call_arg.parallel     = parallel;
    call_arg.copy         = copy;
    call_arg.preserve     = preserve;

    /*
     * Note: the assumption currently is made that sizeof(args) = size
     */
    if (size > 0 && args != NULL)
    {
       /*
	* Assume worst case that the argument 'args' is a pointer to
	* local memory.  For this reason, 'args' is copied to global
	* memory to ensure validity across all processors accessed by
	* DXAddTask.
	*/

	if ((args_copy_global = DXAllocate (size)) == ERROR)
	    goto error;

	memcpy (args_copy_global, args, size);
	call_arg.args = args_copy_global;
    }


    /*
     * When not copying, then order is preserved as a byproduct of
     * the modify in place operation.  Preserve setting can be ignored.
     */

    switch (DXGetObjectClass (call_arg.self))
    {
        case CLASS_FIELD:
            call_arg.parallel = 0;

        case CLASS_GROUP:
        case CLASS_XFORM:
        case CLASS_SCREEN:
        case CLASS_CLIPPED:
            if ((call_arg.copy == 1) && (call_arg.parallel == 1))
                /* 
                 * placeholder will be needed in cases of explicit preserve
                 * order, or for series groups, which imply this.
                 */
                if (((placeholder = DXNewField()) == NULL)
                     ||
                     !DXReference ((Object)placeholder))
                    goto error;

            if (call_arg.parallel == 1)
            {
                if (!DXCreateTaskGroup() ||
                    ! _recurse (&call_arg, placeholder) ||
                    !DXExecuteTaskGroup())
                    goto error;
            }
            else
                if (! _recurse (&call_arg, placeholder))
                    goto error;

            DXDelete ((Object)placeholder);
            break;

        default:
            DXSetError (ERROR_BAD_CLASS, "#10190", "Object");
	    goto error;
    }

    if (args_copy_global && !DXFree (args_copy_global))
        goto error;
    else
        args_copy_global = NULL;

    return (call_arg.self);

    error:
        if ( copy && ( call_arg.self != object ) )
            DXDelete ( call_arg.self );

        if ( args_copy_global )
            DXFree ( args_copy_global );

        DXDelete ( (Object)placeholder );

        return ERROR;
}



extern
Object _dxfProcessPartsG ( Object  object,
                           Group   (*process_part) ( Group, char *, int ),
                           Pointer args,
                           int     size,
                           int     copy,
                           int     preserve )
{
    Object child  = NULL;
    Group  result = NULL;
    int    i;

/* Parallel !!! */

#if 0
"#11500"
/* ProcessParts: worker output cannot equal input when called with copy */
"#11520"
/* ProcessParts: args is NULL but size is nonzero */
#endif

    if ( ( copy     != 0 ) ||
         ( preserve != 1 )   )
        DXErrorGoto
            ( ERROR_NOT_IMPLEMENTED,
              "DXProcessPartsG must be called with copy = 0, preserve = 1" );

    switch ( DXGetObjectClass ( object ) )
    {
        case CLASS_FIELD:
        case CLASS_ARRAY:
        default:
#if 0
            DXErrorGoto ( ERROR_BAD_CLASS, "encountered in object traversal" );
#endif
            break;

        case CLASS_GROUP:
            switch ( DXGetGroupClass ( (Group) object ) )
            {
                case CLASS_GROUP:
                default:
                    for (i = 0;
                         ( NULL != ( child = DXGetEnumeratedMember
                                                 ( (Group)object, i, NULL ) ) );
                         i++ )
                        if ( !_dxfProcessPartsG
                                  ( child, process_part,
                                    args, size, copy, preserve ) )
                            goto error;
#if 0
DXSetEnumeratedMember((Group)o, i, n);
#endif

                case CLASS_COMPOSITEFIELD:
                case CLASS_SERIES:
                    if ( ERROR == ( result = process_part
                                                 ( (Group)object,
                                                   (char*)args, size ) ) )
                        goto error;

                    if ( ( copy == 0 )
                         &&
                         ( result != (Group)object ) )
                        DXErrorGoto
                            ( ERROR_ASSERTION, "#11510"
                              /* ProcessParts: worker output must equal
                                 input when called with nocopy */ );
            }
            break;

        case CLASS_XFORM:
            if ( !DXGetXformInfo ( (Xform)object, &child, NULL ) )
                goto error;

            if ( !_dxfProcessPartsG
                      ( child, process_part, args, size, copy, preserve ) )
                goto error;
            break;

#if 0
DXSetXformObject((Xform)o,n);
#endif

        case CLASS_SCREEN:
            if ( !DXGetScreenInfo((Screen)object, &child, NULL, NULL ) )
                goto error;

            if ( !_dxfProcessPartsG
                      ( child, process_part, args, size, copy, preserve ) )
                goto error;
            break;

#if 0
DXSetScreenObject((Screen)o,n);
#endif

        case CLASS_CLIPPED:
            if ( !DXGetClippedInfo((Clipped)object, &child, NULL ) )
                goto error;

            if ( !_dxfProcessPartsG
                      ( child, process_part, args, size, copy, preserve ) )
                goto error;
            break;

#if 0
DXSetClippedObjects((Clipped)o,n,NULL);
#endif
    }

    return object;

    error:
        return ERROR;

}
