/*
 * Benchmarks surface-to-surface blits in various formats.
 *
 *  Written by Ryan C. Gordon.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "SDL.h"

static SDL_Surface *dest = NULL;
static SDL_Surface *src = NULL;
static int testSeconds = 10;


static int
percent(int val, int total)
{
    return ((int) ((((float) val) / ((float) total)) * 100.0f));
}

static int
randRange(int lo, int hi)
{
    return (lo + (int) (((double) hi) * rand() / (RAND_MAX + 1.0)));
}

static void
copy_trunc_str(char *str, size_t strsize, const char *flagstr)
{
    if ((strlen(str) + strlen(flagstr)) >= (strsize - 1))
        strcpy(str + (strsize - 5), " ...");
    else
        strcat(str, flagstr);
}

static void
__append_sdl_surface_flag(SDL_Surface * _surface, char *str,
                          size_t strsize, Uint32 flag, const char *flagstr)
{
    if (_surface->flags & flag)
        copy_trunc_str(str, strsize, flagstr);
}


#define append_sdl_surface_flag(a, b, c, fl) __append_sdl_surface_flag(a, b, c, fl, " " #fl)
#define print_tf_state(str, val) printf("%s: {%s}\n", str, (val) ? "true" : "false" )

static void
output_videoinfo_details(void)
{
    const SDL_VideoInfo *info = SDL_GetVideoInfo();
    printf("SDL_GetVideoInfo():\n");
    if (info == NULL)
        printf("  (null.)\n");
    else {
        print_tf_state("  hardware surface available", info->hw_available);
        print_tf_state("  window manager available", info->wm_available);
        print_tf_state("  accelerated hardware->hardware blits",
                       info->blit_hw);
        print_tf_state("  accelerated hardware->hardware colorkey blits",
                       info->blit_hw_CC);
        print_tf_state("  accelerated hardware->hardware alpha blits",
                       info->blit_hw_A);
        print_tf_state("  accelerated software->hardware blits",
                       info->blit_sw);
        print_tf_state("  accelerated software->hardware colorkey blits",
                       info->blit_sw_CC);
        print_tf_state("  accelerated software->hardware alpha blits",
                       info->blit_sw_A);
        print_tf_state("  accelerated color fills", info->blit_fill);
        printf("  video memory: (%d)\n", info->video_mem);
    }

    printf("\n");
}

static void
output_surface_details(const char *name, SDL_Surface * surface)
{
    printf("Details for %s:\n", name);

    if (surface == NULL) {
        printf("-WARNING- You've got a NULL surface!");
    } else {
        char f[256];
        printf("  width      : %d\n", surface->w);
        printf("  height     : %d\n", surface->h);
        printf("  depth      : %d bits per pixel\n",
               surface->format->BitsPerPixel);
        printf("  pitch      : %d\n", (int) surface->pitch);
        printf("  alpha      : %d\n", (int) surface->format->alpha);
        printf("  colorkey   : 0x%X\n",
               (unsigned int) surface->format->colorkey);

        printf("  red bits   : 0x%08X mask, %d shift, %d loss\n",
               (int) surface->format->Rmask,
               (int) surface->format->Rshift, (int) surface->format->Rloss);
        printf("  green bits : 0x%08X mask, %d shift, %d loss\n",
               (int) surface->format->Gmask,
               (int) surface->format->Gshift, (int) surface->format->Gloss);
        printf("  blue bits  : 0x%08X mask, %d shift, %d loss\n",
               (int) surface->format->Bmask,
               (int) surface->format->Bshift, (int) surface->format->Bloss);
        printf("  alpha bits : 0x%08X mask, %d shift, %d loss\n",
               (int) surface->format->Amask,
               (int) surface->format->Ashift, (int) surface->format->Aloss);

        f[0] = '\0';

        /*append_sdl_surface_flag(surface, f, sizeof (f), SDL_SWSURFACE); */
        if ((surface->flags & SDL_HWSURFACE) == 0)
            copy_trunc_str(f, sizeof(f), " SDL_SWSURFACE");

        append_sdl_surface_flag(surface, f, sizeof(f), SDL_HWSURFACE);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_ASYNCBLIT);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_ANYFORMAT);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_HWPALETTE);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_DOUBLEBUF);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_FULLSCREEN);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_OPENGL);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_RESIZABLE);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_NOFRAME);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_HWACCEL);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_SRCCOLORKEY);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_RLEACCELOK);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_RLEACCEL);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_SRCALPHA);
        append_sdl_surface_flag(surface, f, sizeof(f), SDL_PREALLOC);

        if (f[0] == '\0')
            strcpy(f, " (none)");

        printf("  flags      :%s\n", f);
    }

    printf("\n");
}

static void
output_details(void)
{
    output_videoinfo_details();
    output_surface_details("Source Surface", src);
    output_surface_details("Destination Surface", dest);
}

