/*
  Copyright 1997-2022 Sam Lantinga
  Copyright 2022 Collabora Ltd.

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely.
*/

#include "SDL_image.h"

#include "SDL.h"
#include "SDL_test.h"

#if defined(SDL_FILESYSTEM_OS2) || defined(SDL_FILESYSTEM_WINDOWS)
static const char pathsep[] = "\\";
#elif defined(SDL_FILESYSTEM_RISCOS)
static const char pathsep[] = ".";
#else
static const char pathsep[] = "/";
#endif

#if defined(__APPLE__) && !defined(SDL_IMAGE_USE_COMMON_BACKEND)
# define USING_IMAGEIO 1
#else
# define USING_IMAGEIO 0
#endif

typedef enum
{
    TEST_FILE_DIST,
    TEST_FILE_BUILT
} TestFileType;

static SDL_bool
GetStringBoolean(const char *value, SDL_bool default_value)
{
    if (!value || !*value) {
        return default_value;
    }
    if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
        return SDL_FALSE;
    }
    return SDL_TRUE;
}

/*
 * Return the absolute path to a resource file, similar to GLib's
 * g_test_build_filename().
 *
 * If type is TEST_FILE_DIST, look for it in $SDL_TEST_SRCDIR or next
 * to the executable.
 *
 * If type is TEST_FILE_BUILT, look for it in $SDL_TEST_BUILDDIR or next
 * to the executable.
 *
 * Fails and returns NULL if out of memory.
 */
static char *
GetTestFilename(TestFileType type, const char *file)
{
    const char *env;
    char *base = NULL;
    char *path = NULL;
    SDL_bool needPathSep = SDL_TRUE;

    if (type == TEST_FILE_DIST) {
        env = SDL_getenv("SDL_TEST_SRCDIR");
    } else {
        env = SDL_getenv("SDL_TEST_BUILDDIR");
    }

    if (env != NULL) {
        base = SDL_strdup(env);
        if (base == NULL) {
            SDL_OutOfMemory();
            return NULL;
        }
    }

    if (base == NULL) {
        base = SDL_GetBasePath();
        /* SDL_GetBasePath() guarantees a trailing path separator */
        needPathSep = SDL_FALSE;
    }

    if (base != NULL) {
        size_t len = SDL_strlen(base) + SDL_strlen(pathsep) + SDL_strlen(file) + 1;

        path = SDL_malloc(len);

        if (path == NULL) {
            SDL_OutOfMemory();
            return NULL;
        }

        if (needPathSep) {
            SDL_snprintf(path, len, "%s%s%s", base, pathsep, file);
        } else {
            SDL_snprintf(path, len, "%s%s", base, file);
        }

        SDL_free(base);
    } else {
        path = SDL_strdup(file);
        if (path == NULL) {
            SDL_OutOfMemory();
            return NULL;
        }
    }

    return path;
}

static SDLTest_CommonState *state;

typedef struct
{
    const char *name;
    const char *sample;
    const char *reference;
    int w;
    int h;
    int tolerance;
    int initFlag;
    SDL_bool canLoad;
    SDL_bool canSave;
    int (SDLCALL * checkFunction)(SDL_RWops *src);
    SDL_Surface *(SDLCALL * loadFunction)(SDL_RWops *src);
} Format;

