#include <common/dib.hpp>
#include <common/vector2d.hpp>
#include <common/bitmap.hpp>
#include <common/boverlay.hpp>
#include <common/resbmp.hpp>
#include <common/math.hpp>

void DIBitmap::createBitmap(const PureDevice &somePureDevice)
{
	destroyBitmap();
	mhBitmap=::CreateDIBSection(somePureDevice,(BITMAPINFO*)(static_cast<BitmapInfo&>(*this)),DIB_RGB_COLORS,&mlpBmBits,(HANDLE)0,0L);
	if(!mhBitmap)::GetLastError();
	if(mhBitmap)mImageExtent=BitmapInfo::imageExtent();
}

void DIBitmap::createBitmap(void)
{
  destroyBitmap();
	mhBitmap=::CreateDIBSection((HDC)0,(BITMAPINFO*)(static_cast<BitmapInfo&>(*this)),DIB_RGB_COLORS,&mlpBmBits,(HANDLE)0,0L);
	if(mhBitmap)mImageExtent=BitmapInfo::imageExtent();
}

WORD DIBitmap::size(DWORD newWidth,DWORD newHeight)
{
  if(!isOkay())return FALSE;
	verifyDimensions(newWidth,newHeight);
	width(newWidth);
	height(newHeight);
	createBitmap(mBitmapDevice);
	mBitmapDevice.select(mhBitmap);
  return isOkay();
}

void DIBitmap::verifyDimensions(DWORD &desiredWidth,DWORD desiredHeight)const
{
	DWORD imageExtent;

	imageExtent=(((((LONG)desiredWidth*8)+31)&~31)>>3)*(LONG)desiredHeight;
	if(imageExtent==(LONG)desiredWidth*(LONG)desiredHeight)return;
	desiredWidth=(WORD)(imageExtent/(LONG)desiredHeight);
}

void DIBitmap::setBits(BYTE charByte)
{
  if(!charByte)clearBits();
  BYTE *lpImagePtr=(BYTE*)mlpBmBits;
  for(DWORD ptrIndex=0;ptrIndex<imageExtent();ptrIndex++)*lpImagePtr++=charByte;
}

void DIBitmap::clearBits(void)
{
	DWORD *lpImagePtr=(DWORD*)mlpBmBits;
  DWORD extent(imageExtent()/sizeof(DWORD));
	for(unsigned long ptrIndex=0;ptrIndex<extent;ptrIndex++)*lpImagePtr++=0L;
}

bool DIBitmap::copyBits(unsigned char *ptrDataChar,DWORD length)
{
  DWORD writeExtent=length<=imageExtent()?length:imageExtent();
	DWORD numDWORDS=writeExtent>>2;
  DWORD numBytes=0;
	int *ptrSrcDataInt=0;
	int *ptrDstDataInt=0;
	char *ptrSrcDataChar;
	char *ptrDstDataChar;

  if(!writeExtent)return false;
	numBytes=(writeExtent%writeExtent);
	ptrSrcDataInt=(int*)ptrDataChar;
	ptrDstDataInt=(int*)mlpBmBits;
	for(DWORD count=0;count<numDWORDS;count++)*ptrDstDataInt++=*ptrSrcDataInt++;
	ptrSrcDataChar=(char*)ptrSrcDataInt;
	ptrDstDataChar=(char*)ptrDstDataInt;
	for(count=0;count<numBytes;count++)*ptrDstDataChar++=*ptrSrcDataChar++;
	return true;
}

void DIBitmap::copyBits(unsigned char *lpDstPtr,unsigned char *lpSrcPtr,DWORD length)const
{
	for(DWORD count=0;count<length;count++)*lpDstPtr++=*lpSrcPtr++;
}

DIBitmap &DIBitmap::operator+=(BitmapOverlay &someBitmapOverlay)
{
  overlay(someBitmapOverlay.placementPoint(),someBitmapOverlay.bitmap().width(),someBitmapOverlay.bitmap().height(),someBitmapOverlay.bitmap().getDataPtr());
	return *this;
}

