/*
 * Copyright © 2009 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Red Hat not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  Red Hat makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, 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.
 */

#ifdef HAVE_CONFIG_H
#include <pixman-config.h>
#endif
#include <stdlib.h>
#include "pixman-private.h"

pixman_implementation_t *
_pixman_implementation_create (pixman_implementation_t *fallback,
			       const pixman_fast_path_t *fast_paths)
{
    pixman_implementation_t *imp;

    assert (fast_paths);

    if ((imp = malloc (sizeof (pixman_implementation_t))))
    {
	pixman_implementation_t *d;

	memset (imp, 0, sizeof *imp);

	imp->fallback = fallback;
	imp->fast_paths = fast_paths;
	
	/* Make sure the whole fallback chain has the right toplevel */
	for (d = imp; d != NULL; d = d->fallback)
	    d->toplevel = imp;
    }

    return imp;
}

#define N_CACHED_FAST_PATHS 8

typedef struct
{
    struct
    {
	pixman_implementation_t *	imp;
	pixman_fast_path_t		fast_path;
    } cache [N_CACHED_FAST_PATHS];
} cache_t;

PIXMAN_DEFINE_THREAD_LOCAL (cache_t, fast_path_cache)

static void
dummy_composite_rect (pixman_implementation_t *imp,
		      pixman_composite_info_t *info)
{
}

void
_pixman_implementation_lookup_composite (pixman_implementation_t  *toplevel,
					 pixman_op_t               op,
					 pixman_format_code_t      src_format,
					 uint32_t                  src_flags,
					 pixman_format_code_t      mask_format,
					 uint32_t                  mask_flags,
					 pixman_format_code_t      dest_format,
					 uint32_t                  dest_flags,
					 pixman_implementation_t **out_imp,
					 pixman_composite_func_t  *out_func)
{
    pixman_implementation_t *imp;
    cache_t *cache;
    int i;

    /* Check cache for fast paths */
    cache = PIXMAN_GET_THREAD_LOCAL (fast_path_cache);

    for (i = 0; i < N_CACHED_FAST_PATHS; ++i)
    {
	const pixman_fast_path_t *info = &(cache->cache[i].fast_path);

	/* Note that we check for equality here, not whether
	 * the cached fast path matches. This is to prevent
	 * us from selecting an overly general fast path
	 * when a more specific one would work.
	 */
	if (info->op == op			&&
	    info->src_format == src_format	&&
	    info->mask_format == mask_format	&&
	    info->dest_format == dest_format	&&
	    info->src_flags == src_flags	&&
	    info->mask_flags == mask_flags	&&
	    info->dest_flags == dest_flags	&&
	    info->func)
	{
	    *out_imp = cache->cache[i].imp;
	    *out_func = cache->cache[i].fast_path.func;

	    goto update_cache;
	}
    }

    for (imp = toplevel; imp != NULL; imp = imp->fallback)
    {
	const pixman_fast_path_t *info = imp->fast_paths;

	while (info->op != PIXMAN_OP_NONE)
	{
	    if ((info->op == op || info->op == PIXMAN_OP_any)		&&
		/* Formats */
		((info->src_format == src_format) ||
		 (info->src_format == PIXMAN_any))			&&
		((info->mask_format == mask_format) ||
		 (info->mask_format == PIXMAN_any))			&&
		((info->dest_format == dest_format) ||
		 (info->dest_format == PIXMAN_any))			&&
		/* Flags */
		(info->src_flags & src_flags) == info->src_flags	&&
		(info->mask_flags & mask_flags) == info->mask_flags	&&
		(info->dest_flags & dest_flags) == info->dest_flags)
	    {
		*out_imp = imp;
		*out_func = info->func;

		/* Set i to the last spot in the cache so that the
		 * move-to-front code below will work
		 */
		i = N_CACHED_FAST_PATHS - 1;

		goto update_cache;
	    }

	    ++info;
	}
    }

    /* We should never reach this point */
    _pixman_log_error (
        FUNC,
        "No composite function found\n"
        "\n"
        "The most likely cause of this is that this system has issues with\n"
        "thread local storage\n");

    *out_imp = NULL;
    *out_func = dummy_composite_rect;
    return;

update_cache:
    if (i)
    {
	while (i--)
	    cache->cache[i + 1] = cache->cache[i];

	cache->cache[0].imp = *out_imp;
	cache->cache[0].fast_path.op = op;
	cache->cache[0].fast_path.src_format = src_format;
	cache->cache[0].fast_path.src_flags = src_flags;
	cache->cache[0].fast_path.mask_format = mask_format;
	cache->cache[0].fast_path.mask_flags = mask_flags;
	cache->cache[0].fast_path.dest_format = dest_format;
	cache->cache[0].fast_path.dest_flags = dest_flags;
	cache->cache[0].fast_path.func = *out_func;
    }
}

static void
dummy_combine (pixman_implementation_t *imp,
	       pixman_op_t              op,
	       uint32_t *               pd,
	       const uint32_t *         ps,
	       const uint32_t *         pm,
	       int                      w)
{
}

