//-----------------------------------------------------------------------------
// AScript gif module
//-----------------------------------------------------------------------------
#include <ascript.h>

AScript_BeginModule(gif)

AScript_DeclarePrivSymbol(noimage);

AScript_DeclarePrivSymbol(images);

AScript_DeclarePrivSymbol(none);
AScript_DeclarePrivSymbol(keep);
AScript_DeclarePrivSymbol(background);
AScript_DeclarePrivSymbol(previous);

AScript_DeclarePrivSymbol(gif_desc);
AScript_DeclarePrivSymbol(gif_gctl);

AScript_DeclarePrivSymbol(hdr_Signature);
AScript_DeclarePrivSymbol(hdr_Version);

AScript_DeclarePrivSymbol(lsd_LogicalScreenWidth);
AScript_DeclarePrivSymbol(lsd_LogicalScreenHeight);
AScript_DeclarePrivSymbol(lsd_GlobalColorTableFlag);
AScript_DeclarePrivSymbol(lsd_ColorResolution);
AScript_DeclarePrivSymbol(lsd_SortFlag);
AScript_DeclarePrivSymbol(lsd_SizeOfGlobalColorTable);
AScript_DeclarePrivSymbol(lsd_BackgroundColorIndex);
AScript_DeclarePrivSymbol(lsd_BackgroundColor);
AScript_DeclarePrivSymbol(lsd_PixelAspectRatio);

AScript_DeclarePrivSymbol(cmmt_CommentData);

AScript_DeclarePrivSymbol(ptxt_TextGridLeftPosition);
AScript_DeclarePrivSymbol(ptxt_TextGridTopPosition);
AScript_DeclarePrivSymbol(ptxt_TextGridWidth);
AScript_DeclarePrivSymbol(ptxt_TextGridHeight);
AScript_DeclarePrivSymbol(ptxt_CharacterCellWidth);
AScript_DeclarePrivSymbol(ptxt_CharacterCellHeight);
AScript_DeclarePrivSymbol(ptxt_TextForegroundColorIndex);
AScript_DeclarePrivSymbol(ptxt_TextBackgroundColorIndex);
AScript_DeclarePrivSymbol(ptxt_PlainTextData);

AScript_DeclarePrivSymbol(appl_ApplicationIdentifier);
AScript_DeclarePrivSymbol(appl_AuthenticationCode);
AScript_DeclarePrivSymbol(appl_ApplicationData);

AScript_DeclarePrivSymbol(DisposalMethod);
AScript_DeclarePrivSymbol(UserInputFlag);
AScript_DeclarePrivSymbol(TransparentColorFlag);
AScript_DeclarePrivSymbol(DelayTime);
AScript_DeclarePrivSymbol(TransparentColorIndex);

AScript_DeclarePrivSymbol(ImageLeftPosition);
AScript_DeclarePrivSymbol(ImageTopPosition);
AScript_DeclarePrivSymbol(ImageWidth);
AScript_DeclarePrivSymbol(ImageHeight);
AScript_DeclarePrivSymbol(LocalColorTableFlag);
AScript_DeclarePrivSymbol(InterlaceFlag);
AScript_DeclarePrivSymbol(SortFlag);
AScript_DeclarePrivSymbol(SizeOfLocalColorTable);

//-----------------------------------------------------------------------------
// ImageStreamer_GIF
//-----------------------------------------------------------------------------
class ImageStreamer_GIF : public ImageStreamer {
public:
	inline ImageStreamer_GIF() : ImageStreamer("gif") {}
	virtual bool IsResponsible(Signal sig, Stream &stream);
	virtual bool Read(Environment &env, Signal sig, Object_Image *pObjImage, Stream &stream);
	virtual bool Write(Environment &env, Signal sig, Object_Image *pObjImage, Stream &stream);
};

//-----------------------------------------------------------------------------
// GIF
//-----------------------------------------------------------------------------
class GIF {
public:
	enum {
		SEP_ImageDescriptor		= 0x2c,
		SEP_ExtensionIntroducer	= 0x21,
		SEP_Trailer				= 0x3b,
	};
	struct Header {
		char Signature[3];
		char Version[3];
		inline Header() {
			::memcpy(Signature, "GIF", 3);
			::memcpy(Version, "89a", 3);
		}
	};
	struct LogicalScreenDescriptor {
		XPackedUShort_LE(LogicalScreenWidth);
		XPackedUShort_LE(LogicalScreenHeight);
		unsigned char PackedFields;
		unsigned char BackgroundColorIndex;
		unsigned char PixelAspectRatio;
		inline unsigned char GlobalColorTableFlag() const { return (PackedFields >> 7) & 1; }
		inline unsigned char ColorResolution() const { return (PackedFields >> 4) & 7; }
		inline unsigned char SortFlag() const { return (PackedFields >> 3) & 1; }
		inline size_t SizeOfGlobalColorTable() const { return (PackedFields >> 0) & 7; }
	};
	struct ImageDescriptor {
		XPackedUShort_LE(ImageLeftPosition);
		XPackedUShort_LE(ImageTopPosition);
		XPackedUShort_LE(ImageWidth);
		XPackedUShort_LE(ImageHeight);
		unsigned char PackedFields;
		inline ImageDescriptor() {
			XPackUShort(ImageLeftPosition, 0);
			XPackUShort(ImageTopPosition, 0);
			XPackUShort(ImageWidth, 0);
			XPackUShort(ImageHeight, 0);
			PackedFields = 0x00;
		}
		inline unsigned char LocalColorTableFlag() const { return (PackedFields >> 7) & 1; }
		inline unsigned char InterlaceFlag() const { return (PackedFields >> 6) & 1; }
		inline unsigned char SortFlag() const { return (PackedFields >> 5) & 1; }
		inline unsigned char SizeOfLocalColorTable() const { return (PackedFields >> 0) & 7; }
	};
	struct GraphicControlExtension {
		unsigned char BlockSize;
		unsigned char PackedFields;
		XPackedUShort_LE(DelayTime);
		unsigned char TransparentColorIndex;
		enum { Label = 0xf9 };
		inline GraphicControlExtension() {
			BlockSize = 4;
			PackedFields = 0x00;
			XPackUShort(DelayTime, 0);
			TransparentColorIndex = 0;
		}
		inline unsigned char DisposalMethod() const { return (PackedFields >> 2) & 7; }
		inline unsigned char UserInputFlag() const { return (PackedFields >> 1) & 1; }
		inline unsigned char TransparentColorFlag() const { return (PackedFields >> 0) & 1; }
	};
	struct CommentExtension {
		Binary CommentData;
		bool validFlag;
		enum { Label = 0xfe };
		inline CommentExtension() {
			validFlag = false;
		}
	};
	struct PlainTextExtension {
		unsigned char BlockSize;
		XPackedUShort_LE(TextGridLeftPosition);
		XPackedUShort_LE(TextGridTopPosition);
		XPackedUShort_LE(TextGridWidth);
		XPackedUShort_LE(TextGridHeight);
		unsigned char CharacterCellWidth;
		unsigned char CharacterCellHeight;
		unsigned char TextForegroundColorIndex;
		unsigned char TextBackgroundColorIndex;
		Binary PlainTextData;
		bool validFlag;
		enum { Label = 0x01 };
		inline PlainTextExtension() {
			BlockSize = 12;
			XPackUShort(TextGridLeftPosition, 0);
			XPackUShort(TextGridTopPosition, 0);
			XPackUShort(TextGridWidth, 0);
			XPackUShort(TextGridHeight, 0);
			CharacterCellWidth = 0;
			CharacterCellHeight = 0;
			TextForegroundColorIndex = 0;
			TextBackgroundColorIndex = 0;
			validFlag = false;
		}
	};
	struct ApplicationExtension {
		unsigned char BlockSize;
		char ApplicationIdentifier[8];
		char AuthenticationCode[3];
		Binary ApplicationData;
		bool validFlag;
		enum { Label = 0xff };
		inline ApplicationExtension() {
			BlockSize = 11;
			::memset(ApplicationIdentifier, 0x00, 8);
			::memset(AuthenticationCode, 0x00, 3);
			validFlag = false;
		}
	};
	struct Extensions {
		CommentExtension comment;
		PlainTextExtension plainText;
		ApplicationExtension application;
	};
	class ImageDataBlock {
	private:
		int _bitOffset;
		int _bitsRead;
		unsigned char _blockData[256];
	public:
		ImageDataBlock();
		bool ReadCode(Signal sig, Stream &stream, unsigned short &code, int bitsOfCode);
		bool WriteCode(Signal sig, Stream &stream, unsigned short code, int bitsOfCode);
		bool Flush(Signal sig, Stream &stream);
	};
	typedef std::map<Binary, unsigned short> TransMap;
private:
	Header _header;
	LogicalScreenDescriptor _logicalScreenDescriptor;
	Object_Palette *_pObjPaletteGlobal;
	Extensions _exts;
	ValueList _valList;
public:
	GIF();
	~GIF();
	bool Read(Environment &env, Signal sig, Stream &stream,
					Object_Image *pObjImageTgt, Object_Image::Format format);
	bool Write(Environment &env, Signal sig, Stream &stream,
					const Color &colorBackground, unsigned short loopCount);
	bool ReadColorTable(Signal sig, Stream &stream, Object_Palette *pObjPalette);
	bool WriteColorTable(Signal sig, Stream &stream, const Object_Palette *pObjPalette);
	bool ReadDataBlocks(Signal sig, Stream &stream, Binary &binary);
	bool WriteDataBlocks(Signal sig, Stream &stream, const Binary &binary);
	bool SkipImageDescriptor(Signal sig, Stream &stream);
	bool ReadImageDescriptor(Environment &env, Signal sig, Stream &stream,
		const GraphicControlExtension &graphicControl, Object_Image *pObjImage);
	bool WriteGraphicControl(Signal sig, Stream &stream,
									const GraphicControlExtension &graphiControl);
	bool WriteImageDescriptor(Environment &env, Signal sig, Stream &stream,
		const GraphicControlExtension &graphicControl, Object_Image *pObjImage);
	inline Header &GetHeader() { return _header; }
	inline LogicalScreenDescriptor &GetLogicalScreenDescriptor() {
		return _logicalScreenDescriptor;
	}
	inline Object_Palette *GetGlobalPaletteObj() { return _pObjPaletteGlobal; }
	inline Extensions &GetExtensions() { return _exts; }
	inline ValueList &GetList() { return _valList; }
	void AddImage(const Value &value,
			unsigned short imageLeftPosition, unsigned short imageTopPosition,
			unsigned short delayTime, unsigned char disposalMethod);
	static bool ReadBuff(Signal sig, Stream &stream, void *buff, size_t bytes);
	static bool WriteBuff(Signal sig, Stream &stream, const void *buff, size_t bytes);
	static void Dump(unsigned char *data, int bytes);
	static const Symbol *DisposalMethodToSymbol(unsigned char disposalMethod);
	static unsigned char DisposalMethodFromSymbol(Signal sig, const Symbol *pSymbol);
	static ImageDescriptor *GetImageDescriptor(const Object_Image *pObjImage);
	static GraphicControlExtension *GetGraphicControl(const Object_Image *pObjImage);
	static int GetPlausibleBackgroundIndex(Object_Palette *pObjPalette, Object_Image *pObjImage);
};