bool DIBitmap::getAt(SmartPointer<DIBitmap> &bitmap,const Rect &areaRect)
{
	bitmap.destroy();
	bitmap=::new DIBitmap(getDevice(),areaRect.width(),areaRect.height(),getPalette());
	bitmap.disposition(PointerDisposition::Delete);
	for(int col=areaRect.left(),dstCol=0;col<=areaRect.right();col++,dstCol++)
	{
	  for(int row=areaRect.top(),dstRow=0;row<=areaRect.bottom();row++,dstRow++)
		{
		  bitmap->setByte(dstRow,dstCol,getByte(row,col));
		}
	}
	return true;
}

bool DIBitmap::overlay(Bitmap &bitmap,const Point &placementPoint)
{
  return overlay(placementPoint,bitmap.width(),bitmap.height(),bitmap.getDataPtr());
}

bool DIBitmap::overlay(ResBitmap &resBitmap,const Point &placementPoint)
{
  return overlay(placementPoint,resBitmap.width(),resBitmap.height(),resBitmap.ptrData());
}

bool DIBitmap::overlay(DIBitmap &bitmap,const Point &placementPoint)
{
  return overlay(placementPoint,bitmap.width(),bitmap.height(),(unsigned char*)bitmap.ptrData());
} 

bool DIBitmap::overlay(const Point &placementPoint,int srcWidth,int srcHeight,UCHAR *pBmBits)
{
	DWORD newSrcY;
	DWORD newDstY;
	DWORD srcIndex;
	DWORD dstIndex;
	UINT srcRow;
  UINT srcCol;

	if(!isOkay())return false;
	if(srcWidth>width())srcWidth=width();
	if(srcHeight>height())srcHeight=height();
	for(srcRow=0;srcRow<srcHeight;srcRow++)
	{
		for(srcCol=0;srcCol<srcWidth;srcCol++)
		{
			newSrcY=srcRow;
			srcIndex=newSrcY*srcWidth+srcCol;
			newDstY=height()-srcHeight-placementPoint.y()+srcRow;
			dstIndex=newDstY*width()+srcCol+placementPoint.x();
			 *((char*)mlpBmBits+dstIndex)=*(pBmBits+srcIndex);
		}
	}
	return true;
}

void DIBitmap::colorRect(const Rect &rect,BYTE byteValue)
{
  int bmHeight=rect.height();
  for(int row=0;row<bmHeight;row++)
	{
	  line(Point(rect.left(),rect.top()+row),Point(rect.right(),rect.top()+row),byteValue);
	}
}

void DIBitmap::outlineRect(const Rect &outlineRect,BYTE byteValue)
{
  Point p1;
	Point p2;

	p1.x(outlineRect.left());
	p1.y(outlineRect.top());
	p2.x(outlineRect.right());
	p2.y(outlineRect.top());
	line(p1,p2,byteValue);
  p1.x(outlineRect.right());
	p1.y(outlineRect.top());
	p2.x(outlineRect.right());
	p2.y(outlineRect.bottom());
	line(p1,p2,byteValue);
  p1.x(outlineRect.left());
	p1.y(outlineRect.bottom());
	p2.x(outlineRect.right());
	p2.y(outlineRect.bottom());
	line(p1,p2,byteValue);
  p1.x(outlineRect.left());
	p1.y(outlineRect.top());
	p2.x(outlineRect.left());
	p2.y(outlineRect.bottom());
	line(p1,p2,byteValue);
}

