The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "ppport.h"
#include <SDL.h>
#include "helper.h"

/* SV input should be a mortal SV */
SV *create_mortal_rect( SV *rect )
{
    SV *retval = NULL;

    if( !SvOK(rect) )
    {
        /* create a new zero sized rectangle */
        SDL_Rect* r = safemalloc( sizeof(SDL_Rect) );
        r->x        = 0;
        r->y        = 0;
        r->w        = 0;
        r->h        = 0;
        retval      = obj2bag( sizeof( SDL_Rect *), (void *)(r), "SDL::Rect" );
        sv_2mortal(retval) ;
    }
    else if( sv_derived_from(rect, "ARRAY") )
    {
        /* create a new rectangle from the array */
        SDL_Rect* r      = safemalloc( sizeof(SDL_Rect) );
        AV* recta        = (AV*)SvRV(rect);
        int len          = av_len(recta);
        int i;
        int ra[4];
        for(i = 0; i < 4; i++)
        { 
            SV* iv = i > len ? NULL : AvARRAY(recta)[i];
            ra[i]  = ( iv == NULL || !SvOK( iv ) || iv == &PL_sv_undef )
                   ? 0
                   : SvIV( iv );
        }

        r->x   = ra[0]; r->y = ra[1]; r->w = ra[2]; r->h= ra[3];
        retval = obj2bag( sizeof( SDL_Rect *), (void *)(r), "SDL::Rect" );
        sv_2mortal(retval) ;
    }
    else if( sv_isobject(rect) && sv_derived_from(rect, "SDL::Rect") )
    {
        /* we already had a good mortal rect . Just pass it along */
        retval = rect;
    }
    else
        croak("Rect must be number or arrayref or SDL::Rect or undef");

    return retval; 
}

void assert_surface( SV *surface )
{
    if( sv_isobject(surface) && sv_derived_from(surface, "SDL::Surface"))
        return;

    croak("Surface must be SDL::Surface or SDLx::Surface");
    /* does not return */
}

char *_color_format( SV *color )
{
    char *retval = NULL;
    if( !SvOK(color) || SvIOK(color) )
        retval = "number";
    else if( sv_derived_from(color, "ARRAY") )
        retval = "arrayref";
    else if( sv_isobject(color) && sv_derived_from(color, "SDL::Color") )
        retval = "SDL::Color";
    else
        croak("Color must be number or arrayref or SDL::Color");

    return retval;
}

SV *_color_number( SV *color, SV *alpha )
{
    int          c      = SvIV(color);
    int          a      = SvIV(alpha);
    unsigned int retval = SvUV(color);

    if( !SvOK(color) || color < 0 )
    {
        if( color < 0 )
            warn("Color was a negative number");
        retval = a == 1
               ? 0x000000FF
               : 0;
    }
    else
    {
        if( a == 1 && (c > 0xFFFFFFFF) )
        {
            warn("Color was number greater than maximum expected: 0xFFFFFFFF");
            retval = 0xFFFFFFFF; 
        }
        else if ( a != 1 && ( c > 0xFFFFFF) )
        {
            warn("Color was number greater than maximum expected: 0xFFFFFF");
            retval = 0xFFFFFF;
        }
    }

    return newSVuv(retval);
}

/* returns a new mortal AV* */
AV *_color_arrayref( AV *color, SV *alpha )
{
    AV *retval = (AV*)sv_2mortal((SV*)newAV());
    int length = SvTRUE(alpha) ? 4 : 3;
    int i      = 0;
    for(i = 0; i < length; i++)
    {
        if( av_len(color) < i || !SvOK(AvARRAY(color)[i]) )
            av_push(retval, newSVuv(i == 3 ? 0xFF : 0));
        else
        {
            int c = SvIV(AvARRAY(color)[i]);
            if( c > 0xFF )
            {
                warn("Number in color arrayref was greater than maximum expected: 0xFF");
                av_push(retval, newSVuv(0xFF));
            }
            else if( c < 0 )
            {
                warn("Number in color arrayref was negative");
                av_push(retval, newSVuv(0));
            }
            else
                av_push(retval, newSVuv(c));
        }
    }
    
    return retval;
}