static const Format formats[] =
{
    {
        "AVIF",
        "sample.avif",
        "sample.bmp",
        23,
        42,
        300,
        IMG_INIT_AVIF,
#ifdef LOAD_AVIF
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isAVIF,
        IMG_LoadAVIF_RW,
    },
    {
        "BMP",
        "sample.bmp",
        "sample.png",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_BMP
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isBMP,
        IMG_LoadBMP_RW,
    },
    {
        "CUR",
        "sample.cur",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_BMP
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isCUR,
        IMG_LoadCUR_RW,
    },
    {
        "GIF",
        "palette.gif",
        "palette.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#if USING_IMAGEIO || defined(LOAD_GIF)
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isGIF,
        IMG_LoadGIF_RW,
    },
    {
        "ICO",
        "sample.ico",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_BMP
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isICO,
        IMG_LoadICO_RW,
    },
    {
        "JPG",
        "sample.jpg",
        "sample.bmp",
        23,
        42,
        100,
        IMG_INIT_JPG,
#if (USING_IMAGEIO && defined(JPG_USES_IMAGEIO)) || defined(SDL_IMAGE_USE_WIC_BACKEND) || defined(LOAD_JPG)
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_IMAGE_SAVE_JPG,
        IMG_isJPG,
        IMG_LoadJPG_RW,
    },
    {
        "JXL",
        "sample.jxl",
        "sample.bmp",
        23,
        42,
        300,
        IMG_INIT_JXL,
#ifdef LOAD_JXL
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isJXL,
        IMG_LoadJXL_RW,
    },
#if 0
    {
        "LBM",
        "sample.lbm",
        "sample.bmp",
        23,
        42,
        0,              /* lossless? */
        0,              /* no initialization */
#ifdef LOAD_LBM
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isLBM,
        IMG_LoadLBM_RW,
    },
#endif
    {
        "PCX",
        "sample.pcx",
        "sample.bmp",
        23,
        42,
        0,              /* lossless? */
        0,              /* no initialization */
#ifdef LOAD_PCX
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isPCX,
        IMG_LoadPCX_RW,
    },
    {
        "PNG",
        "sample.png",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        IMG_INIT_PNG,
#if (USING_IMAGEIO && defined(PNG_USES_IMAGEIO)) || defined(SDL_IMAGE_USE_WIC_BACKEND) || defined(LOAD_PNG)
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_IMAGE_SAVE_PNG,
        IMG_isPNG,
        IMG_LoadPNG_RW,
    },
    {
        "PNM",
        "sample.pnm",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_PNM
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isPNM,
        IMG_LoadPNM_RW,
    },
    {
        "QOI",
        "sample.qoi",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_QOI
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isQOI,
        IMG_LoadQOI_RW,
    },
    {
        "SVG",
        "svg.svg",
        "svg.bmp",
        32,
        32,
        100,
        0,              /* no initialization */
#ifdef LOAD_SVG
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isSVG,
        IMG_LoadSVG_RW,
    },
    {
        "SVG-sized",
        "svg.svg",
        "svg64.bmp",
        64,
        64,
        100,
        0,              /* no initialization */
#ifdef LOAD_SVG
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isSVG,
        IMG_LoadSVG_RW,
    },
    {
        "SVG-class",
        "svg-class.svg",
        "svg-class.bmp",
        82,
        82,
        0,              /* lossless? */
        0,              /* no initialization */
#ifdef LOAD_SVG
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isSVG,
        IMG_LoadSVG_RW,
    },
    {
        "TGA",
        "sample.tga",
        "sample.bmp",
        23,
        42,
        0,              /* lossless? */
        0,              /* no initialization */
#if USING_IMAGEIO || defined(LOAD_TGA)
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        NULL,
        IMG_LoadTGA_RW,
    },
    {
        "TIF",
        "sample.tif",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        IMG_INIT_TIF,
#if USING_IMAGEIO || defined(SDL_IMAGE_USE_WIC_BACKEND) || defined(LOAD_TIF)
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isTIF,
        IMG_LoadTIF_RW,
    },
    {
        "WEBP",
        "sample.webp",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        IMG_INIT_WEBP,
#ifdef LOAD_WEBP
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isWEBP,
        IMG_LoadWEBP_RW,
    },
    {
        "XCF",
        "sample.xcf",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_XCF
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isXCF,
        IMG_LoadXCF_RW,
    },
    {
        "XPM",
        "sample.xpm",
        "sample.bmp",
        23,
        42,
        0,              /* lossless */
        0,              /* no initialization */
#ifdef LOAD_XPM
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isXPM,
        IMG_LoadXPM_RW,
    },
#if 0
    {
        "XV",
        "sample.xv",
        "sample.bmp",
        23,
        42,
        0,              /* lossless? */
        0,              /* no initialization */
#ifdef LOAD_XV
        SDL_TRUE,
#else
        SDL_FALSE,
#endif
        SDL_FALSE,      /* can save */
        IMG_isXV,
        IMG_LoadXV_RW,
    },
#endif
};

static SDL_bool
StrHasSuffix(const char *str, const char *suffix)
{
    size_t str_len = SDL_strlen(str);
    size_t suffix_len = SDL_strlen(suffix);

    return (str_len >= suffix_len
            && SDL_strcmp(str + (str_len - suffix_len), suffix) == 0);
}

