
/*
**
**  $VER: dispatch.c 1.3 (23.12.96)
**  film.datatype 1.0
**
**  Dispatch routine for a DataTypes class
**
**  Written 1996 by Roland 'Gizzy' Mainz
**  Original example source from David N. Junod
**
*/

/* main includes */
#include "classbase.h"

/* ansi includes */
#include <limits.h>

/* film.datatype class instance data */
struct FilmInstData
{
    /* Misc */
    struct SignalSemaphore  fid_SigSem;          /* Instance data lock                      */
    UWORD                   fid_Pad0;
    APTR                    fid_Pool;            /* Memory pool (for frame(-nodes) etc.)    */
    struct BitMapHeader    *fid_BMH;             /* Shortcut to PDTA_BitMapHeader           */
    struct BitMap          *fid_KeyBitMap;       /* Key BitMap                              */
    struct MinList          fid_FrameList;       /* List of frames                          */
    STRPTR                  fid_ProjectName;     /* Shortcut to DTA_Name                    */

    /* Prefs */
    ULONG                   fid_ModeID;
    BOOL                    fid_LoadAll;         /* Load all frames of the animation        */
    BOOL                    fid_NoRemap;         /* Set ADTA_Remap to FALSE if possible     */
    UWORD                   fid_FPS;             /* fps of stream (maybe modified by prefs) */
    UWORD                   fid_Volume;          /* Sample volume                           */
    ULONG                   fid_Period;          /* Sample period                           */

    /* Disk-loading section */
    BPTR                    fid_FH;
    LONG                    fid_CurrFilePos;
};


/* node which holds information about a single animation/movie frame */
struct FrameNode
{
    struct MinNode   fn_Node;

/* Timing section */
    ULONG            fn_TimeStamp;
    ULONG            fn_Frame;

/* Bitmap section */
    struct BitMap   *fn_BitMap;

/* BitMap loading section */
    LONG             fn_BMOffset; /* File offset (0 is begin of file) */
    ULONG            fn_BMSize;   /* Chunk size  */

/* Sample section */
    BYTE            *fn_Sample;
    ULONG            fn_SampleLength;
};

/*****************************************************************************/

/* local prototypes */
static DISPATCHERFLAGS ULONG             Dispatch( REGA0 struct IClass *, REGA2 Object *, REGA1 Msg );
static                 STRPTR            GetPrefsVar( struct ClassBase *, STRPTR );
static                 BOOL              matchstr( struct ClassBase *, STRPTR, STRPTR );
static                 void              ReadENVPrefs( struct ClassBase *, struct FilmInstData * );
static                 LONG              LoadFrames( struct ClassBase *, Object * );
static                 struct FrameNode *AllocFrameNode( struct ClassBase *, APTR );
static                 struct FrameNode *FindFrameNode( struct MinList *, ULONG );
static                 void              CopyBitMap( struct ClassBase *, struct BitMap *, struct BitMap * );
static                 struct BitMap    *AllocBitMapPooled( struct ClassBase *, ULONG, ULONG, ULONG, APTR );
static                 BOOL              FillCMAP( struct ClassBase *, Object *, struct StoredProperty * );
static                 APTR              AllocVecPooled( struct ClassBase *, APTR, ULONG );
static                 void              FreeVecPooled( struct ClassBase *, APTR, APTR );


/*****************************************************************************/

/* Create "film.datatype" BOOPSI class */
struct IClass *initClass( struct ClassBase *cb )
{
    struct IClass *cl;

    /* Create our class... */
    if( cl = MakeClass( FILMDTCLASS, ANIMATIONDTCLASS, NULL, (ULONG)sizeof( struct FilmInstData ), 0UL ) )
    {
      cl -> cl_Dispatcher . h_Entry = (HOOKFUNC)Dispatch;
      cl -> cl_UserData             = (ULONG)cb;

      AddClass( cl );
    }

    return( cl );
}

/*****************************************************************************/

/* IFF errors to DOS errors */
const
LONG ifferr2doserr[] =
{
    0L,                         /* End of file (not an error).                   */
    0L,                         /* End of context (not an error).                */
    DTERROR_INVALID_DATA,       /* No lexical scope.                             */
    ERROR_NO_FREE_STORE,        /* Insufficient memory.                          */
    ERROR_SEEK_ERROR,           /* Stream read error.                            */
    ERROR_SEEK_ERROR,           /* Stream write error.                           */
    ERROR_SEEK_ERROR,           /* Stream seek error.                            */
    DTERROR_INVALID_DATA,       /* File is corrupt.                              */
    DTERROR_INVALID_DATA,       /* IFF syntax error.                             */
    ERROR_OBJECT_WRONG_TYPE,    /* Not an IFF file.                              */
    ERROR_REQUIRED_ARG_MISSING, /* Required call-back hook missing.              */
    0xDEADDEAD,                 /* Return to client. You should never see this ! */
};

/*****************************************************************************/