//-----------------------------------------------------------------------------
// Object_GIF
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(GIF);

class Object_GIF : public Object {
public:
	AScript_DeclareObjectAccessor(GIF)
private:
	GIF _gif;
public:
	inline Object_GIF() : Object(AScript_PrivClass(GIF)) {}
	virtual ~Object_GIF();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
	inline GIF &GetGIF() { return _gif; }
};

//-----------------------------------------------------------------------------
// Object_GraphicControl
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(GraphicControl);

class Object_GraphicControl : public Object {
public:
	AScript_DeclareObjectAccessor(GraphicControl)
private:
	GIF::GraphicControlExtension _gctl;
public:
	inline Object_GraphicControl(const GIF::GraphicControlExtension &gctl) :
					Object(AScript_PrivClass(GraphicControl)), _gctl(gctl) {}
	virtual ~Object_GraphicControl();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
	inline GIF::GraphicControlExtension &GetGraphicControl() { return _gctl; }
};

//-----------------------------------------------------------------------------
// Object_ImageDescriptor
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(ImageDescriptor);

class Object_ImageDescriptor : public Object {
public:
	AScript_DeclareObjectAccessor(ImageDescriptor)
private:
	GIF::ImageDescriptor _desc;
public:
	inline Object_ImageDescriptor(const GIF::ImageDescriptor &desc) :
					Object(AScript_PrivClass(ImageDescriptor)), _desc(desc) {}
	virtual ~Object_ImageDescriptor();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
	inline GIF::ImageDescriptor &GetImageDescriptor() { return _desc; }
};

//-----------------------------------------------------------------------------
// GIF
//-----------------------------------------------------------------------------
GIF::GIF() : _pObjPaletteGlobal(NULL)
{
}

GIF::~GIF()
{
	Object::Delete(_pObjPaletteGlobal);
}

bool GIF::Read(Environment &env, Signal sig, Stream &stream,
					Object_Image *pObjImageTgt, Object_Image::Format format)
{
	if (!ReadBuff(sig, stream, &_header, 6)) return false;
	if (::memcmp(_header.Signature, "GIF", 3) != 0) {
		sig.SetError(ERR_FormatError, "Not a GIF file");
		return false;
	}
	if (::memcmp(_header.Version, "87a", 3) != 0 &&
							::memcmp(_header.Version, "89a", 3) != 0) {
		sig.SetError(ERR_FormatError, "unsupported version of GIF file");
		return false;
	}
	if (!ReadBuff(sig, stream, &_logicalScreenDescriptor, 7)) return false;
	if (_logicalScreenDescriptor.GlobalColorTableFlag()) {
		int nEntries = 1 << (_logicalScreenDescriptor.SizeOfGlobalColorTable() + 1);
		Object::Delete(_pObjPaletteGlobal);
		_pObjPaletteGlobal = new Object_Palette(env, nEntries);
		if (!ReadColorTable(sig, stream, _pObjPaletteGlobal)) return false;
	}
	GetList().clear();
	GraphicControlExtension graphicControl;
	do {
		// set default values
		graphicControl.BlockSize = 4;
		graphicControl.PackedFields = (1 << 2) | (0 << 1) | (0 << 0);
		XPackUShort(graphicControl.DelayTime, 0);
		graphicControl.TransparentColorIndex = 0;
	} while (0);
	for (;;) {
		unsigned char imageSeparator;
		size_t bytesRead = stream.Read(sig, &imageSeparator, 1);
		if (bytesRead < 1) break;
		//::printf("%02x\n", imageSeparator);
		if (imageSeparator == SEP_ImageDescriptor) {
			if (pObjImageTgt != NULL) {
				ReadImageDescriptor(env, sig, stream, graphicControl, pObjImageTgt);
				break;
			} else if (format == Object_Image::FORMAT_None) {
				if (!SkipImageDescriptor(sig, stream)) break;
			} else {
				Object_Image *pObjImage = new Object_Image(env, format);
				if (!ReadImageDescriptor(env, sig, stream, graphicControl, pObjImage)) break;
				Value value(pObjImage);
				GetList().push_back(value);
			}
		} else if (imageSeparator == SEP_ExtensionIntroducer) {
			unsigned char label;
			if (!ReadBuff(sig, stream, &label, 1)) break;
			//::printf("%02x - %02x\n", imageSeparator, label);
			if (label == GraphicControlExtension::Label) {
				if (!ReadBuff(sig, stream, &graphicControl, 5)) break;
				unsigned char blockTerminator;
				if (!ReadBuff(sig, stream, &blockTerminator, 1)) break;
			} else if (label == CommentExtension::Label) {
				_exts.comment.validFlag = true;
				if (!ReadDataBlocks(sig, stream, _exts.comment.CommentData)) break;
			} else if (label == PlainTextExtension::Label) {
				_exts.plainText.validFlag = true;
				if (!ReadBuff(sig, stream, &_exts.plainText, 13)) break;
				if (!ReadDataBlocks(sig, stream, _exts.plainText.PlainTextData)) break;
			} else if (label == ApplicationExtension::Label) {
				_exts.application.validFlag = true;
				if (!ReadBuff(sig, stream, &_exts.application, 12)) break;
				if (!ReadDataBlocks(sig, stream, _exts.application.ApplicationData)) break;
			}
			if (sig.IsSignalled()) break;
		} else if (imageSeparator == SEP_Trailer) {
			//::printf("%02x\n", imageSeparator);
			break;
		} else {
			sig.SetError(ERR_FormatError, "unknown separator %02x", imageSeparator);
			break;
		}
	}
	return !sig.IsSignalled();
}