typedef enum
{
    LOAD_CONVENIENCE = 0,
    LOAD_RW,
    LOAD_TYPED_RW,
    LOAD_FORMAT_SPECIFIC,
    LOAD_SIZED
} LoadMode;

/* Convert to RGBA for comparison, if necessary */
static SDL_bool
ConvertToRgba32(SDL_Surface **surface_p)
{
    if ((*surface_p)->format->format != SDL_PIXELFORMAT_RGBA32) {
        SDL_Surface *temp;

        temp = SDL_ConvertSurfaceFormat(*surface_p, SDL_PIXELFORMAT_RGBA32, 0);
        SDLTest_AssertCheck(temp != NULL,
                            "Converting to RGBA should succeed (%s)",
                            SDL_GetError());
        if (temp == NULL) {
            return SDL_FALSE;
        }
        SDL_FreeSurface(*surface_p);
        *surface_p = temp;
    }
    return SDL_TRUE;
}

static void
DumpPixels(const char *filename, SDL_Surface *surface)
{
    const unsigned char *pixels = surface->pixels;
    const unsigned char *p;
    size_t w, h, pitch;
    size_t i, j;

    SDL_Log("%s:\n", filename);

    if (surface->format->palette) {
        size_t n = 0;

        if (surface->format->palette->ncolors >= 0) {
            n = (size_t) surface->format->palette->ncolors;
        }

        SDL_Log("  Palette:\n");
        for (i = 0; i < n; i++) {
            SDL_Log("    RGBA[0x%02x] = %02x%02x%02x%02x\n",
                    (unsigned) i,
                    surface->format->palette->colors[i].r,
                    surface->format->palette->colors[i].g,
                    surface->format->palette->colors[i].b,
                    surface->format->palette->colors[i].a);
        }
    }

    if (surface->w < 0) {
        SDL_Log("    Invalid width %d\n", surface->w);
        return;
    }

    if (surface->h < 0) {
        SDL_Log("    Invalid height %d\n", surface->h);
        return;
    }

    if (surface->pitch < 0) {
        SDL_Log("    Invalid pitch %d\n", surface->pitch);
        return;
    }

    w = (size_t) surface->w;
    h = (size_t) surface->h;
    pitch = (size_t) surface->pitch;

    SDL_Log("  Pixels:\n");

    for (j = 0; j < h; j++) {
        SDL_Log("    ");

        for (i = 0; i < w; i++) {
            p = pixels + (j * pitch) + (i * surface->format->BytesPerPixel);

            switch (surface->format->BitsPerPixel) {
                case 1:
                case 4:
                case 8:
                    SDL_Log("%02x ", *p);
                    break;

                case 12:
                case 15:
                case 16:
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x ", *p);
                    break;

                case 24:
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x ", *p);
                    break;

                case 32:
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x", *p++);
                    SDL_Log("%02x ", *p);
                    break;
            }
        }

        SDL_Log("\n");
    }
}

