/*
 * Image.c -- An extension to the Server_image object to support
 * xv_find().  This is done by creating a list of all the image
 * objects of this type and attaching it to the owner (screen).
 * The "screen" is chosen because when xv_find() is called, you
 * need to find the object associated with the same "screen" as
 * the owner's screen, otherwise what we return may not render
 * correctly.
 */
#include "image_impl.h"

static int image_init(), image_destroy();
static Xv_opaque image_get(), image_set(), image_find();
static void image_repaint();

Xv_pkg image_pkg =
{
    "Image",                    /* package name */
    ATTR_PKG_IMAGE,             /* package ID */
    sizeof(Image_public),       /* size of the public struct */
    SERVER_IMAGE,               /* subclassed from the server image */
    image_init,
    image_set,
    image_get,
    image_destroy,
    image_find
};

/* initialize the image object -- create (alloc) an instance of it and
 * connect the public and private parts.  Since this package supports
 * xv_find(), createa linked list and/or append the newly created
 * instance of this object to the end of the list.
 */
static int
image_init(owner, image_public, avlist)
Xv_Screen       owner;
Image_public   *image_public;
Attr_avlist     avlist;            /* ignored here */
{
    Attr_attribute *attrs;
    Image_private *image_private = xv_alloc(Image_private);
    Image_private *list; /* linked list of image instances */
    Xv_Screen screen = owner ? owner : xv_default_screen;

    if(!image_private || !screen)
        return XV_ERROR;

    /* link the public to the private and vice-versa */
    image_public->private_data = (Xv_opaque)image_private;
    image_private->public_self = (Xv_opaque)image_public;

    for(attrs = avlist; *attrs; attrs = attr_next(attrs))
        if(attrs[0] == SERVER_IMAGE_BITMAP_FILE)
            image_private->filename =
                strcpy(malloc(strlen(attrs[1]) + 1), attrs[1]);

    image_private->next = (Image_private *)NULL;
    image_private->screen = screen;

    /* get the list of existing images from the screen */
    if(list = (Image_private *)xv_get(screen,
                                      XV_KEY_DATA, ATTR_PKG_IMAGE))
    {
        /* follow list till the end */
        while(list->next)
            list = list->next;

        /* assign new image object to end of list */
        list->next = image_private;
    }
    else
    {
        /* no image objects on this screen -- create a new list */
        xv_set(screen,
               XV_KEY_DATA, ATTR_PKG_IMAGE, image_private,
               NULL);
    }

    return XV_OK;
}

/* image_set() -- needed to track whether "filename" changes */
static Xv_opaque
image_set(image_public, avlist)
Image_public   *image_public;
Attr_avlist     avlist;
{
    Attr_attribute *attrs;
    Image_private *image_private = IMAGE_PRIVATE(image_public);

    /* loop thru attrs looking for anything that would invalidate
     * the fact that the filename is set to a valid file.  If the
     * programmer is assigning a new pixmap or data to this server
     * image, the filename that was originally associated with the
     * object is no longer valid.  Disable for later get/find calls.
     */
    if(image_private->filename)
        for(attrs = avlist; *attrs; attrs = attr_next(attrs))
            if(attrs[0] == SERVER_IMAGE_PIXMAP ||
                    attrs[0] == SERVER_IMAGE_BITS   ||
                    attrs[0] == SERVER_IMAGE_X_BITS)
            {
                free(image_private->filename);
                image_private->filename = NULL;
            }

    return (Xv_opaque)XV_OK;
}

/* image_get() -- Support xv_get() for SERVER_IMAGE_BITMAP_FILE
 * and XV_SCREEN.  Warning: because this package is subclassed
 * from the Server_image package, we must not attempt to return
 * values for attrs that the Server_image package supports.
 */
static Xv_opaque
image_get(image_public, status, attr, args)
Image_public   *image_public;
int            *status;
Attr_attribute  attr;
Attr_avlist     args;
{
    Image_private *image_private = IMAGE_PRIVATE(image_public);

    switch((int) attr)
    {
    case SERVER_IMAGE_BITMAP_FILE :
        return (Xv_opaque)image_private->filename;

    case XV_SCREEN :
        return (Xv_opaque)image_private->screen;

    default :
        *status = xv_check_bad_attr(IMAGE, attr);
        return (Xv_opaque)XV_OK;
    }
}