bool GIF::Write(Environment &env, Signal sig, Stream &stream,
						const Color &colorBackground, unsigned short loopCount)
{
	if (GetList().empty()) {
		sig.SetError(ERR_ValueError, "no image to write");
		return false;
	}
	Object::Delete(_pObjPaletteGlobal);
	_pObjPaletteGlobal = new Object_Palette(env, 256);
	size_t logicalScreenWidth = 0, logicalScreenHeight = 0;
	ValueList &valList = GetList();
	foreach (ValueList, pValue, valList) {
		if (!pValue->IsImage()) continue;
		const Object_Image *pObjImage = pValue->GetImageObj();
		const Object_Palette *pObjPalette = pObjImage->GetPaletteObj();
		if (pObjPalette != NULL && pObjPalette->CountEntries() <= 256) {
			if (_pObjPaletteGlobal->UpdateByPalette(pObjPalette, false)) {
				// nothing to do
			} else if (_pObjPaletteGlobal->Prepare(sig, AScript_Symbol(websafe))) {
				break;
			} else {
				return false;
			}
		} else if (_pObjPaletteGlobal->UpdateByImage(pObjImage, false)) {
			// nothing to do
		} else if (_pObjPaletteGlobal->Prepare(sig, AScript_Symbol(websafe))) {
			break;
		} else {
			return false;
		}
	}
	int backgroundColorIndex = -1;
	unsigned char transparentColorIndex = 0;
	unsigned char transparentColorFlag = 0;
	do {
		size_t idxBlank = _pObjPaletteGlobal->NextBlankIndex();
		if (idxBlank < _pObjPaletteGlobal->CountEntries()) {
			// add an entry for transparent color
			_pObjPaletteGlobal->SetEntry(idxBlank, 128, 128, 128, 0);
			transparentColorIndex = static_cast<unsigned char>(idxBlank);
			transparentColorFlag = 1;
			idxBlank++;
		}
		_pObjPaletteGlobal->Shrink(idxBlank);
	} while (0);
	if (colorBackground.IsValid()) {
		backgroundColorIndex = static_cast<int>(
				_pObjPaletteGlobal->LookupNearest(colorBackground));
	}
	foreach (ValueList, pValue, valList) {
		if (!pValue->IsImage()) continue;
		Object_Image *pObjImage = pValue->GetImageObj();
		if (!pObjImage->CheckValid(sig)) return false;
		pObjImage->SetPaletteObj(Object_Palette::Reference(_pObjPaletteGlobal));
		ImageDescriptor *pImageDescriptor = GetImageDescriptor(pObjImage);
		size_t width = pObjImage->GetWidth() +
						XUnpackUShort(pImageDescriptor->ImageLeftPosition);
		size_t height = pObjImage->GetHeight() +
						XUnpackUShort(pImageDescriptor->ImageTopPosition);
		if (logicalScreenWidth < width) logicalScreenWidth = width;
		if (logicalScreenHeight < height) logicalScreenHeight = height;
		GraphicControlExtension *pGraphicControl = GetGraphicControl(pObjImage);
		pGraphicControl->TransparentColorIndex = transparentColorIndex;
		pGraphicControl->PackedFields |= transparentColorFlag;
		if (backgroundColorIndex < 0) {
			backgroundColorIndex = GetPlausibleBackgroundIndex(
											_pObjPaletteGlobal, pObjImage);
		}
	}
	if (backgroundColorIndex < 0) backgroundColorIndex = 0;
	do {
		unsigned char globalColorTableFlag = 1;
		unsigned char colorResolution = 7;
		unsigned char sortFlag = 0;
		unsigned char sizeOfGlobalColorTable = 0;
		int nEntries = static_cast<int>(_pObjPaletteGlobal->CountEntries());
		for ( ; nEntries > (1 << sizeOfGlobalColorTable); sizeOfGlobalColorTable++) ;
		sizeOfGlobalColorTable--;
		XPackUShort(_logicalScreenDescriptor.LogicalScreenWidth,
						static_cast<unsigned short>(logicalScreenWidth));
		XPackUShort(_logicalScreenDescriptor.LogicalScreenHeight,
						static_cast<unsigned short>(logicalScreenHeight));
		_logicalScreenDescriptor.PackedFields =
				(globalColorTableFlag << 7) | (colorResolution << 4) |
				(sortFlag << 3) | (sizeOfGlobalColorTable << 0);
		_logicalScreenDescriptor.BackgroundColorIndex =
						static_cast<unsigned char>(backgroundColorIndex);
		_logicalScreenDescriptor.PixelAspectRatio = 0;
	} while (0);
	do {
		_exts.application.validFlag = true;
		_exts.application.BlockSize = 11;
		::memcpy(_exts.application.ApplicationIdentifier, "NETSCAPE", 8);
		::memcpy(_exts.application.AuthenticationCode, "2.0", 3);
		unsigned char applicationData[3];
		applicationData[0] = 0x01;
		applicationData[1] = static_cast<unsigned char>(loopCount & 0xff);
		applicationData[2] = static_cast<unsigned char>((loopCount >> 8) & 0xff);
		_exts.application.ApplicationData =
						Binary(reinterpret_cast<char *>(applicationData), 3);
	} while (0);
	// Header
	if (!WriteBuff(sig, stream, &_header, 6)) return false;
	// Logical Screen Descriptor
	if (!WriteBuff(sig, stream, &_logicalScreenDescriptor, 7)) return false;
	// Global Color Table
	if (_logicalScreenDescriptor.GlobalColorTableFlag()) {
		if (!WriteColorTable(sig, stream, _pObjPaletteGlobal)) return false;
	}
	if (_exts.application.validFlag) {
		// Application
		const unsigned char buff[] = {
			SEP_ExtensionIntroducer, ApplicationExtension::Label
		};
		if (!WriteBuff(sig, stream, &buff, 2)) return false;
		if (!WriteBuff(sig, stream, &_exts.application, 12)) return false;
		if (!WriteDataBlocks(sig, stream, _exts.application.ApplicationData)) return false;
	}
	foreach (ValueList, pValue, GetList()) {
		if (!pValue->IsImage()) continue;
		Object_Image *pObjImage = pValue->GetImageObj();
		GraphicControlExtension *pGraphicControl = GetGraphicControl(pObjImage);
		if (!WriteGraphicControl(sig, stream, *pGraphicControl)) return false;
		if (!WriteImageDescriptor(env, sig, stream, *pGraphicControl, pObjImage)) return false;
	}
	do {
		// Trailer
		const unsigned char buff[] = { SEP_Trailer };
		if (!WriteBuff(sig, stream, buff, 1)) return false;
	} while (0);
	return false;
}

bool GIF::ReadDataBlocks(Signal sig, Stream &stream, Binary &binary)
{
	for (;;) {
		unsigned char blockSize;
		if (!ReadBuff(sig, stream, &blockSize, 1)) return false;
		if (blockSize == 0) break;
		char buff[256];
		if (!ReadBuff(sig, stream, buff, blockSize)) return false;
		binary.append(buff, blockSize);
	}
	return true;
}

bool GIF::WriteDataBlocks(Signal sig, Stream &stream, const Binary &binary)
{
	size_t size = binary.size();
	for (size_t offset = 0; offset < size; ) {
		unsigned char blockSize =
			static_cast<unsigned char>((size - offset <= 255)? size - offset : 255);
		if (!WriteBuff(sig, stream, &blockSize, 1)) return false;
		if (!WriteBuff(sig, stream, binary.data() + offset, blockSize)) return false;
		offset += blockSize;
	}
	do {
		unsigned char blockSize = 0x00;
		if (!WriteBuff(sig, stream, &blockSize, 1)) return false;
	} while (0);
	return true;
}