static void
FormatLoadTest(const Format *format,
               LoadMode mode)
{
    SDL_Surface *reference = NULL;
    SDL_Surface *surface = NULL;
    SDL_RWops *src = NULL;
    char *filename = GetTestFilename(TEST_FILE_DIST, format->sample);
    char *refFilename = GetTestFilename(TEST_FILE_DIST, format->reference);
    int initResult = 0;
    int diff;

    if (!SDLTest_AssertCheck(filename != NULL,
                             "Building filename should succeed (%s)",
                             SDL_GetError())) {
        goto out;
    }
    if (!SDLTest_AssertCheck(refFilename != NULL,
                             "Building ref filename should succeed (%s)",
                             SDL_GetError())) {
        goto out;
    }

    if (StrHasSuffix(format->reference, ".bmp")) {
        reference = SDL_LoadBMP(refFilename);
        if (!SDLTest_AssertCheck(reference != NULL,
                                 "Loading reference should succeed (%s)",
                                 SDL_GetError())) {
            goto out;
        }
    }
    else if (StrHasSuffix (format->reference, ".png")) {
#ifdef LOAD_PNG
        reference = IMG_Load(refFilename);
        if (!SDLTest_AssertCheck(reference != NULL,
                                 "Loading reference should succeed (%s)",
                                 SDL_GetError())) {
            goto out;
        }
#endif
    }

    if (format->initFlag) {
        initResult = IMG_Init(format->initFlag);
        if (!SDLTest_AssertCheck(initResult != 0,
                                 "Initialization should succeed (%s)",
                                 SDL_GetError())) {
            goto out;
        }
        SDLTest_AssertCheck(initResult & format->initFlag,
                            "Expected at least bit 0x%x set, got 0x%x",
                            format->initFlag, initResult);
    }

    if (mode != LOAD_CONVENIENCE) {
        src = SDL_RWFromFile(filename, "rb");
        SDLTest_AssertCheck(src != NULL,
                            "Opening %s should succeed (%s)",
                            filename, SDL_GetError());
        if (src == NULL)
            goto out;
    }

    switch (mode) {
        case LOAD_CONVENIENCE:
            surface = IMG_Load(filename);
            break;

        case LOAD_RW:
            if (format->checkFunction != NULL) {
                SDL_RWops *ref_src;
                int check;

                ref_src = SDL_RWFromFile(refFilename, "rb");
                SDLTest_AssertCheck(ref_src != NULL,
                                    "Opening %s should succeed (%s)",
                                    refFilename, SDL_GetError());
                if (ref_src != NULL) {
                    check = format->checkFunction(ref_src);
                    SDLTest_AssertCheck(!check,
                                        "Should not detect %s as %s -> %d",
                                        refFilename, format->name, check);
                    SDL_RWclose(ref_src);
                }
            }

            if (format->checkFunction != NULL) {
                int check = format->checkFunction(src);

                SDLTest_AssertCheck(check,
                                    "Should detect %s as %s -> %d",
                                    filename, format->name, check);
            }

            surface = IMG_Load_RW(src, SDL_TRUE);
            src = NULL;      /* ownership taken */
            break;

        case LOAD_TYPED_RW:
            surface = IMG_LoadTyped_RW(src, SDL_TRUE, format->name);
            src = NULL;      /* ownership taken */
            break;

        case LOAD_FORMAT_SPECIFIC:
            surface = format->loadFunction(src);
            break;

        case LOAD_SIZED:
            if (SDL_strcmp(format->name, "SVG-sized") == 0) {
                surface = IMG_LoadSizedSVG_RW(src, 64, 64);
            }
            break;
    }

    if (!SDLTest_AssertCheck(surface != NULL,
                             "Load %s (%s)", filename, SDL_GetError())) {
        goto out;
    }

    SDLTest_AssertCheck(surface->w == format->w,
                        "Expected width %d px, got %d",
                        format->w, surface->w);
    SDLTest_AssertCheck(surface->h == format->h,
                        "Expected height %d px, got %d",
                        format->h, surface->h);

    if (GetStringBoolean(SDL_getenv("SDL_IMAGE_TEST_DEBUG"), SDL_FALSE)) {
        DumpPixels(filename, surface);
    }

    if (reference != NULL) {
        ConvertToRgba32(&reference);
        ConvertToRgba32(&surface);
        diff = SDLTest_CompareSurfaces(surface, reference, format->tolerance);
        SDLTest_AssertCheck(diff == 0,
                            "Surface differed from reference by at most %d in %d pixels",
                            format->tolerance, diff);
        if (diff != 0 || GetStringBoolean(SDL_getenv("SDL_IMAGE_TEST_DEBUG"), SDL_FALSE)) {
            DumpPixels(filename, surface);
            DumpPixels(refFilename, reference);
        }
    }

out:
    if (surface != NULL) {
        SDL_FreeSurface(surface);
    }
    if (reference != NULL) {
        SDL_FreeSurface(reference);
    }
    if (src != NULL) {
        SDL_RWclose(src);
    }
    if (refFilename != NULL) {
        SDL_free(refFilename);
    }
    if (filename != NULL) {
        SDL_free(filename);
    }
    if (initResult) {
        IMG_Quit();
    }
}