pixman_combine_32_func_t
_pixman_implementation_lookup_combiner (pixman_implementation_t *imp,
					pixman_op_t		 op,
					pixman_bool_t		 component_alpha,
					pixman_bool_t		 narrow)
{
    while (imp)
    {
	pixman_combine_32_func_t f = NULL;

	switch ((narrow << 1) | component_alpha)
	{
	case 0: /* not narrow, not component alpha */
	    f = (pixman_combine_32_func_t)imp->combine_float[op];
	    break;
	    
	case 1: /* not narrow, component_alpha */
	    f = (pixman_combine_32_func_t)imp->combine_float_ca[op];
	    break;

	case 2: /* narrow, not component alpha */
	    f = imp->combine_32[op];
	    break;

	case 3: /* narrow, component_alpha */
	    f = imp->combine_32_ca[op];
	    break;
	}

	if (f)
	    return f;

	imp = imp->fallback;
    }

    /* We should never reach this point */
    _pixman_log_error (FUNC, "No known combine function\n");
    return dummy_combine;
}

pixman_bool_t
_pixman_implementation_blt (pixman_implementation_t * imp,
                            uint32_t *                src_bits,
                            uint32_t *                dst_bits,
                            int                       src_stride,
                            int                       dst_stride,
                            int                       src_bpp,
                            int                       dst_bpp,
                            int                       src_x,
                            int                       src_y,
                            int                       dest_x,
                            int                       dest_y,
                            int                       width,
                            int                       height)
{
    while (imp)
    {
	if (imp->blt &&
	    (*imp->blt) (imp, src_bits, dst_bits, src_stride, dst_stride,
			 src_bpp, dst_bpp, src_x, src_y, dest_x, dest_y,
			 width, height))
	{
	    return TRUE;
	}

	imp = imp->fallback;
    }

    return FALSE;
}

pixman_bool_t
_pixman_implementation_fill (pixman_implementation_t *imp,
                             uint32_t *               bits,
                             int                      stride,
                             int                      bpp,
                             int                      x,
                             int                      y,
                             int                      width,
                             int                      height,
                             uint32_t                 filler)
{
    while (imp)
    {
	if (imp->fill &&
	    ((*imp->fill) (imp, bits, stride, bpp, x, y, width, height, filler)))
	{
	    return TRUE;
	}

	imp = imp->fallback;
    }

    return FALSE;
}

static uint32_t *
get_scanline_null (pixman_iter_t *iter, const uint32_t *mask)
{
    return NULL;
}

void
_pixman_implementation_iter_init (pixman_implementation_t *imp,
                                  pixman_iter_t           *iter,
                                  pixman_image_t          *image,
                                  int                      x,
                                  int                      y,
                                  int                      width,
                                  int                      height,
                                  uint8_t                 *buffer,
                                  iter_flags_t             iter_flags,
                                  uint32_t                 image_flags)
{
    pixman_format_code_t format;

    iter->image = image;
    iter->buffer = (uint32_t *)buffer;
    iter->x = x;
    iter->y = y;
    iter->width = width;
    iter->height = height;
    iter->iter_flags = iter_flags;
    iter->image_flags = image_flags;
    iter->fini = NULL;

    if (!iter->image)
    {
	iter->get_scanline = get_scanline_null;
	return;
    }

    format = iter->image->common.extended_format_code;

    while (imp)
    {
        if (imp->iter_info)
        {
            const pixman_iter_info_t *info;

            for (info = imp->iter_info; info->format != PIXMAN_null; ++info)
            {
                if ((info->format == PIXMAN_any || info->format == format) &&
                    (info->image_flags & image_flags) == info->image_flags &&
                    (info->iter_flags & iter_flags) == info->iter_flags)
                {
                    iter->get_scanline = info->get_scanline;
                    iter->write_back = info->write_back;

                    if (info->initializer)
                        info->initializer (iter, info);
                    return;
                }
            }
        }

        imp = imp->fallback;
    }
}

pixman_bool_t
_pixman_disabled (const char *name)
{
    const char *env;

    if ((env = getenv ("PIXMAN_DISABLE")))
    {
	do
	{
	    const char *end;
	    int len;

	    if ((end = strchr (env, ' ')))
		len = end - env;
	    else
		len = strlen (env);

	    if (strlen (name) == len && strncmp (name, env, len) == 0)
	    {
		printf ("pixman: Disabled %s implementation\n", name);
		return TRUE;
	    }

	    env += len;
	}
	while (*env++);
    }

    return FALSE;
}

static const pixman_fast_path_t empty_fast_path[] =
{
    { PIXMAN_OP_NONE }
};

pixman_implementation_t *
_pixman_choose_implementation (void)
{
    pixman_implementation_t *imp;

    imp = _pixman_implementation_create_general();

    if (!_pixman_disabled ("fast"))
	imp = _pixman_implementation_create_fast_path (imp);

    imp = _pixman_x86_get_implementations (imp);
    imp = _pixman_arm_get_implementations (imp);
    imp = _pixman_ppc_get_implementations (imp);
    imp = _pixman_mips_get_implementations (imp);
    imp = _pixman_riscv_get_implementations (imp);

    imp = _pixman_implementation_create_noop (imp);

    if (_pixman_disabled ("wholeops"))
    {
        pixman_implementation_t *cur;

        /* Disable all whole-operation paths except the general one,
         * so that optimized iterators are used as much as possible.
         */
        for (cur = imp; cur->fallback; cur = cur->fallback)
            cur->fast_paths = empty_fast_path;
    }

    return imp;
}
