#include <objgfx40/ogImage.h>
#include <objgfx40/objgfx.h>


#ifndef __UBIXOS_KERNEL__
extern "C" {
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
}
#endif

#ifdef __UBIXOS_KERNEL__
extern "C" {
#include <math.h>
#include <lib/kmalloc.h>
#include <vfs/vfs.h>
#include <string.h>
}


bool Win3xBitmapHeader::Deserialize(std::istream& stream)
{
	if (!stream) return false;

	// Read in each field, and check for any failure. Return false on failure
	if (!stream.read(reinterpret_cast<char *>(&ImageFileType), sizeof(ImageFileType))) return false;
	if (!stream.read(reinterpret_cast<char *>(&FileSize), sizeof(FileSize))) return false;
	if (!stream.read(reinterpret_cast<char *>(&Reserved1), sizeof(Reserved1))) return false;
	if (!stream.read(reinterpret_cast<char *>(&Reserved2), sizeof(Reserved2))) return false;
	if (!stream.read(reinterpret_cast<char *>(&ImageDataOffset), sizeof(ImageDataOffset))) return false;

	return true;
} // bool Win3xBitmapHeader::Deserialize()

std::string Win3xBitmapHeader::ToString() 
{
	return 
		"ImageFileType = " + std::to_string(ImageFileType) + "\r\n" +
		"FileSize = " + std::to_string(FileSize) + "\r\n" +
		"Reserved1 = " + std::to_string(Reserved1) + "\r\n" +
		"Reserved2 = " + std::to_string(Reserved2) + "\r\n" +
		"ImageDataOffset = " + std::to_string(ImageDataOffset) + "\r\n";
} // string Win3xBitmapHeader::ToString()

bool Win3xBitmapHeader::IsMatch() 
{
	return 
		(ImageFileType == 0x4D42 && 
		FileSize != 0 && 
		Reserved1 == 0 &&
		Reserved2 == 0 &&
		ImageDataOffset != 0);
} // bool Win3xBitmapHeader::IsMatch()

bool Win3xBitmapHeader::Serialize(std::ostream& stream)
{
	if (!stream) return false;

	// Read in each field, and check for any failure. Return false on failure
	if (!stream.write(reinterpret_cast<char *>(&ImageFileType), sizeof(ImageFileType))) return false;
	if (!stream.write(reinterpret_cast<char *>(&FileSize), sizeof(FileSize))) return false;
	if (!stream.write(reinterpret_cast<char *>(&Reserved1), sizeof(Reserved1))) return false;
	if (!stream.write(reinterpret_cast<char *>(&Reserved2), sizeof(Reserved2)))	return false;
	if (!stream.write(reinterpret_cast<char *>(&ImageDataOffset), sizeof(ImageDataOffset))) return false;

	return true;
} // bool Win3xBitmapHeader::Serialize()

size_t Win3xBitmapHeader::Size() 
{
	return
		sizeof(ImageFileType) +
		sizeof(FileSize) + 
		sizeof(Reserved1) + 
		sizeof(Reserved2) + 
		sizeof(ImageDataOffset);
} // size_t Win3xBitmapHeader::Size() 