static void
FormatSaveTest(const Format *format,
               SDL_bool rw)
{
    char *refFilename = GetTestFilename(TEST_FILE_DIST, "sample.bmp");
    char filename[64] = { 0 };
    SDL_Surface *reference = NULL;
    SDL_Surface *surface = NULL;
    SDL_RWops *dest = NULL;
    int initResult = 0;
    int diff;
    int result;

    SDL_snprintf(filename, sizeof(filename),
                 "save%s.%s",
                 rw ? "Rwops" : "",
                 format->name);

    if (!SDLTest_AssertCheck(refFilename != NULL,
                             "Building ref filename should succeed (%s)",
                             SDL_GetError())) {
        goto out;
    }

    reference = SDL_LoadBMP(refFilename);
    if (!SDLTest_AssertCheck(reference != NULL,
                             "Loading reference should succeed (%s)",
                             SDL_GetError())) {
        goto out;
    }

    if (format->initFlag) {
        initResult = IMG_Init(format->initFlag);
        if (!SDLTest_AssertCheck(initResult != 0,
                                 "Initialization should succeed (%s)",
                                 SDL_GetError())) {
            goto out;
        }
        SDLTest_AssertCheck(initResult & format->initFlag,
                            "Expected at least bit 0x%x set, got 0x%x",
                            format->initFlag, initResult);
    }

    if (SDL_strcmp (format->name, "PNG") == 0) {
        if (rw) {
            dest = SDL_RWFromFile(filename, "wb");
            result = IMG_SavePNG_RW(reference, dest, SDL_FALSE);
            SDL_RWclose(dest);
        } else {
            result = IMG_SavePNG(reference, filename);
        }
    } else if (SDL_strcmp(format->name, "JPG") == 0) {
        if (rw) {
            dest = SDL_RWFromFile(filename, "wb");
            result = IMG_SaveJPG_RW(reference, dest, SDL_FALSE, 90);
            SDL_RWclose(dest);
        } else {
            result = IMG_SaveJPG(reference, filename, 90);
        }
    } else {
        SDLTest_AssertCheck(SDL_FALSE, "How do I save %s?", format->name);
        goto out;
    }

    SDLTest_AssertCheck(result == 0, "Save %s (%s)", filename, SDL_GetError());

    if (format->canLoad) {
        surface = IMG_Load(filename);

        if (!SDLTest_AssertCheck(surface != NULL,
                                 "Load %s (%s)", "saved file", SDL_GetError())) {
            goto out;
        }

        ConvertToRgba32(&reference);
        ConvertToRgba32(&surface);

        SDLTest_AssertCheck(surface->w == format->w,
                            "Expected width %d px, got %d",
                            format->w, surface->w);
        SDLTest_AssertCheck(surface->h == format->h,
                            "Expected height %d px, got %d",
                            format->h, surface->h);

        diff = SDLTest_CompareSurfaces(surface, reference, format->tolerance);
        SDLTest_AssertCheck(diff == 0,
                            "Surface differed from reference by at most %d in %d pixels",
                            format->tolerance, diff);
        if (diff != 0 || GetStringBoolean(SDL_getenv("SDL_IMAGE_TEST_DEBUG"), SDL_FALSE)) {
            DumpPixels(filename, surface);
            DumpPixels(refFilename, reference);
        }
    }

out:
    if (surface != NULL) {
        SDL_FreeSurface(surface);
    }
    if (reference != NULL) {
        SDL_FreeSurface(reference);
    }
    if (refFilename != NULL) {
        SDL_free(refFilename);
    }
    if (initResult) {
        IMG_Quit();
    }
}

static void
FormatTest(const Format *format)
{
    SDL_bool forced;
    char envVar[64] = { 0 };

    SDL_snprintf(envVar, sizeof(envVar), "SDL_IMAGE_TEST_REQUIRE_LOAD_%s",
                 format->name);

    forced = GetStringBoolean(SDL_getenv(envVar), SDL_FALSE);
    if (forced) {
        SDLTest_AssertCheck(format->canLoad,
                            "%s loading should be enabled", format->name);
    }

    if (format->canLoad || forced) {
        SDLTest_Log("Testing ability to load format %s", format->name);

        if (SDL_strcmp(format->name, "SVG-sized") == 0) {
            FormatLoadTest(format, LOAD_SIZED);
        } else {
            FormatLoadTest(format, LOAD_CONVENIENCE);

            if (SDL_strcmp(format->name, "TGA") == 0) {
                SDLTest_Log("SKIP: Recognising %s by magic number is not supported", format->name);
            } else {
                FormatLoadTest(format, LOAD_RW);
            }

            FormatLoadTest(format, LOAD_TYPED_RW);

            if (format->loadFunction != NULL) {
                FormatLoadTest(format, LOAD_FORMAT_SPECIFIC);
            }
        }
    } else {
        SDLTest_Log("Format %s is not supported", format->name);
    }

    SDL_snprintf(envVar, sizeof(envVar), "SDL_IMAGE_TEST_REQUIRE_SAVE_%s",
                 format->name);

    forced = GetStringBoolean(SDL_getenv(envVar), SDL_FALSE);
    if (forced) {
        SDLTest_AssertCheck(format->canSave,
                            "%s saving should be enabled", format->name);
    }

    if (format->canSave || forced) {
        SDLTest_Log("Testing ability to save format %s", format->name);
        FormatSaveTest(format, SDL_FALSE);
        FormatSaveTest(format, SDL_TRUE);
    } else {
        SDLTest_Log("Saving format %s is not supported", format->name);
    }
}

