#include <common/math.hpp>
#include <common/purehdc.hpp>
#include <common/window.hpp>
#include <common/brush.hpp>

PureDevice::PureDevice(const Window &someWindow)
: mhDC(0), mhParent((HWND)someWindow), mDisposition(ReleaseDC), mGDIObjPrev(0), mPrevDrawMode(INVALIDROP)
{
	if(!mhParent||!::IsWindow(mhParent))return;
	mhDC=::GetDC(mhParent);
}

PureDevice::~PureDevice()
{
  unselect();
	destroyDevice();
}

WORD PureDevice::isOkay(void)const
{
	switch(mDisposition)
	{
		case DeleteDC :
		case AssumedDC :
			return (mhDC?TRUE:FALSE);
		case ReleaseDC :
      return (mhDC&&(mhParent?::IsWindow(mhParent):TRUE));
		case InvalidDC :
		default :
			return FALSE;
	}
}

void PureDevice::destroyDevice(void)
{
	if(mhDC)
	{
    setDrawMode(INVALIDROP);
		if(DeleteDC==mDisposition&&isOkay())::DeleteDC(mhDC);
		else if(ReleaseDC==mDisposition&&isOkay())::ReleaseDC(mhParent,mhDC);
		mhDC=0;
		mhParent=0;
		mDisposition=InvalidDC;
	}
}

WORD PureDevice::select(const GDIObj someGDIObj,WORD selState)
{
	if(!isOkay()||!someGDIObj)return FALSE;
	if(selState)
	{
		if(mGDIObjPrev)::SelectObject(mhDC,mGDIObjPrev);
		mGDIObjPrev=::SelectObject(mhDC,someGDIObj);
		return TRUE;
	}
	if(!mGDIObjPrev)return FALSE;
	::SelectObject(mhDC,mGDIObjPrev);
	mGDIObjPrev=0;
	return TRUE;
}

WORD PureDevice::compatibleDevice(const PureDevice &somePureDevice)
{
	if(!somePureDevice.isOkay())return FALSE;
	destroyDevice();
	mhDC=::CreateCompatibleDC((HDC)somePureDevice);
	mDisposition=DeleteDC;
	return isOkay();
}

WORD PureDevice::compatibleDevice(void)
{
	destroyDevice();
	mhDC=::CreateCompatibleDC(0);
	mDisposition=DeleteDC;
	return isOkay();
}

WORD PureDevice::displayDevice(void)
{
	destroyDevice();
	mhDC=::CreateDC("DISPLAY",0,0,0);
	mDisposition=DeleteDC;
	return isOkay();
}

WORD PureDevice::screenDevice(void)
{
	destroyDevice();
	mhDC=::GetDC(0);
	mDisposition=ReleaseDC;
  return isOkay();
}

WORD PureDevice::roundRectangle(const Point &centerPoint,int radius)
{
  if(!isOkay())return false;
	Point upperLeft;
	Point lowerRight;
	int diameter;

	diameter=radius*2;
  upperLeft.x(centerPoint.x()-radius);
	upperLeft.y(centerPoint.y()-radius);
	lowerRight.x(centerPoint.x()+radius);
	lowerRight.y(centerPoint.y()+radius);
	return roundRectangle(upperLeft,lowerRight,diameter,diameter);
}

WORD PureDevice::floodFill(const Point &xyPoint,const RGBColor &fillColor,const RGBColor &boundaryColor)const
{
	HBRUSH hFillBrush(::CreateSolidBrush(fillColor));
	HBRUSH hPrevBrush((HBRUSH)::SelectObject(mhDC,hFillBrush));
	WORD returnCode;

	returnCode=::FloodFill(mhDC,xyPoint.x(),xyPoint.y(),boundaryColor);
	::SelectObject(mhDC,hPrevBrush);
	::DeleteObject(hFillBrush);
	return returnCode;
}

BOOL PureDevice::fillRect(const Rect &rect,Brush &brush)
{
  if(!isOkay())return FALSE;
	return ::FillRect(mhDC,(RECT*)((Rect&)rect),(HBRUSH)(GDIObj)brush);
}

WORD PureDevice::colorRect(const Rect &rect,const RGBColor &lineColor)const
{
  if(!isOkay())return FALSE;
	for(short index=rect.left();index<=rect.right();index++)
		line(Point(index,rect.bottom()),Point(index,rect.top()),lineColor);
	return TRUE;
}

