/******************************************************************************
 *
 * Copyright (c) 1999	TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *  
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *	DIBSection.h
 *
 *****************************************************************************/

// SOL++2000
// 2000.07.26 Updated - Some methods are experimetal.

#include <sol\DIBSection.h>
#include <sol\Stdio.h>
#include <sol\BinaryFile.h>


DIBSection::DIBSection()
	:GdiObject(NULL), memDC(), hbitmap(NULL),hprevBitmap(NULL),
	bi(NULL), 
	buffer(NULL)
{ 
	sx			= 0;
	sy			= 0;
	ratio		= 100;		
	operation	= SRCCOPY;
	bitCount	= 32;
	bmiSize     = 0;
	colorCount  = 0;
	bmiSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * colorCount;
}



DIBSection::DIBSection(DC& dc, int width, int height)
	:GdiObject(NULL), memDC(dc), hbitmap(NULL), hprevBitmap(NULL),
	bi(NULL), 
	bmiSize(0),
	buffer(NULL)
{ 
	sx			= 0;
	sy			= 0;
	ratio		= 100;	 
	operation	= SRCCOPY;
	bitCount    = 32;
	colorCount  = getColorCount(bitCount);

	bmiSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * colorCount;

	bi = (BITMAPINFO*)new char[bmiSize];
	memset(bi, 0, bmiSize);

	if (width > 0 && height > 0) {
	
		bi->bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
		bi->bmiHeader.biWidth       = width;
		bi->bmiHeader.biHeight      = height;
		bi->bmiHeader.biPlanes      = 1;
		bi->bmiHeader.biBitCount    = (WORD)bitCount; //2000/11/18
		bi->bmiHeader.biCompression = BI_RGB;

		Printf("DIBSection::DIBSection(DC& dc, int width, int height, int count=%d\r\n",
				bitCount);

		hbitmap = ::CreateDIBSection(memDC.get(), bi,	
				DIB_RGB_COLORS, (void**)&buffer, NULL, 0);

		if (hbitmap && buffer) {
			hprevBitmap = (HBITMAP)memDC.select(hbitmap);
			
		} else {
			destroy();
			MessageBox(NULL, "Failed to create DIBSection", "SOL++ Fatal Error",
				MB_OK);
		}
	}
}


DIBSection::~DIBSection()
{
	destroy();
}

void DIBSection::destroy()
{
	if (hbitmap) {
		memDC.select(hprevBitmap);

		DIBSECTION ds;
		::GetObject(hbitmap, sizeof(ds), &ds);
		::CloseHandle(ds.dshSection);
		::DeleteObject(hbitmap);
		hbitmap = NULL;
		hprevBitmap = NULL;
	}

	char* tmp = (char*)bi;
	delete [] tmp;
	bi = NULL;

	buffer    = NULL;
}


int DIBSection::getWidth() 
{
	int w = 0;
	if (bi) {
		w = bi->bmiHeader.biWidth;
	}
	return w;
}


int DIBSection::getHeight() 
{
	int h = 0;
	if (bi) {
		h = bi->bmiHeader.biHeight;
	}
	return h;
}


void	DIBSection::clear()
{
	for(int y = 0; y<getHeight(); y++) {
		for(int x = 0; x<getWidth(); x++) {
			setPixel(x, y, RGB(255, 255, 255));
		}
	}

	flush();
}


