/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2004 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package 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 "CXWindowsClipboardAnyBitmapConverter.h"

// BMP info header structure
struct CBMPInfoHeader {
public:
	UInt32				biSize;
	SInt32				biWidth;
	SInt32				biHeight;
	UInt16				biPlanes;
	UInt16				biBitCount;
	UInt32				biCompression;
	UInt32				biSizeImage;
	SInt32				biXPelsPerMeter;
	SInt32				biYPelsPerMeter;
	UInt32				biClrUsed;
	UInt32				biClrImportant;
};

// BMP is little-endian

static
void
toLE(UInt8*& dst, UInt16 src)
{
	dst[0] = static_cast<UInt8>(src & 0xffu);
	dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
	dst += 2;
}

static
void
toLE(UInt8*& dst, SInt32 src)
{
	dst[0] = static_cast<UInt8>(src & 0xffu);
	dst[1] = static_cast<UInt8>((src >>  8) & 0xffu);
	dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
	dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
	dst += 4;
}

static
void
toLE(UInt8*& dst, UInt32 src)
{
	dst[0] = static_cast<UInt8>(src & 0xffu);
	dst[1] = static_cast<UInt8>((src >>  8) & 0xffu);
	dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
	dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
	dst += 4;
}

static inline
UInt16
fromLEU16(const UInt8* data)
{
	return static_cast<UInt16>(data[0]) |
			(static_cast<UInt16>(data[1]) << 8);
}

static inline
SInt32
fromLES32(const UInt8* data)
{
	return static_cast<SInt32>(static_cast<UInt32>(data[0]) |
			(static_cast<UInt32>(data[1]) <<  8) |
			(static_cast<UInt32>(data[2]) << 16) |
			(static_cast<UInt32>(data[3]) << 24));
}

static inline
UInt32
fromLEU32(const UInt8* data)
{
	return static_cast<UInt32>(data[0]) |
			(static_cast<UInt32>(data[1]) <<  8) |
			(static_cast<UInt32>(data[2]) << 16) |
			(static_cast<UInt32>(data[3]) << 24);
}


//
// CXWindowsClipboardAnyBitmapConverter
//

CXWindowsClipboardAnyBitmapConverter::CXWindowsClipboardAnyBitmapConverter()
{
	// do nothing
}

CXWindowsClipboardAnyBitmapConverter::~CXWindowsClipboardAnyBitmapConverter()
{
	// do nothing
}

IClipboard::EFormat
CXWindowsClipboardAnyBitmapConverter::getFormat() const
{
	return IClipboard::kBitmap;
}

int
CXWindowsClipboardAnyBitmapConverter::getDataSize() const
{
	return 8;
}

CString
CXWindowsClipboardAnyBitmapConverter::fromIClipboard(const CString& bmp) const
{
	// fill BMP info header with native-endian data
	CBMPInfoHeader infoHeader;
	const UInt8* rawBMPInfoHeader = reinterpret_cast<const UInt8*>(bmp.data());
	infoHeader.biSize             = fromLEU32(rawBMPInfoHeader +  0);
	infoHeader.biWidth            = fromLES32(rawBMPInfoHeader +  4);
	infoHeader.biHeight           = fromLES32(rawBMPInfoHeader +  8);
	infoHeader.biPlanes           = fromLEU16(rawBMPInfoHeader + 12);
	infoHeader.biBitCount         = fromLEU16(rawBMPInfoHeader + 14);
	infoHeader.biCompression      = fromLEU32(rawBMPInfoHeader + 16);
	infoHeader.biSizeImage        = fromLEU32(rawBMPInfoHeader + 20);
	infoHeader.biXPelsPerMeter    = fromLES32(rawBMPInfoHeader + 24);
	infoHeader.biYPelsPerMeter    = fromLES32(rawBMPInfoHeader + 28);
	infoHeader.biClrUsed          = fromLEU32(rawBMPInfoHeader + 32);
	infoHeader.biClrImportant     = fromLEU32(rawBMPInfoHeader + 36);

	// check that format is acceptable
	if (infoHeader.biSize != 40 ||
		infoHeader.biWidth == 0 || infoHeader.biHeight == 0 ||
		infoHeader.biPlanes != 0 || infoHeader.biCompression != 0 ||
		(infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32)) {
		return CString();
	}

	// convert to image format
	const UInt8* rawBMPPixels = rawBMPInfoHeader + 40;
	if (infoHeader.biBitCount == 24) {
		return doBGRFromIClipboard(rawBMPPixels,
							infoHeader.biWidth, infoHeader.biHeight);
	}
	else {
		return doBGRAFromIClipboard(rawBMPPixels,
							infoHeader.biWidth, infoHeader.biHeight);
	}
}

CString
CXWindowsClipboardAnyBitmapConverter::toIClipboard(const CString& image) const
{
	// convert to raw BMP data
	UInt32 w, h, depth;
	CString rawBMP = doToIClipboard(image, w, h, depth);
	if (rawBMP.empty() || w == 0 || h == 0 || (depth != 24 && depth != 32)) {
		return CString();
	}

	// fill BMP info header with little-endian data
	UInt8 infoHeader[40];
	UInt8* dst = infoHeader;
	toLE(dst, static_cast<UInt32>(40));
	toLE(dst, static_cast<SInt32>(w));
	toLE(dst, static_cast<SInt32>(h));
	toLE(dst, static_cast<UInt16>(1));
	toLE(dst, static_cast<UInt16>(depth));
	toLE(dst, static_cast<UInt32>(0));		// BI_RGB
	toLE(dst, static_cast<UInt32>(image.size()));
	toLE(dst, static_cast<SInt32>(2834));	// 72 dpi
	toLE(dst, static_cast<SInt32>(2834));	// 72 dpi
	toLE(dst, static_cast<UInt32>(0));
	toLE(dst, static_cast<UInt32>(0));

	// construct image
	return CString(reinterpret_cast<const char*>(infoHeader),
							sizeof(infoHeader)) + rawBMP;
}