bool GIF::SkipImageDescriptor(Signal sig, Stream &stream)
{
	ImageDescriptor imageDescriptor;
	if (!ReadBuff(sig, stream, &imageDescriptor, 9)) return false;
	if (imageDescriptor.LocalColorTableFlag()) {
		int nEntries = 1 << (imageDescriptor.SizeOfLocalColorTable() + 1);
		stream.Seek(sig, nEntries * 3, Stream::SeekCur);
	}
	unsigned char mininumBitsOfCode;
	if (!ReadBuff(sig, stream, &mininumBitsOfCode, 1)) return false;
	for (;;) {
		unsigned char blockSize;
		if (!ReadBuff(sig, stream, &blockSize, 1)) return false;
		if (blockSize == 0) break;
		if (!stream.Seek(sig, blockSize, Stream::SeekCur)) return false;
	}
	return true;
}

bool GIF::ReadImageDescriptor(Environment &env, Signal sig, Stream &stream,
		const GraphicControlExtension &graphicControl, Object_Image *pObjImage)
{
	ImageDescriptor imageDescriptor;
	if (!ReadBuff(sig, stream, &imageDescriptor, 9)) return false;
	size_t imageWidth = XUnpackUShort(imageDescriptor.ImageWidth);
	size_t imageHeight = XUnpackUShort(imageDescriptor.ImageHeight);
	if (!pObjImage->AllocBuffer(sig, imageWidth, imageHeight, 0xff)) return false;
	Object_Palette *pObjPalette = NULL;
	if (imageDescriptor.LocalColorTableFlag()) {
		size_t nEntries = 1 << (imageDescriptor.SizeOfLocalColorTable() + 1);
		pObjPalette = pObjImage->CreateEmptyPalette(nEntries);
		if (!ReadColorTable(sig, stream, pObjPalette)) return false;
	} else {
		pObjPalette = _pObjPaletteGlobal;
		pObjImage->SetPaletteObj(Object_Palette::Reference(pObjPalette));
	}
	do {
		Value value(new Object_GraphicControl(graphicControl));
		pObjImage->AssignValue(AScript_PrivSymbol(gif_gctl), value, false);
	} while (0);
	do {
		Value value(new Object_ImageDescriptor(imageDescriptor));
		pObjImage->AssignValue(AScript_PrivSymbol(gif_desc), value, false);
	} while (0);
	short transparentColorIndex = graphicControl.TransparentColorIndex;
	if (!graphicControl.TransparentColorFlag() ||
					pObjImage->GetFormat() != Object_Image::FORMAT_RGBA) {
		transparentColorIndex = -1;
	}
	const int maximumBitsOfCode = 12;
	unsigned char mininumBitsOfCode;
	if (!ReadBuff(sig, stream, &mininumBitsOfCode, 1)) return false;
	int bitsOfCode = mininumBitsOfCode + 1;
	if (bitsOfCode > maximumBitsOfCode) {
		sig.SetError(ERR_FormatError, "illegal code size");
		return false;
	}
	bool interlaceFlag = (imageDescriptor.InterlaceFlag() != 0);
	const unsigned short codeInvalid = 0xffff;
	unsigned short codeMaxCeiling = 1 << maximumBitsOfCode;
	unsigned short codeBoundary = 1 << mininumBitsOfCode;
	unsigned short codeClear = codeBoundary;
	unsigned short codeEnd = codeBoundary + 1;
	unsigned short codeMax = codeBoundary + 2;
	unsigned short codeFirst = codeInvalid, codeOld = codeInvalid;
	const size_t bytesCodeTable = codeMaxCeiling * 2;
	const size_t bytesCodeStack = codeMaxCeiling * 2;
	unsigned short *codeTable = new unsigned short [bytesCodeTable];
	unsigned short *codeStack = new unsigned short [bytesCodeStack];
	unsigned short *pCodeStack = codeStack;
	do {
		::memset(codeTable, 0x00, bytesCodeTable);
		for (unsigned short code = 0; code < codeBoundary; code++) {
			codeTable[code * 2 + 1] = code;
		}
	} while (0);
	ImageDataBlock imageDataBlock;
	size_t x = 0, y = 0;
	int iPass = 0;
	unsigned char *dstp = pObjImage->GetPointer(0);
	for (;;) {
		unsigned short code = 0;
		if (pCodeStack > codeStack) {
			pCodeStack--;
			code = *pCodeStack;
		} else {
			if (!imageDataBlock.ReadCode(sig, stream, code, bitsOfCode)) break;
			// LZW (Lempel-Ziv-Welch) decompression algorithm
			if (code == codeClear) {
				//::printf("clear\n");
				unsigned short code = 0;
				::memset(codeTable, 0x00, bytesCodeTable);
				for (unsigned short code = 0; code < codeBoundary; code++) {
					codeTable[code * 2 + 1] = code;
				}
				pCodeStack = codeStack;
				bitsOfCode = mininumBitsOfCode + 1;
				codeMax = codeBoundary + 2;
				codeFirst = codeOld = codeInvalid;
				continue;
			} else if (code == codeEnd) {
				// skip trailing blocks
				for (;;) {
					unsigned char blockSize;
					if (!ReadBuff(sig, stream, &blockSize, 1)) break;
					if (blockSize == 0) break;
					if (!stream.Seek(sig, blockSize, Stream::SeekCur)) break;
				}
				break;
			} else if (codeFirst == codeInvalid) {
				codeFirst = codeOld = code;
			} else {
				unsigned short codeIn = code;
				if (code >= codeMax) {
					*pCodeStack++ = codeFirst;
					code = codeOld;
				}
				while (code >= codeBoundary) {
					*pCodeStack++ = codeTable[code * 2 + 1];
					if (code == codeTable[code * 2 + 0]) {
						sig.SetError(ERR_FormatError, "invalid GIF format");
						goto done;
					}
					code = codeTable[code * 2 + 0];
				}
				codeFirst = codeTable[code * 2 + 1];
				*pCodeStack++ = codeFirst;
				if (codeMax < codeMaxCeiling) {
					codeTable[codeMax * 2 + 0] = codeOld;
					codeTable[codeMax * 2 + 1] = codeFirst;
					codeMax++;
					if (codeMax >= (1 << bitsOfCode) &&
											(1 << bitsOfCode) < codeMaxCeiling) {
						bitsOfCode++;
					}
				}
				codeOld = codeIn;
				if (pCodeStack > codeStack) {
					pCodeStack--;
					code = *pCodeStack;
				}
			}
		}
		const unsigned char *srcp = pObjPalette->GetEntry(code);
		if (x >= imageWidth) {
			x = 0;
			if (interlaceFlag) {
				static const int yStepTbl[] = { 8, 8, 4, 2, };
				y += yStepTbl[iPass];
				if (y >= imageHeight) {
					static const int yTbl[] = { 4, 2, 1, };
					if (iPass >= 3) break;
					y = yTbl[iPass++];
				}
			} else {
				y++;
				if (y >= imageHeight) break;
			}
			dstp = pObjImage->GetPointer(y);
		}
		//::printf("+ %3d,%3d code = %03x\n", x, y, code);
		*(dstp + 0) = *srcp++;
		*(dstp + 1) = *srcp++;
		*(dstp + 2) = *srcp++;
		if (code == transparentColorIndex) *(dstp + 3) = 0x00;
		dstp += pObjImage->GetBytesPerPixel();
		x++;
	}
done:
	delete[] codeTable;
	delete[] codeStack;
	return !sig.IsSignalled();
}

bool GIF::WriteGraphicControl(Signal sig, Stream &stream,
								const GraphicControlExtension &graphicControl)
{
	const unsigned char buff[] = {
		SEP_ExtensionIntroducer, GraphicControlExtension::Label
	};
	if (!WriteBuff(sig, stream, &buff, 2)) return false;
	if (!WriteBuff(sig, stream, &graphicControl, 5)) return false;
	unsigned char blockTerminator = 0x00;
	if (!WriteBuff(sig, stream, &blockTerminator, 1)) return false;
	return true;
}

