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

AScript_BeginModule(bmp)

//-----------------------------------------------------------------------------
// ImageStreamer_BMP
//-----------------------------------------------------------------------------
class ImageStreamer_BMP : public ImageStreamer {
public:
	inline ImageStreamer_BMP() : ImageStreamer("bmp") {}
	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);
public:
	static bool ReadStream(Signal sig, Object_Image *pObjImage, Stream &stream);
	static bool WriteStream(Signal sig, Object_Image *pObjImage, Stream &stream);
	static void SetError_InvalidBMPFormat(Signal sig);
};

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

AScript_ImplementMethod(Image, bmpread)
{
	Object_Image *pSelf = Object_Image::GetSelfObj(args);
	if (!ImageStreamer_BMP::ReadStream(sig, pSelf, args.GetStream(0))) return Value::Null;
	return args.GetSelf();
}

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

AScript_ImplementMethod(Image, bmpwrite)
{
	Object_Image *pSelf = Object_Image::GetSelfObj(args);
	if (!ImageStreamer_BMP::WriteStream(sig, pSelf, args.GetStream(0))) return Value::Null;
	return args.GetSelf();
}

//-----------------------------------------------------------------------------
// AScript module functions: bmp
//-----------------------------------------------------------------------------
// Module entry
AScript_ModuleEntry()
{
	AScript_AssignMethodTo(VTYPE_Image, Image, bmpread);
	AScript_AssignMethodTo(VTYPE_Image, Image, bmpwrite);
	ImageStreamer::Register(new ImageStreamer_BMP());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// ImageStreamer_BMP
//-----------------------------------------------------------------------------
bool ImageStreamer_BMP::IsResponsible(Signal sig, Stream &stream)
{
	if (stream.IsReadable()) {
		char buff[2];
		size_t bytesPeeked = stream.Peek(sig, buff, 2);
		if (sig.IsSignalled()) return false;
		return bytesPeeked == 2 && ::memcmp(buff, "BM", 2) == 0;
	}
	return stream.HasNameSuffix(".bmp");
}

bool ImageStreamer_BMP::Read(Environment &env, Signal sig,
									Object_Image *pObjImage, Stream &stream)
{
	return ImageStreamer_BMP::ReadStream(sig, pObjImage, stream);
}

bool ImageStreamer_BMP::Write(Environment &env, Signal sig,
									Object_Image *pObjImage, Stream &stream)
{
	return ImageStreamer_BMP::WriteStream(sig, pObjImage, stream);
}

bool ImageStreamer_BMP::ReadStream(Signal sig, Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckEmpty(sig)) return false;
	BitmapFileHeader bfh;
	if (stream.Read(sig, &bfh, BitmapFileHeader::Size) < BitmapFileHeader::Size) {
		SetError_InvalidBMPFormat(sig);
		return false;
	}
	if (XUnpackUShort(bfh.bfType) != 0x4d42) {
		sig.SetError(ERR_FormatError, "Not a BMP file");
		return false;
	}
	unsigned long bfOffBits = XUnpackULong(bfh.bfOffBits);
	BitmapInfoHeader bih;
	if (stream.Read(sig, &bih, BitmapInfoHeader::Size) < BitmapInfoHeader::Size) {
		sig.SetError(ERR_FormatError, "invalid DIB format");
		return false;
	}
	int biWidth = XUnpackLong(bih.biWidth);
	int biHeight = XUnpackLong(bih.biHeight);
	unsigned short biBitCount = XUnpackUShort(bih.biBitCount);
	if (!pObjImage->ReadDIBPalette(sig, stream, biBitCount)) return false;
	if (bfOffBits != 0) {
		stream.Seek(sig, bfOffBits, Stream::SeekSet);
		if (sig.IsSignalled()) return false;
	}
	return pObjImage->ReadDIB(sig, stream, biWidth, biHeight, biBitCount, false);
}

bool ImageStreamer_BMP::WriteStream(Signal sig, Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckValid(sig)) return false;
	int biWidth = static_cast<int>(pObjImage->GetWidth());
	int biHeight = static_cast<int>(pObjImage->GetHeight());
	int biBitCount = pObjImage->CalcDIBBitCount();
	do {
		BitmapFileHeader bfh;
		::memset(&bfh, 0x00, BitmapFileHeader::Size);
		unsigned long bfOffBits = BitmapFileHeader::Size + BitmapInfoHeader::Size;
		bfOffBits += static_cast<unsigned long>(Object_Image::CalcDIBPaletteSize(biBitCount));
		unsigned long bfSize = static_cast<unsigned long>(pObjImage->GetBufferSize() + bfOffBits);
		XPackUShort(bfh.bfType,			0x4d42);
		XPackULong(bfh.bfSize,			bfSize);
		XPackULong(bfh.bfOffBits,		bfOffBits);
		if (stream.Write(sig, &bfh, BitmapFileHeader::Size) < BitmapFileHeader::Size) {
			sig.SetError(ERR_IOError, "failed to write bitmap data");
			return false;
		}
	} while (0);
	do {
		BitmapInfoHeader bih;
		::memset(&bih, 0x00, BitmapInfoHeader::Size);
		XPackULong(bih.biSize,			BitmapInfoHeader::Size);
		XPackULong(bih.biWidth,			biWidth);
		XPackULong(bih.biHeight,		biHeight);
		XPackUShort(bih.biPlanes,		1);
		XPackUShort(bih.biBitCount,		biBitCount);
		XPackULong(bih.biCompression,	0);
		XPackULong(bih.biSizeImage,		0);
		XPackULong(bih.biXPelsPerMeter,	3780);
		XPackULong(bih.biYPelsPerMeter,	3780);
		XPackULong(bih.biClrUsed,		0);
		XPackULong(bih.biClrImportant,	0);
		if (stream.Write(sig, &bih, BitmapInfoHeader::Size) < BitmapInfoHeader::Size) {
			sig.SetError(ERR_IOError, "failed to write bitmap data");
			return false;
		}
	} while (0);
	if (!pObjImage->WriteDIBPalette(sig, stream, biBitCount)) return false;
	return pObjImage->WriteDIB(sig, stream, biBitCount, false);
}

void ImageStreamer_BMP::SetError_InvalidBMPFormat(Signal sig)
{
	sig.SetError(ERR_FormatError, "invalid BMP format");
}

AScript_EndModule(bmp, bmp)

AScript_RegisterModule(bmp)