/* class dispatcher */
static
DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase     *cb = (struct ClassBase *)(cl -> cl_UserData);
    struct FilmInstData  *fid;
    ULONG                 retval = 0UL;

    switch( msg -> MethodID )
    {
/****** film.datatype/OM_NEW *************************************************
*
*    NAME
*        OM_NEW -- Create a film.datatype object.
*
*    FUNCTION
*        The OM_NEW method is used to create an instance of the film.datatype
*        class.  This method is passed to the superclass first. After this,
*        film.datatype parses the description file and makes a scan through
*        the data to get index information. The samples are loaded directly
*        (due their small size), frame bitmaps are loaded if the input
*        stream isn't seekable (e.g. IFF handle/clipboard).
*
*        Subclasses of film.datatype are not supported. Any attempt to
*        create a subclass object of film.datatype will be rejected by this
*        method.
*
*    ATTRIBUTES
*        The following attributes can be specified at creation time.
*
*        DTA_SourceType (ULONG) -- Determinates the type of DTA_Handle
*            attribute. Both DTST_FILE and DTST_CLIPBOARD are supported.
*            If any other type was set in a given DTA_SourceType,
*            OM_NEW will be rejected.
*            Defaults to DTST_FILE.
*
*        DTA_Handle -- For both supported DTST_FILE and DTST_CLIPBOARD, a
*            (struct IFFHandle *) is expected.
*            (DTST_FILE expects a IFF Stream handle because this is a IFF
*            type DataType (DTF_IFF)).
*
*    BUGS
*        Before V1.3, this autodoc described that a DTA_Handle with a
*        DTA_SourceType of DTST_FILE takes a DOS filehandle as an argument.
*        This was WRONG ! Because this is a DTF_IFF type datatype, DTST_FILE
*        takes also an IFF Stream handle as an argument.
*
*    RESULT
*        If the object was created a pointer to the object is returned,
*        otherwise NULL is returned.
*
******************************************************************************
*
*/
      case OM_NEW:
      {
          struct TagItem *ti;

          /* We only support DTST_FILE or DTST_CLIPBOARD as source type */
          if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
          {
            if( ((ti -> ti_Data) != DTST_FILE) && ((ti -> ti_Data) != DTST_CLIPBOARD) )
            {
              SetIoErr( ERROR_OBJECT_WRONG_TYPE );

              break;
            }
          }

          /* This must not be a subclass of film.datatype (not implemented nor supported) */
          if( o == (Object *)cl )
          {
            if( retval = DoSuperMethodA( cl, o, msg ) )
            {
              LONG error;

              /* Load frames... */
              if( error = LoadFrames( cb, (Object *)retval ) )
              {
                /* Something went fatally wrong, dispose object */
                CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
                retval = 0UL;
              }

              SetIoErr( error );
            }
          }
          else
          {
            /* Subclasses of film.datatype are not implemented */
            SetIoErr( ERROR_NOT_IMPLEMENTED );
          }
      }
          break;

/****** film.datatype/OM_DISPOSE *********************************************
*
*    NAME
*        OM_DISPOSE -- Delete a film.datatype object.
*
*    FUNCTION
*        The OM_DISPOSE method is used to delete an instance of the
*        film.datatype class. This method is pass to the superclass when
*        it has completed.
*        This method frees all frame nodes and their contents (bitmaps,
*        sounds etc.).
*
*    RESULT
*        The object is deleted. 0UL is returned.
*
******************************************************************************
*
*/
      case OM_DISPOSE:
      {
          /* Get a pointer to our object data */
          fid = (struct FilmInstData *)INST_DATA( cl, o );

          /* Free our key bitmap */
          FreeBitMap( (fid -> fid_KeyBitMap) );

          /* Delete the frame pool */
          DeletePool( (fid -> fid_Pool) );

          if( fid -> fid_FH )
          {
            Close( (fid -> fid_FH) );
          }
          
          /* Dispose object */
          DoSuperMethodA( cl, o, msg );
      }
          break;

      case OM_UPDATE:
      {
          if( DoMethod( o, ICM_CHECKLOOP ) )
          {
            break;
          }
      }
      case OM_SET:
      {
          /* Pass the attributes to the animation class and force a refresh if we need it */
          if( retval = DoSuperMethodA( cl, o, msg ) )
          {
/* The following statement is useless because film.datatype does not support subclasses */
#ifdef COMMENTED_OUT
            /* Top instance ? */
            if( OCLASS( o ) == cl )
#endif /* COMMENTED_OUT */
            {
              struct RastPort *rp;

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct opSet *)msg) -> ops_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct opSet *)msg) -> ops_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_UPDATE;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );

                retval = 0UL;
              }
            }
          }
      }
          break;

/****** film.datatype/GM_LAYOUT *****************************************
*
*   NAME
*       GM_LAYOUT -- Layout animation object
*
*   FUNCTION
*       This method is passed to the superclass first.
*       Then it collects information from superclass, set up
*       struct DTSpecialInfo and send an OM_NOTIFY containing the following
*       attributes:
*       DTA_VisibleVert,   (DTA_Domain -> Height)
*       DTA_TotalVert,     (same as ADTA_Height)
*       DTA_NominalVert,   (same as ADTA_Height)
*       DTA_VertUnit,      1UL,
*       DTA_VisibleHoriz,  (DTA_Domain -> Width),
*       DTA_TotalHoriz,    (same as ADTA_Width),
*       DTA_NominalHoriz,  (same as ADTA_Width),
*       DTA_HorizUnit,     1UL,
*       GA_ID,             (GadgetID),
*       DTA_Title,         (same as DTA_Name),
*       DTA_Busy,          FALSE,
*       DTA_Sync,          TRUE
*
*    RESULT
*        The value from the superclass is returned.
*
*******************************************************************************
*
*/
      case GM_LAYOUT:
      {
          /* Attributes obtained from super-class */
          struct IBox *domain;
          STRPTR       title;

          ULONG        animwidth,
                       animheight;

          /* Let the super-class partake */
          retval = DoSuperMethodA( cl, o, msg );

          /* Get all the attributes that we are going to need for a successful layout */
          if( GetDTAttrs( o,
                          DTA_Domain,  (&domain),
                          DTA_Name,    (&title),
                          ADTA_Width,  (&animwidth),
                          ADTA_Height, (&animheight),
                          TAG_DONE ) == 4UL )
          {
            struct DTSpecialInfo *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
            struct TagItem        NotifyTags[ 13 ];
            struct opUpdate       opu;

            /* Lock the global object data so that nobody else can manipulate it
             * (when modifying DTSpecialInfo and building the notify attributes)
             */
            ObtainSemaphore( (&(si -> si_Lock)) );

            /* Compute the lines and columns type information */
            si -> si_VertUnit  = 1L;
            si -> si_VisVert   = (LONG)(domain -> Height);
            si -> si_TotVert   = (LONG)animheight;

            si -> si_HorizUnit = 1L;
            si -> si_VisHoriz  = (LONG)(domain -> Width);
            si -> si_TotHoriz  = (LONG)animwidth;

            /* Build notify attrs */
            NotifyTags[  0 ] . ti_Tag  = DTA_VisibleVert;
            NotifyTags[  0 ] . ti_Data = (si -> si_VisVert);
            NotifyTags[  1 ] . ti_Tag  = DTA_TotalVert;
            NotifyTags[  1 ] . ti_Data = (si -> si_TotVert);
            NotifyTags[  2 ] . ti_Tag  = DTA_NominalVert;
            NotifyTags[  2 ] . ti_Data = animheight;
            NotifyTags[  3 ] . ti_Tag  = DTA_VertUnit;
            NotifyTags[  3 ] . ti_Data = (si -> si_VertUnit);
            NotifyTags[  4 ] . ti_Tag  = DTA_VisibleHoriz;
            NotifyTags[  4 ] . ti_Data = (si -> si_VisHoriz);
            NotifyTags[  5 ] . ti_Tag  = DTA_TotalHoriz;
            NotifyTags[  5 ] . ti_Data = (si -> si_TotHoriz);
            NotifyTags[  6 ] . ti_Tag  = DTA_NominalHoriz;
            NotifyTags[  6 ] . ti_Data = animwidth;
            NotifyTags[  7 ] . ti_Tag  = DTA_HorizUnit;
            NotifyTags[  7 ] . ti_Data = (ULONG)(si -> si_HorizUnit);
            NotifyTags[  8 ] . ti_Tag  = GA_ID;
            NotifyTags[  8 ] . ti_Data = (ULONG)(G( o ) -> GadgetID);
            NotifyTags[  9 ] . ti_Tag  = DTA_Title;
            NotifyTags[  9 ] . ti_Data = (ULONG)title;
            NotifyTags[ 10 ] . ti_Tag  = DTA_Busy;
            NotifyTags[ 10 ] . ti_Data = (ULONG)FALSE;
            NotifyTags[ 11 ] . ti_Tag  = DTA_Sync;
            NotifyTags[ 11 ] . ti_Data = (ULONG)TRUE;
            NotifyTags[ 12 ] . ti_Tag  = TAG_DONE;
            NotifyTags[ 12 ] . ti_Data = 0UL;

            /* Release the global data lock */
            ReleaseSemaphore( (&(si -> si_Lock)) );

            /* Tell the world of our newest attributes */
            opu . MethodID     = OM_NOTIFY;
            opu . opu_AttrList = NotifyTags;
            opu . opu_GInfo    = ((struct gpLayout *)msg) -> gpl_GInfo;
            opu . opu_Flags    = 0UL;

            DoMethodA( o, (Msg)(&opu) );
          }
      }
          break;