bool GIF::WriteImageDescriptor(Environment &env, Signal sig, Stream &stream,
		const GraphicControlExtension &graphicControl, Object_Image *pObjImage)
{
	do {
		const unsigned char buff[] = { SEP_ImageDescriptor };
		if (!WriteBuff(sig, stream, buff, 1)) return false;
	} while (0);
	const Object_Palette *pObjPalette = _pObjPaletteGlobal;
	ImageDescriptor &imageDescriptor = *GetImageDescriptor(pObjImage);
	if (!WriteBuff(sig, stream, &imageDescriptor, 9)) return false;
	if (imageDescriptor.LocalColorTableFlag()) {
		if (!WriteColorTable(sig, stream, pObjPalette)) return false;
	}
	unsigned char transparentColorIndex = graphicControl.TransparentColorIndex;
	bool transparentColorFlag = (pObjImage->GetFormat() == Object_Image::FORMAT_RGBA) &&
					(graphicControl.TransparentColorFlag() != 0);
	const int maximumBitsOfCode = 12;
	unsigned char minimumBitsOfCode = 8;
	if (!WriteBuff(sig, stream, &minimumBitsOfCode, 1)) return false;
	Binary word;
	int bitsOfCode = minimumBitsOfCode + 1;
	unsigned short code = 0x0000;
	unsigned short codeBoundary = 1 << minimumBitsOfCode;
	unsigned short codeClear = codeBoundary;
	unsigned short codeEnd = codeBoundary + 1;
	unsigned short codeMax = codeBoundary + 2;
	unsigned short codeMaxCeiling = 1 << maximumBitsOfCode;
	TransMap transMap;
	ImageDataBlock imageDataBlock;
	if (!imageDataBlock.WriteCode(sig, stream, codeClear, bitsOfCode)) return false;
	for (size_t y = 0; y < pObjImage->GetHeight(); y++) {
		const unsigned char *pPixel = pObjImage->GetPointer(y);
		for (size_t x = 0; x < pObjImage->GetWidth();
							x++, pPixel += pObjImage->GetBytesPerPixel()) {
			// LZW (Lempel-Ziv-Welch) compression algorithm
			unsigned char k;
			if (transparentColorFlag && Object_Image::GetPixelA(pPixel) < 128) {
				k = transparentColorIndex;
			} else {
				k = static_cast<unsigned char>(pObjPalette->LookupNearest(pPixel));
			}
			//::printf("- %3d,%3d code = %03x\n", x, y, k);
			if (word.empty()) {
				word = k;
				code = k;
				continue;
			}
			Binary wordK = word + static_cast<char>(k);
			TransMap::iterator iter = transMap.find(wordK);
			if (iter != transMap.end()) {
				word = wordK;
				code = iter->second;
				continue;
			}
			if (!imageDataBlock.WriteCode(sig, stream, code, bitsOfCode)) return false;
			transMap[wordK] = codeMax;
			if (codeMax < (1 << bitsOfCode)) {
				codeMax++;
			} else if ((1 << bitsOfCode) < codeMaxCeiling) {
				codeMax++;
				bitsOfCode++;
			} else {
				if (!imageDataBlock.WriteCode(sig, stream,
										codeClear, bitsOfCode)) return false;
				//::printf("clear\n");
				transMap.clear();
				codeMax = codeBoundary + 2;
				bitsOfCode = minimumBitsOfCode + 1;
			}
			word = k;
			code = k;
		}
	}
	if (!imageDataBlock.WriteCode(sig, stream, code, bitsOfCode)) return false;
	if (!imageDataBlock.WriteCode(sig, stream, codeEnd, bitsOfCode)) return false;
	if (!imageDataBlock.Flush(sig, stream)) return false;
	return true;
}

bool GIF::ReadBuff(Signal sig, Stream &stream, void *buff, size_t bytes)
{
	size_t bytesRead = stream.Read(sig, buff, bytes);
	if (sig.IsSignalled()) return false;
	if (bytesRead < bytes) {
		sig.SetError(ERR_FormatError, "invalid GIF format");
		return false;
	}
	return true;
}

bool GIF::WriteBuff(Signal sig, Stream &stream, const void *buff, size_t bytes)
{
	size_t bytesWritten = stream.Write(sig, buff, bytes);
	return !sig.IsSignalled();
}

bool GIF::ReadColorTable(Signal sig, Stream &stream, Object_Palette *pObjPalette)
{
	unsigned char buff[3];
	size_t nEntries = pObjPalette->CountEntries();
	for (size_t idx = 0; idx < nEntries; idx++) {
		if (!GIF::ReadBuff(sig, stream, buff, 3)) return false;
		pObjPalette->SetEntry(idx, buff[0], buff[1], buff[2]);
	}
	return true;
}

bool GIF::WriteColorTable(Signal sig, Stream &stream, const Object_Palette *pObjPalette)
{
	unsigned char buff[3];
	int nEntries = static_cast<int>(pObjPalette->CountEntries());
	int idx = 0;
	for ( ; idx < nEntries; idx++) {
		const unsigned char *pEntry = pObjPalette->GetEntry(idx);
		buff[0] = *(pEntry + Object_Image::OffsetRed);
		buff[1] = *(pEntry + Object_Image::OffsetGreen);
		buff[2] = *(pEntry + Object_Image::OffsetBlue);
		if (!GIF::WriteBuff(sig, stream, buff, 3)) return false;
	}
	int nBits = 0;
	for ( ; nEntries > (1 << nBits); nBits++) ;
	::memset(buff, 0x00, 3);
	for ( ; idx < (1 << nBits); idx++) {
		if (!GIF::WriteBuff(sig, stream, buff, 3)) return false;
	}
	return true;
}

void GIF::AddImage(const Value &value, unsigned short delayTime,
		unsigned short imageLeftPosition, unsigned short imageTopPosition,
		unsigned char disposalMethod)
{
	Object_Image *pObjImage = dynamic_cast<Object_Image *>(value.GetObject());
	do {
		GIF::GraphicControlExtension graphicControl;
		GIF::GraphicControlExtension graphicControlOrg;
		GIF::GraphicControlExtension *pGraphicControl = GetGraphicControl(pObjImage);
		if (pGraphicControl != NULL) {
			graphicControlOrg = *pGraphicControl;
		}
		graphicControl.PackedFields =
			(disposalMethod << 2) |
			(graphicControlOrg.UserInputFlag() << 1) |
			(graphicControlOrg.TransparentColorFlag() << 0);
		XPackUShort(graphicControl.DelayTime, delayTime);
		graphicControl.TransparentColorIndex = graphicControlOrg.TransparentColorIndex;
		Value value(new Object_GraphicControl(graphicControl));
		pObjImage->AssignValue(AScript_PrivSymbol(gif_gctl), value, false);
	} while (0);
	do {
		GIF::ImageDescriptor imageDescriptor;
		GIF::ImageDescriptor imageDescriptorOrg;
		GIF::ImageDescriptor *pImageDescriptor = GetImageDescriptor(pObjImage);
		if (pImageDescriptor != NULL) {
			imageDescriptorOrg = *pImageDescriptor;
		}
		XPackUShort(imageDescriptor.ImageLeftPosition, imageLeftPosition);
		XPackUShort(imageDescriptor.ImageTopPosition, imageTopPosition);
		XPackUShort(imageDescriptor.ImageWidth,
					static_cast<unsigned short>(pObjImage->GetWidth()));
		XPackUShort(imageDescriptor.ImageHeight,
					static_cast<unsigned short>(pObjImage->GetHeight()));
		imageDescriptor.PackedFields =
			(imageDescriptorOrg.LocalColorTableFlag() << 7) |
			(imageDescriptorOrg.InterlaceFlag() << 6) |
			(imageDescriptorOrg.SortFlag() << 5) |
			(imageDescriptorOrg.SizeOfLocalColorTable() << 0);
		Value value(new Object_ImageDescriptor(imageDescriptor));
		pObjImage->AssignValue(AScript_PrivSymbol(gif_desc), value, false);
	} while (0);
	GetList().push_back(value);
}

void GIF::Dump(unsigned char *data, int bytes)
{
	for (int i = 0; i < bytes; i++) {
		int iCol = i % 16;
		::printf((iCol == 0)? "%02x" :
					(iCol == 15)? " %02x\n" : " %02x", data[i]);
	}
	if (bytes % 16 != 0) ::printf("\n");
}