WORD PureDevice::circle(Window &displayWindow,const Point &xyPoint,WORD radius,const RGBColor &someRGBColor)const
{
	return circle(aspectValue(displayWindow),xyPoint,radius,someRGBColor);
}

WORD PureDevice::outlineRect(const Rect &rect,const Pen &pen)
{
  Point p1;
	Point p2;

	if(!isOkay())return FALSE;
  p1.x(rect.left());
  p1.y(rect.top());
	p2.x(rect.right());
	p2.y(rect.top());
	line(p1,p2,pen);

  p1.x(rect.right());
  p1.y(rect.top());
	p2.x(rect.right());
	p2.y(rect.bottom());
	line(p1,p2,pen);

  p1.x(rect.right());
  p1.y(rect.bottom());
	p2.x(rect.left());
	p2.y(rect.bottom());
	line(p1,p2,pen);

  p1.x(rect.left());
  p1.y(rect.bottom());
	p2.x(rect.left());
	p2.y(rect.top());
	line(p1,p2,pen);
	return TRUE;
}

WORD PureDevice::line(const Point &firstPoint,const Point &secondPoint,const Pen &somePen)const
{
	if(!isOkay())return FALSE;
	HPEN hPrevPen((HPEN)::SelectObject(mhDC,(HPEN)somePen));
  lineDraw(firstPoint,secondPoint);
	::SelectObject(mhDC,hPrevPen);
	return TRUE;
}

WORD PureDevice::line(const Point &firstPoint,const Point &secondPoint)const
{
	if(!isOkay())return FALSE;
  lineDraw(firstPoint,secondPoint);
	return TRUE;
}

WORD PureDevice::crossHair(const Point &midPoint,WORD sectionLength,const Pen &somePen)
{
	Point leftPoint(midPoint);
	Point topPoint(midPoint);
	Point rightPoint(midPoint);
	Point bottomPoint(midPoint);
  HPEN hPrevPen;

	if(!isOkay())return FALSE;
	hPrevPen=(HPEN)::SelectObject(mhDC,(HPEN)somePen);
	leftPoint.x(leftPoint.x()-sectionLength);
	rightPoint.x(rightPoint.x()+sectionLength);
	topPoint.y(topPoint.y()-sectionLength);
	bottomPoint.y(bottomPoint.y()+sectionLength);
	lineDraw(leftPoint,rightPoint);
	lineDraw(topPoint,bottomPoint);
	::SelectObject(mhDC,hPrevPen);
  return TRUE;
}

WORD PureDevice::crossHairs(Array<Point> &midPoints,WORD sectionLength,const Pen &somePen)
{
	WORD itemCount;
	Point leftPoint;
	Point topPoint;
	Point rightPoint;
	Point bottomPoint;
	HPEN hPrevPen;

	if(!isOkay()||!(itemCount=midPoints.size()))return FALSE;
	hPrevPen=(HPEN)::SelectObject(mhDC,(HPEN)somePen);
	for(short itemIndex=0;itemIndex<itemCount;itemIndex++)
	{
		leftPoint=midPoints[itemIndex];
		topPoint=leftPoint;
		rightPoint=leftPoint;
		bottomPoint=leftPoint;
		leftPoint.x(leftPoint.x()-sectionLength);
		rightPoint.x(rightPoint.x()+sectionLength);
		topPoint.y(topPoint.y()-sectionLength);
		bottomPoint.y(bottomPoint.y()+sectionLength);
		lineDraw(leftPoint,rightPoint);
    lineDraw(topPoint,bottomPoint);
	}
	::SelectObject(mhDC,hPrevPen);
  return TRUE;
}

WORD PureDevice::crossHairs(Array<Point> &midPoints,WORD sectionLength,RGBColor rgbColor)
{
	WORD itemCount;
	Point leftPoint;
	Point topPoint;
	Point rightPoint;
	Point bottomPoint;

	if(!isOkay()||!(itemCount=midPoints.size()))return FALSE;
	for(short itemIndex=0;itemIndex<itemCount;itemIndex++)
	{
		leftPoint=midPoints[itemIndex];
		topPoint=leftPoint;
		rightPoint=leftPoint;
		bottomPoint=leftPoint;
		leftPoint.x(leftPoint.x()-sectionLength);
		rightPoint.x(rightPoint.x()+sectionLength);
		topPoint.y(topPoint.y()-sectionLength);
		bottomPoint.y(bottomPoint.y()+sectionLength);
		line(leftPoint,rightPoint,rgbColor);
    line(topPoint,bottomPoint,rgbColor);
	}
  return TRUE;
}

