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

AScript_BeginModule(msicon)

AScript_DeclarePrivSymbol(images);

//-----------------------------------------------------------------------------
// Data structure for Windows ICO format
// http://msdn.microsoft.com/en-us/library/ms997538.aspx
//-----------------------------------------------------------------------------
struct IconDir {
	enum { Size = 6 };
	XPackedUShort_LE(idReserved);
	XPackedUShort_LE(idType);
	XPackedUShort_LE(idCount);
};

struct IconDirEntry {
	enum { Size = 16 };
	unsigned char bWidth;
	unsigned char bHeight;
	unsigned char bColorCount;
	unsigned char bReserved;
	XPackedUShort_LE(wPlanes);
	XPackedUShort_LE(wBitCount);
	XPackedULong_LE(dwBytesInRes);
	XPackedULong_LE(dwImageOffset);
};

//-----------------------------------------------------------------------------
// Object_MSIcon
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(MSIcon);

class Object_MSIcon : public Object {
public:
	AScript_DeclareObjectAccessor(MSIcon)
private:
	ValueList _valList;
public:
	inline Object_MSIcon() : Object(AScript_PrivClass(MSIcon)) {}
	inline Object_MSIcon(const Object_MSIcon &obj) : Object(obj) {}
	virtual ~Object_MSIcon();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
	bool Read(Environment &env, Signal sig, Stream &stream, Object_Image::Format format);
	bool Write(Environment &env, Signal sig, Stream &stream);
	void AddImage(const Value &value);
};

Object_MSIcon::~Object_MSIcon()
{
}

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

Value Object_MSIcon::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(images))) {
		return Value(new Object_List(env, _valList));
	}
	evaluatedFlag = false;
	return Value::Null;
}

