/* 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 "common/config-manager.h"
#include "common/macresman.h"
#include "graphics/surface.h"
#include "graphics/macgui/macwidget.h"
#include "image/bmp.h"
#include "image/jpeg.h"
#include "image/pict.h"
#include "image/png.h"

#include "director/director.h"
#include "director/cast.h"
#include "director/images.h"
#include "director/movie.h"
#include "director/picture.h"
#include "director/score.h"
#include "director/window.h"
#include "director/castmember/bitmap.h"
#include "director/lingo/lingo-the.h"

namespace Director {

BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1)
		: CastMember(cast, castId, stream) {
	_type = kCastBitmap;
	_picture = new Picture();
	_ditheredImg = nullptr;
	_matte = nullptr;
	_noMatte = false;
	_bytes = 0;
	_pitch = 0;
	_flags2 = 0;
	_regX = _regY = 0;
	_clut = CastMemberID(0, 0);
	_ditheredTargetClut = CastMemberID(0, 0);
	_bitsPerPixel = 0;
	_external = false;

	if (debugChannelSet(5, kDebugLoading)) {
		stream.hexdump(stream.size());
	}

	if (version < kFileVer400) {
		_flags1 = flags1;	// region: 0 - auto, 1 - matte, 2 - disabled

		_bytes = stream.readUint16();
		// A little context about how bitmap bounding boxes are stored.
		// In the Director editor, images can be edited on a big scrolling canvas with
		// the image in the middle. _initialRect describes the location on that virtual
		// canvas, with the top-left being the start position of the image.
		// _regX and _regY is the registration offset, in canvas space.
		// This means if a bitmap cast member is placed at (64, 64) on the score, the
		// registration offset of the image is placed at (64, 64).
		// By default the registration offset is the dead centre of the image.
		// _boundingRect I think is used internally by the editor and not elsewhere.
		_initialRect = Movie::readRect(stream);
		_boundingRect = Movie::readRect(stream);
		_regY = stream.readSint16();
		_regX = stream.readSint16();

		if (_bytes & 0x8000) {
			_bitsPerPixel = stream.readUint16();
			int clutId = stream.readSint16();

			if (clutId <= 0) // builtin palette
				_clut = CastMemberID(clutId - 1, -1);
			else
				_clut = CastMemberID(clutId, DEFAULT_CAST_LIB);
		} else {
			_bitsPerPixel = 1;
			_clut = CastMemberID(kClutSystemMac, -1);
		}

		_pitch = _initialRect.width();
		if (_pitch % 16)
			_pitch += 16 - (_initialRect.width() % 16);

		_pitch *= _bitsPerPixel;
		_pitch >>= 3;

	} else if (version >= kFileVer400 && version < kFileVer600) {
		_flags1 = flags1;
		_pitch = stream.readUint16();
		_pitch &= 0x0fff;

		_initialRect = Movie::readRect(stream);
		_boundingRect = Movie::readRect(stream);
		_regY = stream.readUint16();
		_regX = stream.readUint16();

		_bitsPerPixel = 0;

		if (stream.pos() < stream.size()) {
			// castSize is > 22 bytes
			stream.readByte();
			_bitsPerPixel = stream.readByte();
			int clutCastLib = -1;
			if (version >= kFileVer500) {
				clutCastLib = stream.readSint16();
			}
			int clutId = stream.readSint16();

			if (clutId <= 0) // builtin palette
				_clut = CastMemberID(clutId - 1, -1);
			else if (clutId > 0) {
				if (clutCastLib == -1) {
					clutCastLib = _cast->_castLibID;
				}
				_clut = CastMemberID(clutId, clutCastLib);
			}
			if (stream.pos() < stream.size()) {
				// castSize > 28 bytes on D4, > 30 bytes on D5
				stream.readUint16();
				/* uint16 unk1 = */ stream.readUint16();
				stream.readUint16();

				stream.readUint32();
				stream.readUint32();

				_flags2 = stream.readUint16();
			}
		}

		if (_bitsPerPixel == 0)
			_bitsPerPixel = 1;

		int tail = stream.size() - stream.pos();
		if (tail > 0) {
			warning("BUILDBOT: BitmapCastMember: %d bytes left", tail);
			if (debugChannelSet(2, kDebugLoading)) {
				byte buf[256];
				tail = MIN(256, tail);
				stream.read(buf, tail);
				debug("BitmapCastMember: tail");
				Common::hexdump(buf, tail);
			}
		}
	} else {
		warning("STUB: BitmapCastMember::BitmapCastMember(): Bitmaps not yet supported for version %d", version);
	}

	_tag = castTag;
}

BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1)
	: CastMember(cast, castId) {
	_type = kCastBitmap;
	_matte = nullptr;
	_noMatte = false;
	_bytes = 0;
	if (img != nullptr) {
		_picture = new Picture(*img);
	}
	_ditheredImg = nullptr;
	_clut = CastMemberID(0, 0);
	_ditheredTargetClut = CastMemberID(0, 0);
	_initialRect = Common::Rect(0, 0, img->getSurface()->w, img->getSurface()->h);
	_pitch = img->getSurface()->pitch;
	_bitsPerPixel = img->getSurface()->format.bytesPerPixel * 8;
	_regY = img->getSurface()->h / 2;
	_regX = img->getSurface()->w / 2;
	_flags1 = flags1;
	_flags2 = 0;
	_tag = 0;
	_external = false;
}

BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, BitmapCastMember &source)
	: CastMember(cast, castId) {
	_type = kCastBitmap;
	// force a load so we can copy the cast resource information
	source.load();
	_loaded = true;

	_initialRect = source._initialRect;
	_boundingRect = source._boundingRect;
	_children = source._children;

	_picture = source._picture ? new Picture(*source._picture) : nullptr;
	_ditheredImg = nullptr;
	_matte = nullptr;

	_pitch = source._pitch;
	_regX = source._regX;
	_regY = source._regY;
	_flags2 = source._regY;
	_bytes = source._bytes;
	_clut = source._clut;
	_ditheredTargetClut = source._ditheredTargetClut;

	_bitsPerPixel = source._bitsPerPixel;

	_tag = source._tag;
	_noMatte = source._noMatte;
	_external = source._external;

	warning("BitmapCastMember(): Duplicating source %d to target %d! This is unlikely to work properly, as the resource loader is based on the cast ID", source._castId, castId);
}

BitmapCastMember::~BitmapCastMember() {
	delete _picture;

	if (_ditheredImg) {
		_ditheredImg->free();
		delete _ditheredImg;
	}

	if (_matte) {
		_matte->free();
		delete _matte;
	}
}

Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
	if (!_picture) {
		warning("BitmapCastMember::createWidget: No picture");
		return nullptr;
	}

	// skip creating widget when the bbox is not available, maybe we should create it using initialRect
	if (!bbox.width() || !bbox.height())
		return nullptr;

	// Check if we need to dither the image
	int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel;
	int srcBpp = _picture->_surface.format.bytesPerPixel;

	const byte *pal = _picture->_palette;
	bool previouslyDithered = _ditheredImg != nullptr;

	// _ditheredImg should contain a cached copy of the bitmap after any expensive
	// colourspace transformations (e.g. palette remapping or dithering).
	// We also want to make sure that
	if (isModified() || (((srcBpp == 1) || (srcBpp > 1 && dstBpp == 1)) && !previouslyDithered)) {
		if (_ditheredImg) {
			_ditheredImg->free();
			delete _ditheredImg;
			_ditheredImg = nullptr;
			_ditheredTargetClut = CastMemberID(0, 0);
		}

		if (dstBpp == 1) {
			// ScummVM using 8-bit video

			if (srcBpp > 1
			// At least early directors were not remapping 8bpp images. But in case it is
			// needed, here is the code
#if 0
			|| (srcBpp == 1 &&
				memcmp(g_director->_wm->getPalette(), _img->_palette, _img->_paletteSize))
#endif
				) {

				_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, nullptr, 0, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());

				pal = g_director->_wm->getPalette();
			} else if (srcBpp == 1) {
				_ditheredImg = getDitherImg();
			}
		} else {
			// ScummVM using 32-bit video
			//if (srcBpp > 1 && srcBpp != 4) {
				// non-indexed surface, convert to 32-bit
			//	_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, nullptr, 0, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());

			//} else
			if (srcBpp == 1) {
				_ditheredImg = getDitherImg();
			}
		}

		Movie *movie = g_director->getCurrentMovie();
		Score *score = movie->getScore();

		if (_ditheredImg) {
			debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Dithering cast %d from source palette %s to target palette %s", _castId, _clut.asString().c_str(), score->getCurrentPalette().asString().c_str());
		} else if (previouslyDithered) {
			debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Removed dithered image for cast %d, score palette %s matches cast member", _castId, score->getCurrentPalette().asString().c_str());

		}
	}

	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);

	// scale for drawing a different size sprite
	copyStretchImg(
		_ditheredImg ? _ditheredImg : &_picture->_surface,
		widget->getSurface()->surfacePtr(),
		_initialRect,
		bbox,
		pal
	);

	return widget;
}