bool Win3xBitmapInfoHeader::Deserialize(std::istream& stream)
{
	if (!stream) return false;

	// Read in each field, and check for any failure. Return false on failure
	if (!stream.read(reinterpret_cast<char *>(&HeaderSize), sizeof(HeaderSize))) return false;
	if (!stream.read(reinterpret_cast<char *>(&ImageWidth), sizeof(ImageWidth))) return false;
	if (!stream.read(reinterpret_cast<char *>(&ImageHeight), sizeof(ImageHeight))) return false;
	if (!stream.read(reinterpret_cast<char *>(&NumberOfImagePlanes), sizeof(NumberOfImagePlanes))) return false;
	if (!stream.read(reinterpret_cast<char *>(&BitsPerPixel), sizeof(BitsPerPixel))) return false;
	if (!stream.read(reinterpret_cast<char *>(&CompressionMethod), sizeof(CompressionMethod))) return false;
	if (!stream.read(reinterpret_cast<char *>(&SizeOfBitmap), sizeof(SizeOfBitmap))) return false;
	if (!stream.read(reinterpret_cast<char *>(&HorzResolution), sizeof(HorzResolution))) return false;
	if (!stream.read(reinterpret_cast<char *>(&VertResolution), sizeof(VertResolution))) return false;
	if (!stream.read(reinterpret_cast<char *>(&NumColoursUsed), sizeof(NumColoursUsed))) return false;
	if (!stream.read(reinterpret_cast<char *>(&NumSignificantColours), sizeof(NumSignificantColours))) return false;

	return true;
} // bool Win3xBitmapInfoHeader::Deserialize()

std::string Win3xBitmapInfoHeader::ToString() 
{
	return
		"HeaderSize = " + std::to_string(HeaderSize) + "\r\n" +
		"ImageWidth = " + std::to_string(ImageWidth) + "\r\n" +
		"ImageHeight = " + std::to_string(ImageHeight) + "\r\n" +
		"NumberOfImagePlanes = " + std::to_string(NumberOfImagePlanes) + "\r\n" +
		"BitsPerPixel = "+ std::to_string(BitsPerPixel) + "\r\n" +
		"CompressionMethod = " + std::to_string(CompressionMethod) + "\r\n" +
		"SizeOfBitmap = " + std::to_string(SizeOfBitmap) + "\r\n" +
		"HorzResolution = " + std::to_string(HorzResolution) + "\r\n" +
		"VertResolution = " + std::to_string(VertResolution) + "\r\n" +
		"NumColoursUsed = " + std::to_string(NumColoursUsed) + "\r\n" +
		"NumSignificantColours = " + std::to_string(NumSignificantColours) + "\r\n";
} // string Win3xBitmapInfoHeader::ToString() 

bool Win3xBitmapInfoHeader::IsMatch()
{
	return 
		(HeaderSize != 0 &&
		ImageWidth != 0 &&
		ImageHeight != 0 &&
		CompressionMethod == 0 &&
		(BitsPerPixel == 8 || BitsPerPixel == 24));
}
bool Win3xBitmapInfoHeader::Serialize(std::ostream& stream)
{
	if (!stream) return false;

	// Read in each field, and return false on failure
	if (!stream.write(reinterpret_cast<char *>(&HeaderSize), sizeof(HeaderSize))) return false;
	if (!stream.write(reinterpret_cast<char *>(&ImageWidth), sizeof(ImageWidth))) return false;
	if (!stream.write(reinterpret_cast<char *>(&ImageHeight), sizeof(ImageHeight))) return false;
	if (!stream.write(reinterpret_cast<char *>(&NumberOfImagePlanes), sizeof(NumberOfImagePlanes))) return false;
	if (!stream.write(reinterpret_cast<char *>(&BitsPerPixel), sizeof(BitsPerPixel))) return false;
	if (!stream.write(reinterpret_cast<char *>(&CompressionMethod), sizeof(CompressionMethod))) return false;
	if (!stream.write(reinterpret_cast<char *>(&SizeOfBitmap), sizeof(SizeOfBitmap))) return false;
	if (!stream.write(reinterpret_cast<char *>(&HorzResolution), sizeof(HorzResolution))) return false;
	if (!stream.write(reinterpret_cast<char *>(&VertResolution), sizeof(VertResolution))) return false;
	if (!stream.write(reinterpret_cast<char *>(&NumColoursUsed), sizeof(NumColoursUsed))) return false;
	if (!stream.write(reinterpret_cast<char *>(&NumSignificantColours), sizeof(NumSignificantColours))) return false;

	return true;
} // bool Win3xBitmapInfoHeader::Serialize()

