/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/
#include "skin_surface.h"
#include "skin_argb.h"
#include <SDL.h>

#define  DEBUG  1

#if DEBUG
#include "android_debug.h"
#define  D(...)   VERBOSE_PRINT(surface,__VA_ARGS__)
#else
#define  D(...)   ((void)0)
#endif

struct SkinSurface {
    int                  refcount;
    uint32_t*            pixels;
    SDL_Surface*         surface;
    SkinSurfaceDoneFunc  done_func;
    void*                done_user;
};

static void
skin_surface_free( SkinSurface*  s )
{
    if (s->done_func) {
        s->done_func( s->done_user );
        s->done_func = NULL;
    }
    if (s->surface) {
        SDL_FreeSurface(s->surface);
        s->surface = NULL;
    }
    free(s);
}

extern SkinSurface*
skin_surface_ref( SkinSurface*  surface )
{
    if (surface)
        surface->refcount += 1;
    return surface;
}

extern void
skin_surface_unrefp( SkinSurface*  *psurface )
{
    SkinSurface*  surf = *psurface;
    if (surf) {
        if (--surf->refcount <= 0)
            skin_surface_free(surf);
        *psurface = NULL;
    }
}


void
skin_surface_set_done( SkinSurface*  s, SkinSurfaceDoneFunc  done_func, void*  done_user )
{
    s->done_func = done_func;
    s->done_user = done_user;
}

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#  define  ARGB32_R_MASK  0xff000000
#  define  ARGB32_G_MASK  0x00ff0000
#  define  ARGB32_B_MASK  0x0000ff00
#  define  ARGB32_A_MASK  0x000000ff
#else
#  define  ARGB32_R_MASK  0x000000ff
#  define  ARGB32_G_MASK  0x0000ff00
#  define  ARGB32_B_MASK  0x00ff0000
#  define  ARGB32_A_MASK  0xff000000
#endif

static SDL_Surface*
_sdl_surface_create_rgb( int  width,
                         int  height,
                         int  depth,
                         int  flags )
{
   Uint32   rmask, gmask, bmask, amask;

    if (depth == 8) {
        rmask = gmask = bmask = 0;
        amask = 0xff;
    } else if (depth == 32) {
        rmask = ARGB32_R_MASK;
        gmask = ARGB32_G_MASK;
        bmask = ARGB32_B_MASK;
        amask = ARGB32_A_MASK;
    } else
        return NULL;

    return SDL_CreateRGBSurface( flags, width, height, depth,
                                 rmask, gmask, bmask, amask );
}


static SDL_Surface*
_sdl_surface_create_rgb_from( int   width,
                              int   height,
                              int   pitch,
                              void* pixels,
                              int   depth )
{
   Uint32   rmask, gmask, bmask, amask;

    if (depth == 8) {
        rmask = gmask = bmask = 0;
        amask = 0xff;
    } else if (depth == 32) {
        rmask = ARGB32_R_MASK;
        gmask = ARGB32_G_MASK;
        bmask = ARGB32_B_MASK;
        amask = ARGB32_A_MASK;
    } else
        return NULL;

    return SDL_CreateRGBSurfaceFrom( pixels, width, height, pitch, depth,
                                     rmask, gmask, bmask, amask );
}


static SkinSurface*
_skin_surface_create( SDL_Surface*  surface,
                      void*         pixels )
{
    SkinSurface*  s = malloc(sizeof(*s));
    if (s != NULL) {
        s->refcount = 1;
        s->pixels   = pixels;
        s->surface  = surface;
        s->done_func = NULL;
        s->done_user = NULL;
    }
    else {
        SDL_FreeSurface(surface);
        free(pixels);
        D( "not enough memory to allocate new skin surface !" );
    }
    return  s;
}


SkinSurface*
skin_surface_create_fast( int  w, int  h )
{
    SDL_Surface*  surface;

    surface = _sdl_surface_create_rgb( w, h, 32, SDL_HWSURFACE );
    if (surface == NULL) {
        surface = _sdl_surface_create_rgb( w, h, 32, SDL_SWSURFACE );
        if (surface == NULL) {
            D( "could not create fast %dx%d ARGB32 surface: %s",
               w, h, SDL_GetError() );
            return NULL;
        }
    }
    return _skin_surface_create( surface, NULL );
}


