/*
 * Portions of this file are copyright Rebirth contributors and licensed as
 * described in COPYING.txt.
 * Portions of this file are copyright Parallax Software and licensed
 * according to the Parallax license below.
 * See COPYING.txt for license details.

THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/
/*
 *
 * Protypes for rle functions.
 *
 */

#pragma once

#include "pstypes.h"
#include "gr.h"

#include <cstdint>
#include "dxxsconf.h"
#include "dsx-ns.h"
#include "compiler-poison.h"
#include <iterator>

struct rle_position_t
{
	const uint8_t *src;
	uint8_t *dst;
};

static inline const uint8_t *end(const grs_bitmap &b)
{
	return &b.bm_data[b.bm_h * b.bm_w];
}

static inline uint8_t *end(grs_bitmap &b)
{
	return &b.get_bitmap_data()[b.bm_h * b.bm_w];
}

static inline rle_position_t rle_end(const auto &src, auto &dst)
{
	return {end(src), end(dst)};
}

namespace dcx {
uint8_t *gr_rle_decode(const uint8_t *sb, uint8_t *db, rle_position_t e);
void gr_bitmap_rle_compress(grs_bitmap &bmp);
#if !DXX_USE_OGL
void gr_rle_expand_scanline_masked(uint8_t *dest, const uint8_t *src, uint_fast32_t x1, uint_fast32_t x2);
#endif
void gr_rle_expand_scanline(uint8_t *dest, const uint8_t *src, uint_fast32_t x1, uint_fast32_t x2);
grs_bitmap *_rle_expand_texture(const grs_bitmap &bmp);

[[nodiscard]]
static inline const grs_bitmap *rle_expand_texture(const grs_bitmap &bmp)
{
	if (bmp.get_flag_mask(BM_FLAG_RLE))
		return _rle_expand_texture(bmp);
	return &bmp;
}

void rle_cache_close();
void rle_cache_flush();
void rle_swap_0_255(grs_bitmap &bmp);
void rle_remap(grs_bitmap &bmp, const std::array<color_palette_index, 256> &colormap);
#if !DXX_USE_OGL
#define gr_rle_expand_scanline_generic(C,D,DX,DY,S,X1,X2) gr_rle_expand_scanline_generic(D,DX,DY,S,X1,X2)
void gr_rle_expand_scanline_generic(grs_canvas &, grs_bitmap &dest, int dx, int dy, const ubyte *src, int x1, int x2 );
#endif

class bm_rle_expand_range
{
	uint8_t *iter_dbits;
	uint8_t *const end_dbits;
public:
	bm_rle_expand_range(uint8_t *const i, uint8_t *const e) :
		iter_dbits(i), end_dbits(e)
	{
	}
	template <std::size_t N>
		bm_rle_expand_range(std::array<uint8_t, N> &a) :
			iter_dbits(a.data()), end_dbits(std::next(iter_dbits, N))
	{
	}
	uint8_t *get_begin_dbits() const
	{
		return iter_dbits;
	}
	uint8_t *get_end_dbits() const
	{
		return end_dbits;
	}
	void consume_dbits(const unsigned w)
	{
		iter_dbits += w;
	}
};

class bm_rle_src_stride
{
	/* Width of an individual element of the table of row lengths.  This
	 * is sizeof(uint8_t) if the bitmap is not RLE_BIG, or is
	 * sizeof(uint16_t) otherwise.
	 */
	const unsigned src_bit_stride_size;
	/* Bitmask used for filtering the table load.  To minimize
	 * branching, the code always loads two bytes from the table of row
	 * lengths.  If the bitmap is not RLE_BIG, then `src_bit_load_mask`
	 * will be 0xff and will be used to mask out the high byte from the
	 * load.  Otherwise, `src_bit_load_mask` will be 0xffff and the mask
	 * operation will leave the loaded value unchanged.
	 */
	const unsigned src_bit_load_mask;
protected:
	/* Pointer to the table of row lengths.  The table is uint8_t[] if
	 * the bitmap is not RLE_BIG, or is uint16_t[] otherwise.  The table
	 * is not required to be aligned.
	 */
	const uint8_t *ptr_src_bit_lengths;
	/* Pointer to the table of RLE-encoded bitmap elements.
	 */
	const uint8_t *src_bits;
public:
	bm_rle_src_stride(const grs_bitmap &src, const unsigned rle_big) :
		/* Jump threading should collapse the separate ?: uses */
		src_bit_stride_size{rle_big ? unsigned{sizeof(uint16_t)} : unsigned{sizeof(uint8_t)}},
		src_bit_load_mask{rle_big ? 0xffffu : 0xffu},
		ptr_src_bit_lengths{&src.bm_data[4]},
		src_bits{&ptr_src_bit_lengths[rle_big ? src.bm_h * 2 : src.bm_h]}
	{
	}
	void advance_src_bits();
};

class bm_rle_expand : bm_rle_src_stride
{
	/* Pointer to the first byte that is not part of the table of row
	 * lengths.  When `ptr_src_bit_lengths` == `end_src_bit_lengths`, no
	 * further rows are available.
	 */
	const uint8_t *const end_src_bit_lengths;
	/* Pointer to the first byte that is not part of the bitmap data.
	 * When `end_src_bm` == `src_bits`, no further bitmap elements are
	 * available.
	 */
	const uint8_t *const end_src_bm;
public:
	enum step_result
	{
		/* A row decoded successfully.  Act on the decoded buffer as
		 * necessary, then call this class again.
		 *
		 * This result is returned even if the returned row is the last
		 * row.  The first call after the last row will return
		 * src_exhausted, which is the trigger to stop calling the
		 * class.
		 */
		again,
		/* The source was exhausted before any work was done.  No data
		 * is available.  No further calls should be made with this
		 * source.
		 */
		src_exhausted,
		/* The destination was exhausted before decoding completed.  The
		 * source is left at the beginning of the row which failed to
		 * decode.  The caller may try again with a larger destination
		 * buffer or may abandon the attempt.  Further calls with the
		 * same source and destination will continue to return
		 * `dst_exhausted`.
		 */
		dst_exhausted,
	};
	bm_rle_expand(const grs_bitmap &src) :
		bm_rle_src_stride{src, src.get_flag_mask(BM_FLAG_RLE_BIG)},
		end_src_bit_lengths{src_bits},
		end_src_bm{end(src)}
	{
	}
	/* Decode one row of the bitmap, then return control to the
	 * caller.  If the return value is `again`, then the caller
	 * should perform any per-row processing, then call step()
	 * again.  If the return value is not `again`, then the
	 * destination buffer is undefined and the caller should not
	 * access it or call step().
	 *
	 * Use `const auto &` to ensure that t is only modified by the caller
	 * and that the caller does not accidentally provide an
	 * implementation of `get_begin_dbits` that moves the
	 * destination pointer.
	 */
	step_result step(const auto &t)
	{
		/* Poison the memory first, so that it is undefined even if
		 * the source is exhausted.
		 */
		const auto b = t.get_begin_dbits();
		const auto e = t.get_end_dbits();
		DXX_MAKE_MEM_UNDEFINED(std::span(b, e));
		/* Check for source exhaustion, so that empty bitmaps are
		 * not read at all.  This allows callers to treat
		 * src_exhausted as a definitive end-of-record with no data
		 * available.
		 */
		if (ptr_src_bit_lengths == end_src_bit_lengths)
			return src_exhausted;
		return step_internal(b, e);
	}
	/* Decode until source or destination is exhausted.  The
	 * destination position is updated automatically as each row is
	 * decoded.  There is no opportunity for callers to perform
	 * per-row processing.  Callers should call step() directly if
	 * per-row processing is required.
	 *
	 * Use `auto &&` since some callers may not care about the state of `t`
	 * afterward; `auto &&` lets them pass an anonymous temporary.
	 */
	bool loop(const unsigned bm_w, auto &&t)
	{
		for (;;)
		{
			switch (step(t))
			{
				case again:
					/* Step succeeded.  Notify `t` to update its
					 * dbits position, then loop around.
					 */
					t.consume_dbits(bm_w);
					break;
				case src_exhausted:
					/* Success: source buffer exhausted and no error
					 * conditions detected.
					 */
					return true;
				case dst_exhausted:
					/* Failure: destination buffer too small to hold
					 * expanded source.
					 */
					return false;
			}
		}
	}
private:
	step_result step_internal(uint8_t *begin_dbits, uint8_t *end_dbits);
};

}