const Symbol *GIF::DisposalMethodToSymbol(unsigned char disposalMethod)
{
	if (disposalMethod == 0) {
		return AScript_PrivSymbol(none);
	} else if (disposalMethod == 1) {
		return AScript_PrivSymbol(keep);
	} else if (disposalMethod == 2) {
		return AScript_PrivSymbol(background);
	} else if (disposalMethod == 3) {
		return AScript_PrivSymbol(previous);
	} else {
		return AScript_PrivSymbol(none);
	}
}

unsigned char GIF::DisposalMethodFromSymbol(Signal sig, const Symbol *pSymbol)
{
	unsigned char disposalMethod;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(none))) {
		disposalMethod = 0;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(keep))) {
		disposalMethod = 1;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(background))) {
		disposalMethod = 2;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(previous))) {
		disposalMethod = 3;
	} else {
		sig.SetError(ERR_ValueError, "invalid symbol for disposal method: %s",
															pSymbol->GetName());
		return 0;
	}
	return disposalMethod;
}

GIF::GraphicControlExtension *GIF::GetGraphicControl(const Object_Image *pObjImage)
{
	const Value *pValue = pObjImage->LookupValue(
							AScript_PrivSymbol(gif_gctl), false);
	if (pValue == NULL) return NULL;
	return &dynamic_cast<Object_GraphicControl *>(
							pValue->GetObject())->GetGraphicControl();
}

GIF::ImageDescriptor *GIF::GetImageDescriptor(const Object_Image *pObjImage)
{
	const Value *pValue = pObjImage->LookupValue(
							AScript_PrivSymbol(gif_desc), false);
	if (pValue == NULL) return NULL;
	return &dynamic_cast<Object_ImageDescriptor *>(
							pValue->GetObject())->GetImageDescriptor();
}

int GIF::GetPlausibleBackgroundIndex(Object_Palette *pObjPalette, Object_Image *pObjImage)
{
	int histTbl[256];
	::memset(histTbl, 0x00, sizeof(histTbl));
	std::auto_ptr<Object_Image::Scanner> pScanner(pObjImage->CreateScanner());
	size_t iPixelEnd = pScanner->CountPixels() - 1;
	size_t iLineEnd = pScanner->CountLines() - 1;
	for (;;) {
		int idx = static_cast<int>(pObjPalette->LookupNearest(pScanner->GetPointer()));
		histTbl[idx]++;
		if (pScanner->GetPixelIdx() >= iPixelEnd) break;
		pScanner->FwdPixel();
	}
	for (;;) {
		int idx = static_cast<int>(pObjPalette->LookupNearest(pScanner->GetPointer()));
		histTbl[idx]++;
		if (pScanner->GetLineIdx() >= iLineEnd) break;
		pScanner->FwdLine();
	}
	for (;;) {
		int idx = static_cast<int>(pObjPalette->LookupNearest(pScanner->GetPointer()));
		histTbl[idx]++;
		if (pScanner->GetPixelIdx() == 0) break;
		pScanner->BwdPixel();
	}
	for (;;) {
		int idx = static_cast<int>(pObjPalette->LookupNearest(pScanner->GetPointer()));
		histTbl[idx]++;
		if (pScanner->GetLineIdx() == 0) break;
		pScanner->BwdLine();
	}
	int idxMax = 0;
	int histMax = histTbl[0];
	for (int idx = 1; idx < NUMBEROF(histTbl); idx++) {
		if (histMax < histTbl[idx]) {
			idxMax = idx;
			histMax = histTbl[idx];
		}
	}
	return idxMax;
}

//-----------------------------------------------------------------------------
// GIF::ImageDataBlock
//-----------------------------------------------------------------------------
GIF::ImageDataBlock::ImageDataBlock() : _bitOffset(0), _bitsRead(0)
{
	::memset(_blockData, 0x00, 256);
}

bool GIF::ImageDataBlock::ReadCode(Signal sig, Stream &stream,
										unsigned short &code, int bitsOfCode)
{
	for (int bitsAccum = 0; bitsAccum < bitsOfCode; ) {
		int bitsRest = bitsOfCode - bitsAccum;
		if (_bitOffset >= _bitsRead) {
			unsigned char blockSize;
			if (!ReadBuff(sig, stream, &blockSize, 1)) return false;
			if (blockSize == 0) return false;
			if (!ReadBuff(sig, stream, _blockData, blockSize)) return false;
			_bitsRead = blockSize * 8;
			_bitOffset = 0;
		}
		unsigned short ch = static_cast<unsigned short>(_blockData[_bitOffset >> 3]);
		int bitsRight = _bitOffset & 7;
		int bitsLeft = 8 - bitsRight;
		if (bitsRest < bitsLeft) {
			code |= ((ch >> bitsRight) & ((1 << bitsRest) - 1)) << bitsAccum;
			_bitOffset += bitsRest;
			break;
		} else {
			code |= (ch >> bitsRight) << bitsAccum;
			bitsAccum += bitsLeft;
			_bitOffset += bitsLeft;
		}
	}
	return true;
}

bool GIF::ImageDataBlock::WriteCode(Signal sig, Stream &stream,
										unsigned short code, int bitsOfCode)
{
	const int bitsFull = 8 * 255;
	for (int bitsAccum = 0; bitsAccum < bitsOfCode; ) {
		int bitsRest = bitsOfCode - bitsAccum;
		if (_bitOffset >= bitsFull) {
			unsigned char blockSize = 255;
			if (!GIF::WriteBuff(sig, stream, &blockSize, 1)) return false;
			if (!GIF::WriteBuff(sig, stream, _blockData, blockSize)) return false;
			::memset(_blockData, 0x00, 256);
			_bitOffset -= bitsFull;
		}
		int bitsRight = _bitOffset & 7;
		int bitsLeft = 8 - bitsRight;
		_blockData[_bitOffset >> 3] |= static_cast<unsigned char>(code << bitsRight);
		if (bitsRest < bitsLeft) {
			_bitOffset += bitsRest;
			break;
		} else {
			code >>= bitsLeft;
			bitsAccum += bitsLeft;
			_bitOffset += bitsLeft;
		}
	}
	return true;
}

bool GIF::ImageDataBlock::Flush(Signal sig, Stream &stream)
{
	if (_bitOffset > 0) {
		unsigned char blockSize = static_cast<unsigned char>((_bitOffset + 7) / 8);
		if (!GIF::WriteBuff(sig, stream, &blockSize, 1)) return false;
		if (!GIF::WriteBuff(sig, stream, _blockData, blockSize)) return false;
	}
	do {
		unsigned char blockSize = 0x00;
		if (!WriteBuff(sig, stream, &blockSize, 1)) return false;
	} while (0);
	return true;
}

//-----------------------------------------------------------------------------
// Object_GIF
//-----------------------------------------------------------------------------
Object_GIF::~Object_GIF()
{
}

Object *Object_GIF::Clone() const
{
	return NULL;
}