bool Object_MSIcon::Read(Environment &env, Signal sig,
									Stream &stream, Object_Image::Format format)
{
	int cntIcons = 0;
	do {
		IconDir iconDir;
		if (stream.Read(sig, &iconDir, IconDir::Size) < IconDir::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		cntIcons = XUnpackUShort(iconDir.idCount);
	} while (0);
	LongList imageOffsets;
	for (int iIcon = 0; iIcon < cntIcons; iIcon++) {
		IconDirEntry iconDirEntry;
		if (stream.Read(sig, &iconDirEntry, IconDirEntry::Size) < IconDirEntry::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		long imageOffset = XUnpackLong(iconDirEntry.dwImageOffset);
		imageOffsets.push_back(imageOffset);
	}
	foreach (LongList, pImageOffset, imageOffsets) {
		long imageOffset = *pImageOffset;
		if (!stream.Seek(sig, imageOffset, Stream::SeekSet)) return false;
		BitmapInfoHeader bih;
		if (stream.Read(sig, &bih, BitmapInfoHeader::Size) < BitmapInfoHeader::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		int biWidth = XUnpackLong(bih.biWidth);
		int biHeight = XUnpackLong(bih.biHeight) / 2;
		unsigned short biBitCount = XUnpackUShort(bih.biBitCount);
		Object_Image *pObjImage = new Object_Image(env, format);
		if (!pObjImage->ReadDIBPalette(sig, stream, biBitCount)) return false;
		if (!pObjImage->ReadDIB(sig, stream, biWidth, biHeight, biBitCount, true)) {
			return false;
		}
		_valList.push_back(Value(pObjImage));
	}
	return true;
}

bool Object_MSIcon::Write(Environment &env, Signal sig, Stream &stream)
{
	int cntIcons = static_cast<int>(_valList.size());
	do {
		IconDir iconDir;
		::memset(&iconDir, 0x00, sizeof(iconDir));
		XPackUShort(iconDir.idReserved,	0x0000);
		XPackUShort(iconDir.idType,		0x0001);
		XPackUShort(iconDir.idCount,	cntIcons);
		stream.Write(sig, &iconDir, IconDir::Size);
		if (sig.IsSignalled()) return false;
	} while (0);
	unsigned long dwImageOffset = IconDir::Size + IconDirEntry::Size * cntIcons;
	foreach (ValueList, pValue, _valList) {
		Object_Image *pObjImage = pValue->GetImageObj();
		size_t width = pObjImage->GetWidth(), height = pObjImage->GetHeight();
		if (width > 256 || height > 256) {
			sig.SetError(ERR_FormatError, "image %dx%d is too big for icon format",
																width, height);
			return false;
		}
		int biBitCount = pObjImage->CalcDIBBitCount();
		unsigned long dwBytesInRes = static_cast<unsigned long>(
						BitmapInfoHeader::Size +
						Object_Image::CalcDIBPaletteSize(biBitCount) +
						pObjImage->CalcDIBImageSize(biBitCount, true));
		IconDirEntry iconDirEntry;
		::memset(&iconDirEntry, 0x00, sizeof(iconDirEntry));
		iconDirEntry.bWidth = static_cast<unsigned char>(width & 0xff);
		iconDirEntry.bHeight = static_cast<unsigned char>(height & 0xff);
		iconDirEntry.bColorCount = (biBitCount < 8)?
						static_cast<unsigned char>(1 << biBitCount) : 0;
		iconDirEntry.bReserved = 0;
		unsigned short wPlanes = (biBitCount >= 8)? 1 : 0;
		XPackUShort(iconDirEntry.wPlanes, wPlanes);
		unsigned short wBitCount = (biBitCount >= 8)? biBitCount : 0;
		XPackUShort(iconDirEntry.wBitCount, wBitCount);
		XPackULong(iconDirEntry.dwBytesInRes, dwBytesInRes);
		XPackULong(iconDirEntry.dwImageOffset, dwImageOffset);
		stream.Write(sig, &iconDirEntry, IconDirEntry::Size);
		if (sig.IsSignalled()) return false;
		dwImageOffset += dwBytesInRes;
	}
	foreach (ValueList, pValue, _valList) {
		Object_Image *pObjImage = pValue->GetImageObj();
		size_t width = pObjImage->GetWidth(), height = pObjImage->GetHeight();
		int biBitCount = pObjImage->CalcDIBBitCount();
		BitmapInfoHeader bih;
		::memset(&bih, 0x00, sizeof(bih));
		XPackULong(bih.biSize,			BitmapInfoHeader::Size);
		XPackLong(bih.biWidth,			width);
		XPackLong(bih.biHeight,			height * 2);
		XPackUShort(bih.biPlanes,		1);
		XPackUShort(bih.biBitCount,		biBitCount);
		XPackULong(bih.biCompression,	0);	// just set to zero
		XPackULong(bih.biSizeImage,		0);	// just set to zero
		XPackLong(bih.biXPelsPerMeter,	0);	// just set to zero
		XPackLong(bih.biYPelsPerMeter,	0);	// just set to zero
		XPackULong(bih.biClrUsed,		0);	// just set to zero
		XPackULong(bih.biClrImportant,	0);	// just set to zero
		stream.Write(sig, &bih, BitmapInfoHeader::Size);
		if (sig.IsSignalled()) return false;
		if (!pObjImage->WriteDIBPalette(sig, stream, biBitCount)) return false;
		if (!pObjImage->WriteDIB(sig, stream, biBitCount, true)) return false;
	}
	if (!stream.Flush(sig)) return false;
	return true;
}

void Object_MSIcon::AddImage(const Value &value)
{
	_valList.push_back(value);
}

String Object_MSIcon::ToString(Signal sig, bool exprFlag)
{
	String str;
	str = "<msicon:";
	if (_valList.empty()) {
		str += "empty";
	} else {
		bool followFlag = false;
		foreach_const (ValueList, pValue, _valList) {
			if (!pValue->IsImage()) continue;
			Object_Image *pObjImage = pValue->GetImageObj();
			char buff[64];
			if (followFlag) str += ",";
			::sprintf(buff, "%dx%d-%dbpp",
				pObjImage->GetWidth(), pObjImage->GetHeight(), pObjImage->CalcDIBBitCount());
			str += buff;
			followFlag = true;
		}
	}
	str += ">";
	return str;
}

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

AScript_ImplementMethod(MSIcon, write)
{
	Object_MSIcon *pSelf = Object_MSIcon::GetSelfObj(args);
	Stream &stream = args.GetStream(0);
	if (!pSelf->Write(env, sig, stream)) return Value::Null;
	return args.GetSelf();
}

// msicon.msicon#addimage(image:image):map:reduce
AScript_DeclareMethod(MSIcon, addimage)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "image", VTYPE_Image);
}

AScript_ImplementMethod(MSIcon, addimage)
{
	Object_MSIcon *pSelf = Object_MSIcon::GetSelfObj(args);
	pSelf->AddImage(args.GetValue(0));
	return args.GetSelf();
}

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

//-----------------------------------------------------------------------------
// ImageStreamer_ICO
//-----------------------------------------------------------------------------
class ImageStreamer_ICO : public ImageStreamer {
public:
	inline ImageStreamer_ICO() : ImageStreamer("msicon") {}
	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, int idx);
};

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Image
// These methods are available after importing msicon module.
//-----------------------------------------------------------------------------
// image#msiconread(stream:stream, idx:number => 0):reduce
AScript_DeclareMethod(Image, msiconread)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
	DeclareArg(env, "idx", VTYPE_Number, OCCUR_Once,
								false, false, new Expr_Value(Value(0)));
	SetHelp("Reads an ICO image from a stream.");
}

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