HBITMAP DIBSection::create(DC& dc, int width, int height)
{
	if (width <=0 || height <= 0) {
		return NULL;
	}

	destroy();

	bitCount = 32;
	colorCount = getColorCount(bitCount);

	int	size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * colorCount;

	bi = (BITMAPINFO*)new char[size];
	memset(bi, 0, size);
		
	Printf("DIBSection::create/4 bitCount=%d\r\n", bitCount);

	memset(&(bi->bmiHeader), 0, sizeof(bi->bmiHeader));

	bi->bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);

	bi->bmiHeader.biWidth       = width;
	bi->bmiHeader.biHeight      = height;
	bi->bmiHeader.biPlanes      = 1;
	bi->bmiHeader.biBitCount    = (WORD)bitCount; //2000/11/18
	bi->bmiHeader.biCompression = BI_RGB;
	
	HDC hdc = dc.get();
		
	HDC cdc = ::CreateCompatibleDC(hdc);
		
	memDC.set(cdc);
	buffer     = NULL;

	hbitmap = ::CreateDIBSection(cdc, bi,
					DIB_RGB_COLORS, (void**)&buffer, NULL, 0);
		
	if (hbitmap && buffer) {
		hprevBitmap = (HBITMAP)memDC.select(hbitmap);
	} else {
		destroy();
		MessageBox(NULL, "Failed to create DIBSection", "SOL++ Fatal Error",
				MB_OK);
	}
	return hbitmap;
}


HBITMAP DIBSection::create(DC& dc) 
{
	if (bi == NULL) {
		return NULL;
	}

	HDC hdc = dc.get();
		
	HDC cdc = ::CreateCompatibleDC(hdc);
		
	memDC.set(cdc);

	int width  = bi->bmiHeader.biWidth;
	int height = bi->bmiHeader.biHeight ;
	
	Printf("DIBSection::create  bitCount %d\r\n", bitCount);
	
	buffer    = NULL;

	hbitmap = ::CreateDIBSection(cdc, bi,
					DIB_RGB_COLORS, (void**)&buffer, NULL, 0);

	if (hbitmap && buffer) {
		hprevBitmap = (HBITMAP)memDC.select(hbitmap);
	} else {
		destroy();
		MessageBox(NULL, "Failed to create DIBSection", "SOL++ Fatal Error",
		MB_OK);
	}

	return hbitmap;
}


unsigned char DIBSection::getColorIndex(COLORREF color)
{
	unsigned char index = 0;

	if (bitCount <16) {

		if (bi && hbitmap ) {

			RGBQUAD* rgb = bi->bmiColors;

			for(int i = 0; i<colorCount; i++) {
				if (color == RGB(rgb->rgbRed, rgb->rgbGreen, rgb->rgbBlue)) {
					index = (unsigned char)i; //2000/11/18
					break;
				}
				rgb++;
			}
		}
	}
	return index;
}


RGBTRIPLE* DIBSection::getRGBTriple(int x, int y)
{
	unsigned char* triple = NULL;

	int width  = getWidth();
	int height = getHeight();
	
	if (bitCount == 24 && width >0 && height > 0) {
			
		if (buffer && x >= 0 && x <width &&
				y >= 0 && y < height && hbitmap ) {

			unsigned char* tripleBuffer    = buffer;
			
			int dwLength = 0;
			int twidth = width * 3;
			if ( ((twidth) % 4) ==0) {
				dwLength = twidth;
			}
			else {
				dwLength = twidth + (4 - twidth % 4);
            }
			
			triple = (tripleBuffer + (height - y -1) * dwLength + x * 3);
			unsigned char* top = buffer + (height) * dwLength;

			if (triple >= top) {
				return NULL;
			}
		}
	}
	return (RGBTRIPLE*)triple;
}


WORD DIBSection::getColor(int x, int y)
{
	WORD color = 0;
	int width  = getWidth();
	int height = getHeight();

	if (bitCount == 16 && width >0 && height > 0) {	
		if (buffer && x >= 0 && x <width &&
			y >= 0 && y <height && hbitmap ) {
			
			WORD* colorBuffer    = (WORD*)buffer;

			WORD* buff = colorBuffer + (height - y -1) * width + x;
			WORD* top  = colorBuffer + (height) * width;

			if (buff >= top) {
				return color;
			}
			color = *buff;
		}	
	}
	return color;
}