Value Object_GIF::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	GIF::Extensions &exts = _gif.GetExtensions();
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(images))) {
		return Value(new Object_List(env, _gif.GetList()));
	}
	do {
		GIF::Header &hdr = _gif.GetHeader();
		if (pSymbol->IsIdentical(AScript_PrivSymbol(hdr_Signature))) {
			Value value;
			value.InitAsBinary(env, hdr.Signature, sizeof(hdr.Signature));
			return value;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(hdr_Version))) {
			Value value;
			value.InitAsBinary(env, hdr.Version, sizeof(hdr.Version));
			return value;
		}
	} while (0);
	do {
		GIF::LogicalScreenDescriptor &lsd = _gif.GetLogicalScreenDescriptor();
		if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_LogicalScreenWidth))) {
			return Value(XUnpackUShort(lsd.LogicalScreenWidth));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_LogicalScreenHeight))) {
			return Value(XUnpackUShort(lsd.LogicalScreenHeight));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_GlobalColorTableFlag))) {
			return Value(lsd.GlobalColorTableFlag());
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_ColorResolution))) {
			return Value(lsd.ColorResolution());
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_SortFlag))) {
			return Value(lsd.SortFlag());
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_SizeOfGlobalColorTable))) {
			return Value(static_cast<unsigned int>(lsd.SizeOfGlobalColorTable()));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_BackgroundColorIndex))) {
			return Value(lsd.BackgroundColorIndex);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_BackgroundColor))) {
			size_t idx = lsd.BackgroundColorIndex;
			Object_Palette *pObjPalette = _gif.GetGlobalPaletteObj();
			if (pObjPalette == NULL || pObjPalette->CountEntries() < idx) {
				return Value::Null;
			}
			return pObjPalette->GetColorValue(idx);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(lsd_PixelAspectRatio))) {
			return Value(lsd.PixelAspectRatio);
		}
	} while (0);
	if (exts.comment.validFlag) {
		GIF::CommentExtension &cmnt = exts.comment;
		if (pSymbol->IsIdentical(AScript_PrivSymbol(cmmt_CommentData))) {
			Value value;
			value.InitAsBinary(env, cmnt.CommentData);
			return value;
		}
	}
	if (exts.plainText.validFlag) {
		GIF::PlainTextExtension &pltxt = exts.plainText;
		if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextGridLeftPosition))) {
			return Value(XUnpackUShort(pltxt.TextGridLeftPosition));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextGridTopPosition))) {
			return Value(XUnpackUShort(pltxt.TextGridTopPosition));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextGridWidth))) {
			return Value(XUnpackUShort(pltxt.TextGridWidth));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextGridHeight))) {
			return Value(XUnpackUShort(pltxt.TextGridHeight));
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_CharacterCellWidth))) {
			return Value(pltxt.CharacterCellWidth);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_CharacterCellHeight))) {
			return Value(pltxt.CharacterCellHeight);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextForegroundColorIndex))) {
			return Value(pltxt.TextForegroundColorIndex);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_TextBackgroundColorIndex))) {
			return Value(pltxt.TextBackgroundColorIndex);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ptxt_PlainTextData))) {
			Value value;
			value.InitAsBinary(env, pltxt.PlainTextData);
			return value;
		}
	}
	if (exts.application.validFlag) {
		GIF::ApplicationExtension &app = exts.application;
		if (pSymbol->IsIdentical(AScript_PrivSymbol(appl_ApplicationIdentifier))) {
			Value value;
			value.InitAsBinary(env, app.ApplicationIdentifier, sizeof(app.ApplicationIdentifier));
			return value;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(appl_AuthenticationCode))) {
			Value value;
			value.InitAsBinary(env, app.AuthenticationCode, sizeof(app.AuthenticationCode));
			return value;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(appl_ApplicationData))) {
			Value value;
			value.InitAsBinary(env, app.ApplicationData);
			return value;
		}
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_GIF::ToString(Signal sig, bool exprFlag)
{
	String str = "<gif.gif:";
	do {
		char buff[32];
		const ValueList &valList = _gif.GetList();
		::sprintf(buff, "%dimages", valList.size());
		str += buff;
	} while (0);
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_GIF
//-----------------------------------------------------------------------------
// gif.gif#write(stream:stream):reduce
AScript_DeclareMethod(GIF, write)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	SetHelp("Writes a GIF image to a stream.");
}

AScript_ImplementMethod(GIF, write)
{
	GIF &gif = Object_GIF::GetSelfObj(args)->GetGIF();
	Stream &stream = args.GetStream(0);
	unsigned short loopCount = 0;
	if (!gif.Write(env, sig, stream, Color::Null, loopCount)) {
		return Value::Null;
	}
	return args.GetSelf();
}

// gif.gif#addimage(image:image, delayTime:number => 0, 
//	leftPos:number => 0, topPos:number => 0, disposalMethod:symbol => `none):map:reduce
AScript_DeclareMethod(GIF, addimage)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "image", VTYPE_Image);
	DeclareArg(env, "delayTime", VTYPE_Number, OCCUR_Once,
						false, false, new Expr_Value(10));
	DeclareArg(env, "leftPos", VTYPE_Number, OCCUR_Once,
						false, false, new Expr_Value(0));
	DeclareArg(env, "topPos", VTYPE_Number, OCCUR_Once,
						false, false, new Expr_Value(0));
	DeclareArg(env, "disposalMethod", VTYPE_Symbol, OCCUR_Once,
						false, false, new Expr_Symbol(AScript_PrivSymbol(none)));
	SetHelp(
	"Adds an image to GIF information. If you add multiple images, they are to be\n"
	"rendered in sequence like an animation. You can specify the delay time to\n"
	"switch to the next image by delayTime argument in 10 msec step.\n"
	"The offset for rendering is specified by leftPost and topPos.\n"
	"Parameter disposalMethod determine how the image should be treated after\n"
	"the time expires, and it takes one of `none, `keep, `background and `previous.\n");
}

AScript_ImplementMethod(GIF, addimage)
{
	GIF &gif = Object_GIF::GetSelfObj(args)->GetGIF();
	unsigned char disposalMethod = GIF::DisposalMethodFromSymbol(sig, args.GetSymbol(4));
	if (sig.IsSignalled()) return Value::Null;
	gif.AddImage(args.GetValue(0), args.GetUShort(1),
					args.GetUShort(2), args.GetUShort(3), disposalMethod);
	return args.GetSelf();
}

// implementation of class GIF
AScript_ImplementPrivClass(GIF)
{
	AScript_AssignMethod(GIF, write);
	AScript_AssignMethod(GIF, addimage);
}

//-----------------------------------------------------------------------------
// Object_GraphicControl
//-----------------------------------------------------------------------------
Object_GraphicControl::~Object_GraphicControl()
{
}

Object *Object_GraphicControl::Clone() const
{
	return NULL;
}

Value Object_GraphicControl::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(DisposalMethod))) {
		return Value(GIF::DisposalMethodToSymbol(_gctl.DisposalMethod()));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(UserInputFlag))) {
		return Value(_gctl.UserInputFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(TransparentColorFlag))) {
		return Value(_gctl.TransparentColorFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(DelayTime))) {
		return Value(XUnpackUShort(_gctl.DelayTime));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(TransparentColorIndex))) {
		return Value(_gctl.TransparentColorIndex);
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_GraphicControl::ToString(Signal sig, bool exprFlag)
{
	return String("gif:graphiccontrol");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_GraphicControl
//-----------------------------------------------------------------------------
// implementation of class GraphicControl
AScript_ImplementPrivClass(GraphicControl)
{
}

//-----------------------------------------------------------------------------
// Object_ImageDescriptor
//-----------------------------------------------------------------------------
Object_ImageDescriptor::~Object_ImageDescriptor()
{
}

Object *Object_ImageDescriptor::Clone() const
{
	return NULL;
}

Value Object_ImageDescriptor::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(ImageLeftPosition))) {
		return Value(XUnpackUShort(_desc.ImageLeftPosition));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ImageTopPosition))) {
		return Value(XUnpackUShort(_desc.ImageTopPosition));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ImageWidth))) {
		return Value(XUnpackUShort(_desc.ImageWidth));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(ImageHeight))) {
		return Value(XUnpackUShort(_desc.ImageHeight));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(LocalColorTableFlag))) {
		return Value(_desc.LocalColorTableFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(InterlaceFlag))) {
		return Value(_desc.InterlaceFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(SortFlag))) {
		return Value(_desc.SortFlag());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(SizeOfLocalColorTable))) {
		return Value(static_cast<unsigned int>(_desc.SizeOfLocalColorTable()));
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_ImageDescriptor::ToString(Signal sig, bool exprFlag)
{
	return String("gif:imagedescriptor");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_ImageDescriptor
//-----------------------------------------------------------------------------
// implementation of class ImageDescriptor
AScript_ImplementPrivClass(ImageDescriptor)
{
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Image
// These methods are available after importing gif module.
//-----------------------------------------------------------------------------
// image#gifread(stream:stream):reduce
AScript_DeclareMethod(Image, gifread)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	SetHelp("Reads a GIF image from a stream.");
}

AScript_ImplementMethod(Image, gifread)
{
	Object_Image *pSelf = Object_Image::GetSelfObj(args);
	if (!pSelf->CheckEmpty(sig)) return Value::Null;
	Stream &stream = args.GetStream(0);
	if (!GIF().Read(env, sig, stream, pSelf, pSelf->GetFormat())) {
		return Value::Null;
	}
	return args.GetSelf();
}

// image#gifwrite(stream:stream):reduce
AScript_DeclareMethod(Image, gifwrite)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	SetHelp("Writes a GIF image to a stream.");
}

AScript_ImplementMethod(Image, gifwrite)
{
	Object_Image *pSelf = Object_Image::GetSelfObj(args);
	if (!pSelf->CheckValid(sig)) return Value::Null;
	Stream &stream = args.GetStream(0);
	GIF gif;
	gif.AddImage(args.GetSelf(), 0, 0, 0, 1);
	unsigned short loopCount = 0;
	if (!gif.Write(env, sig, stream, Color::Null, loopCount)) {
		return Value::Null;
	}
	return args.GetSelf();
}

//-----------------------------------------------------------------------------
// AScript module functions: gif
//-----------------------------------------------------------------------------
// gif.read(stream:stream, format.symbol => `rgba) {block?}
AScript_DeclareFunction(read)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "format", VTYPE_Symbol, OCCUR_Once,
						false, false, new Expr_Symbol(AScript_Symbol(rgba)));
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Reads a GIF data from a stream and returns an object that contains\n"
	"GIF related information and images of a specified format. format is\n"
	"is `rgb, `rgba or `noimage. If `noimage is specified, only the\n"
	"information data is read");
}