/****** film.datatype/DTM_FRAMEBOX ***************************************
*
*    NAME
*        DTM_FRAMEBOX -- Set up object for a specified environment
*
*    FUNCTION
*        The DTM_FRAMEBOX method is used to set up for a specified
*        environment. This method is passed to the superclass after
*        film.datatype instance finished it's work.
*
*        Only if FRAMEF_SPECIFY is set in (dtf -> dtf_FrameFlags), the
*        film.datatype instance will modify the ADTA_Remap to FALSE if
*        the NOREMAP switch in the prefs file was set and the animation
*        depth matches the given (frameinfo -> fri_Screen -> BitMap . Depth)
*        depth.
*
*    RESULT
*        The value from the superclass is returned.
*
*    SEE ALSO
*        3.1_Examples2:DataTypes/Src/ClipView/clipview.c
*
******************************************************************************
*
*/
      case DTM_FRAMEBOX:
      {
          struct dtFrameBox *dtf;

          fid = (struct FilmInstData *)INST_DATA( cl, o );
          dtf = (struct dtFrameBox *)msg;

          /* Make do with the dimensions of FrameBox provided ? */
          if( (dtf -> dtf_FrameFlags) & FRAMEF_SPECIFY )
          {
            /* NOREMAP switch set ? */
            if( fid -> fid_NoRemap )
            {
              ULONG depth;

              /* Get animation depth */
              if( GetAttr( ADTA_Depth, o, (&depth) ) == 1UL )
              {
                /* Match ? */
                if( depth == (dtf -> dtf_ContentsInfo -> fri_Dimensions . Depth) )
                {
                  SetAttrs( o, ADTA_Remap, FALSE, TAG_DONE );
                }
              }
            }
          }

          retval = DoSuperMethodA( cl, o, msg );
      }
          break;

/****** film.datatype/DTM_WRITE **********************************************
*
*    NAME
*        DTM_WRITE -- Save data
*
*    FUNCTION
*        This method saves the object's contents to disk.
*
*        If dtw_Mode is DTWM_IFF, the method is passed unchanged to the
*        superclass, animation.datatype, which writes a single IFF ILBM
*        picture.
*        The local data format (e.g. dtw_Mode == DTWM_RAW) is currently
*        not supported, this method returns 0UL
*        (and result2 == ERROR_NOT_IMPLEMENTED) in that case.
*
*    RESULT
*        Returns 0 for failure (IoErr() returns result2), non-zero
*        for success.
*
******************************************************************************
*
*/
      case DTM_WRITE:
      {
          struct dtWrite *dtw;

          dtw = (struct dtWrite *)msg;

          /* Local data format not supported yet... */
          if( (dtw -> dtw_Mode) == DTWM_RAW )
          {
            /* Return result = 0UL and result2 = ERROR_NOT_IMPLEMENTED */
            SetIoErr( ERROR_NOT_IMPLEMENTED );
          }
          else
          {
            /* Pass msg to superclass (which writes a single frame as an IFF ILBM picture)... */
            retval = DoSuperMethodA( cl, o, msg );
          }
      }
          break;