Graphics::Surface *BitmapCastMember::getDitherImg() {
	Graphics::Surface *dither = nullptr;

	// Convert indexed image to indexed palette
	Movie *movie = g_director->getCurrentMovie();
	Score *score = movie->getScore();
	int targetBpp = g_director->_wm->_pixelformat.bytesPerPixel;

	// Get the current score palette. Note that this is the ID of the palette in the list, not the cast member!
	CastMemberID currentPaletteId = score->getCurrentPalette();
	if (currentPaletteId.isNull())
		currentPaletteId = movie->_defaultPalette;
	PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
	if (!currentPalette) {
		currentPaletteId = CastMemberID(kClutSystemMac, -1);
		currentPalette = g_director->getPalette(currentPaletteId);
	}
	CastMemberID castPaletteId = _clut;
	// It is possible for Director to have saved an invalid ID in _clut;
	// if this is the case, do no dithering.
	if (castPaletteId.isNull())
		castPaletteId = currentPaletteId;

	// Check if the palette is in the middle of a color fade event
	bool isColorCycling = score->isPaletteColorCycling();

	const byte *dstPalette = targetBpp == 1 ? currentPalette->palette : nullptr;
	int dstPaletteCount = targetBpp == 1 ? currentPalette->length : 0;

	// First, check if the palettes are different
	switch (_bitsPerPixel) {
	// 1bpp - this is preconverted to 0x00 and 0xff, change nothing.
	case 1:
		break;
	// 2bpp - convert to nearest using the standard 2-bit palette.
	case 2:
		{
			const PaletteV4 &srcPal = g_director->getLoaded4Palette();
			dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
		}
		break;
	// 4bpp - if using a builtin palette, use one of the corresponding 4-bit ones.
	case 4:
		{
			const auto pals = g_director->getLoaded16Palettes();
			// in D4 you aren't allowed to use custom palettes for 4-bit images, so uh...
			// I guess default to the mac palette?
			CastMemberID palIndex = pals.contains(castPaletteId) ? castPaletteId : CastMemberID(kClutSystemMac, -1);
			const PaletteV4 &srcPal = pals.getVal(palIndex);
			dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
		}
		break;
	// 8bpp - if using a different palette, and we're not doing a color cycling operation, convert using nearest colour matching
	case 8:
		// "break" means falling back to the default of rendering the image with
		// the current 8-bit palette. The below is only about -redithering colours-;
		// i.e. redrawing the picture to use the current palette.
		// Only redither 8-bit images in 8-bit mode if we have the remap palette flag set, or it is external
		if (targetBpp == 1 && !movie->_remapPalettesWhenNeeded && !_external)
			break;
		// If we're in 32-bit mode, and not in puppet palette mode, then "redither" as well.
		if (targetBpp == 4 && score->_puppetPalette && !_external)
			break;
		if (_external || (castPaletteId != currentPaletteId && !isColorCycling)) {
			const auto pals = g_director->getLoadedPalettes();
			CastMemberID palIndex = pals.contains(castPaletteId) ? castPaletteId : CastMemberID(kClutSystemMac, -1);
			const PaletteV4 &srcPal = pals.getVal(palIndex);

			// If it is an external image, use the included palette.
			// For BMP images especially, they'll often have the right colors
			// but in the wrong palette order.
			const byte *palPtr = _external ? _picture->_palette : srcPal.palette;
			int palCount = _external ? _picture->getPaletteCount() : srcPal.length;
			dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, palPtr, palCount, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
		}
		break;
	default:
		break;
	}

	if (dither) {
		// Save the palette ID so we can check if a redraw is required
		_ditheredTargetClut = currentPaletteId;

		if (!_external && targetBpp == 1) {
			// Finally, the first and last colours in the palette are special. No matter what the palette remap
			// does, we need to scrub those to be the same.
			const Graphics::Surface *src = &_picture->_surface;
			for (int y = 0; y < src->h; y++) {
				for (int x = 0; x < src->w; x++) {
					const int test = *(const byte *)src->getBasePtr(x, y);
					if (test == 0 || test == (1 << _bitsPerPixel) - 1) {
						*(byte *)dither->getBasePtr(x, y) = test == 0 ? 0x00 : 0xff;
					}
				}
			}
		}
	}
	return dither;

}