RGBQUAD* DIBSection::getRGBQuad(int x, int y) 
{
	RGBQUAD* quad = NULL;

	if (bi == NULL || buffer == NULL || hbitmap == NULL) {
		return quad;
	}

	int width  = getWidth();
	int height = getHeight();

	if (width <= 0 || height <= 0) {
		return quad;
	}

	if (x <0 || x >= width || y <0 || y >= height) {
		return quad;
	}

	if (bitCount==32) {
		RGBQUAD* quadBuffer    = (RGBQUAD*)buffer;
		quad = quadBuffer + (height - y -1)*width + x;
		RGBQUAD* top = quadBuffer + (height)*width ;

		if (quad >= top) {
			return NULL;
		}
		return quad;
	}

	// Sorry, not implemented for case of bitCount = 16;

	if (bitCount == 8) {
		quad = NULL;
			
		int nwidth = width;
		nwidth = (nwidth + 3)/4 *4;

		unsigned char* buff = (buffer + (height- y -1) * nwidth + x);
		unsigned char* top  = buffer + (height) * nwidth;
		
		if (buff < top) {
			unsigned char index = *buff;
			if (index >=0 && index <colorCount) {
				RGBQUAD* rgbQuad = bi->bmiColors;
				if (rgbQuad) {
					quad = &rgbQuad[index];
				}
			}
		}
		return quad;
	}


	if (bitCount == 4) {
		quad = NULL;

		int nwidth = (width +7)/8 * 8;
		unsigned char* buff = buffer + ((height- y -1) * nwidth + x)/2;
		unsigned char* top  = buffer + (height ) * nwidth;

		if (buff >= top) {
			return quad;
		}

		unsigned char index = *buff;
					
		if ((x % 2) == 0) {
			index = (unsigned char)(index >>4);

		} else {
			index = (unsigned char)(index & 0xf);
		}

		if (index >=0 && index <colorCount) {
			RGBQUAD* rgbQuad = bi->bmiColors;
			if (rgbQuad) {
				quad = &rgbQuad[index];
			}
		}

		return quad;
	}
	
	return quad;
}


COLORREF DIBSection::getPixel(int x, int y) 
{
	
	COLORREF color = 0;

	if (bitCount == 24) {
		RGBTRIPLE* triple = getRGBTriple(x, y);
		if (triple) {
			color = RGB(triple->rgbtRed, triple->rgbtGreen, triple->rgbtBlue);
		}
		return color;
	}

	// bitCount == 32, 8, 4
	RGBQUAD*  quad = getRGBQuad(x, y);
	color = 0;	
	if (quad) {
		color = RGB(quad->rgbRed, quad->rgbGreen, quad->rgbBlue);
	}
	return color;
}


BOOL DIBSection::getPixel(int x, int y, int& r, int& g, int& b) 
{
	BOOL	rc = FALSE;

	if (bitCount == 24) {
		RGBTRIPLE* triple = getRGBTriple(x, y);
		if (triple) {
			r  = triple->rgbtRed;
			g  = triple->rgbtGreen;
			b  = triple->rgbtBlue;
			rc = TRUE;
		} 
		return rc;		
	}

	RGBQUAD*	quad = getRGBQuad(x, y);
	if (quad) {
		r  = quad->rgbRed;
		g  = quad->rgbGreen;
		b  = quad->rgbBlue;
		rc = TRUE;
	}
	return rc;
}


void DIBSection::setPixel(int x, int y, COLORREF color) 
{
	if (bi == NULL || buffer == NULL || hbitmap == NULL) {
		return;
	}

	int width  = getWidth();
	int height = getHeight();

	if (width <= 0 || height <= 0) {
		return;	
	}

	if (x < 0 || x >=width || y <0 || y >= height) {
		return;
	}

	if (bitCount == 32) {
		RGBQUAD*	quad = getRGBQuad(x, y);
		if (quad) {
			quad->rgbRed   = GetRValue(color);
			quad->rgbGreen = GetGValue(color);
			quad->rgbBlue  = GetBValue(color);
		}
	}

	if (bitCount == 24) {
		RGBTRIPLE*	quad = getRGBTriple(x, y);
		if (quad) {
			quad->rgbtRed   = GetRValue(color);
			quad->rgbtGreen = GetGValue(color);
			quad->rgbtBlue  = GetBValue(color);
		}
	}

	// Sorry, not implemented for the cases of lower bitCount.
	/*
	*/
}


