/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ags/lib/std/algorithm.h"
#include "ags/engine/ac/walk_behind.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/ac/common_defines.h"
#include "ags/engine/ac/room_status.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/gfx/graphics_driver.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/globals.h"

namespace AGS3 {

using namespace AGS::Shared;
using namespace AGS::Engine;

// Generates walk-behinds as separate sprites
void walkbehinds_generate_sprites() {
	const Bitmap *mask = _GP(thisroom).WalkBehindMask.get();
	const Bitmap *bg = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get();

	const int coldepth = bg->GetColorDepth();
	Bitmap wbbmp; // temp buffer
	// Iterate through walk-behinds and generate a texture for each of them
	for (int wb = 1 /* 0 is "no area" */; wb < MAX_WALK_BEHINDS; ++wb) {
		const Rect pos = _G(walkBehindAABB)[wb];
		if (pos.Right > 0) {
			wbbmp.CreateTransparent(pos.GetWidth(), pos.GetHeight(), coldepth);
			// Copy over all solid pixels belonging to this WB area
			const int sx = pos.Left, ex = pos.Right, sy = pos.Top, ey = pos.Bottom;
			for (int y = sy; y <= ey; ++y) {
				const uint8_t *check_line = mask->GetScanLine(y);
				const uint8_t *src_line = bg->GetScanLine(y);
				uint8_t *dst_line = wbbmp.GetScanLineForWriting(y - sy);
				for (int x = sx; x <= ex; ++x) {
					if (check_line[x] != wb) continue;
					switch (coldepth) {
					case 8:
						dst_line[(x - sx)] = src_line[x];
						break;
					case 16:
						reinterpret_cast<uint16_t *>(dst_line)[(x - sx)] =
							reinterpret_cast<const uint16_t *>(src_line)[x];
						break;
					case 32:
						reinterpret_cast<uint32_t *>(dst_line)[(x - sx)] =
							reinterpret_cast<const uint32_t *>(src_line)[x];
						break;
					default: assert(0); break;
					}
				}
			}
			// Add to walk-behinds image list
			add_walkbehind_image(wb, &wbbmp, pos.Left, pos.Top);
		}
	}

	_G(walkBehindsCachedForBgNum) = _GP(play).bg_frame;
}

// Edits the given game object's sprite, cutting out pixels covered by walk-behinds;
// returns whether any pixels were updated;
bool walkbehinds_cropout(Bitmap *sprit, int sprx, int spry, int basel, int zoom) {
	if (_G(noWalkBehindsAtAll))
		return false;

	const int maskcol = sprit->GetMaskColor();
	const int spcoldep = sprit->GetColorDepth();

	bool pixels_changed = false;
	// pass along the sprite's pixels, but skip those that lie outside the mask
	for (int x = MAX(0, 0 - sprx);
		(x < sprit->GetWidth()) && (x + sprx < _GP(thisroom).WalkBehindMask->GetWidth()); ++x) {
		// select the WB column at this x
		const auto &wbcol = _G(walkBehindCols)[x + sprx];
		// skip if no area, or sprite lies outside of all areas in this column
		if ((!wbcol.Exists) ||
			(wbcol.Y2 <= spry) ||
			(wbcol.Y1 > spry + sprit->GetHeight()))
			continue;

		// ensure we only check within the valid areas (between Y1 and Y2)
		// we assume that Y1 and Y2 are always within the mask
		for (int y = MAX(0, wbcol.Y1 - spry);
				(y < sprit->GetHeight()) && (y + spry < wbcol.Y2); ++y) {
			const int wb = _GP(thisroom).WalkBehindMask->GetScanLine(y + spry)[x + sprx];
			if (wb < 1) continue; // "no area"
			if (_G(croom)->walkbehind_base[wb] <= basel) continue;

			pixels_changed = true;
			uint8_t *dst_line = sprit->GetScanLineForWriting(y);
			switch (spcoldep) {
			case 8:
				dst_line[x] = maskcol;
				break;
			case 16:
				reinterpret_cast<uint16_t *>(dst_line)[x] = maskcol;
				break;
			case 32:
				reinterpret_cast<uint32_t *>(dst_line)[x] = maskcol;
				break;
			default:
				assert(0);
				break;
			}
		}
	}
	return pixels_changed;
}

void walkbehinds_recalc() {
	// Reset all data
	_G(walkBehindCols).clear();
	for (int wb = 0; wb < MAX_WALK_BEHINDS; ++wb) {
		_G(walkBehindAABB)[wb] = Rect(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN);
	}
	_G(noWalkBehindsAtAll) = true;

	// Recalculate everything; note that mask is always 8-bit
	const Bitmap *mask = _GP(thisroom).WalkBehindMask.get();
	_G(walkBehindCols).resize(mask->GetWidth());
	for (int col = 0; col < mask->GetWidth(); ++col) {
		auto &wbcol = _G(walkBehindCols)[col];
		for (int y = 0; y < mask->GetHeight(); ++y) {
			int wb = mask->GetScanLine(y)[col];
			// Valid areas start with index 1, 0 = no area
			if ((wb >= 1) && (wb < MAX_WALK_BEHINDS)) {
				if (!wbcol.Exists) {
					wbcol.Y1 = y;
					wbcol.Exists = true;
					_G(noWalkBehindsAtAll) = false;
				}
				wbcol.Y2 = y + 1; // +1 to allow bottom line of screen to work (CHECKME??)
				// resize the bounding rect
				_G(walkBehindAABB)[wb].Left = MIN(col, _G(walkBehindAABB)[wb].Left);
				_G(walkBehindAABB)[wb].Top = MIN(y, _G(walkBehindAABB)[wb].Top);
				_G(walkBehindAABB)[wb].Right = MAX(col, _G(walkBehindAABB)[wb].Right);
				_G(walkBehindAABB)[wb].Bottom = MAX(y, _G(walkBehindAABB)[wb].Bottom);
			}
		}
	}

	if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
		walkbehinds_generate_sprites();
	}
}

} // namespace AGS3