//-----------------------------------------------------------------------------
// AScript module functions: msicon
//-----------------------------------------------------------------------------
// msicon.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);
}

AScript_ImplementFunction(read)
{
	Object_MSIcon *pObj = new Object_MSIcon();
	Stream &stream = args.GetStream(0);
	Object_Image::Format format = Object_Image::SymbolToFormat(sig, args.GetSymbol(1));
	if (sig.IsSignalled()) return Value::Null;
	if (!pObj->Read(env, sig, stream, format)) return Value::Null;
	return Value(pObj);
}

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

AScript_ImplementFunction(msicon)
{
	Object_MSIcon *pObj = new Object_MSIcon();
	return Value(pObj);
}

// Module entry
AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(images);
	// class realization
	AScript_RealizePrivClass(MSIcon, "msicon", env.LookupClass(VTYPE_Object));
	// method assignment to image class
	AScript_AssignMethodTo(VTYPE_Image, Image, msiconread);
	// function assignment
	AScript_AssignFunction(read);
	AScript_AssignFunction(msicon);
	// image streamer registration
	ImageStreamer::Register(new ImageStreamer_ICO());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// ImageStreamer_ICO
//-----------------------------------------------------------------------------
bool ImageStreamer_ICO::IsResponsible(Signal sig, Stream &stream)
{
	return stream.HasNameSuffix(".ico");
}

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

bool ImageStreamer_ICO::Write(Environment &env, Signal sig,
									Object_Image *pObjImage, Stream &stream)
{
	sig.SetError(ERR_SystemError, "not implemented yet");
	return false;
	//return pObjImage->WriteICO(sig, stream, false);
}

bool ImageStreamer_ICO::ReadStream(Signal sig,
						Object_Image *pObjImage, Stream &stream, int idx)
{
	int cntIcons = 0;
	do {
		IconDir iconDir;
		if (stream.Read(sig, &iconDir, IconDir::Size) < IconDir::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		cntIcons = XUnpackUShort(iconDir.idCount);
	} while (0);
	if (idx >= cntIcons) {
		sig.SetError(ERR_FormatError, "index of icon is out of range");
		return false;
	}
	long dwImageOffset = 0;
	do {
		if (!stream.Seek(sig, idx * IconDirEntry::Size, Stream::SeekCur)) return false;
		IconDirEntry iconDirEntry;
		if (stream.Read(sig, &iconDirEntry, IconDirEntry::Size) < IconDirEntry::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		dwImageOffset = XUnpackLong(iconDirEntry.dwImageOffset);
	} while (0);
	do {
		if (!stream.Seek(sig, dwImageOffset, Stream::SeekSet)) return false;
		BitmapInfoHeader bih;
		if (stream.Read(sig, &bih, BitmapInfoHeader::Size) < BitmapInfoHeader::Size) {
			sig.SetError(ERR_FormatError, "invalid ICO format");
			return false;
		}
		int biWidth = XUnpackLong(bih.biWidth);
		int biHeight = XUnpackLong(bih.biHeight) / 2;
		unsigned short biBitCount = XUnpackUShort(bih.biBitCount);
		if (!pObjImage->ReadDIBPalette(sig, stream, biBitCount)) return false;
		if (!pObjImage->ReadDIB(sig, stream, biWidth, biHeight, biBitCount, true)) {
			return false;
		}
	} while (0);
	return true;
}

AScript_EndModule(msicon, msicon)

AScript_RegisterModule(msicon)