void DIBSection::draw(DC& dc, int x, int y)
{
	draw(dc.get(), x, y);
}


void DIBSection::draw(HDC hdc, int x, int y)
{
	if(hbitmap) {
		int w = getWidth();
		int h = getHeight();
		::StretchBlt(hdc,   x, y, w*ratio/100, h*ratio/100,
		   	   memDC.get(), sx, sy, w, h, operation);
	}
}



int DIBSection::getColorCount(BITMAPINFOHEADER* bi)
{
	if (bi == NULL) {
		return 0;
	}

	if (bi->biClrUsed != 0) {
		return (int)bi->biClrUsed;
	}

	return getColorCount(bi->biBitCount);
}



int DIBSection::getColorCount(int bits)
{
    switch (bits){
        case 1:
			return 2;
        case 4:
			return 16;
        case 8:
			return 256;
        default:
			break;
    }
	return 0;
}


BOOL DIBSection::load(DC& dc, const char* filename)
{
	BinaryFile bmpFile;
	BOOL rc = FALSE;

	const int MAX =	16000;
	
	if (bmpFile.openReadOnly(filename)) {
		char* fileHeader = new char[MAX];
		bmpFile.read(0, fileHeader, MAX);
		
		BITMAPFILEHEADER* fh = (BITMAPFILEHEADER*)fileHeader;
		DWORD offset = fh->bfOffBits;

		if(memcmp(fileHeader,"BM",2) != 0){
			return rc;
		}

		destroy();

		int  n = sizeof(BITMAPFILEHEADER);

		BITMAPINFOHEADER* bih = (BITMAPINFOHEADER*)&fileHeader[n];
		colorCount = getColorCount(bih);

		bmiSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * colorCount;

		char* bmi = new char[bmiSize];
		memcpy(bmi, &fileHeader[n], bmiSize);

		bi = (BITMAPINFO*)bmi;	
		
		bitCount = bi->bmiHeader.biBitCount;
		long filesize = bmpFile.seek(0, FILE_END);
		
		long imageSize = filesize - fh->bfOffBits;
	
		Printf("imageFile=[%s] filesize=[%d] bitCount=[%d]\r\n", filename, 
			filesize, bitCount);
		Printf("ImageSize = [%d]\r\n", filesize - fh->bfOffBits);
		Printf("width %d height = %d size = %d sizeImage = %d\r\n",
				bi->bmiHeader.biWidth, bi->bmiHeader.biHeight,
				bi->bmiHeader.biSize,
				bi->bmiHeader.biSizeImage);

		if (create(dc) == NULL){
			delete [] fileHeader;
			return rc;
		}

		long readSize = 0;
		long restSize = imageSize;

		char* ptr = (char*)buffer;
		int	  size = MAX;
		long  totalReadSize = 0;
		
		while(restSize> size) {		
			Printf("ptr %d  offset = %d  RequestSize=[%d]  restSize=[%d]\r\n",
					ptr, offset, size, restSize);
			readSize  = bmpFile.read(offset, ptr,  size);
			offset   += readSize;
			ptr      += readSize;
			restSize -= readSize;

			if (readSize == 0) {
				break;
			}
			totalReadSize += readSize;
		}

		if (restSize > 0) {
			Printf("Out ptr %d pos = %d  RequestSize=[%d]  rest=[%d]\r\n",
					ptr, offset, size, restSize);
			for(int i = 0; i<restSize; i++) {
				totalReadSize += bmpFile.read(offset++, ptr++, 1);
			}			
			Printf("Out totalReadSize=[%d] rest=[%d]\r\n",
					totalReadSize, restSize);
		}

		delete [] fileHeader;

		rc = TRUE;
		
		flush();
	} 
	return rc;
}
