//-----------------------------------------------------------------------------
// AScript tiff module
// http://www.libtiff.org/
// Following site is useful with many samples.
// http://www.ibm.com/developerworks/jp/linux/library/l-libtiff/
// http://www.ibm.com/developerworks/jp/linux/library/l-libtiff2/
//-----------------------------------------------------------------------------
#include <ascript.h>
#include <tiffio.h>

AScript_BeginModule(tiff)

//-----------------------------------------------------------------------------
// ImageStreamer_TIFF
//-----------------------------------------------------------------------------
class ImageStreamer_TIFF : public ImageStreamer {
public:
	inline ImageStreamer_TIFF() : ImageStreamer("tiff") {}
	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);
};

//-----------------------------------------------------------------------------
// Handler
//-----------------------------------------------------------------------------
class Handler {
private:
	Signal _sig;
	Stream &_stream;
public:
	inline Handler(Signal sig, Stream &stream) : _sig(sig), _stream(stream) {}
	inline size_t Read(void *buff, size_t bytes) {
		return _stream.Read(_sig, buff, bytes);
	}
	inline size_t Write(void *buff, size_t bytes) {
		return _stream.Write(_sig, buff, bytes);
	}
	inline bool Seek(size_t offset, Stream::SeekMode seekMode) {
		_stream.Seek(_sig, offset, seekMode);
		return !_sig.IsSignalled();
	}
	inline bool Flush() {
		_stream.Flush(_sig);
		return !_sig.IsSignalled();
	}
	inline size_t GetSize() {
		return _stream.GetSize();
	}
public:
	static tsize_t TiffRead(thandle_t fd, tdata_t buf, tsize_t size);
	static toff_t TiffSeek(thandle_t fd, toff_t offset, int origin);
	static tsize_t TiffWrite(thandle_t fd, tdata_t buf, tsize_t size);
	static int TiffClose(thandle_t fd);
	static toff_t TiffSize(thandle_t fd);
};

tsize_t Handler::TiffRead(thandle_t fd, tdata_t buf, tsize_t size)
{
	//::printf("TiffRead(%d)\n", size);
	Handler *pHandler = reinterpret_cast<Handler *>(fd);
	size = pHandler->Read(buf, size);
	return size;
}

toff_t Handler::TiffSeek(thandle_t fd, toff_t offset, int origin)
{
	//::printf("TiffSeek(%d, %d)\n", offset, origin);
	Handler *pHandler = reinterpret_cast<Handler *>(fd);
	Stream::SeekMode seekMode;
	if (origin == SEEK_SET) {
		seekMode = Stream::SeekSet;
	} else if (origin == SEEK_CUR) {
		seekMode = Stream::SeekCur;
	} else {
		return 0;
	}
	if (!pHandler->Seek(offset, seekMode)) {
	}
	return offset;
}

tsize_t Handler::TiffWrite(thandle_t fd, tdata_t buf, tsize_t size)
{
	Handler *pHandler = reinterpret_cast<Handler *>(fd);
	size = pHandler->Write(buf, size);
	return size;
}

int Handler::TiffClose(thandle_t fd)
{
	Handler *pHandler = reinterpret_cast<Handler *>(fd);
	pHandler->Flush();
	return 0;
}

toff_t Handler::TiffSize(thandle_t fd)
{
	Handler *pHandler = reinterpret_cast<Handler *>(fd);
	return pHandler->GetSize();
}

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

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

//-----------------------------------------------------------------------------
// AScript module functions: tiff
//-----------------------------------------------------------------------------
// tiff.test()
AScript_DeclareFunction(test)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stream", VTYPE_Stream);
}

AScript_ImplementFunction(test)
{
	return Value::Null;
}

// Module entry
AScript_ModuleEntry()
{
	// function assignment
	AScript_AssignFunction(test);
	// method assignment to image
	AScript_AssignMethodTo(VTYPE_Image, Image, tiffread);
	//AScript_AssignMethodTo(VTYPE_Image, Image, tiffwrite);
	// image streamer registration
	ImageStreamer::Register(new ImageStreamer_TIFF());
}

AScript_ModuleTerminate()
{
}

//-----------------------------------------------------------------------------
// ImageStreamer_TIFF
//-----------------------------------------------------------------------------
bool ImageStreamer_TIFF::IsResponsible(Signal sig, Stream &stream)
{
	if (stream.IsReadable()) {
	}
	return stream.HasNameSuffix(".tif") || stream.HasNameSuffix(".tiff");
}

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

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

bool ImageStreamer_TIFF::ReadStream(Signal sig, Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckEmpty(sig)) return false;
	Handler handler(sig, stream);
	TIFF *tiff = ::TIFFClientOpen("AScript", "rm",
		reinterpret_cast<thandle_t>(&handler),
		Handler::TiffRead, Handler::TiffWrite, Handler::TiffSeek,
		Handler::TiffClose, Handler::TiffSize, NULL, NULL);
	uint32 width, height;
	::TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
	::TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
	if (!pObjImage->AllocBuffer(sig, width, height, 0xff)) {
		::TIFFClose(tiff);
		return false;
	}
	size_t bytesRaster = (width * height + 1) * sizeof(uint32);
	OAL::Memory memory;
	uint32 *raster = reinterpret_cast<uint32 *>(memory.Allocate(bytesRaster));
	if (!::TIFFReadRGBAImage(tiff, width, height, raster, 0)) {
		sig.SetError(ERR_FormatError, "invalid TIFF image");
		::TIFFClose(tiff);
		return false;
	}
	uint32 *pSrc = raster;
	std::auto_ptr<Object_Image::Scanner>
			pScannerDst(pObjImage->CreateScanner(Object_Image::SCAN_LeftBottomHorz));
	if (pObjImage->GetFormat() == Object_Image::FORMAT_RGBA) {
		do {
			pScannerDst->StorePixel(
				static_cast<unsigned char>(*pSrc & 0xff),
				static_cast<unsigned char>((*pSrc >> 8) & 0xff),
				static_cast<unsigned char>((*pSrc >> 16) & 0xff),
				static_cast<unsigned char>((*pSrc >> 24) & 0xff));
			pSrc++;
		} while (pScannerDst->Next());
	} else {
		do {
			pScannerDst->StorePixel(
				static_cast<unsigned char>(*pSrc & 0xff),
				static_cast<unsigned char>((*pSrc >> 8) & 0xff),
				static_cast<unsigned char>((*pSrc >> 16) & 0xff));
			pSrc++;
		} while (pScannerDst->Next());
	}
	::TIFFClose(tiff);
	return true;
}

bool ImageStreamer_TIFF::WriteStream(Signal sig, Object_Image *pObjImage, Stream &stream)
{
	if (!pObjImage->CheckValid(sig)) return false;
	return true;
}

AScript_EndModule(tiff, tiff)

AScript_RegisterModule(tiff)
