/*
 * Copyright (C) 2021-2022 libass contributors
 *
 * This file is part of libass.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "ass_compat.h"

#include "checkasm.h"
#include "ass_rasterizer.h"

#define HEIGHT 34
#define STRIDE 96
#define BORD_X 32
#define BORD_Y 1
#define REP_COUNT 8
#define MAX_SEG 8


static void generate_segment(struct segment *line, int tile_size, int y1, int y2)
{
    unsigned flags = rnd();
    line->a = rnd() & 0x3FFFFFFF;
    line->b = rnd() & 0x3FFFFFFF;
    if (flags % 3u != 1)
        line->a |= 0x40000000;
    if (flags % 3u != 2)
        line->b |= 0x40000000;
    int32_t max_ab = FFMAX(line->a, line->b);  // 2^30 <= max_ab < 2^31
    if (flags & 1)
        line->a = -line->a;
    if (flags & 2)
        line->b = -line->b;

    int mask = (64 * tile_size << 16) - 1;
    int x = rnd() & mask, y = rnd() & mask;
    line->c = (line->a * (int64_t) x + line->b * (int64_t) y + (1 << 15)) >> 16;
    // |line->c| <= 2^(tile_order + 7) * max_ab < 2^(tile_order + 38)

#if 0
    line->scale = ((uint64_t) 1 << 60) / max_ab;
#else
    line->scale = (rnd() & 0x1FFFFFFF) | 0x20000000;
    if (line->scale * (uint64_t) max_ab > (uint64_t) 1 << 60)
        line->scale ^= 0x20000000;
#endif

    line->flags = flags & 4 ? SEGFLAG_EXACT_LEFT : 0;  // only left is used
    if (line->a >= 0)
        line->flags ^= SEGFLAG_DN | SEGFLAG_UL_DR;
    if (line->b > 0)
        line->flags ^= SEGFLAG_UL_DR;

    line->x_min = flags & 8 ? 0x1234ABCD : 0;
    line->x_max = 0xDEADC0DE;  // not used

    y1 &= 64 * tile_size - 1;
    y2 &= 64 * tile_size - 1;
    line->y_min = FFMIN(y1, y2 + 1);
    line->y_max = FFMAX(y1, y2 + 1);
}


static void check_fill_solid(FillSolidTileFunc func, const char *name, int tile_size)
{
    ALIGN(uint8_t buf_ref[STRIDE * HEIGHT], 32);
    ALIGN(uint8_t buf_new[STRIDE * HEIGHT], 32);
    declare_func(void,
                 uint8_t *buf, ptrdiff_t stride, int set);

    if (check_func(func, name, tile_size)) {
        for(int set = 0; set <= 1; set++) {
            for (int i = 0; i < sizeof(buf_ref); i++)
                buf_ref[i] = buf_new[i] = rnd();

            call_ref(buf_ref + BORD_Y * STRIDE + BORD_X, STRIDE, set);
            call_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, set);

            if (memcmp(buf_ref, buf_new, sizeof(buf_ref))) {
                fail();
                break;
            }
        }

        bench_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, 1);
    }

    report(name, tile_size);
}

static void check_fill_halfplane(FillHalfplaneTileFunc func, const char *name, int tile_size)
{
    struct segment line;
    ALIGN(uint8_t buf_ref[STRIDE * HEIGHT], 32);
    ALIGN(uint8_t buf_new[STRIDE * HEIGHT], 32);
    declare_func(void,
                 uint8_t *buf, ptrdiff_t stride,
                 int32_t a, int32_t b, int64_t c, int32_t scale);

    if (check_func(func, name, tile_size)) {
        for(int rep = 0; rep < REP_COUNT; rep++) {
            for (int i = 0; i < sizeof(buf_ref); i++)
                buf_ref[i] = buf_new[i] = rnd();

            generate_segment(&line, tile_size, 0, 0);
            call_ref(buf_ref + BORD_Y * STRIDE + BORD_X, STRIDE, line.a, line.b, line.c, line.scale);
            call_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, line.a, line.b, line.c, line.scale);

            if (memcmp(buf_ref, buf_new, sizeof(buf_ref))) {
                fail();
                break;
            }
        }

        bench_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, line.a, line.b, line.c, line.scale);
    }

    report(name, tile_size);
}

static void check_fill_generic(FillGenericTileFunc func, const char *name, int tile_size)
{
    struct segment line[MAX_SEG];
    ALIGN(uint8_t buf_ref[STRIDE * HEIGHT], 32);
    ALIGN(uint8_t buf_new[STRIDE * HEIGHT], 32);
    declare_func(void,
                 uint8_t *buf, ptrdiff_t stride,
                 const struct segment *line, size_t n_lines,
                 int winding);

    if (check_func(func, name, tile_size)) {
        for(int rep = 0; rep < REP_COUNT; rep++) {
            for (int i = 0; i < sizeof(buf_ref); i++)
                buf_ref[i] = buf_new[i] = rnd();

            int n = rnd() % MAX_SEG + 1;
            for (int i = 0; i < n; i++)
                generate_segment(line + i, tile_size, rnd(), rnd());

            int winding = rnd() % 5 - 2;
            call_ref(buf_ref + BORD_Y * STRIDE + BORD_X, STRIDE, line, n, winding);
            call_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, line, n, winding);

            if (memcmp(buf_ref, buf_new, sizeof(buf_ref))) {
                fail();
                break;
            }
        }

        generate_segment(line + 0, tile_size, 3 * 64 + 0, 7 * 64 - 1);
        generate_segment(line + 1, tile_size, 3 * 64 + 5, 7 * 64 - 5);
        generate_segment(line + 2, tile_size, 5 * 64 + 3, 5 * 64 + 9);
        generate_segment(line + 3, tile_size, 5 * 64 + 9, 5 * 64 + 8);
        bench_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, line, 4, 0);
    }

    report(name, tile_size);
}

static void check_merge_tile(MergeTileFunc func, const char *name, int tile_size)
{
    ALIGN(uint8_t src[32 * 32], 32);
    ALIGN(uint8_t buf_ref[STRIDE * HEIGHT], 32);
    ALIGN(uint8_t buf_new[STRIDE * HEIGHT], 32);
    declare_func(void,
                 uint8_t *buf, ptrdiff_t stride, const uint8_t *tile);

    if (check_func(func, name, tile_size)) {
        for (int i = 0; i < sizeof(src); i++)
            src[i] = rnd();
        for (int i = 0; i < sizeof(buf_ref); i++)
            buf_ref[i] = buf_new[i] = rnd();

        call_ref(buf_ref + BORD_Y * STRIDE + BORD_X, STRIDE, src);
        call_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, src);

        if (memcmp(buf_ref, buf_new, sizeof(buf_ref)))
            fail();

        bench_new(buf_new + BORD_Y * STRIDE + BORD_X, STRIDE, src);
    }

    report(name, tile_size);
}


void checkasm_check_rasterizer(unsigned cpu_flag)
{
    BitmapEngine engine[2] = {
        ass_bitmap_engine_init(cpu_flag),
        ass_bitmap_engine_init(cpu_flag | ASS_FLAG_LARGE_TILES)
    };
    for (int i = 0; i < 2; i++) {
        int tile_size = 1 << engine[i].tile_order;
        check_fill_solid(engine[i].fill_solid, "fill_solid_tile%d", tile_size);
        check_fill_halfplane(engine[i].fill_halfplane, "fill_halfplane_tile%d", tile_size);
        check_fill_generic(engine[i].fill_generic, "fill_generic_tile%d", tile_size);
        check_merge_tile(engine[i].merge, "merge_tile%d", tile_size);
    }
}