SkinSurface*
skin_surface_create_slow( int  w, int  h )
{
    SDL_Surface*  surface;

    surface = _sdl_surface_create_rgb( w, h, 32, SDL_SWSURFACE );
    if (surface == NULL) {
        D( "could not create slow %dx%d ARGB32 surface: %s",
            w, h, SDL_GetError() );
        return NULL;
    }
    return _skin_surface_create( surface, NULL );
}


SkinSurface*
skin_surface_create_argb32_from(
                        int                  w,
                        int                  h,
                        int                  pitch,
                        uint32_t*            pixels,
                        int                  do_copy )
{
    SDL_Surface*  surface;
    uint32_t*     pixcopy = NULL;

    if (do_copy) {
        size_t  size = h*pitch;
        pixcopy = malloc( size );
        if (pixcopy == NULL && size > 0) {
            D( "not enough memory to create %dx%d ARGB32 surface",
               w, h );
            return NULL;
        }
        memcpy( pixcopy, pixels, size );
    }

    surface = _sdl_surface_create_rgb_from( w, h, pitch,
                                            pixcopy ? pixcopy : pixels,
                                            32 );
    if (surface == NULL) {
        D( "could not create %dx%d slow ARGB32 surface: %s",
            w, h, SDL_GetError() );
        return NULL;
    }
    return _skin_surface_create( surface, pixcopy );
}




extern int
skin_surface_lock( SkinSurface*  s, SkinSurfacePixels  *pix )
{
    if (!s || !s->surface) {
        D( "error: trying to lock stale surface %p", s );
        return -1;
    }
    if ( SDL_LockSurface( s->surface ) != 0 ) {
        D( "could not lock surface %p: %s", s, SDL_GetError() );
        return -1;
    }
    pix->w      = s->surface->w;
    pix->h      = s->surface->h;
    pix->pitch  = s->surface->pitch;
    pix->pixels = s->surface->pixels;
    return 0;
}

/* unlock a slow surface that was previously locked */
extern void
skin_surface_unlock( SkinSurface*  s )
{
    if (s && s->surface)
        SDL_UnlockSurface( s->surface );
}


#if 0
static uint32_t
skin_surface_map_argb( SkinSurface*  s, uint32_t  c )
{
    if (s && s->surface) {
        return SDL_MapRGBA( s->surface->format,
                            ((c) >> 16) & 255,
                            ((c) >> 8) & 255,
                            ((c) & 255),
                            ((c) >> 24) & 255 );
    }
    return 0x00000000;
}
#endif

typedef struct {
    int   x;
    int   y;
    int   w;
    int   h;
    int   sx;
    int   sy;

    uint8_t*      dst_line;
    int           dst_pitch;
    SDL_Surface*  dst_lock;

    uint8_t*      src_line;
    int           src_pitch;
    SDL_Surface*  src_lock;
    uint32_t      src_color;

} SkinBlit;


static int
skin_blit_init_fill( SkinBlit*     blit,
                     SkinSurface*  dst,
                     SkinRect*     dst_rect,
                     uint32_t      color )
{
    int  x = dst_rect->pos.x;
    int  y = dst_rect->pos.y;
    int  w = dst_rect->size.w;
    int  h = dst_rect->size.h;
    int  delta;

    if (x < 0) {
        w += x;
        x  = 0;
    }
    delta = (x + w) - dst->surface->w;
    if (delta > 0)
        w -= delta;

    if (y < 0) {
        h += y;
        y  = 0;
    }
    delta = (y + h) - dst->surface->h;
    if (delta > 0)
        h -= delta;

    if (w <= 0 || h <= 0)
        return 0;

    blit->x = x;
    blit->y = y;
    blit->w = w;
    blit->h = h;

    if ( !SDL_LockSurface(dst->surface) )
        return 0;

    blit->dst_lock  = dst->surface;
    blit->dst_pitch = dst->surface->pitch;
    blit->dst_line  = dst->surface->pixels + y*blit->dst_pitch;

    blit->src_lock  = NULL;
    blit->src_color = color;

    return 1;
}