/****** film.datatype/ADTM_LOADFRAME *****************************************
*
*    NAME
*        ADTM_LOADFRAME -- Load frame
*
*    FUNCTION
*        The ADTM_LOADFRAME method is used to obtain the bitmap and timing
*        data of the animation.
*        The given timestamp will be used to find a matching timestamp
*        in the internal FrameNode list. If it was found, the corresponding
*        timing, bitmap, and sound data are stored into the struct
*        adtFrame. If the bitmap wasn't loaded, this method attempts to
*        load it from disk.
*
*    RESULT
*        Returns always non-zero,
*        the bitmap ptr if a bitmap was found,
*        1UL otherwise.
*
******************************************************************************
*
*/

      case ADTM_LOADFRAME:
      {
          struct FrameNode *fn;
          struct adtFrame  *alf;

          fid = (struct FilmInstData *)INST_DATA( cl, o );
          alf = (struct adtFrame *)msg;

          ObtainSemaphore( (&(fid -> fid_SigSem)) );

          /* Find frame by timestamp */
          if( fn = FindFrameNode( (&(fid -> fid_FrameList)), (alf -> alf_TimeStamp) ) )
          {
            /* Load bitmaps only if we don't cache the whole anim */
            if( (fid -> fid_LoadAll) == FALSE )
            {
              /* If no bitmap is loaded, load it... */
              if( (fn -> fn_BitMap) == NULL )
              {
                if( fn -> fn_BitMap = AllocBitMapPooled( cb, (ULONG)(fid -> fid_BMH -> bmh_Width), (ULONG)(fid -> fid_BMH -> bmh_Height), (ULONG)(fid -> fid_BMH -> bmh_Depth), (fid -> fid_Pool) ) )
                {
                  if( Seek( (fid -> fid_FH), (((fn -> fn_BMOffset) + 8UL) - (fid -> fid_CurrFilePos)), OFFSET_CURRENT ) != (-1L) )
                  {
                    /* Load ILBM BODY directly into bitmap, assuming an interleaved bitmap */
                    if( Read( (fid -> fid_FH), (fn -> fn_BitMap -> Planes[ 0 ]), (fn -> fn_BMSize) ) == (fn -> fn_BMSize) )
                    {
                      /* Bump file pos */
                      fid -> fid_CurrFilePos = ((fn -> fn_BMOffset) + 8UL) + (fn -> fn_BMSize);

                      /* Success ! */
                      retval = 1UL;
                    }
                  }

                  if( retval == 0UL )
                  {
                    /* Error, rewind stream */
                    Seek( (fid -> fid_FH), 0L, OFFSET_BEGINNING );
                    fid -> fid_CurrFilePos = 0L;
                  }
                }
              }
            }

            /* Store frame/context information */
            alf -> alf_Frame    = fn -> fn_Frame;
            alf -> alf_UserData = (APTR)fn;        /* Links back to this FrameNode (used by ADTM_UNLOADFRAME) */

            /* Store bitmap information */
            alf -> alf_BitMap = fn -> fn_BitMap;

            /* Is there a sample to play ? */
            if( fn -> fn_Sample )
            {
              /* Store sound information */
              alf -> alf_Sample       = fn -> fn_Sample;
              alf -> alf_SampleLength = fn -> fn_SampleLength;
              alf -> alf_Period       = fid -> fid_Period;
            }
            else
            {
              /* No sound */
              alf -> alf_Sample       = NULL;
              alf -> alf_SampleLength = 0UL;
              alf -> alf_Period       = 0UL;
            }

            /* Return bitmap ptr of possible, 1UL otherwise */
            retval = (alf -> alf_BitMap)?((ULONG)(alf -> alf_BitMap)):(1UL);
          }
          else
          {
            /* no matching frame found */
            retval = 1UL;
          }

          ReleaseSemaphore( (&(fid -> fid_SigSem)) );
      }
          break;

/****** film.datatype/ADTM_UNLOADFRAME ***************************************
*
*    NAME
*        ADTM_UNLOADFRAME -- Load frame contents
*
*    FUNCTION
*        The ADTM_UNLOADFRAME method is used to release the contents of a
*        animation frame.
*
*        This method frees the bitmap data found in adtFrame.
*
*    RESULT
*        Returns always 0UL.
*
******************************************************************************
*
*/


      case ADTM_UNLOADFRAME:
      {
          struct FrameNode *fn;
          struct adtFrame  *alf;

          fid = (struct FilmInstData *)INST_DATA( cl, o );
          alf = (struct adtFrame *)msg;

          /* Free bitmaps only if we don't cache the whole anim */
          if( (fid -> fid_LoadAll) == FALSE )
          {
            ObtainSemaphore( (&(fid -> fid_SigSem)) );

            if( fn = (struct FrameNode *)(alf -> alf_UserData) )
            {
              if( fn -> fn_BitMap )
              {
                FreeVecPooled( cb, (fid -> fid_Pool), (fn -> fn_BitMap) );
                fn -> fn_BitMap = NULL;
              }
            }

            ReleaseSemaphore( (&(fid -> fid_SigSem)) );
          }
      }
          break;

      /* Let the superclass handle everything else */
      default:
      {
          retval = DoSuperMethodA( cl, o, msg );
      }
          break;
    }

    return( retval );
}


/****** film.datatype/preferences ********************************************
*
*   NAME
*       preferences
*
*   DESCRIPTION
*       The "ENV:Classes/DataTypes/film.prefs" file contains global
*       settings for the datatype.
*       The preferences file is an ASCII file containing one line where the
*       preferences can be set.
*       It can be superset by a local variable with the same name.
*
*       Each line can contain settings, special settings for some projects
*       can be set using the MATCHPROJECT option.
*       Lines beginning with a '#' or ';' chars are treated as comments.
*       Lines are limitted to 256 chars.
*
*   TEMPLATE
*       MATCHPROJECT/K,MODEID/K/N,NOREMAP/S,FPS/K/N,VOLUME/K/N
*
*       MATCHPROJECT -- The settings in this line belongs only to this
*           project(s), e.g. if the case-insensitive pattern does not match,
*           this line is ignored.
*           The maximum length of the pattern is 128 chars.
*           Defaults to #?, which matches any project.
*
*       MODEID -- Select screen mode id of datatype (will be stored in
*           ADTA_ModeID). Note that the DOS ReadArgs function used for parsing
*           fetches a SIGNED long. The bit 31 will be represented by minus
*           '-'. (example: "MODEID=266240" sets the mode to the A2024 screen
*           mode id)
*           Defaults to 0, which means: Use the best screenmode available
*           for the given width, height and depth.
*
*       FPS -- frames per second
*           Defaults to 0, which means: overtake fps rate from video stream.
*           Setting the FPS value also affects an attched sound. The period
*           of the sample (e.g. the playback speed) will everytimes as long
*           as the frame is displayed.
*
*       VOLUME -- Volume of the sound when playing.
*           Defaults to 64, which is the maximum. A value greater than 64 will
*           be set to 64.
*
*   NOTE
*       An invalid prefs file line will force the default settings for this
*       line.
*
*   BUGS
*       - Low memory may cause that the prefs file won't be parsed.
*
*       - Lines are limitted to 256 chars
*
*       - An invalid prefs file line will force the default settings for this
*         line, but no error will be prompted.
*
******************************************************************************
*
*/


static
STRPTR GetPrefsVar( struct ClassBase *cb, STRPTR name )
{
          STRPTR buff;
    const ULONG  buffsize = 16UL;

    if( buff = (STRPTR)AllocVec( (buffsize + 2UL), (MEMF_PUBLIC | MEMF_CLEAR) ) )
    {
      if( GetVar( name, buff, buffsize, GVF_BINARY_VAR ) != (-1L) )
      {
        ULONG varsize = IoErr();

        varsize += 2UL;

        if( varsize > buffsize )
        {
          FreeVec( buff );

          if( buff = (STRPTR)AllocVec( (varsize + 2UL), (MEMF_PUBLIC | MEMF_CLEAR) ) )
          {
            if( GetVar( name, buff, varsize, GVF_BINARY_VAR ) != (-1L) )
            {
              return( buff );
            }
          }
        }
        else
        {
          return( buff );
        }
      }

      FreeVec( buff );
    }

    return( NULL );
}