bool BitmapCastMember::isModified() {
	if (CastMember::isModified()) {
		// Let's us use "setChanged" when changing the picture through Lingo
		return true;
	}
	// Check for palette changes.
	// If a bitmap has a custom palette assigned to it, createWidget()
	// will dither the image so that it fits within the current palette.
	// When the score palette changes, we need to flag that the widget needs
	// to be recreated.
	if (!_clut.isNull()) {
		Movie *movie = g_director->getCurrentMovie();
		Score *score = movie->getScore();
		CastMemberID currentPaletteId = score->getCurrentPalette();
		if (currentPaletteId.isNull())
			currentPaletteId = movie->_defaultPalette;
		PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
		if (!currentPalette) {
			currentPaletteId = CastMemberID(kClutSystemMac, -1);
			currentPalette = g_director->getPalette(currentPaletteId);
		}
		CastMemberID castPaletteId = _clut;
		if (castPaletteId.isNull())
			castPaletteId = movie->_defaultPalette;

		return !_ditheredTargetClut.isNull() && _ditheredTargetClut != currentPaletteId;
	}
	return false;
}

void BitmapCastMember::createMatte(Common::Rect &bbox) {
	// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels
	// are transparent
	Graphics::Surface tmp;
	tmp.create(bbox.width(), bbox.height(), g_director->_pixelformat);

	copyStretchImg(
		_ditheredImg ? _ditheredImg : &_picture->_surface,
		&tmp,
		_initialRect,
		bbox
	);

	_noMatte = true;

	// Searching white color in the corners
	uint32 whiteColor = 0;
	bool colorFound = false;

	if (g_director->_pixelformat.bytesPerPixel == 1) {
		for (int y = 0; y < tmp.h; y++) {
			for (int x = 0; x < tmp.w; x++) {
				byte color = *(byte *)tmp.getBasePtr(x, y);

				if (g_director->getPalette()[color * 3 + 0] == 0xff &&
						g_director->getPalette()[color * 3 + 1] == 0xff &&
						g_director->getPalette()[color * 3 + 2] == 0xff) {
					whiteColor = color;
					colorFound = true;
					break;
				}
			}
		}
	} else {
		whiteColor = g_director->_wm->_colorWhite;
		colorFound = true;
	}

	if (!colorFound) {
		debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image");
	} else {
		if (_matte) {
			_matte->free();
			delete _matte;
		}

		Graphics::FloodFill matteFill(&tmp, whiteColor, 0, true);

		for (int yy = 0; yy < tmp.h; yy++) {
			matteFill.addSeed(0, yy);
			matteFill.addSeed(tmp.w - 1, yy);
		}

		for (int xx = 0; xx < tmp.w; xx++) {
			matteFill.addSeed(xx, 0);
			matteFill.addSeed(xx, tmp.h - 1);
		}

		matteFill.fillMask();
		Graphics::Surface *matteSurf = matteFill.getMask();
		// convert the mask to the same surface format used for 1bpp bitmaps.
		// this uses the director palette scheme, so white is 0x00 and black is 0xff.
		_matte = new Graphics::Surface();
		_matte->create(matteSurf->w, matteSurf->h, Graphics::PixelFormat::createFormatCLUT8());
		for (int y = 0; y < matteSurf->h; y++) {
			for (int x = 0; x < matteSurf->w; x++) {
				_matte->setPixel(x, y, matteSurf->getPixel(x, y) ? 0x00 : 0xff);
			}
		}
		_noMatte = false;
	}

	tmp.free();
}

Graphics::Surface *BitmapCastMember::getMatte(Common::Rect &bbox) {
	// Lazy loading of mattes
	if (!_matte && !_noMatte) {
		createMatte(bbox);
	}

	// check for the scale matte
	if (_matte && (_matte->w != bbox.width() || _matte->h != bbox.height())) {
		createMatte(bbox);
	}

	return _matte;
}