static int
TestFormats(void *arg)
{
    size_t i;

    for (i = 0; i < SDL_arraysize(formats); i++) {
        FormatTest(&formats[i]);
    }

    return TEST_COMPLETED;
}

static const SDLTest_TestCaseReference formatsTestCase = {
    TestFormats, "Images", "Load and save various image formats", TEST_ENABLED
};

static const SDLTest_TestCaseReference *testCases[] =  {
    &formatsTestCase,
    NULL
};
static SDLTest_TestSuiteReference testSuite = {
    "img",
    NULL,
    testCases,
    NULL
};
static SDLTest_TestSuiteReference *testSuites[] =  {
    &testSuite,
    NULL
};

/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
    SDLTest_CommonQuit(state);
    exit(rc);
}

int
main(int argc, char *argv[])
{
    int result;
    int testIterations = 1;
    Uint64 userExecKey = 0;
    char *userRunSeed = NULL;
    char *filter = NULL;
    int i, done;
    SDL_Event event;

    /* Initialize test framework */
    state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
    if (!state) {
        return 1;
    }

    /* Parse commandline */
    for (i = 1; i < argc;) {
        int consumed;

        consumed = SDLTest_CommonArg(state, i);
        if (consumed == 0) {
            consumed = -1;
            if (SDL_strcasecmp(argv[i], "--iterations") == 0) {
                if (argv[i + 1]) {
                    testIterations = SDL_atoi(argv[i + 1]);
                    if (testIterations < 1) testIterations = 1;
                    consumed = 2;
                }
            }
            else if (SDL_strcasecmp(argv[i], "--execKey") == 0) {
                if (argv[i + 1]) {
                    SDL_sscanf(argv[i + 1], "%"SDL_PRIu64, &userExecKey);
                    consumed = 2;
                }
            }
            else if (SDL_strcasecmp(argv[i], "--seed") == 0) {
                if (argv[i + 1]) {
                    userRunSeed = SDL_strdup(argv[i + 1]);
                    consumed = 2;
                }
            }
            else if (SDL_strcasecmp(argv[i], "--filter") == 0) {
                if (argv[i + 1]) {
                    filter = SDL_strdup(argv[i + 1]);
                    consumed = 2;
                }
            }
        }
        if (consumed < 0) {

#if SDL_VERSION_ATLEAST(2, 0, 10)
            static const char *options[] = { "[--iterations #]", "[--execKey #]", "[--seed string]", "[--filter suite_name|test_name]", NULL };
            SDLTest_CommonLogUsage(state, argv[0], options);
#else
            SDLTest_CommonUsage(state);
#endif
            quit(1);
        }

        i += consumed;
    }

    /* Initialize common state */
    if (!SDLTest_CommonInit(state)) {
        quit(2);
    }

    /* Create the windows, initialize the renderers */
    for (i = 0; i < state->num_windows; ++i) {
        SDL_Renderer *renderer = state->renderers[i];
        SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
        SDL_RenderClear(renderer);
    }

    /* Call Harness */
    result = SDLTest_RunSuites(testSuites, (const char *)userRunSeed, userExecKey, (const char *)filter, testIterations);

    /* Empty event queue */
    done = 0;
    for (i=0; i<100; i++)  {
      while (SDL_PollEvent(&event)) {
        SDLTest_CommonEvent(state, &event, &done);
      }
      SDL_Delay(10);
    }

    /* Clean up */
    SDL_free(userRunSeed);
    SDL_free(filter);

    /* Shutdown everything */
    quit(result);
    return(result);
}

/* vi: set ts=4 sw=4 expandtab: */