static Uint32
blit(SDL_Surface * dst, SDL_Surface * src, int x, int y)
{
    Uint32 start = 0;
    SDL_Rect srcRect;
    SDL_Rect dstRect;

    srcRect.x = 0;
    srcRect.y = 0;
    dstRect.x = x;
    dstRect.y = y;
    dstRect.w = srcRect.w = src->w;     /* SDL will clip as appropriate. */
    dstRect.h = srcRect.h = src->h;

    start = SDL_GetTicks();
    SDL_BlitSurface(src, &srcRect, dst, &dstRect);
    return (SDL_GetTicks() - start);
}

static void
blitCentered(SDL_Surface * dst, SDL_Surface * src)
{
    int x = (dst->w - src->w) / 2;
    int y = (dst->h - src->h) / 2;
    blit(dst, src, x, y);
}

static int
atoi_hex(const char *str)
{
    if (str == NULL)
        return 0;

    if (strlen(str) > 2) {
        int retval = 0;
        if ((str[0] == '0') && (str[1] == 'x'))
            sscanf(str + 2, "%X", &retval);
        return (retval);
    }

    return (atoi(str));
}


static int
setup_test(int argc, char **argv)
{
    const char *dumpfile = NULL;
    SDL_Surface *bmp = NULL;
    Uint32 dstbpp = 32;
    Uint32 dstrmask = 0x00FF0000;
    Uint32 dstgmask = 0x0000FF00;
    Uint32 dstbmask = 0x000000FF;
    Uint32 dstamask = 0x00000000;
    Uint32 dstflags = 0;
    int dstw = 640;
    int dsth = 480;
    Uint32 srcbpp = 32;
    Uint32 srcrmask = 0x00FF0000;
    Uint32 srcgmask = 0x0000FF00;
    Uint32 srcbmask = 0x000000FF;
    Uint32 srcamask = 0x00000000;
    Uint32 srcflags = 0;
    int srcw = 640;
    int srch = 480;
    Uint32 origsrcalphaflags = 0;
    Uint32 origdstalphaflags = 0;
    Uint32 srcalphaflags = 0;
    Uint32 dstalphaflags = 0;
    int srcalpha = 255;
    int dstalpha = 255;
    int screenSurface = 0;
    int i = 0;

    for (i = 1; i < argc; i++) {
        const char *arg = argv[i];

        if (strcmp(arg, "--dstbpp") == 0)
            dstbpp = atoi(argv[++i]);
        else if (strcmp(arg, "--dstrmask") == 0)
            dstrmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--dstgmask") == 0)
            dstgmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--dstbmask") == 0)
            dstbmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--dstamask") == 0)
            dstamask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--dstwidth") == 0)
            dstw = atoi(argv[++i]);
        else if (strcmp(arg, "--dstheight") == 0)
            dsth = atoi(argv[++i]);
        else if (strcmp(arg, "--dsthwsurface") == 0)
            dstflags |= SDL_HWSURFACE;
        else if (strcmp(arg, "--srcbpp") == 0)
            srcbpp = atoi(argv[++i]);
        else if (strcmp(arg, "--srcrmask") == 0)
            srcrmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--srcgmask") == 0)
            srcgmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--srcbmask") == 0)
            srcbmask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--srcamask") == 0)
            srcamask = atoi_hex(argv[++i]);
        else if (strcmp(arg, "--srcwidth") == 0)
            srcw = atoi(argv[++i]);
        else if (strcmp(arg, "--srcheight") == 0)
            srch = atoi(argv[++i]);
        else if (strcmp(arg, "--srchwsurface") == 0)
            srcflags |= SDL_HWSURFACE;
        else if (strcmp(arg, "--seconds") == 0)
            testSeconds = atoi(argv[++i]);
        else if (strcmp(arg, "--screen") == 0)
            screenSurface = 1;
        else if (strcmp(arg, "--dumpfile") == 0)
            dumpfile = argv[++i];
        /* !!! FIXME: set colorkey. */
        else if (0) {           /* !!! FIXME: we handle some commandlines elsewhere now */
            fprintf(stderr, "Unknown commandline option: %s\n", arg);
            return (0);
        }
    }

    if (SDL_Init(SDL_INIT_VIDEO) == -1) {
        fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
        return (0);
    }

    bmp = SDL_LoadBMP("sample.bmp");
    if (bmp == NULL) {
        fprintf(stderr, "SDL_LoadBMP failed: %s\n", SDL_GetError());
        SDL_Quit();
        return (0);
    }

    if ((dstflags & SDL_HWSURFACE) == 0)
        dstflags |= SDL_SWSURFACE;
    if ((srcflags & SDL_HWSURFACE) == 0)
        srcflags |= SDL_SWSURFACE;

    if (screenSurface)
        dest = SDL_SetVideoMode(dstw, dsth, dstbpp, dstflags);
    else {
        dest = SDL_CreateRGBSurface(dstflags, dstw, dsth, dstbpp,
                                    dstrmask, dstgmask, dstbmask, dstamask);
    }

    if (dest == NULL) {
        fprintf(stderr, "dest surface creation failed: %s\n", SDL_GetError());
        SDL_Quit();
        return (0);
    }

    src = SDL_CreateRGBSurface(srcflags, srcw, srch, srcbpp,
                               srcrmask, srcgmask, srcbmask, srcamask);
    if (src == NULL) {
        fprintf(stderr, "src surface creation failed: %s\n", SDL_GetError());
        SDL_Quit();
        return (0);
    }

    /* handle alpha settings... */
    srcalphaflags = (src->flags & SDL_SRCALPHA) | (src->flags & SDL_RLEACCEL);
    dstalphaflags =
        (dest->flags & SDL_SRCALPHA) | (dest->flags & SDL_RLEACCEL);
    origsrcalphaflags = srcalphaflags;
    origdstalphaflags = dstalphaflags;
    srcalpha = src->format->alpha;
    dstalpha = dest->format->alpha;
    for (i = 1; i < argc; i++) {
        const char *arg = argv[i];

        if (strcmp(arg, "--srcalpha") == 0)
            srcalpha = atoi(argv[++i]);
        else if (strcmp(arg, "--dstalpha") == 0)
            dstalpha = atoi(argv[++i]);
        else if (strcmp(arg, "--srcsrcalpha") == 0)
            srcalphaflags |= SDL_SRCALPHA;
        else if (strcmp(arg, "--srcnosrcalpha") == 0)
            srcalphaflags &= ~SDL_SRCALPHA;
        else if (strcmp(arg, "--srcrleaccel") == 0)
            srcalphaflags |= SDL_RLEACCEL;
        else if (strcmp(arg, "--srcnorleaccel") == 0)
            srcalphaflags &= ~SDL_RLEACCEL;
        else if (strcmp(arg, "--dstsrcalpha") == 0)
            dstalphaflags |= SDL_SRCALPHA;
        else if (strcmp(arg, "--dstnosrcalpha") == 0)
            dstalphaflags &= ~SDL_SRCALPHA;
        else if (strcmp(arg, "--dstrleaccel") == 0)
            dstalphaflags |= SDL_RLEACCEL;
        else if (strcmp(arg, "--dstnorleaccel") == 0)
            dstalphaflags &= ~SDL_RLEACCEL;
    }
    if ((dstalphaflags != origdstalphaflags)
        || (dstalpha != dest->format->alpha))
        SDL_SetAlpha(dest, dstalphaflags, (Uint8) dstalpha);
    if ((srcalphaflags != origsrcalphaflags)
        || (srcalpha != src->format->alpha))
        SDL_SetAlpha(src, srcalphaflags, (Uint8) srcalpha);

    /* set some sane defaults so we can see if the blit code is broken... */
    SDL_FillRect(dest, NULL, SDL_MapRGB(dest->format, 0, 0, 0));
    SDL_FillRect(src, NULL, SDL_MapRGB(src->format, 0, 0, 0));

    blitCentered(src, bmp);
    SDL_FreeSurface(bmp);

    if (dumpfile)
        SDL_SaveBMP(src, dumpfile);     /* make sure initial convert is sane. */

    output_details();

    return (1);
}