Common::String BitmapCastMember::formatInfo() {
	return Common::String::format(
		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, regX: %d, regY: %d, pitch: %d, bitsPerPixel: %d, palette: %s",
		_initialRect.width(), _initialRect.height(),
		_initialRect.left, _initialRect.top,
		_boundingRect.width(), _boundingRect.height(),
		_boundingRect.left, _boundingRect.top,
		getForeColor(), getBackColor(),
		_regX, _regY, _pitch, _bitsPerPixel, _clut.asString().c_str()
	);
}

void BitmapCastMember::load() {
	if (_loaded && !_needsReload)
		return;

	_needsReload = false;

	uint32 tag = _tag;
	uint16 imgId = _castId;
	uint16 realId = 0;

	Image::ImageDecoder *img = nullptr;
	Common::SeekableReadStream *pic = nullptr;

	if (_cast->_version >= kFileVer400) {
		for (auto &it : _children) {
			if (it.tag == MKTAG('B', 'I', 'T', 'D')) {
				imgId = it.index;
				tag = it.tag;

				pic = _cast->getResource(tag, imgId);
				break;
			}
		}

		Common::String imageFilename = _cast->getLinkedPath(_castId);

		if ((pic == nullptr || pic->size() == 0)
				&& !imageFilename.empty()) {
			// image file is linked, load from the filesystem
			Common::Path location = findPath(imageFilename);
			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(location);
			if (file) {
				debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", imageFilename.c_str(), imgId);
				// Detect the filetype. Director will ignore file extensions, as do we.
				Image::ImageDecoder *decoder = nullptr;
				uint32 fileType = file->readUint32BE();
				file->seek(0);

				if ((fileType >> 16) == MKTAG16('B', 'M')) {
					// Windows Bitmap file
					decoder = new Image::BitmapDecoder();
				} else if ((fileType == 0xffd8ffe0) || (fileType == 0xffd8ffe1) || (fileType == 0xffd8ffe2)) {
					// JPEG file
					decoder = new Image::JPEGDecoder();
				} else {
					// Well... Director allowed someone to add it, so it must be a PICT. No further questions!
					decoder = new Image::PICTDecoder();
				}

				bool res = decoder->loadStream(*file);
				delete file;

				if (res) {
					setPicture(*decoder, decoder->hasPalette());
					_external = true;

					const Graphics::Surface *surf = decoder->getSurface();
					if (decoder->hasPalette()) {
						// For BMPs this sometimes gets set to 16 in the cast record,
						// we should go with what the target image has.
						_bitsPerPixel = 8;
					}

					debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %s", imgId, surf->w, surf->h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut.asString().c_str());

					if (ConfMan.getBool("dump_scripts")) {

						Common::String prepend = "stream";
						Common::String filename = Common::String::format("./dumps/%s-%s-%d.png", encodePathForDump(prepend).c_str(), tag2str(tag), imgId);
						Common::DumpFile bitmapFile;

						bitmapFile.open(Common::Path(filename), true);
						Image::writePNG(bitmapFile, *decoder->getSurface(), decoder->getPalette());

						bitmapFile.close();
					}

					delete pic;
					delete decoder;
					_loaded = true;
					return;
				} else {
					delete decoder;
					warning("BUILDBOT: BitmapCastMember::load(): wrong format for external picture '%s'", location.toString(Common::Path::kNativeSeparator).c_str());
				}
			} else {
				warning("BitmapCastMember::load(): cannot open external picture '%s'", location.toString(Common::Path::kNativeSeparator).c_str());
			}
		}
	} else {
		realId = imgId + _cast->_castIDoffset;
		pic = _cast->getResource(tag, realId);
	}

	if (pic == nullptr) {
		warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
		return;
	}

	int w = _initialRect.width();
	int h = _initialRect.height();

	switch (tag) {
	case MKTAG('D', 'I', 'B', ' '):
		debugC(2, kDebugLoading, "****** Loading 'DIB ' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
		img = new DIBDecoder();
		break;

	case MKTAG('B', 'I', 'T', 'D'):
		debugC(2, kDebugLoading, "****** Loading 'BITD' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());

		if (w > 0 && h > 0) {
			if (_cast->_version < kFileVer600) {
				img = new BITDDecoder(w, h, _bitsPerPixel, _pitch, g_director->getPalette(), _cast->_version);
			} else {
				img = new Image::BitmapDecoder();
			}
		} else {
			warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
		}

		break;

	default:
		warning("BitmapCastMember::load(): Unknown Bitmap CastMember Tag: [%d] %s", tag, tag2str(tag));
		break;
	}

	if (!img || !img->loadStream(*pic)) {
		warning("BitmapCastMember::load(): Unable to load id: %d", imgId);
		delete pic;
		delete img;
		return;
	}

	setPicture(*img, true);

	if (ConfMan.getBool("dump_scripts")) {

		Common::String prepend = "stream";
		Common::String filename = Common::String::format("./dumps/%s-%s-%d.png", encodePathForDump(prepend).c_str(), tag2str(tag), imgId);
		Common::DumpFile bitmapFile;

		bitmapFile.open(Common::Path(filename), true);
		Image::writePNG(bitmapFile, *img->getSurface(), img->getPalette());

		bitmapFile.close();
	}

	delete img;
	delete pic;

	debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %s", imgId, w, h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut.asString().c_str());

	_loaded = true;
}

void BitmapCastMember::unload() {
	if (!_loaded)
		return;

	delete _picture;
	_picture = new Picture();

	delete _ditheredImg;
	_ditheredImg = nullptr;

	_loaded = false;
}

PictureReference *BitmapCastMember::getPicture() const {
	auto picture = new PictureReference;

	// Not sure if we can make the assumption that the owning
	// BitmapCastMember will live as long as any reference,
	// so we'll make a copy of the Picture.
	picture->_picture = new Picture(*_picture);

	return picture;
}

void BitmapCastMember::setPicture(PictureReference &picture) {
	delete _picture;
	_picture = new Picture(*picture._picture);

	// Force redither
	delete _ditheredImg;
	_ditheredImg = nullptr;

	// Make sure we get redrawn
	setModified(true);
	// TODO: Should size be adjusted?
}

void BitmapCastMember::setPicture(Image::ImageDecoder &image, bool adjustSize) {
	delete _picture;
	_picture = new Picture(image);
	if (adjustSize) {
		auto surf = image.getSurface();
		_size = surf->pitch * surf->h + _picture->getPaletteSize();
	}
	// Make sure we get redrawn
	setModified(true);
}

Common::Point BitmapCastMember::getRegistrationOffset() {
	return Common::Point(_regX - _initialRect.left, _regY - _initialRect.top);
}

Common::Point BitmapCastMember::getRegistrationOffset(int16 width, int16 height) {
	Common::Point offset = getRegistrationOffset();
	return Common::Point(offset.x * width / MAX((int16)1, _initialRect.width()), offset.y * height / MAX((int16)1, _initialRect.height()));
}

bool BitmapCastMember::hasField(int field) {
	switch (field) {
	case kTheDepth:
	case kTheRegPoint:
	case kThePalette:
	case kThePaletteRef:
	case kThePicture:
		return true;
	default:
		break;
	}
	return CastMember::hasField(field);
}

Datum BitmapCastMember::getField(int field) {
	Datum d;

	switch (field) {
	case kTheDepth:
		d = _bitsPerPixel;
		break;
	case kTheRegPoint:
		d.type = POINT;
		d.u.farr = new FArray;
		d.u.farr->arr.push_back(_regX);
		d.u.farr->arr.push_back(_regY);
		break;
	case kThePalette:
		// D5 and below return an integer for this field
		if (_clut.castLib > 0) {
			d = Datum(_clut.toMultiplex());
		} else {
			d = Datum(_clut.member);
		}
		break;
	case kThePaletteRef:
		if (_clut.castLib > 0) {
			d = _clut;
		} else if (_clut.castLib == -1) {
			switch (_clut.member) {
			case kClutSystemMac:
				d = Datum("systemMac");
				d.type = SYMBOL;
				break;
			case kClutSystemWin:
				d = Datum("systemWinDir4");
				d.type = SYMBOL;
				break;
			case kClutSystemWinD5:
				d = Datum("systemWin");
				d.type = SYMBOL;
				break;
			case kClutGrayscale:
				d = Datum("grayscale");
				d.type = SYMBOL;
				break;
			case kClutMetallic:
				d = Datum("metallic");
				d.type = SYMBOL;
				break;
			case kClutNTSC:
				d = Datum("NTSC");
				d.type = SYMBOL;
				break;
			case kClutPastels:
				d = Datum("pastels");
				d.type = SYMBOL;
				break;
			case kClutRainbow:
				d = Datum("rainbow");
				d.type = SYMBOL;
				break;
			case kClutVivid:
				d = Datum("vivid");
				d.type = SYMBOL;
				break;
			default:
				break;
			}
		}
		break;
	case kThePicture:
		d.type = PICTUREREF;
		d.u.picture = getPicture();
		break;
	default:
		d = CastMember::getField(field);
	}

	return d;
}

bool BitmapCastMember::setField(int field, const Datum &d) {
	switch (field) {
	case kTheDepth:
		warning("BitmapCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->field2str(field), _castId);
		return false;
	case kTheRegPoint:
		if (d.type == POINT || (d.type == ARRAY && d.u.farr->arr.size() >= 2)) {
			Score *score = g_director->getCurrentMovie()->getScore();
			score->invalidateRectsForMember(this);
			_regX = d.u.farr->arr[0].asInt();
			_regY = d.u.farr->arr[1].asInt();
			_modified = true;
		} else {
			warning("BitmapCastMember::setField(): Wrong Datum type %d for kTheRegPoint", d.type);
			return false;
		}
		return true;
	case kThePalette:
		{
			CastMemberID newClut;
			if (d.isCastRef()) {
				newClut = *d.u.cast;
			} else {
				int id = d.asInt();
				if (id > 0) {
					// For palette IDs, D5 and above use multiples of 0x20000 to denote
					// the castLib in the integer representation
					newClut = CastMemberID().fromMultiplex(id);
				} else if (id < 0) {
					// Negative integer refers to one of the builtin palettes
					newClut = CastMemberID(id, -1);
				} else {
					// 0 indicates a fallback to the default palette settings
					newClut = CastMemberID(0, 0);
				}
			}
			if (newClut != _clut) {
				_clut = newClut;
				_modified = true;
			}
			return true;
		}
	case kThePaletteRef:
		{
			CastMemberID newClut = _clut;
			if (d.isCastRef()) {
				newClut = *d.u.cast;
			} else if (d.type == SYMBOL) {
				Common::String name = *d.u.s;
				if (name.equalsIgnoreCase("systemMac")) {
					newClut = CastMemberID(kClutSystemMac, -1);
				} else if (name.equalsIgnoreCase("systemWinDir4")) {
					newClut = CastMemberID(kClutSystemWin, -1);
				} else if (name.equalsIgnoreCase("systemWin")) {
					newClut = CastMemberID(kClutSystemWinD5, -1);
				} else if (name.equalsIgnoreCase("grayscale")) {
					newClut = CastMemberID(kClutGrayscale, -1);
				} else if (name.equalsIgnoreCase("metallic")) {
					newClut = CastMemberID(kClutMetallic, -1);
				} else if (name.equalsIgnoreCase("NTSC")) {
					newClut = CastMemberID(kClutNTSC, -1);
				} else if (name.equalsIgnoreCase("pastels")) {
					newClut = CastMemberID(kClutPastels, -1);
				} else if (name.equalsIgnoreCase("rainbow")) {
					newClut = CastMemberID(kClutRainbow, -1);
				} else if (name.equalsIgnoreCase("vivid")) {
					newClut = CastMemberID(kClutVivid, -1);
				}
			}
			if (newClut != _clut) {
				_clut = newClut;
				_modified = true;
			}
		}
		return true;
	case kThePicture:
		if (d.type == PICTUREREF && d.u.picture != nullptr) {
			setPicture(*d.u.picture);
			// This is a random PICT from somewhere,
			// set the external flag so we remap the palette.
			_external = true;
			// Remove the canvas-space transformation
			_regX -= _initialRect.left;
			_regY -= _initialRect.top;
			_initialRect = Common::Rect(_picture->_surface.w, _picture->_surface.h);
			return true;
		} else {
			warning("BitmapCastMember::setField(): Wrong Datum type %d for kThePicture (or nullptr)", d.type);
		}
		return false;
	default:
		break;
	}

	return CastMember::setField(field, d);
}

} // End of namespace Director