static
BOOL matchstr( struct ClassBase *cb, STRPTR pat, STRPTR s )
{
    TEXT buff[ 256 ];

    if( pat && s )
    {
      if( ParsePatternNoCase( pat, buff, 255L ) != (-1L) )
      {
        if( MatchPatternNoCase( buff, s ) )
        {
          return( TRUE );
        }
      }
    }

    return( FALSE );
}


static
void ReadENVPrefs( struct ClassBase *cb, struct FilmInstData *fid )
{
    struct RDArgs envvarrda =
    {
      NULL,
      256L,
      0L,
      0L,
      NULL,
      0L,
      NULL,
      RDAF_NOPROMPT
    };

    struct
    {
      STRPTR  matchproject;
      long   *modeid;
      long   *noremap;
      long   *fps;
      long   *volume;
    } animargs;

    TEXT   varbuff[ 258 ];
    STRPTR var;

    if( var = GetPrefsVar( cb, "Classes/DataTypes/film.prefs" ) )
    {
      STRPTR prefsline      = var,
             nextprefsline;
      ULONG  linecount      = 1UL;

      /* Be sure that "var" contains at least one break-char */
      strcat( var, "\n" );

      while( nextprefsline = strpbrk( prefsline, "\n" ) )
      {
        stccpy( varbuff, prefsline, MIN( (sizeof( varbuff ) - 2UL), (((ULONG)(nextprefsline - prefsline)) + 1UL) ) );

        /* be sure that this line isn't a comment line or an empty line */
        if( (varbuff[ 0 ] != '#') && (varbuff[ 0 ] != ';') && (varbuff[ 0 ] != '\n') && (strlen( varbuff ) > 2UL) )
        {
          /* Prepare ReadArgs processing */
          strcat( varbuff, "\n" );                                       /* Add NEWLINE-char            */
          envvarrda . RDA_Source . CS_Buffer = varbuff;                  /* Buffer                      */
          envvarrda . RDA_Source . CS_Length = strlen( varbuff ) + 1UL;  /* Set up input buffer length  */
          envvarrda . RDA_Source . CS_CurChr = 0L;
          envvarrda . RDA_Buffer = NULL;
          envvarrda . RDA_BufSiz = 0L;
          memset( (void *)(&animargs), 0, sizeof( animargs ) );          /* Clear result array          */

          if( ReadArgs( "MATCHPROJECT/K,"
                        "MODEID/K/N,"
                        "NOREMAP/S,"
                        "FPS/K/N,"
                        "VOLUME/K/N,", (LONG *)(&animargs), (&envvarrda) ) )
          {
            BOOL noignore = TRUE;

            if( (animargs . matchproject) && (fid -> fid_ProjectName) )
            {
              noignore = matchstr( cb, (animargs . matchproject), (fid -> fid_ProjectName) );
            }

            if( noignore )
            {
              if( animargs . modeid )
              {
                fid -> fid_ModeID = *(animargs . modeid);
              }

              if( animargs . noremap )
              {
                fid -> fid_NoRemap = TRUE;
              }

              if( animargs . fps )
              {
                fid -> fid_FPS = *(animargs . fps);
              }

              if( animargs . volume )
              {
                fid -> fid_Volume = *(animargs . volume);

                if( (fid -> fid_Volume) > 64UL )
                {
                  fid -> fid_Volume = 64UL;
                }
              }
              else
              {
                fid -> fid_Volume = 64UL;
              }
            }
            else
            {
#ifdef COMMENTED_OUT
              verbose_printf( cb, "prefs line %lu ignored\n", linecount );
#endif /* COMMENTED_OUT */
            }

            FreeArgs( (&envvarrda) );
          }
          else
          {
            LONG ioerr = IoErr();
            TEXT errbuff[ 256 ];

            Fault( ioerr, "Classes/DataTypes/film.prefs", errbuff, sizeof( errbuff ) );

#ifdef COMMENTED_OUT
            verbose_printf( cb, "preferences \"%s\" line %lu\n", errbuff, linecount );
#endif /* COMMENTED_OUT */
          }
        }

        prefsline = ++nextprefsline;
        linecount++;
      }

      FreeVec( var );
    }
}