size_t Win3xBitmapInfoHeader::Size() 
{
	return
		sizeof(HeaderSize) +
		sizeof(ImageWidth) +
		sizeof(ImageHeight) + 
		sizeof(NumberOfImagePlanes) + 
		sizeof(BitsPerPixel) +
		sizeof(CompressionMethod) + 
		sizeof(SizeOfBitmap) +
		sizeof(HorzResolution) +
		sizeof(VertResolution) +
		sizeof(NumColoursUsed) +
		sizeof(NumSignificantColours);
} // size_t Win3xBitmapInfoHeader::Size() 

bool RGBQuad::Deserialize(std::istream& stream)
{
	if (!stream) return false;

	// Read in each field, and return false on failure
	if (!stream.read(reinterpret_cast<char *>(&rgbBlue), sizeof(rgbBlue))) return false;
	if (!stream.read(reinterpret_cast<char *>(&rgbGreen), sizeof(rgbGreen))) return false;
	if (!stream.read(reinterpret_cast<char *>(&rgbRed), sizeof(rgbRed))) return false;
	if (!stream.read(reinterpret_cast<char *>(&rgbReserved), sizeof(rgbReserved))) return false;

	return true;
} // bool RGBQuad::Deserialize()

std::string RGBQuad::ToString()
{
	return "rgbRed = " + to_string(rgbRed) + "\r\n"
		+ "rgbGreen = " + to_string(rgbGreen) + "\r\n"
		+ "rgbBlue = " + to_string(rgbBlue) + "\r\n"
		+ "rgbReserved = " + to_string(rgbReserved) + "\r\n";
} // string RGBQuad::ToString()

bool RGBQuad::Serialize(std::ostream& stream)
{
	if (!stream) return false;

	// Read in each field, and return false on failure
	if (!stream.write(reinterpret_cast<char *>(&rgbBlue), sizeof(rgbBlue))) return false;
	if (!stream.write(reinterpret_cast<char *>(&rgbGreen), sizeof(rgbGreen))) return false;
	if (!stream.write(reinterpret_cast<char *>(&rgbRed), sizeof(rgbRed))) return false;
	if (!stream.write(reinterpret_cast<char *>(&rgbReserved), sizeof(rgbReserved))) return false;

	return true;
} // bool RGBQuad::Serialize()
size_t RGBQuad::Size()
{
	return 
		sizeof(rgbBlue) +
		sizeof(rgbGreen) + 
		sizeof(rgbRed) +
		sizeof(rgbReserved);
} // size_t RGBQuad::Size()

bool IsBMP(std::istream& stream)
{

	// Check for BMP
	Win3xBitmapHeader bmpHeader;
	bmpHeader.Deserialize(stream);
	Win3xBitmapInfoHeader bmpInfoHeader;
	bmpInfoHeader.Deserialize(stream);

	if (bmpHeader.IsMatch() && bmpInfoHeader.IsMatch()) 
	{
		std::cout << bmpHeader.ToString();
		std::cout << bmpInfoHeader.ToString();
		return true;
	}
	return false;
} // bool IsBMP()

/**********************************************
 *			ogImage 
 **********************************************/
static const std::map<ogImageType, std::function<bool(std::istream&)>>& CreateIsImageMap()
{
	static std::map<ogImageType, std::function<bool(std::istream&)>> IsImage;

	//IsImage[NoImage] = ([&](std::istream&) -> bool { return false; });
	IsImage[BMP] = &IsBMP;
	return IsImage;
} // map<> CreateIsImageMap()

std::map<ogImageType, std::function<bool(std::istream&)>> ogImage::IsImage = CreateIsImageMap();

ogImage::ogImage() :
	surface(nullptr),
	options(nullptr),
	input(nullptr),
	output(nullptr)
{
	Decode[NoImage] = &ogImage::NoOp;
	Decode[BMP] = &ogImage::DecodeBMP;

	Encode[NoImage] = &ogImage::NoOp;
	Encode[BMP] = &ogImage::EncodeBMP;

} // ogImage::ogImage()