static void
test_blit_speed(void)
{
    Uint32 clearColor = SDL_MapRGB(dest->format, 0, 0, 0);
    Uint32 iterations = 0;
    Uint32 elasped = 0;
    Uint32 end = 0;
    Uint32 now = 0;
    Uint32 last = 0;
    int testms = testSeconds * 1000;
    int wmax = (dest->w - src->w);
    int hmax = (dest->h - src->h);
    int isScreen = (SDL_GetVideoSurface() == dest);
    SDL_Event event;

    printf("Testing blit speed for %d seconds...\n", testSeconds);

    now = SDL_GetTicks();
    end = now + testms;

    do {
        /* pump the event queue occasionally to keep OS happy... */
        if (now - last > 1000) {
            last = now;
            while (SDL_PollEvent(&event)) {     /* no-op. */
            }
        }

        iterations++;
        elasped += blit(dest, src, randRange(0, wmax), randRange(0, hmax));
        if (isScreen) {
            SDL_Flip(dest);     /* show it! */
            SDL_FillRect(dest, NULL, clearColor);       /* blank it for next time! */
        }

        now = SDL_GetTicks();
    }
    while (now < end);

    printf("Non-blitting crap accounted for %d percent of this run.\n",
           percent(testms - elasped, testms));

    printf("%d blits took %d ms (%d fps).\n",
           (int) iterations,
           (int) elasped,
           (int) (((float) iterations) / (((float) elasped) / 1000.0f)));
}

int
main(int argc, char **argv)
{
    int initialized = setup_test(argc, argv);
    if (initialized) {
        test_blit_speed();
        SDL_Quit();
    }
    return (!initialized);
}

/* end of testblitspeed.c ... */