static
LONG LoadFrames( struct ClassBase *cb, Object *o )
{
    struct FilmInstData *fid  = (struct FilmInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );
    LONG                 error = 0L;

    InitSemaphore( (&(fid -> fid_SigSem)) );
    NewList( (struct List *)(&(fid -> fid_FrameList)) );

    /* Create a memory pool for frame nodes */
    if( fid -> fid_Pool = CreatePool( MEMF_PUBLIC, 16384UL, 16384UL ) )
    {
      APTR                 fh;               /* IFF stream handle               */
      ULONG                sourcetype;       /* type of stream (either DTST_FILE or DTST_CLIPBOARD */
      ULONG                pos        = 0UL; /* current file pos in IFF stream  */
      struct BitMapHeader *bmh;              /* obj's bitmapheader              */
      ULONG                modeid     = 0UL; /* anim view mode                  */
      ULONG                animwidth  = 0UL, /* anim width                      */
                           animheight = 0UL, /* anim height                     */
                           animdepth  = 0UL; /* anim depth                      */
      ULONG                timestamp  = 0UL; /* timestamp                       */
      ULONG                sps        = 0UL; /* samples per second              */
      ULONG                spf        = 0UL; /* samples per frame               */

      /* Prefs defaults */
      fid -> fid_Volume = 64U;

      /* Read prefs */
      ReadENVPrefs( cb, fid );

      /* Get file handle, handle type and BitMapHeader */
      if( GetDTAttrs( o, DTA_SourceType,    (&sourcetype),
                         DTA_Handle,        (&fh),
                         DTA_Name,          (&(fid -> fid_ProjectName)),
                         PDTA_BitMapHeader, (&bmh),
                         TAG_DONE ) == 4UL )
      {
        struct IFFHandle *iff = NULL;

        fid -> fid_BMH = bmh; /* Store BitMapHeader */

        switch( sourcetype )
        {
          case DTST_CLIPBOARD:
          {
              fid -> fid_LoadAll = TRUE; /* Cache all frames because we cannot load randomly from stream */

              iff = (struct IFFHandle *)fh;
          }
              break;

          case DTST_FILE:
          {
              iff = (struct IFFHandle *)fh;

              /* Set up a filehandle for disk-based loading (random loading) */
              if( !(fid -> fid_FH = (LONG)Open( (fid -> fid_ProjectName), MODE_OLDFILE )) )
              {
                /* Can't open file */
                error = IoErr();
              }
          }
              break;

          default:
          {
              /* unsupported source type */
              error = ERROR_NOT_IMPLEMENTED;
          }
              break;
        }

        /* Any error ? */
        if( (error == 0L) && iff )
        {
          struct StoredProperty *bmhdprop = NULL, /* ILBM BMHD (struct BitMapHeader)        */
                                *cmapprop = NULL, /* ILBM CMAP (global colormap)            */
                                *camgprop = NULL, /* ILBM CAMG (amiga view mode id)         */
                                *vhdrprop = NULL, /* 8SVX VHDR (struct VoiceHeader)         */
                                *chanprop = NULL; /* 8SVX CHAN (left/right/stereo channel)  */

          ULONG chan_channel = CHAN_RIGHT; /* or CHAN_LEFT - whatever you want, but only ONE channel */

#define NUM_PROPCHUNKS (5L)
          const
          LONG propchunks[ (NUM_PROPCHUNKS * 2) ] =
          {
            ID_ILBM, ID_BMHD,
            ID_ILBM, ID_CAMG,
            ID_ILBM, ID_CMAP,
            ID_8SVX, ID_VHDR,
            ID_8SVX, ID_CHAN
          };

          if( !(error = PropChunks( iff, (LONG *)propchunks, NUM_PROPCHUNKS )) )
          {
#define NUM_STOPCHUNKS (3L)
            const
            LONG stopchunks[ (NUM_STOPCHUNKS * 2) ] =
            {
              ID_CELL, ID_CAT,
              ID_ILBM, ID_BODY,
              ID_8SVX, ID_BODY,
            };

            if( !(error = StopChunks( iff, (LONG *)stopchunks, NUM_STOPCHUNKS )) )
            {
              struct FrameNode *fn = NULL;

              /* Scan IFF stream until an error or an EOF occurs */
              for( ;; )
              {
                struct ContextNode *cn;

                if( error = ParseIFF( iff, IFFPARSE_SCAN ) )
                {
                  /* EOF (End Of File) is no error here... */
                  if( error == IFFERR_EOF )
                  {
                    error = 0L;
                  }

                  break;
                }

                /* Get file position */
                if( cn = CurrentChunk( iff ) )
                {
                  pos = 0UL;

                  while( cn = ParentChunk( cn ) )
                  {
                    pos += cn -> cn_Scan;
                  }
                }

                /* bmhd header loaded ? */
                if( bmhdprop == NULL )
                {
                  if( bmhdprop = FindProp( iff, ID_ILBM, ID_BMHD ) )
                  {
                    *bmh = *((struct BitMapHeader *)(bmhdprop -> sp_Data));

                    animwidth  = bmh -> bmh_Width;
                    animheight = bmh -> bmh_Height;
                    animdepth  = bmh -> bmh_Depth;

                    /* ILBM BODY __must__ not be compressed for IFF FILM */
                    if( (bmh -> bmh_Compression) != cmpNone )
                    {
                      error = DTERROR_INVALID_DATA;
                    }
                  }
                }

                /* cmap loaded ? */
                if( cmapprop == NULL )
                {
                  if( cmapprop = FindProp( iff, ID_ILBM, ID_CMAP ) )
                  {
                    if( !FillCMAP( cb, o, cmapprop ) )
                    {
                      error = ERROR_NO_FREE_STORE;
                      break;
                    }
                  }
                }

                /* camg loaded ? */
                if( camgprop == NULL )
                {
                  if( camgprop = FindProp( iff, ID_ILBM, ID_CAMG ) )
                  {
                    modeid = *(ULONG *)(camgprop -> sp_Data);
                  }
                }

                /* vhdr loaded ? */
                if( vhdrprop == NULL )
                {
                  if( vhdrprop = FindProp( iff, ID_8SVX, ID_VHDR ) )
                  {
                    struct VoiceHeader *vhdr = (struct VoiceHeader *)(vhdrprop -> sp_Data);

                    if( (vhdr -> vh_SamplesPerSec) == 0UL )
                    {
                      /* vh_SamplesPerSec must not be 0 */
                      error = DTERROR_INVALID_DATA;
                      break;
                    }

                    sps = vhdr -> vh_SamplesPerSec;
                  }
                }

                /* chan loaded ? */
                if( chanprop == NULL )
                {
                  if( chanprop = FindProp( iff, ID_8SVX, ID_CHAN ) )
                  {
                    chan_channel = *(ULONG *)(chanprop -> sp_Data);

                    if( (chan_channel != CHAN_RIGHT) &&
                        (chan_channel != CHAN_LEFT)  &&
                        (chan_channel != CHAN_STEREO) )
                    {
                      /* unsupported 8SVX/CHAN channel ID, use the default */
                      chan_channel = CHAN_RIGHT; /* or CHAN_LEFT, see upstairs */
                    }
                  }
                }

                if( cn = CurrentChunk( iff ) )
                {
                  switch( (cn -> cn_Type) )
                  {
                    /* New IFF FILM CELL found */
                    case ID_CELL:
                    {
                        if( (cn -> cn_ID) == ID_CAT )
                        {
                          /* Move previous cell ito frame list */
                          if( fn )
                          {
                            AddTail( (struct List *)(&(fid -> fid_FrameList)), (struct Node *)(&(fn -> fn_Node)) );
                          }

                          /* Create an prepare a new frame node */
                          if( fn = AllocFrameNode( cb, (fid -> fid_Pool) ) )
                          {
                            fn -> fn_TimeStamp = timestamp++;
                            fn -> fn_Frame     = fn -> fn_TimeStamp;
                          }
                          else
                          {
                            /* can't alloc frame node */
                            error = ERROR_NO_FREE_STORE;
                          }
                        }
                    }
                        break;

                    case ID_ILBM:
                    {
                        if( (cn -> cn_ID) == ID_BODY )
                        {
                          if( fn )
                          {
                            /* Store position of BODY (pos points to the BODY ID) */
                            fn -> fn_BMOffset = pos;
                            fn -> fn_BMSize   = cn -> cn_Size;

                            if( (fn -> fn_BitMap) == NULL )
                            {
                              /* Preload frames only if requested or if this is the key frame (first frame of anim) */
                              if( (fid -> fid_LoadAll) || IsListEmpty( (struct List *)(&(fid -> fid_FrameList)) ) )
                              {
                                if( animwidth && animheight && animdepth )
                                {
                                  if( fn -> fn_BitMap = AllocBitMapPooled( cb, animwidth, animheight, animdepth, (fid -> fid_Pool) ) )
                                  {
                                    (void)ReadChunkBytes( iff, (fn -> fn_BitMap -> Planes[ 0 ]), (cn -> cn_Size) );
                                  }
                                  else
                                  {
                                    /* no bitmap */
                                    error = ERROR_NO_FREE_STORE;
                                  }
                                }
                                else
                                {
                                  /* no dimensions for bitmap (possibly a missing bmhd) */
                                  error = DTERROR_NOT_ENOUGH_DATA;
                                }
                              }
                            }
                            else
                            {
                              /* bitmap already loaded */
                              error = DTERROR_INVALID_DATA;
                            }
                          }
                        }
                    }
                        break;

                    case ID_8SVX:
                    {
                        if( (cn -> cn_ID) == ID_BODY )
                        {
                          if( fn )
                          {
                            BYTE *buff;
                            ULONG bufflength = cn -> cn_Size;

                            /* Calc samples per frame (if not set previously)
                             * If sample data represents mono audio,
                             *     spf = length of sample data
                             * If sample data represents stereo audio (2 channels),
                             *     spf = (length of sample data) / 2
                             */
                            if( spf == 0UL )
                            {
                              spf = ((chan_channel != CHAN_STEREO)?(cn -> cn_Size):((cn -> cn_Size) / 2UL));
                            }

                            if( buff = (BYTE *)AllocPooled( (fid -> fid_Pool), bufflength ) )
                            {
                              (void)ReadChunkBytes( iff, buff, bufflength );

                              if( chan_channel != CHAN_STEREO )
                              {
                                fn -> fn_Sample       = buff;
                                fn -> fn_SampleLength = bufflength;
                              }
                              else
                              {
                                BYTE *sample;
                                ULONG samplelength = bufflength / 2UL;

                                if( sample = (BYTE *)AllocPooled( (fid -> fid_Pool), (samplelength + 4UL) ) )
                                {
                                  UBYTE *left   = buff,
                                        *right  = buff + samplelength,
                                        *dest   = sample;

                                  ULONG  i = samplelength;

                                  /* Merge the two stereo channels together (quality loss) */
                                  while( i-- )
                                  {
                                    *dest++ = (BYTE)((((WORD)(*left++)) + ((WORD)(*right++))) / 2);
                                  }

                                  fn -> fn_Sample       = sample;
                                  fn -> fn_SampleLength = samplelength;
                                }
                                else
                                {
                                  /* no sample mem (stereo) */
                                  error = ERROR_NO_FREE_STORE;
                                }

                                FreePooled( (fid -> fid_Pool), buff, bufflength );
                              }
                            }
                            else
                            {
                              /* no sample mem mono (or stereo temp buffer) */
                              error = ERROR_NO_FREE_STORE;
                            }
                          }
                          else
                          {
                            /* no fn while reading a 8SVX BODY */
                            error = ERROR_NO_FREE_STORE;
                          }
                        }
                    }
                        break;
                  }
                }
                
                /* on error: leave for-loop */
                if( error )
                {
                  break;
                }
              }

              if( fn )
              {
                /* Move last cell into frame list */
                AddTail( (struct List *)(&(fid -> fid_FrameList)), (struct Node *)(&(fn -> fn_Node)) );
              }
            }
          }

          /* bmh, vh and all other required information available  ? */
          if( (bmhdprop == NULL) || (vhdrprop == NULL) || (sps == 0UL) || (spf == 0UL) )
          {
            error = DTERROR_INVALID_DATA;
          }
        }

        /* Any error ? */
        if( error == 0L )
        {
          struct FrameNode *firstfn; /* short cut to the first FrameNode */

          /* Set up fid_FirstFrameNode */
          if( firstfn = (struct FrameNode *)(fid -> fid_FrameList . mlh_Head) )
          {
            /* Alloc interleaved bitmap as key bitmap  */
            if( (fid -> fid_KeyBitMap) = AllocBitMap( animwidth, animheight, animdepth, (BMF_CLEAR | BMF_INTERLEAVED), NULL ) )
            {
              /* Be sure that this is an interleaved bitmap */
              if( GetBitMapAttr( (fid -> fid_KeyBitMap), BMA_FLAGS ) & BMF_INTERLEAVED )
              {
                ULONG clock = ((SysBase -> ex_EClockFrequency) * 5UL); /* amiga clock        */
                ULONG fps;                                             /* frames per second  */
                ULONG period;

                /* Calc sample period */
                period = clock / sps;

                /* Calc samples per second */
                sps = clock / period;

                /* Calc FPS */
                fps = sps / spf; /* (samples per second) / (samples per frame) = (frames per second) */
                fps++;

                /* Other FPS set by prefs ? */
                if( fid -> fid_FPS )
                {
                  period = (period * fps) / (fid -> fid_FPS);
                }
                else
                {
                  fid -> fid_FPS = fps;
                }

                fid -> fid_Period = period; /* Store period */

                /* Assert: Be sure our FPS and period values are valid ! */
                if( ((fid -> fid_FPS) == 0UL) || (period == 0UL) )
                {
                  /* invalid fps or period */
                  error = DTERROR_INVALID_DATA;
                }
                else
                {
                  if( firstfn -> fn_BitMap )
                  {
                    /* Copy first frame into key bitmap */
                    CopyBitMap( cb, (fid -> fid_KeyBitMap), (firstfn -> fn_BitMap) );
                  }

                  if( fid -> fid_ModeID )
                  {
                    modeid = fid -> fid_ModeID;
                  }
                  else
                  {
                    /* No mode id ? */
                    if( modeid == 0UL )
                    {
                      /* Get best modeid for this dimensions */
                      modeid = BestModeID( BIDTAG_NominalWidth,      animwidth,
                                           BIDTAG_NominalHeight,     animheight,
                                           BIDTAG_Depth,             animdepth,
                                           BIDTAG_DIPFMustNotHave,   (DIPF_IS_DUALPF | DIPF_IS_PF2PRI),
                                           TAG_DONE );
                    }
                  }

#ifdef COMMENTED_OUT
                  verbose_printf( cb, "width %lu height %lu depth %lu frames %lu fps %lu volume %lu\n",
                                animwidth,
                                animheight,
                                animdepth,
                                timestamp,
                                (fid -> fid_FPS),
                                (ULONG)(fid -> fid_Volume) );
#endif /* COMMENTED_OUT */

                  /* Set misc attributes */
                  SetDTAttrs( o, NULL, NULL,
                              ADTA_ModeID,          modeid,
                              ADTA_Width,           animwidth,
                              ADTA_Height,          animheight,
                              ADTA_Depth,           animdepth,
                              ADTA_Frames,          timestamp,
                              ADTA_FramesPerSecond, (fid -> fid_FPS),
                              ADTA_KeyFrame,        (fid -> fid_KeyBitMap),
                              ADTA_Sample,          (firstfn -> fn_Sample),
                              ADTA_SampleLength,    (firstfn -> fn_SampleLength),
                              ADTA_Period,          (fid -> fid_Period),
                              ADTA_Volume,          (fid -> fid_Volume),
                              ADTA_Cycles,          1UL,
                              DTA_NominalHoriz,     animwidth,
                              DTA_NominalVert,      animheight,
                              TAG_DONE );
                }
              }
              else
              {
                /* can't alloc interleaved key bitmap
                 * (not enought memory to allocate continous memory, AllocBitMap allocated a non-interleaved one)
                 */
                error = ERROR_NO_FREE_STORE;
              }
            }
            else
            {
              /* can't alloc key bitmap */
              error = ERROR_NO_FREE_STORE;
            }
          }
          else
          {
            /* not enougth frames (at least one required) */
            error = DTERROR_NOT_ENOUGH_DATA;
          }
        }
      }
      else
      {
        /* can't get required attributes from superclass */
        error = ERROR_OBJECT_WRONG_TYPE;
      }
    }
    else
    {
      /* no memory pool */
      error = ERROR_NO_FREE_STORE;
    }

    /* Error codes below 0 are related to the IFFParse.library functions */
    if( error < 0L )
    {
      /* convert IFFParse error to DOS error */
      error = ifferr2doserr[ (-error - 1) ];
    }

    return( error );
}