bool ogImage::DecodeBMP()
{
	// ogImage::ImageType() has determined we're a BMP, so we only need to do
	// minimal sanity checks on the header information.

	Win3xBitmapHeader bmpHeader;
	Win3xBitmapInfoHeader bmpInfoHeader;
	if (!bmpHeader.Deserialize(*input)) return false;
	if (!bmpInfoHeader.Deserialize(*input)) return false;

	size_t lineSize;
	size_t paddington;
	char linePadding[4];
	
	if (bmpInfoHeader.BitsPerPixel == 8)
	{
		if (!surface->ogCreate(bmpInfoHeader.ImageWidth, bmpInfoHeader.ImageHeight, OG_PIXFMT_8BPP)) return false;

		lineSize = ((bmpInfoHeader.ImageWidth+3) >> 2) << 2;	// round up to the nearest 4 bytes
		paddington = lineSize - bmpInfoHeader.ImageWidth;		// see if we have a remainder
		
		if (paddington > 4) return false;						// this would be odd

		RGBQuad quad;
		for (unsigned colour = 0; colour < 256; colour++)
		{
			if (!quad.Deserialize(*input)) return false;
			surface->ogSetPalette(colour, quad.rgbRed, quad.rgbGreen, quad.rgbBlue, quad.rgbReserved);
		} // for colour

		for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0
		{
			char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y));
			if (ptr == nullptr) return false;	// this doesn't have to be a complete failure, fix later
			if (!input->read(ptr, bmpInfoHeader.ImageWidth)) return false;
			if (paddington != 0)
			{
				if (!input->read(linePadding, paddington)) return false;	// double check this
			}
		} // for y
	} 
	else if (bmpInfoHeader.BitsPerPixel == 24)
	{
		ogPixelFmt BMPPixelFmt(24, 16,8,0,0,  8,8,8,0);
		if (!surface->ogCreate(bmpInfoHeader.ImageWidth, bmpInfoHeader.ImageHeight, OG_PIXFMT_24BPP)) return false;	
		size_t widthInBytes = bmpInfoHeader.ImageWidth*3;	// 3 represents how many bytes per pixel
		lineSize = ((widthInBytes+3) >> 2) << 2;			// round up to the nearest 4 bytes
		paddington = lineSize - widthInBytes;		// see if we have a remainder

		if (paddington > 4) return false;

		for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;)
		{
			char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y));
			if (ptr == nullptr) return false;	// this doesn't have to be a complete failure, fix later
			if (!input->read(ptr, widthInBytes)) return false;
			if (paddington != 0)
			{
				if (!input->read(linePadding, paddington)) return false;
			} 
		} // for y
	}

	return true;
} // bool ogImage::DecodeBMP()