WORD PureDevice::circle(WORD aspectValue,const Point &xyPoint,WORD radius,const RGBColor &someRGBColor)const
{
	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;
			setPixel(Point(xyPoint.x()+af,xyPoint.y()+b),someRGBColor);
			setPixel(Point(xyPoint.x()+bf,xyPoint.y()+a),someRGBColor);
			setPixel(Point(xyPoint.x()-af,xyPoint.y()+b),someRGBColor);
			setPixel(Point(xyPoint.x()-bf,xyPoint.y()+a),someRGBColor);
			setPixel(Point(xyPoint.x()-af,xyPoint.y()-b),someRGBColor);
			setPixel(Point(xyPoint.x()-bf,xyPoint.y()-a),someRGBColor);
			setPixel(Point(xyPoint.x()+af,xyPoint.y()-b),someRGBColor);
			setPixel(Point(xyPoint.x()+bf,xyPoint.y()-a),someRGBColor);
      ++b;
		}
		--a;
	}
  return TRUE;
}

PureDevice &PureDevice::operator=(const PureDevice &somePureDevice)
{
	if(!somePureDevice.isOkay())return *this;
	destroyDevice();
	mDisposition=AssumedDC;
	mhDC=(HDC)somePureDevice;
	return *this;
}

PureDevice &PureDevice::operator=(const Window &someWindow)
{
  return *this=(HWND)someWindow;
}

PureDevice &PureDevice::operator=(HWND hWnd)
{
	if(!hWnd)return *this;
	destroyDevice();
	mhParent=hWnd;
	mhDC=::GetDC(mhParent);
	mDisposition=ReleaseDC;
	return *this;
}

WORD PureDevice::horizontalResolution(void)const
{
	if(!isOkay())return FALSE;
	return ::GetDeviceCaps(mhDC,HORZRES);
}

WORD PureDevice::verticalResolution(void)const
{
	if(!isOkay())return FALSE;
	return ::GetDeviceCaps(mhDC,VERTRES);
}

WORD PureDevice::aspectValue(const Window &someWindow)const
{
	if(!isOkay())return FALSE;
	return (((1.00/((float)horizontalResolution()/(float)verticalResolution()))*((float)someWindow.width()/(float)someWindow.height()))*100);
}

WORD PureDevice::square(const Point &centerPoint,WORD lineLength,RGBColor rgbColor)
{
	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,rgbColor);
	line(topRight,bottomRight,rgbColor);
	line(bottomRight,bottomLeft,rgbColor);
  line(bottomLeft,topLeft,rgbColor);
  return TRUE;
}

WORD PureDevice::line(const Point &firstPoint,const Point &secondPoint,const RGBColor &rgbColor)const
{
	LONG xRunning(firstPoint.x());
	LONG yRunning(firstPoint.y());
	LONG xDelta;
	LONG yDelta;
	short xDir(1);
	short yDir(1);
	short steps;

  if(!isOkay())return FALSE;
	xRunning<<=0x10;
	yRunning<<=0x10;
	if(secondPoint.x()<firstPoint.x())xDir=-1;
	if(secondPoint.y()<firstPoint.y())yDir=-1;
	xDelta=secondPoint.x()-firstPoint.x();
	yDelta=secondPoint.y()-firstPoint.y();
	if(xDelta<0L)xDelta=-xDelta;
  if(yDelta<0L)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++)
		{
      setPixel(Point(xRunning>>0x10,yRunning>>0x10),rgbColor);
      xRunning-=xDelta;
			yRunning-=yDelta;
		}
	}
	else if(-1==xDir&&1==yDir)
	{
		for(short stepIndex=0;stepIndex<steps;stepIndex++)
		{
      setPixel(Point(xRunning>>0x10,yRunning>>0x10),rgbColor);
			xRunning-=xDelta;
      yRunning+=yDelta;
    }
	}
	else if(1==xDir&&-1==yDir)
	{
		for(short itemIndex=0;itemIndex<steps;itemIndex++)
		{
      setPixel(Point(xRunning>>0x10,yRunning>>0x10),rgbColor);
			xRunning+=xDelta;
			yRunning-=yDelta;
		}
	}
	else if(1==xDir&&1==yDir)
	{
		for(short itemIndex=0;itemIndex<steps;itemIndex++)
		{
      setPixel(Point(xRunning>>0x10,yRunning>>0x10),rgbColor);
			xRunning+=xDelta;
      yRunning+=yDelta;
    }
	}
  return TRUE;
}