static
struct FrameNode *AllocFrameNode( struct ClassBase *cb, APTR pool )
{
    struct FrameNode *fn;

    if( fn = (struct FrameNode *)AllocPooled( pool, (ULONG)sizeof( struct FrameNode ) ) )
    {
      memset( fn, 0, sizeof( struct FrameNode ) );
    }

    return( fn );
}


static
struct FrameNode *FindFrameNode( struct MinList *fnl, ULONG timestamp )
{
    if( fnl )
    {
      struct FrameNode *worknode,
                       *nextnode;

      worknode = (struct FrameNode *)(fnl -> mlh_Head);

      while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
      {
        if( (worknode -> fn_TimeStamp) == timestamp )
        {
          return( worknode );
        }

        worknode = nextnode;
      }
    }

    return( NULL );
}


/* Copy interleaved source to an interleaved destination */
static
void CopyBitMap( struct ClassBase *cb, struct BitMap *dest, struct BitMap *src )
{
    if( dest && src )
    {
      ULONG planesize = (ULONG)(dest -> BytesPerRow) * (ULONG)(dest -> Rows);

      CopyMem( (src -> Planes[ 0 ]), (dest -> Planes[ 0 ]), planesize );
    }
}


/* This function assumes (0UL < depth) && (depth <= 8UL) */
static
struct BitMap *AllocBitMapPooled( struct ClassBase *cb, ULONG width, ULONG height, ULONG depth, APTR pool )
{
    struct BitMap *bm;
    ULONG          planesize,
                   size;