static int
skin_blit_init_blit( SkinBlit*     blit,
                     SkinSurface*  dst,
                     SkinPos*      dst_pos,
                     SkinSurface*  src,
                     SkinRect*     src_rect )
{
    int  x  = dst_pos->x;
    int  y  = dst_pos->y;
    int  sx = src_rect->pos.x;
    int  sy = src_rect->pos.y;
    int  w  = src_rect->size.w;
    int  h  = src_rect->size.h;
    int  delta;

    if (x < 0) {
        w  += x;
        sx -= x;
        x   = 0;
    }
    if (sx < 0) {
        w  += sx;
        x  -= sx;
        sx  = 0;
    }

    delta = (x + w) - dst->surface->w;
    if (delta > 0)
        w -= delta;

    delta = (sx + w) - src->surface->w;
    if (delta > 0)
        w -= delta;

    if (y < 0) {
        h  += y;
        sy += y;
        y   = 0;
    }
    if (sy < 0) {
        h  += sy;
        y  -= sy;
        sy  = 0;
    }
    delta = (y + h) - dst->surface->h;
    if (delta > 0)
        h -= delta;

    delta = (sy + h) - src->surface->h;

    if (w <= 0 || h <= 0)
        return 0;

    blit->x = x;
    blit->y = y;
    blit->w = w;
    blit->h = h;

    blit->sx = sx;
    blit->sy = sy;

    if ( !SDL_LockSurface(dst->surface) )
        return 0;

    blit->dst_lock  = dst->surface;
    blit->dst_pitch = dst->surface->pitch;
    blit->dst_line  = (uint8_t*) dst->surface->pixels + y*blit->dst_pitch;

    if ( !SDL_LockSurface(src->surface) ) {
        SDL_UnlockSurface(dst->surface);
        return 0;
    }

    blit->src_lock  = src->surface;
    blit->src_pitch = src->surface->pitch;
    blit->src_line  = (uint8_t*) src->surface->pixels + sy*blit->src_pitch;

    return 1;
}

static void
skin_blit_done( SkinBlit*  blit )
{
    if (blit->src_lock)
        SDL_UnlockSurface( blit->src_lock );
    if (blit->dst_lock)
        SDL_UnlockSurface( blit->dst_lock );
    ARGB_DONE;
}

typedef void (*SkinLineFillFunc)( uint32_t*  dst, uint32_t  color, int  len );
typedef void (*SkinLineBlitFunc)( uint32_t*  dst, const uint32_t*  src,  int  len );

static void
skin_line_fill_copy( uint32_t*  dst, uint32_t  color, int  len )
{
    uint32_t*  end = dst + len;

    while (dst + 4 <= end) {
        dst[0] = dst[1] = dst[2] = dst[3] = color;
        dst   += 4;
    }
    while (dst < end) {
        dst[0] = color;
        dst   += 1;
    }
}

static void
skin_line_fill_srcover( uint32_t*  dst, uint32_t  color, int  len )
{
    uint32_t*  end = dst + len;
    uint32_t   alpha = (color >> 24);

    if (alpha == 255)
    {
        skin_line_fill_copy(dst, color, len);
    }
    else
    {
        ARGB_DECL(src_c);
        ARGB_DECL_ZERO();

        alpha  = 255 - alpha;
        alpha += (alpha >> 7);

        ARGB_UNPACK(src_c,color);

        for ( ; dst < end; dst++ )
        {
            ARGB_DECL(dst_c);

            ARGB_READ(dst_c,dst);
            ARGB_MULSHIFT(dst_c,dst_c,alpha,8);
            ARGB_ADD(dst_c,src_c);
            ARGB_WRITE(dst_c,dst);
        }
    }
}