void DIBitmap::line(const Point &firstPoint,const Point &secondPoint,BYTE byteValue)
{
	int xRunning((LONG)firstPoint.x()<<0x10);
	int yRunning((LONG)firstPoint.y()<<0x10);
	int xDelta;
	int yDelta;
	short xDir(1);
	short yDir(1);
	short steps;

	if(secondPoint.x()<firstPoint.x())xDir=-1;
	if(secondPoint.y()<firstPoint.y())yDir=-1;
	xDelta=(int)secondPoint.x()-(int)firstPoint.x();
	yDelta=(int)secondPoint.y()-(int)firstPoint.y();
	if(xDelta<0)xDelta=-xDelta;
  if(yDelta<0)yDelta=-yDelta;
	if(xDelta<yDelta)
	{
		xDelta<<=0x10;
		if(yDelta)xDelta/=yDelta;
    else xDelta=1L;
		steps=yDelta;
    yDelta=0x10000;
	}
	else
	{
		yDelta<<=0x10;
		if(xDelta)yDelta/=xDelta;
    else yDelta=1L;
		steps=xDelta;
    xDelta=0x10000;
	}
	if(-1==xDir&&-1==yDir)
	{
		for(short stepIndex=0;stepIndex<steps;stepIndex++)
		{
      setByte(yRunning>>0x10,xRunning>>0x10,byteValue);
      xRunning-=xDelta;
			yRunning-=yDelta;
		}
	}
	else if(-1==xDir&&1==yDir)
	{
		for(short stepIndex=0;stepIndex<steps;stepIndex++)
		{
      setByte(yRunning>>0x10,xRunning>>0x10,byteValue);
			xRunning-=xDelta;
      yRunning+=yDelta;
    }
	}
	else if(1==xDir&&-1==yDir)
	{
		for(short itemIndex=0;itemIndex<steps;itemIndex++)
		{
      setByte(yRunning>>0x10,xRunning>>0x10,byteValue);
			xRunning+=xDelta;
			yRunning-=yDelta;
		}
	}
	else if(1==xDir&&1==yDir)
	{
		for(short itemIndex=0;itemIndex<steps;itemIndex++)
		{
      setByte(yRunning>>0x10,xRunning>>0x10,byteValue);
			xRunning+=xDelta;
      yRunning+=yDelta;
    }
  }
}

WORD DIBitmap::square(const Point &centerPoint,WORD lineLength,BYTE palIndex)
{
	WORD halfLength(lineLength>>0x01);
	Point topLeft;
	Point topRight;
	Point bottomLeft;
	Point bottomRight;

	if(!isOkay())return FALSE;
	topLeft.x(centerPoint.x()-halfLength);
	topLeft.y(centerPoint.y()-halfLength);
	topRight.x(centerPoint.x()+halfLength);
	topRight.y(centerPoint.y()-halfLength);
	bottomRight.x(centerPoint.x()+halfLength);
	bottomRight.y(centerPoint.y()+halfLength);
	bottomLeft.x(centerPoint.x()-halfLength);
	bottomLeft.y(centerPoint.y()+halfLength);
	line(topLeft,topRight,palIndex);
	line(topRight,bottomRight,palIndex);
	line(bottomRight,bottomLeft,palIndex);
	line(bottomLeft,topLeft,palIndex);
	return TRUE;
}

WORD DIBitmap::circle(WORD aspectValue,const Point &xyPoint,WORD radius,WORD palIndex)
{
	int a(radius);
	int af;
	int bf;
	int b(0);
	int target(1);
	int radiusSquared(radius*radius);

	if(!isOkay())return FALSE;
	while(a>=b)
	{
    b=(int)(Math::sqrt(radiusSquared-(a*a))+.50);
		int temp(target);
		target=b;
		b=temp;
		while(b<target)
		{
		  if(100.00!=aspectValue)af=(aspectValue*a)/100.00,bf=(aspectValue*b)/100.00;
			else af=a,bf=b;
			setByte(xyPoint.x()+af,xyPoint.y()+b,palIndex);
			setByte(xyPoint.x()+bf,xyPoint.y()+a,palIndex);
			setByte(xyPoint.x()-af,xyPoint.y()+b,palIndex);
			setByte(xyPoint.x()-bf,xyPoint.y()+a,palIndex);
			setByte(xyPoint.x()-af,xyPoint.y()-b,palIndex);
			setByte(xyPoint.x()-bf,xyPoint.y()-a,palIndex);
			setByte(xyPoint.x()+af,xyPoint.y()-b,palIndex);
			setByte(xyPoint.x()+bf,xyPoint.y()-a,palIndex);
      ++b;
		}
		--a;
	}
  return TRUE;
}