    planesize = (ULONG)RASSIZE( width, height );
    size      = ((ULONG)sizeof( struct BitMap )) + (planesize * depth) + 64UL;

    if( bm = (struct BitMap *)AllocVecPooled( cb, pool, size ) )
    {
      UWORD    pl;
      PLANEPTR plane;
      ULONG    BytesPerRow;

      InitBitMap( bm, depth, width, height );

      /* Modify bitmap's BytesPerRow to be an interleaved bitmap (BytesPerRow = BytesPerRow * Depth,
       * plane ptr's follows with old BytesPerRow offset)
       */
      BytesPerRow = bm -> BytesPerRow;
      bm -> BytesPerRow *= bm -> Depth;

      plane = (PLANEPTR)(bm + 1); /* First plane follows struct BitMap */

      /* Set up plane data */
      pl = 0U;

      /* Set up plane ptrs */
      while( pl < depth )
      {
        bm -> Planes[ pl ] = plane;

        plane = (PLANEPTR)(((UBYTE *)plane) + BytesPerRow);
        pl++;
      }

      /* Clear the remaining plane ptrs */
      while( pl < 8U )
      {
        bm -> Planes[ pl ] = NULL;

        pl++;
      }
    }

    return( bm );
}


static
BOOL FillCMAP( struct ClassBase *cb, Object *o, struct StoredProperty *sp )
{
    struct ColorRegister *acm;
    ULONG                *acregs;
    ULONG                 nc;
    UBYTE                *rgb;

    rgb = sp -> sp_Data;

    /* file has this many colors */
    nc = (sp -> sp_Size) / 3UL;

    SetDTAttrs( o, NULL, NULL, ADTA_NumColors, nc, TAG_DONE );

    /* Get color context */
    if( GetDTAttrs( o,
                    ADTA_ColorRegisters, (&acm),
                    ADTA_CRegs,          (&acregs),
                    ADTA_NumColors,      (&nc),
                    TAG_DONE ) == 3UL )
    {
      /* All valid ? */
      if( acm && acregs && nc )
      {
        ULONG i;

        for( i = 0UL ; i < nc ; i++, acm++ )
        {
          acm -> red   =  *rgb++;
          acm -> green =  *rgb++;
          acm -> blue  =  *rgb++;
          acregs[ ((i * 3) + 0) ] = ((ULONG)(acm -> red))   << 24UL;
          acregs[ ((i * 3) + 1) ] = ((ULONG)(acm -> green)) << 24UL;
          acregs[ ((i * 3) + 2) ] = ((ULONG)(acm -> blue))  << 24UL;
        }

        return( TRUE );
      }
    }

    return( FALSE );
}


static
APTR AllocVecPooled( struct ClassBase *cb, APTR pool, ULONG memsize )
{
    ULONG *memory = NULL;

    if( pool && memsize )
    {
      memsize += (ULONG)sizeof( ULONG );

      if( memory = (ULONG *)AllocPooled( pool, memsize ) )
      {
        (*memory) = memsize;

        memory++;
      }
    }

    return( (APTR)memory );
}


static
void FreeVecPooled( struct ClassBase *cb, APTR pool, APTR mem )
{
    if( pool && mem )
    {
      ULONG *memory;

      memory = (ULONG *)mem;

      memory--;

      FreePooled( pool, memory, (*memory) );
    }
}