static void
skin_line_fill_dstover( uint32_t*  dst, uint32_t  color, int  len )
{
    uint32_t*  end = dst + len;
    ARGB_DECL(src_c);
    ARGB_DECL_ZERO();

    ARGB_UNPACK(src_c,color);

    for ( ; dst < end; dst++ )
    {
        ARGB_DECL(dst_c);
        ARGB_DECL(val);

        uint32_t   alpha;

        ARGB_READ(dst_c,dst);
        alpha = 256 - (dst[0] >> 24);
        ARGB_MULSHIFT(val,src_c,alpha,8);
        ARGB_ADD(val,dst_c);
        ARGB_WRITE(val,dst);
    }
}

extern void
skin_surface_fill( SkinSurface*  dst,
                   SkinRect*     rect,
                   uint32_t      argb_premul,
                   SkinBlitOp    blitop )
{
    SkinLineFillFunc  fill;
    SkinBlit          blit[1];

    switch (blitop) {
        case SKIN_BLIT_COPY:    fill = skin_line_fill_copy; break;
        case SKIN_BLIT_SRCOVER: fill = skin_line_fill_srcover; break;
        case SKIN_BLIT_DSTOVER: fill = skin_line_fill_dstover; break;
        default: return;
    }

    if ( skin_blit_init_fill( blit, dst, rect, argb_premul ) ) {
        uint8_t*   line  = blit->dst_line;
        int        pitch = blit->dst_pitch;
        uint8_t*   end   = line + pitch*blit->h;

        for ( ; line != end; line += pitch )
            fill( (uint32_t*)line + blit->x, argb_premul, blit->w );
    }
}


static void
skin_line_blit_copy( uint32_t*  dst, const uint32_t*  src, int  len )
{
    memcpy( (char*)dst, (const char*)src, len*4 );
}



static void
skin_line_blit_srcover( uint32_t*  dst, const uint32_t*  src, int  len )
{
    uint32_t*  end = dst + len;
    ARGB_DECL_ZERO();

    for ( ; dst < end; dst++ ) {
        ARGB_DECL(s);
        ARGB_DECL(d);
        ARGB_DECL(v);
        uint32_t  alpha;

        ARGB_READ(s,src);
        alpha = (src[0] >> 24);
        if (alpha > 0) {
            ARGB_READ(d,dst);
            alpha = 256 - alpha;
            ARGB_MULSHIFT(v,d,alpha,8);
            ARGB_ADD(v,d);
            ARGB_WRITE(v,dst);
        }
    }
}

static void
skin_line_blit_dstover( uint32_t*  dst, const uint32_t*  src, int  len )
{
    uint32_t*  end = dst + len;
    ARGB_DECL_ZERO();

    for ( ; dst < end; dst++ ) {
        ARGB_DECL(s);
        ARGB_DECL(d);
        ARGB_DECL(v);
        uint32_t  alpha;

        ARGB_READ(d,dst);
        alpha = (dst[0] >> 24);
        if (alpha < 255) {
            ARGB_READ(s,src);
            alpha = 256 - alpha;
            ARGB_MULSHIFT(v,s,alpha,8);
            ARGB_ADD(v,s);
            ARGB_WRITE(v,dst);
        }
    }
}


extern void
skin_surface_blit( SkinSurface*  dst,
                   SkinPos*      dst_pos,
                   SkinSurface*  src,
                   SkinRect*     src_rect,
                   SkinBlitOp    blitop )
{
    SkinLineBlitFunc  func;
    SkinBlit          blit[1];

    switch (blitop) {
        case SKIN_BLIT_COPY:    func = skin_line_blit_copy; break;
        case SKIN_BLIT_SRCOVER: func = skin_line_blit_srcover; break;
        case SKIN_BLIT_DSTOVER: func = skin_line_blit_dstover; break;
        default: return;
    }

    if ( skin_blit_init_blit( blit, dst, dst_pos, src, src_rect ) ) {
        uint8_t*   line   = blit->dst_line;
        uint8_t*   sline  = blit->src_line;
        int        pitch  = blit->dst_pitch;
        int        spitch = blit->src_pitch;
        uint8_t*   end    = line + pitch*blit->h;

        for ( ; line != end; line += pitch, sline += spitch )
            func( (uint32_t*)line + blit->x, (uint32_t*)sline + blit->sx, blit->w );

        skin_blit_done(blit);
    }
}