AScript_ImplementFunction(read)
{
	Object_GIF *pObjGIF = new Object_GIF();
	Stream &stream = args.GetStream(0);
	const Symbol *pSymbol = args.GetSymbol(1);
	Object_Image::Format format = Object_Image::FORMAT_None;
	if (!pSymbol->IsIdentical(AScript_PrivSymbol(noimage))) {
		format = Object_Image::SymbolToFormat(sig, pSymbol);
		if (sig.IsSignalled()) return Value::Null;
	}
	if (!pObjGIF->GetGIF().Read(env, sig, stream, NULL, format)) {
		return Value::Null;
	}
	return ReturnValue(env, sig, args, Value(pObjGIF));
}

// gif.gif() {block?}
AScript_DeclareFunction(gif)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp("Returns an empty GIF object.");
}

AScript_ImplementFunction(gif)
{
	Object_GIF *pObjGIF = new Object_GIF();
	return ReturnValue(env, sig, args, Value(pObjGIF));
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(noimage);
	AScript_RealizePrivSymbol(images);
	AScript_RealizePrivSymbol(none);
	AScript_RealizePrivSymbol(keep);
	AScript_RealizePrivSymbol(background);
	AScript_RealizePrivSymbol(previous);
	AScript_RealizePrivSymbolEx(gif_desc,						"gif$desc");
	AScript_RealizePrivSymbolEx(gif_gctl,						"gif$gctl");
	AScript_RealizePrivSymbolEx(hdr_Signature,					"hdr$Signature");
	AScript_RealizePrivSymbolEx(hdr_Version,					"hdr$Version");
	AScript_RealizePrivSymbolEx(lsd_LogicalScreenWidth,			"lsd$LogicalScreenWidth");
	AScript_RealizePrivSymbolEx(lsd_LogicalScreenHeight,		"lsd$LogicalScreenHeight");
	AScript_RealizePrivSymbolEx(lsd_GlobalColorTableFlag,		"lsd$GlobalColorTableFlag");
	AScript_RealizePrivSymbolEx(lsd_ColorResolution,			"lsd$ColorResolution");
	AScript_RealizePrivSymbolEx(lsd_SortFlag,					"lsd$SortFlag");
	AScript_RealizePrivSymbolEx(lsd_SizeOfGlobalColorTable,		"lsd$SizeOfGlobalColorTable");
	AScript_RealizePrivSymbolEx(lsd_BackgroundColorIndex,		"lsd$BackgroundColorIndex");
	AScript_RealizePrivSymbolEx(lsd_BackgroundColor,			"lsd$BackgroundColor");
	AScript_RealizePrivSymbolEx(lsd_PixelAspectRatio,			"lsd$PixelAspectRatio");
	AScript_RealizePrivSymbolEx(cmmt_CommentData,				"cmmt$CommentData");
	AScript_RealizePrivSymbolEx(ptxt_TextGridLeftPosition,		"ptxt$TextGridLeftPosition");
	AScript_RealizePrivSymbolEx(ptxt_TextGridTopPosition,		"ptxt$TextGridTopPosition");
	AScript_RealizePrivSymbolEx(ptxt_TextGridWidth,				"ptxt$TextGridWidth");
	AScript_RealizePrivSymbolEx(ptxt_TextGridHeight,			"ptxt$TextGridHeight");
	AScript_RealizePrivSymbolEx(ptxt_CharacterCellWidth,		"ptxt$CharacterCellWidth");
	AScript_RealizePrivSymbolEx(ptxt_CharacterCellHeight,		"ptxt$CharacterCellHeight");
	AScript_RealizePrivSymbolEx(ptxt_TextForegroundColorIndex,	"ptxt$TextForegroundColorIndex");
	AScript_RealizePrivSymbolEx(ptxt_TextBackgroundColorIndex,	"ptxt$TextBackgroundColorIndex");
	AScript_RealizePrivSymbolEx(ptxt_PlainTextData,				"ptxt$PlainTextData");
	AScript_RealizePrivSymbolEx(appl_ApplicationIdentifier,		"appl$ApplicationIdentifier");
	AScript_RealizePrivSymbolEx(appl_AuthenticationCode,		"appl$AuthenticationCode");
	AScript_RealizePrivSymbolEx(appl_ApplicationData,			"appl$ApplicationData");
	AScript_RealizePrivSymbol(DisposalMethod);
	AScript_RealizePrivSymbol(UserInputFlag);
	AScript_RealizePrivSymbol(TransparentColorFlag);
	AScript_RealizePrivSymbol(DelayTime);
	AScript_RealizePrivSymbol(TransparentColorIndex);
	AScript_RealizePrivSymbol(ImageLeftPosition);
	AScript_RealizePrivSymbol(ImageTopPosition);
	AScript_RealizePrivSymbol(ImageWidth);
	AScript_RealizePrivSymbol(ImageHeight);
	AScript_RealizePrivSymbol(LocalColorTableFlag);
	AScript_RealizePrivSymbol(InterlaceFlag);
	AScript_RealizePrivSymbol(SortFlag);
	AScript_RealizePrivSymbol(SizeOfLocalColorTable);
	AScript_RealizePrivClass(GIF, "gif", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(GraphicControl, "graphiccontrol", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(ImageDescriptor, "imagedescriptor", env.LookupClass(VTYPE_Object));
	// method assignment to image class
	AScript_AssignMethodTo(VTYPE_Image, Image, gifread);
	AScript_AssignMethodTo(VTYPE_Image, Image, gifwrite);
	// function assignment
	AScript_AssignFunction(read);
	AScript_AssignFunction(gif);
	// image streamer registration
	ImageStreamer::Register(new ImageStreamer_GIF());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// ImageStreamer_GIF
//-----------------------------------------------------------------------------
bool ImageStreamer_GIF::IsResponsible(Signal sig, Stream &stream)
{
	if (stream.IsReadable()) {
		char buff[6];
		size_t bytesPeeked = stream.Peek(sig, buff, 6);
		if (sig.IsSignalled()) return false;
		return bytesPeeked == 6 &&
			(::memcmp(buff, "GIF87a", 6) == 0 || ::memcmp(buff, "GIF89a", 6) == 0);
	}
	return stream.HasNameSuffix(".gif");
}

bool ImageStreamer_GIF::Read(Environment &env, Signal sig,
								Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckEmpty(sig)) return false;
	return GIF().Read(env, sig, stream, pObjImage, pObjImage->GetFormat());
}

bool ImageStreamer_GIF::Write(Environment &env, Signal sig,
								Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckValid(sig)) return false;
	GIF gif;
	Value value(Object_Image::Reference(pObjImage));
	gif.AddImage(value, 0, 0, 0, 1);
	unsigned short loopCount = 0;
	return gif.Write(env, sig, stream, Color::Null, loopCount);
}

AScript_EndModule(gif, gif)

AScript_RegisterModule(gif)
