/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

// OpenVMS/__DECCXX PORT NOTE
// --------------------------
// Screen::decodeFrameDelta / decodeFrameDeltaPage (and their wrapped_* workers)
// live in this SEPARATE translation unit, split out of screen.cpp, purely to work
// around the HP C++ Alpha (CXX V7.3-009, on OpenVMS 8.4) back-end.  In screen.cpp
// the delta-page worker
// repeatedly crashed the compiler - the crash walked from pass to pass as the code
// was reshaped (GEM_CX_EXTEND_LIFETIME <-> GEM_FG_FIND_BRANCH), and the module-level
// traceback frame generate_one_instantiation_output_file showed the fault fired
// during screen.cpp's TEMPLATE-INSTANTIATION FLUSH - i.e. it was an interaction
// with the many other template instantiations in that 3780-line file, not the
// routine alone.  Isolating these routines in their own small TU (which instantiates
// no Common containers) lets the back-end emit them.  Keeping this file small also
// makes it fast to recompile under the es40 emulator.  Built /NOOPTIMIZE via an
// explicit rule in DESCRIP.MMS.  See the kyra-vms project memory for the full saga.

#include "engines/kyra/screen.h"

#include "common/endian.h"

namespace Kyra {

// ---------------------------------------------------------------------------
// decodeFrameDelta - unpack a delta-compressed frame into a linear buffer.
// De-templated from the upstream template<bool noXor> (HP C++ crashes emitting
// the instantiations); noXor is a plain runtime argument.
// ---------------------------------------------------------------------------
void Screen::decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor) {
	wrapped_decodeFrameDelta(dst, src, noXor);
}

void Screen::wrapped_decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor) {
	while (1) {
		uint8 code = *src++;
		if (code == 0) {
			uint8 len = *src++;
			code = *src++;
			while (len--) {
				if (noXor)
					*dst++ = code;
				else
					*dst++ ^= code;
			}
		} else if (code & 0x80) {
			code -= 0x80;
			if (code != 0) {
				dst += code;
			} else {
				uint16 subcode = READ_LE_UINT16(src); src += 2;
				if (subcode == 0) {
					break;
				} else if (subcode & 0x8000) {
					subcode -= 0x8000;
					if (subcode & 0x4000) {
						uint16 len = subcode - 0x4000;
						code = *src++;
						while (len--) {
							if (noXor)
								*dst++ = code;
							else
								*dst++ ^= code;
						}
					} else {
						while (subcode--) {
							if (noXor)
								*dst++ = *src++;
							else
								*dst++ ^= *src++;
						}
					}
				} else {
					dst += subcode;
				}
			}
		} else {
			while (code--) {
				if (noXor)
					*dst++ = *src++;
				else
					*dst++ ^= *src++;
			}
		}
	}
}

// ---------------------------------------------------------------------------
// decodeFrameDeltaPage - like decodeFrameDelta but wraps every `pitch` output
// bytes onto the next SCREEN_W-stride row.  Decomposed (state struct + per-op
// step function) to keep each routine simple for the fragile Alpha back-end.
// ---------------------------------------------------------------------------
// NOTE (OpenVMS/__DECCXX): the `pitch` parameter MUST be declared `const int`
// here, exactly as in screen.h.  A top-level const on a parameter is not part
// of the function type per the C++ standard, but HP C++ on Alpha encodes it
// into the mangled name anyway, so declaring it `int` here produced a symbol
// that did not match the `..., const int, bool` reference emitted by
// wsamovie.cpp (%LINK-W-NUDFSYMS).  Keep both spellings in sync.
void Screen::decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch, bool noXor) {
	wrapped_decodeFrameDeltaPage(dst, src, pitch, noXor);
}

namespace {

// All mutable state in one struct so exactly one local has its address taken
// (many address-taken locals tripped GEM_CX_EXTEND_LIFETIME).
struct DeltaPageState {
	uint8 *dst;
	uint8 *dstNext;
	const uint8 *src;
	int count;
	int pitch;
};

// Write `len` copies of the constant `code`, wrapping rows every `pitch` bytes.
void deltaPageFill(DeltaPageState *s, uint8 code, int len, bool noXor) {
	while (len--) {
		if (noXor)
			*s->dst++ = code;
		else
			*s->dst++ ^= code;

		if (++s->count == s->pitch) {
			s->count = 0;
			s->dstNext += Screen::SCREEN_W;
			s->dst = s->dstNext;
		}
	}
}

// Copy `len` bytes from s->src, with the same per-byte pitch wrap.
void deltaPageCopy(DeltaPageState *s, int len, bool noXor) {
	while (len--) {
		if (noXor)
			*s->dst++ = *s->src++;
		else
			*s->dst++ ^= *s->src++;

		if (++s->count == s->pitch) {
			s->count = 0;
			s->dstNext += Screen::SCREEN_W;
			s->dst = s->dstNext;
		}
	}
}

// Skip `n` output bytes, advancing rows as needed.
void deltaPageSkip(DeltaPageState *s, int n) {
	s->dst += n;
	s->count += n;
	while (s->count >= s->pitch) {
		s->count -= s->pitch;
		s->dstNext += Screen::SCREEN_W;
		s->dst = s->dstNext + s->count;
	}
}

// Process ONE opcode.  Returns false at the end-of-stream marker so the caller's
// loop is a plain `while (step()) {}` with no break (a while(1){...break} whose
// only exit is a deeply-nested break ACCVIO'd the Alpha CFG builder).
bool deltaPageStep(DeltaPageState *s, bool noXor) {
	uint8 code = *s->src++;
	if (code == 0) {
		uint8 len = *s->src++;
		code = *s->src++;
		deltaPageFill(s, code, len, noXor);
	} else if (code & 0x80) {
		code -= 0x80;
		if (code != 0) {
			deltaPageSkip(s, code);
		} else {
			uint16 subcode = READ_LE_UINT16(s->src); s->src += 2;
			if (subcode == 0) {
				return false;
			} else if (subcode & 0x8000) {
				subcode -= 0x8000;
				if (subcode & 0x4000) {
					uint16 len = subcode - 0x4000;
					code = *s->src++;
					deltaPageFill(s, code, len, noXor);
				} else {
					deltaPageCopy(s, subcode, noXor);
				}
			} else {
				deltaPageSkip(s, subcode);
			}
		}
	} else {
		deltaPageCopy(s, code, noXor);
	}
	return true;
}

} // End of anonymous namespace

// `const int pitch` to match the screen.h declaration - see the note on
// decodeFrameDeltaPage above.
void Screen::wrapped_decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch, bool noXor) {
	DeltaPageState s;
	s.dst = dst;
	s.dstNext = dst;
	s.src = src;
	s.count = 0;
	s.pitch = pitch;

	while (deltaPageStep(&s, noXor)) {
	}
}

} // End of namespace Kyra