/* returns a mortal AV* */
AV* __list_rgb( SV* color )
{
    char *format = _color_format(color);
    AV* RETVAL ;
    if ( 0 == strcmp("number", format) )
    {
        unsigned int _color;
        RETVAL = (AV*)sv_2mortal( (SV *) newAV() );
        _color = SvUV(sv_2mortal(_color_number(color, newSVuv(0))));
        av_push(RETVAL, newSVuv(_color >> 16 & 0xFF));
        av_push(RETVAL, newSVuv(_color >>  8 & 0xFF));
        av_push(RETVAL, newSVuv(_color       & 0xFF));
    }
    else if ( 0 == strcmp("arrayref", format) )
    {
        /* _color_arrayref returns a mortal AV* */
        RETVAL = _color_arrayref((AV *)SvRV(color), sv_2mortal(newSVuv(0)));
    }
    else if ( 0 == strcmp("SDL::Color", format) )
    {
        SDL_Color *_color;
        RETVAL = (AV*)sv_2mortal((SV *) newAV() );
        _color = (SDL_Color *)bag2obj(color);
        av_push(RETVAL, newSVuv(_color->r));
        av_push(RETVAL, newSVuv(_color->g));
        av_push(RETVAL, newSVuv(_color->b));
    }
    else
    {
        RETVAL = (AV*)sv_2mortal((SV *) newAV() );
        av_push(RETVAL, newSVuv(0));
        av_push(RETVAL, newSVuv(0));
        av_push(RETVAL, newSVuv(0));
    }
    
    return RETVAL;
}

AV* __list_rgba( SV* color )
{
    char *format;
    AV* RETVAL;
    format = _color_format(color);
    if ( 0 == strcmp("number", format) )
    {
        unsigned int _color;
        RETVAL = (AV*)sv_2mortal((SV *) newAV() );
        _color = SvUV(sv_2mortal(_color_number(color, sv_2mortal(newSVuv(1)))));
        av_push(RETVAL, newSVuv(_color >> 24 & 0xFF));
        av_push(RETVAL, newSVuv(_color >> 16 & 0xFF));
        av_push(RETVAL, newSVuv(_color >>  8 & 0xFF));
        av_push(RETVAL, newSVuv(_color       & 0xFF));
    }
    else if ( 0 == strcmp("arrayref", format) )
    {
        RETVAL = _color_arrayref((AV *)SvRV(color), sv_2mortal(newSVuv(1)));
    }
    else if ( 0 == strcmp("SDL::Color", format) )
    {
        SDL_Color *_color;
        RETVAL = (AV*)sv_2mortal((SV *) newAV() );
        _color = (SDL_Color*)bag2obj(color);
        av_push(RETVAL, newSVuv(_color->r));
        av_push(RETVAL, newSVuv(_color->g));
        av_push(RETVAL, newSVuv(_color->b));
        av_push(RETVAL, newSVuv(0xFF));
    }
    else
    {
        RETVAL = (AV*)sv_2mortal((SV *) newAV() );
        av_push(RETVAL, newSVuv(0));
        av_push(RETVAL, newSVuv(0));
        av_push(RETVAL, newSVuv(0));
        av_push(RETVAL, newSVuv(0xFF));
    }

    return RETVAL;
}


unsigned int __map_rgb( SV* color, SDL_PixelFormat* format )
{
    Uint8 r, g, b;
    AV* a;
    a = __list_rgb( color );
    r = SvUV(*av_fetch(a, 0, 0));
    g = SvUV(*av_fetch(a, 1, 0));
    b = SvUV(*av_fetch(a, 2, 0));

    return SDL_MapRGB( format, r, g, b ); 
}

unsigned int __map_rgba( SV* color, SDL_PixelFormat* format )
{
    int r, g, b, a;
    AV* ar;
    ar = __list_rgba( color );
    r  = SvUV(*av_fetch(ar, 0, 0));
    g  = SvUV(*av_fetch(ar, 1, 0));
    b  = SvUV(*av_fetch(ar, 2, 0));
    a  = SvUV(*av_fetch(ar, 3, 0));

    return SDL_MapRGBA( format, r, g, b, a );
}