bool ogImage::EncodeBMP()
{
	Win3xBitmapHeader bmpHeader;
	Win3xBitmapInfoHeader bmpInfoHeader;
	size_t paddington;
	size_t lineSize;
	const char linePadding[4] = {0,0,0,0};
	ogRGBA8 ogPalette[256]; // used to get the palette from the surface

	bmpHeader.ImageFileType = 0x4D42;
	bmpHeader.FileSize = 0; // fill in later
	//bmpHeader.Reserved1 = 0;	// set by constructor
	//bmpHeader.Reserved2 = 0;	// set by constructor
	bmpHeader.ImageDataOffset = bmpHeader.Size() + bmpInfoHeader.Size();

	bmpInfoHeader.HeaderSize = bmpInfoHeader.Size();
	bmpInfoHeader.ImageWidth = surface->ogGetMaxX()+1;
	bmpInfoHeader.ImageHeight = surface->ogGetMaxY()+1;
	bmpInfoHeader.NumberOfImagePlanes = 1;
	// bmpInfoHeader.BitsPerPixel is set below
	bmpInfoHeader.CompressionMethod = 0;
	bmpInfoHeader.SizeOfBitmap = 0;
	bmpInfoHeader.HorzResolution = 0;	// option
	bmpInfoHeader.VertResolution = 0;	// option

	switch (surface->ogGetBPP())
	{
	case 8:
		bmpInfoHeader.BitsPerPixel = 8;	// 8bpp, 256 colours
		bmpInfoHeader.NumColoursUsed = 1 << surface->ogGetBPP();
		bmpInfoHeader.NumSignificantColours = 0; // option

		// This is hard to calculate dynamically. Sod it.
		bmpHeader.ImageDataOffset += 1024;// adjust by palette size

		lineSize = ((bmpInfoHeader.ImageWidth+3) >> 2) << 2;	// multiple of 4
		bmpInfoHeader.SizeOfBitmap = lineSize*bmpInfoHeader.ImageHeight;
		bmpHeader.FileSize = bmpHeader.ImageDataOffset+bmpInfoHeader.SizeOfBitmap;

		// Write the headers
		if (!bmpHeader.Serialize(*output)) return false;
		if (!bmpInfoHeader.Serialize(*output)) return false;

		// all rows are aligned to the nearest 4 bytes. Figure out if we need to pad.
		paddington = lineSize-bmpInfoHeader.ImageWidth;

		surface->ogGetPalette(ogPalette);

		for (size_t index = 0; index < sizeof(ogPalette) / sizeof(ogPalette[0]); index++)
		{
			RGBQuad quad(ogPalette[index]);
			if (!quad.Serialize(*output)) return false;
		} // for index

		for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0
		{
			char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y));
			if (ptr != nullptr)
			{
				if (!output->write(ptr, bmpInfoHeader.ImageWidth)) return false;
			}

			// Is there any padding to add to the end of the line?
			if (paddington != 0) 
			{
				if (!output->write(linePadding, paddington)) return false;
			}
		} // for y
		break;
	case 32:
	case 24:
	case 16:
	case 15:
		// 15, 16, and 32 bpp are all treated as 24bpp
		bmpInfoHeader.BitsPerPixel = 24;
		bmpInfoHeader.NumColoursUsed = 0;
		bmpInfoHeader.NumSignificantColours = 0;

		lineSize = ((bmpInfoHeader.ImageWidth*3+3) >> 2) << 2;
		bmpInfoHeader.SizeOfBitmap = lineSize*bmpInfoHeader.ImageHeight;
		bmpHeader.FileSize = bmpHeader.ImageDataOffset+bmpInfoHeader.SizeOfBitmap;

		// Write the headers
		if (!bmpHeader.Serialize(*output)) return false;
		if (!bmpInfoHeader.Serialize(*output)) return false;

		// all rows are aligned to the nearest 4 bytes. Figure out if we need to pad.
		paddington = lineSize-bmpInfoHeader.ImageWidth*3;	// 3 represents how many bytes per pixel
		uInt8 red, green, blue;

		for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0
		{
			for (unsigned x = 0; x <= surface->ogGetMaxX(); x++)
			{
				// Unpack the pixel and write it out.
				surface->ogUnpack(surface->ogGetPixel(x, y), red, green, blue);
				if (!output->write(reinterpret_cast<char *>(&blue), sizeof(blue))) return false;
				if (!output->write(reinterpret_cast<char *>(&green), sizeof(green))) return false;
				if (!output->write(reinterpret_cast<char *>(&red), sizeof(red))) return false;
			} // for x

			// Is there any padding to add to the end of the line?
			if (paddington != 0) 
			{
				if (!output->write(linePadding, paddington)) return false;
			}
		} // for y
		break;
	default:
		return false; // we can't encode anything else (I'm not sure there *is* anything else)
	} // switch


	return true;
}  // bool ogImage::EncodeBMP()