/* destroy method: free the object and remove from linked list */
static int
image_destroy(image_public, status)
Image_public    *image_public;
Destroy_status   status;
{
    Image_private *image_private = IMAGE_PRIVATE(image_public);
    Image_private *list; /* linked list of image instances */
    Xv_Screen screen = image_private->screen;

    if(status == DESTROY_CLEANUP)
    {
        /* get the list of existing images from the screen */
        list = (Image_private *)xv_get(screen,
                                       XV_KEY_DATA, ATTR_PKG_IMAGE);

        if((Image)XV_PUBLIC(list) == (Image)image_public)
            xv_set(screen,
                   XV_KEY_DATA, ATTR_PKG_IMAGE, list->next,
                   NULL);

        for(; list->next; list = list->next)
            if((Image)XV_PUBLIC(list->next) == (Image)image_public)
            {
                list->next = list->next->next;
                break;
            }

        if(list->filename)
            free(list->filename);

        free(list);
    }

    return XV_OK;
}

/* image_find() -- find an existing image object that matches the
 * attribute-values specified here.  If none found, return NULL.
 * XView internals handle the XV_AUTO_CREATE case.  This function
 * supports finding images according to the following attributes:
 * XV_WIDTH, XV_HEIGHT, SERVER_IMAGE_DEPTH, SERVER_IMAGE_PIXMAP
 * and SERVER_IMAGE_BITMAP_FILE.  All others result in a NULL return.
 */
static Xv_opaque
image_find(owner, pkg, avlist)
Xv_Screen       owner;
Xv_pkg         *pkg;
Attr_avlist     avlist;            /* ignored here */
{
    Image_private *list; /* linked list of image instances */
    /* this is what the server image package does */
    Xv_Screen screen = owner ? owner : xv_default_screen;
    Attr_attribute *attrs;
    /* consider all the attrs we allow "find" to match on */
    int     width = -1, height = -1, depth = -1;
    Pixmap  pixmap = (Pixmap)NULL;
    char   *filename = NULL;

    /* get the list of existing images from the screen */
    list = (Image_private *)xv_get(screen,
                                   XV_KEY_DATA, ATTR_PKG_IMAGE);

    if(!list)
        return NULL;

    /* loop thru each attribute requested and save the value
     * associated with it.  Later, we'll loop thru the existing
     * objects looking for the object that has the same values.
     */
    for(attrs = avlist; *attrs; attrs = attr_next(attrs))
        switch((int)attrs[0])
        {
        case XV_WIDTH :
            width = (int)attrs[1];
            break;

        case XV_HEIGHT :
            height = (int)attrs[1];
            break;

        case SERVER_IMAGE_DEPTH :
            depth = (int)attrs[1];
            break;

        case SERVER_IMAGE_PIXMAP :
            pixmap = (Pixmap)attrs[1];
            break;

        case SERVER_IMAGE_BITMAP_FILE :
            filename = (char *)attrs[1];
            break;

        case SERVER_IMAGE_BITS :
        case SERVER_IMAGE_X_BITS :
        case SERVER_IMAGE_COLORMAP :
        case SERVER_IMAGE_CMS :
        case SERVER_IMAGE_SAVE_PIXMAP :
        default :
            return NULL; /* you can't "find" for these attrs */
        }

    /* Now loop thru each object looking for those whose
     * value that match those specified above.
     */
    for(; list; list = list->next)
    {
        /* If it doesn't match, continue to the next object in
         * the list.  Repeat for each requested attribute.
         */
        if(width > -1 &&
                (width != (int)xv_get(XV_PUBLIC(list), XV_WIDTH)))
            continue;

        if(height > -1 &&
                (height != (int)xv_get(XV_PUBLIC(list), XV_HEIGHT)))
            continue;

        if(depth > -1 && (depth != (int)xv_get(XV_PUBLIC(list),
                                               SERVER_IMAGE_DEPTH)))
            continue;

        if(pixmap && (pixmap != (Pixmap)xv_get(XV_PUBLIC(list),
                                               SERVER_IMAGE_PIXMAP)))
            continue;

        if(filename &&
                (!list->filename || strcmp(filename, list->filename)))
            continue;

        /* all matches seemed to be successful, return this object */
        return XV_PUBLIC(list);
    }

    /* nothing found */
    return NULL;
}