ogImageType ogImage::ImageType(std::istream& input)
{
	ogImageType imageType = NoImage;
	// space for the header
	char header[128]; // This really should be as large as the largest header we know of

	for (size_t index = 0; index < std::extent<decltype(header)>::value; index++)
	{
		header[index] = 0;	// clear the header
	} // for index

	size_t size = sizeof(header);

	if (!input.read(header, size)) return NoImage;	// This isn't necessarily true. Might just be a small image.
	// Figure out how many bytes we read in. 
	streamsize bytesRead = input.gcount();

	if (bytesRead != 0) 
	{
		
		// Try to determine what it really is
		for (auto iType : IsImage)
		{
			mstream mb(header, bytesRead);
			// This creates a new istream from a memory stream,
			// which is wrapped adound the header.
			//istream stream(&mstream(header, bytesRead));
            istream stream(&mb);
			if (iType.second(stream))	// Did we find it?
			{
				imageType = iType.first;	// Found it
				break;
			} // if
		} // for iType

		// Now seek backwards in the stream.
		input.seekg(-bytesRead, std::ios_base::cur);
	}
	
	return imageType;
} // ogImageType ogImage::ImageType()

ogImageType ogImage::ImageType(const std::string& filename)
{
	std::ifstream input(filename, ios::in | std::ios_base::binary);
	ogImageType imageType = NoImage;

	// Sanity check
	if (input)
	{
		// Use the stream version
		imageType = ogImage::ImageType(input);

		// close the file
		input.close();
	} // if

	return imageType;
} // ogImageType ogImage::ImageType()

bool ogImage::Load(const std::string& filename, ogSurface & surface)
{
    std::cout << "Loading " << filename << std::endl;
	bool success = false;	// assume failure

	ifstream stream(filename, ios_base::in | ios_base::binary);

	if (stream)
	{
		success = Load(stream, surface);
		stream.close();
	} // if stream

	return success;
} // ogSurface * ogImage::Load()

bool ogImage::Load(std::istream& stream, ogSurface& surface)
{
	// First we need to find out what type of graphics file it is:
	ogImageType imageType = ogImage::ImageType(stream);

	if (imageType == NoImage) return false;

	this->surface = &surface;
	this->input = &stream;
	this->options = nullptr;	// shouldn't be necessary
	this->output = nullptr;		// shouldn't be necessary

	assert(Decode.count(imageType) == 1);	// make sure we can handle it

	return (this->*Decode[imageType])();	// Decode
} // bool ogImage::Load()

bool ogImage::Save(const std::string& filename, 
				   ogSurface &surface, 
				   ogImageType imageType, 
				   ogImageOptions * options)
{
	bool success = false;	// assume failure

	ofstream stream(filename, ios_base::out | ios_base::binary);	// try to open the file

	if (stream) 
	{
		// Use the stream version
		success = Save(stream, surface, imageType, options);

		// close the file
		stream.close();	
	} // if

	return success;
} // bool ogImage::Save()

bool ogImage::Save(std::ostream& stream,
				   ogSurface &surface, 
				   ogImageType imageType, 
				   ogImageOptions * options)
{
	bool success = false;	// assume failure

	if (stream && surface.ogAvail())
	{
		// Since surface is a reference, it cannot [normally] be null,
		// so no checking needs to be done in any of the encoder functions.
		this->surface = &surface;
		this->options = options;
		this->output = &stream;

		assert(Encode.count(imageType) == 1);	// Sanity check

		// Call the specific encoder
		success = (this->*Encode[imageType])();
	} // if

	return success;
} // bool ogImage::Save()

#if 0
class membuf : public basic_streambuf<char>
{
public:
	membuf(char* p, size_t n) {
		setg(p, p, p + n);
		setp(p, p + n);
	}
}
Usage:

char *mybuffer;
size_t length;
// ... allocate "mybuffer", put data into it, set "length"

membuf mb(mybuffer, length);
istream reader(&mb);
// use "reader"
#endif
