#define ASCRIPT_DLL_MODULE
//-----------------------------------------------------------------------------
// AScript canvas module
//-----------------------------------------------------------------------------
#include "Module_canvas.h"

AScript_BeginModule(canvas)

AScript_DeclarePrivSymbol(red);
AScript_DeclarePrivSymbol(blue);
AScript_DeclarePrivSymbol(green);
AScript_DeclarePrivSymbol(cyan);
AScript_DeclarePrivSymbol(light_blue);
AScript_DeclarePrivSymbol(yellow);
AScript_DeclarePrivSymbol(black);
AScript_DeclarePrivSymbol(white);
AScript_DeclarePrivSymbol(grey);

AScript_DeclarePrivSymbol(solid);
AScript_DeclarePrivSymbol(dot);
AScript_DeclarePrivSymbol(dash);
AScript_DeclarePrivSymbol(dot_dash);

AScript_DeclarePrivSymbol(bdiagonal);
AScript_DeclarePrivSymbol(cross);
AScript_DeclarePrivSymbol(diagcross);
AScript_DeclarePrivSymbol(fdiagonal);
AScript_DeclarePrivSymbol(horizontal);
AScript_DeclarePrivSymbol(vertical);

AScript_DeclarePrivSymbol(normal);
AScript_DeclarePrivSymbol(default);
AScript_DeclarePrivSymbol(decorative);
AScript_DeclarePrivSymbol(roman);
AScript_DeclarePrivSymbol(script);
AScript_DeclarePrivSymbol(swiss);
AScript_DeclarePrivSymbol(modern);
AScript_DeclarePrivSymbol(teletype);
AScript_DeclarePrivSymbol(slant);
AScript_DeclarePrivSymbol(italic);
AScript_DeclarePrivSymbol(light);
AScript_DeclarePrivSymbol(bold);

AScript_DeclarePrivSymbol(n);
AScript_DeclarePrivSymbol(ne);
AScript_DeclarePrivSymbol(e);
AScript_DeclarePrivSymbol(se);
AScript_DeclarePrivSymbol(s);
AScript_DeclarePrivSymbol(sw);
AScript_DeclarePrivSymbol(w);
AScript_DeclarePrivSymbol(nw);
AScript_DeclarePrivSymbol(c);


AScript_DeclareClass(Canvas);

//-----------------------------------------------------------------------------
// Anchor
//-----------------------------------------------------------------------------
enum Anchor {
	ANCHOR_N,
	ANCHOR_NE,
	ANCHOR_E,
	ANCHOR_SE,
	ANCHOR_S,
	ANCHOR_SW,
	ANCHOR_W,
	ANCHOR_NW,
	ANCHOR_C,
};

void DeclareAnchorAttrs(Function *pFunc)
{
	pFunc->DeclareAttr(AScript_PrivSymbol(n));
	pFunc->DeclareAttr(AScript_PrivSymbol(ne));
	pFunc->DeclareAttr(AScript_PrivSymbol(e));
	pFunc->DeclareAttr(AScript_PrivSymbol(se));
	pFunc->DeclareAttr(AScript_PrivSymbol(s));
	pFunc->DeclareAttr(AScript_PrivSymbol(sw));
	pFunc->DeclareAttr(AScript_PrivSymbol(w));
	pFunc->DeclareAttr(AScript_PrivSymbol(nw));
	pFunc->DeclareAttr(AScript_PrivSymbol(c));
}

Anchor GetAnchor(const SymbolSet &attrs)
{
	Anchor anchor = ANCHOR_SW;
	if (attrs.IsSet(AScript_PrivSymbol(n))) {
		anchor = ANCHOR_N;
	} else if (attrs.IsSet(AScript_PrivSymbol(ne))) {
		anchor = ANCHOR_NE;
	} else if (attrs.IsSet(AScript_PrivSymbol(e))) {
		anchor = ANCHOR_E;
	} else if (attrs.IsSet(AScript_PrivSymbol(se))) {
		anchor = ANCHOR_SE;
	} else if (attrs.IsSet(AScript_PrivSymbol(s))) {
		anchor = ANCHOR_S;
	} else if (attrs.IsSet(AScript_PrivSymbol(sw))) {
		anchor = ANCHOR_SW;
	} else if (attrs.IsSet(AScript_PrivSymbol(w))) {
		anchor = ANCHOR_W;
	} else if (attrs.IsSet(AScript_PrivSymbol(nw))) {
		anchor = ANCHOR_NW;
	} else if (attrs.IsSet(AScript_PrivSymbol(c))) {
		anchor = ANCHOR_C;
	}
	return anchor;
}

//-----------------------------------------------------------------------------
// Color
//-----------------------------------------------------------------------------
struct Color {
	unsigned char _red, _blue, _green;
	inline Color() : _red(0), _blue(0), _green(0) {}
	inline Color(unsigned char red, unsigned char blue, unsigned char green) :
									_red(red), _blue(blue), _green(green) {}
	Color(Signal sig, const Value &value);
#if defined(WINVER)
	inline COLORREF ToWin32() const { return RGB(_red, _blue, _green); }
#endif
};

//-----------------------------------------------------------------------------
// Device
//-----------------------------------------------------------------------------
class Device {
protected:
	int _cntRef;
	Number _width, _height;
	Number _xCur, _yCur;
public:
	inline Device(Number width, Number height) : _cntRef(1),
						_width(width), _height(height), _xCur(0), _yCur(0) {}
	inline Device *IncRef() { _cntRef++; return this; }
	inline int DecRef() { _cntRef--; return _cntRef; }
	inline int GetRefCnt() const { return _cntRef; }
	inline static void Delete(Device *pDevice) {
		if (pDevice != NULL && pDevice->DecRef() <= 0) delete pDevice;
	}
	inline void SetCurrent(Number x, Number y) { _xCur = x, _yCur = y; }
	virtual ~Device();
	Value Initialize(Environment &env, Signal sig, const Function *pFuncBlock);
	virtual const TCHAR *GetName() const = 0;
	virtual void Close() = 0;
	virtual void Fill(Signal sig, const Value &color) = 0;
	virtual void SetPen(Signal sig, const Value &color, Number width, const Symbol *pSymbol) = 0;
	virtual void SetBrush(Signal sig, const Value &color, const Symbol *pSymbol) = 0;
	virtual void SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const TCHAR *faceName) = 0;
	virtual void SetTextColor(Signal sig, const Value &color) = 0;
	virtual void Text(Signal sig, const TCHAR *text, Number wdBound, Number htBound, Anchor anchor) = 0;
	virtual void TextRot(Signal sig, const TCHAR *text, Number angle) = 0;
	virtual void MoveTo(Signal sig, Number x, Number y) = 0;
	virtual void LineTo(Signal sig, Number x, Number y) = 0;
	virtual void Line(Signal sig, Number x1, Number y1, Number x2, Number y2) = 0;
	virtual void Rectangle(Signal sig, Number width, Number height, Anchor anchor) = 0;
	virtual void Ellipse(Signal sig, Number width, Number height, Anchor anchor) = 0;
	virtual void Pie(Signal sig, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor) = 0;
	virtual void Polygon(Signal sig, const ValueList &xs, const ValueList &ys, bool closeFlag) = 0;
	virtual void Polygon(Signal sig, const ValueList &pts, bool closeFlag) = 0;
	inline Number GetWidth() const { return _width; }
	inline Number GetHeight() const { return _height; }
};

//-----------------------------------------------------------------------------
// Object_Canvas
//-----------------------------------------------------------------------------
class Object_Canvas : public Object {
private:
	Device *_pDevice;
public:
	inline static Object_Canvas *GetSelfObj(Context &context) {
		return dynamic_cast<Object_Canvas *>(context.GetSelfObj());
	}
public:
	inline Object_Canvas(Class *pClass, Device *pDevice) :
									Object(pClass), _pDevice(pDevice) {}
	inline Object_Canvas(const Object_Canvas &obj) :
						Object(obj), _pDevice(obj._pDevice->IncRef()) {}
	virtual ~Object_Canvas();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	Device &Device() { return *_pDevice; }
};

//-----------------------------------------------------------------------------
// Color
//-----------------------------------------------------------------------------
Color::Color(Signal sig, const Value &value)
{
	if (value.IsList()) {
		const ValueList &valList = value.GetList();
		size_t len = valList.size();
		if (len < 3) {
			sig.SetError(ERR_ValueError, _T("insufficient list value for color"));
			return;
		}
		_red = static_cast<unsigned char>(valList[0].GetNumber());
		_green = static_cast<unsigned char>(valList[1].GetNumber());
		_blue = static_cast<unsigned char>(valList[2].GetNumber());
	} else if (value.IsString()) {
		const TCHAR *str = value.GetString();
		if (*str == _T('#')) str++;
		TCHAR *strp;
		unsigned long num = ::_tcstoul(str, &strp, 16);
		if (strp - str != 6) {
			sig.SetError(ERR_ValueError, _T("invalid format of color string"));
			return;
		}
		*this = Color(
			static_cast<unsigned char>(num >> 16),
			static_cast<unsigned char>(num >> 8),
			static_cast<unsigned char>(num >> 0));
	} else if (value.IsSymbol()) {
		const Symbol *pSymbol = value.GetSymbol();
		if (pSymbol->IsIdentical(AScript_PrivSymbol(black))) {
			*this = Color(0, 0, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(blue))) {
			*this = Color(0, 0, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(green))) {
			*this = Color(0, 255, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(light_blue))) {
			*this = Color(0, 255, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(red))) {
			*this = Color(255, 0, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(cyan))) {
			*this = Color(255, 0, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(yellow))) {
			*this = Color(255, 255, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(white))) {
			*this = Color(255, 255, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(grey))) {
			*this = Color(128, 128, 128);
		} else {
			sig.SetError(ERR_ValueError, _T("invalid color symbol %s"), pSymbol->GetName());
			return;
		}
	} else {
		*this = Color(0, 0, 0);
	}
}

//-----------------------------------------------------------------------------
// Device
//-----------------------------------------------------------------------------
Device::~Device()
{
}

Value Device::Initialize(Environment &env, Signal sig, const Function *pFuncBlock)
{
	SetPen(sig, Value(AScript_PrivSymbol(black)), 1., AScript_PrivSymbol(solid));
	SetFont(sig, 8.0, AScript_PrivSymbol(default), AScript_PrivSymbol(normal),
									AScript_PrivSymbol(normal), _T(""));
	Fill(sig, Value(AScript_PrivSymbol(white)));
	Object_Canvas *pObj = new Object_Canvas(AScript_Class(Canvas, env), this);
	Value result(pObj, VTYPE_Object);
	if (pFuncBlock != NULL) {
		Environment envBlock(&env, ENVTYPE_Block);
		pFuncBlock->Eval(envBlock, sig, Context(ValueList(result)));
		
		// temporary handling before GC is correctly implemented
		pObj->Device().Close();
		
		result = Value::Null;
	}
	return result;
}

//-----------------------------------------------------------------------------
// Device_EnhMetaFile
//-----------------------------------------------------------------------------
class Device_EnhMetaFile : public Device {
private:
	HDC _hdc;
	RECT _rc;
	LOGFONT _lf;
public:
	Device_EnhMetaFile(Signal sig, const TCHAR *fileName,
		Number width, Number height, const TCHAR *appName, const TCHAR *imageName);
	inline POINT ToPoint(Number x, Number y) {
		POINT pt;
		pt.x = static_cast<long>(x * 100);
		pt.y = static_cast<long>(y * 100) + _rc.bottom;
		return pt;
	}
	virtual ~Device_EnhMetaFile();
	virtual const TCHAR *GetName() const;
	virtual void Close();
	virtual void Fill(Signal sig, const Value &color);
	virtual void SetPen(Signal sig, const Value &color, Number width, const Symbol *pSymbol);
	virtual void SetBrush(Signal sig, const Value &color, const Symbol *pSymbol);
	virtual void SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const TCHAR *faceName);
	virtual void SetTextColor(Signal sig, const Value &color);
	virtual void Text(Signal sig, const TCHAR *text, Number wdBound, Number htBound, Anchor anchor);
	virtual void TextRot(Signal sig, const TCHAR *text, Number angle);
	virtual void MoveTo(Signal sig, Number x, Number y);
	virtual void LineTo(Signal sig, Number x, Number y);
	virtual void Line(Signal sig, Number x1, Number y1, Number x2, Number y2);
	virtual void Rectangle(Signal sig, Number width, Number height, Anchor anchor);
	virtual void Ellipse(Signal sig, Number width, Number height, Anchor anchor);
	virtual void Pie(Signal sig, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor);
	virtual void Polygon(Signal sig, const ValueList &xs, const ValueList &ys, bool closeFlag);
	virtual void Polygon(Signal sig, const ValueList &pts, bool closeFlag);
private:
	static void PrepareSimpleLogfont(LOGFONT *plf, LONG lHeight, LPCTSTR pszFaceName);
	static void MakeRect(RECT *prc, const POINT &pt, int width, int height, Anchor anchor);
};

Device_EnhMetaFile::Device_EnhMetaFile(Signal sig, const TCHAR *fileName,
		Number width, Number height, const TCHAR *appName, const TCHAR *imageName) :
														Device(width, height)
{
	_rc.left = 0, _rc.top = 0;
	_rc.right = static_cast<long>(width * 100);		// unit: 0.01mm
	_rc.bottom = static_cast<long>(height * 100);	// unit: 0.01mm
	PrepareSimpleLogfont(&_lf, 10, _T(""));
	TCHAR *description = NULL;
	if (appName != NULL) {
		size_t lenAppName = ::_tcslen(appName);
		if (imageName == NULL) {
			description = new TCHAR[lenAppName + 2];
			::_tcscpy(description, appName);
			description[lenAppName + 1] = _T('\0');
		} else {
			size_t lenImageName = ::_tcslen(imageName);
			description = new TCHAR[lenAppName + lenImageName + 3];
			::_tcscpy(description, appName);
			::_tcscpy(description + lenAppName + 1, imageName);
			description[lenAppName + lenImageName + 2] = _T('\0');
		}
	}
	_hdc = ::CreateEnhMetaFile(NULL, fileName, &_rc, description);
	_rc.bottom = -_rc.bottom;
	delete[] description;
	// SetViewPortOrg doesn't work correctly with some devices
	if (_hdc == NULL) {
		sig.SetError(ERR_IOError, _T("failed to create an enhanced meta file"));
		return;
	}
	::SetMapMode(_hdc, MM_HIMETRIC);				// 1 unit = 0.01 mm
}

Device_EnhMetaFile::~Device_EnhMetaFile()
{
	Close();
}

const TCHAR *Device_EnhMetaFile::GetName() const
{
	return _T("EnhancedMetaFile");
}

void Device_EnhMetaFile::Close()
{
	if (_hdc == NULL) return;
	//::_tprintf(_T("CloseEnhMetaFile\n"));
	HENHMETAFILE hEmf = ::CloseEnhMetaFile(_hdc);
	::DeleteEnhMetaFile(hEmf);
	_hdc = NULL;
}

void Device_EnhMetaFile::Fill(Signal sig, const Value &color)
{
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::FillRect(_hdc, &_rc, ::CreateSolidBrush(colorWk.ToWin32()));
}

void Device_EnhMetaFile::SetPen(Signal sig,
					const Value &color, Number width, const Symbol *pSymbol)
{
	if (_hdc == NULL) return;
	if (color.IsInvalid()) {
		::SelectObject(_hdc, ::CreatePen(PS_NULL, 0, RGB(0, 0, 0)));
		return;
	}
	int fnPenStyle = PS_SOLID;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(solid))) {
		fnPenStyle = PS_SOLID;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dash))) {
		fnPenStyle = PS_DASH;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dot))) {
		fnPenStyle = PS_DOT;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dot_dash))) {
		fnPenStyle = PS_DASHDOT;
	} else {
		sig.SetError(ERR_ValueError, _T("invalid pen style %s"), pSymbol->GetName());
		return;
	}
	int nWidth = static_cast<int>(width * 100 * 25.4 * 0.25 / 72);
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::SelectObject(_hdc, ::CreatePen(fnPenStyle, nWidth, colorWk.ToWin32()));
}

void Device_EnhMetaFile::SetBrush(Signal sig, const Value &color, const Symbol *pSymbol)
{
	if (_hdc == NULL) return;
	if (color.IsInvalid()) {
		::SelectObject(_hdc, reinterpret_cast<HBRUSH>(::GetStockObject(NULL_BRUSH)));
		return;
	}
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(solid))) {
		::SelectObject(_hdc, ::CreateSolidBrush(colorWk.ToWin32()));
	} else {
		int fnStyle = 0;
		if (pSymbol->IsIdentical(AScript_PrivSymbol(bdiagonal))) {
			fnStyle = HS_BDIAGONAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(cross))) {
			fnStyle = HS_CROSS;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(diagcross))) {
			fnStyle = HS_DIAGCROSS;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(fdiagonal))) {
			fnStyle = HS_FDIAGONAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(horizontal))) {
			fnStyle = HS_HORIZONTAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(vertical))) {
			fnStyle = HS_VERTICAL;
		} else {
			sig.SetError(ERR_ValueError, _T("invalid brush style %s"), pSymbol->GetName());
			return;
		}
		::SelectObject(_hdc, ::CreateHatchBrush(fnStyle, colorWk.ToWin32()));
	}
}

void Device_EnhMetaFile::SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const TCHAR *faceName)
{
	// 1pt = 25.4/72mm = 0.353mm
	PrepareSimpleLogfont(&_lf, static_cast<int>(height * 100), _T(""));
	if (family->IsIdentical(AScript_PrivSymbol(default))) {
		_lf.lfPitchAndFamily = FF_DONTCARE;
	} else if (family->IsIdentical(AScript_PrivSymbol(decorative))) {
		_lf.lfPitchAndFamily = FF_DECORATIVE;
	} else if (family->IsIdentical(AScript_PrivSymbol(roman))) {
		_lf.lfPitchAndFamily = FF_ROMAN;
	} else if (family->IsIdentical(AScript_PrivSymbol(script))) {
		_lf.lfPitchAndFamily = FF_SCRIPT;
	} else if (family->IsIdentical(AScript_PrivSymbol(swiss))) {
		_lf.lfPitchAndFamily = FF_SWISS;
	} else if (family->IsIdentical(AScript_PrivSymbol(modern))) {
		_lf.lfPitchAndFamily = FF_MODERN;
	} else if (family->IsIdentical(AScript_PrivSymbol(teletype))) {
		_lf.lfPitchAndFamily = FF_SWISS | FIXED_PITCH;
	} else {
		sig.SetError(ERR_ValueError, _T("invalid family symbol %s"), family->GetName());
		return;
	}
	if (style->IsIdentical(AScript_PrivSymbol(italic))) {
		_lf.lfItalic = TRUE;
	} else if (style->IsIdentical(AScript_PrivSymbol(slant))) {
		_lf.lfStrikeOut = TRUE;
	}
	if (weight->IsIdentical(AScript_PrivSymbol(normal))) {
		_lf.lfWeight = FW_NORMAL;
	} else if (weight->IsIdentical(AScript_PrivSymbol(light))) {
		_lf.lfWeight = FW_LIGHT;
	} else if (weight->IsIdentical(AScript_PrivSymbol(bold))) {
		_lf.lfWeight = FW_BOLD;
	} else {
		sig.SetError(ERR_ValueError, _T("invalid weight symbol %s"), weight->GetName());
		return;
	}
	HFONT hFont = ::CreateFontIndirect(&_lf);
	::SelectObject(_hdc, hFont);
}

void Device_EnhMetaFile::SetTextColor(Signal sig, const Value &color)
{
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::SetTextColor(_hdc, colorWk.ToWin32());
}

void Device_EnhMetaFile::Text(Signal sig,
				const TCHAR *text, Number wdBound, Number htBound, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	rc.left = rc.right = rc.top = rc.bottom = 0;
	::DrawText(_hdc, text, -1, &rc, DT_CALCRECT);
	wdBound *= 100, htBound *= 100;
	if (wdBound <= 0) wdBound = rc.right;
	if (htBound <= 0) htBound = -rc.bottom;
	MakeRect(&rc, ToPoint(_xCur, _yCur),
				static_cast<int>(wdBound), static_cast<int>(htBound), anchor);
	//MakeRect(&rc, ToPoint(_xCur, _yCur), rc.right - rc.left, rc.top - rc.bottom, anchor);
	::SetTextAlign(_hdc, TA_TOP);
	::DrawText(_hdc, text, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//::DrawText(_hdc, text, -1, &rc, DT_SINGLELINE);
}

void Device_EnhMetaFile::TextRot(Signal sig, const TCHAR *text, Number angle)
{
	if (_hdc == NULL) return;
	POINT pt = ToPoint(_xCur, _yCur);
	LOGFONT lf = _lf;
	lf.lfEscapement = -static_cast<int>(angle * 10);
	lf.lfOrientation = lf.lfEscapement;
	HFONT hFont = ::CreateFontIndirect(&lf);
	HFONT hFontOld = reinterpret_cast<HFONT>(::SelectObject(_hdc, hFont));
	::SetTextAlign(_hdc, TA_BOTTOM);
	::TextOut(_hdc, pt.x, pt.y, text, ::_tcslen(text));
	::SelectObject(_hdc, hFontOld);
}

void Device_EnhMetaFile::MoveTo(Signal sig, Number x, Number y)
{
	if (_hdc == NULL) return;
	POINT pt = ToPoint(x, y);
	::MoveToEx(_hdc, pt.x, pt.y, NULL);
	SetCurrent(x, y);
}

void Device_EnhMetaFile::LineTo(Signal sig, Number x, Number y)
{
	if (_hdc == NULL) return;
	POINT pt = ToPoint(x, y);
	::LineTo(_hdc, pt.x, pt.y);
	SetCurrent(x, y);
}

void Device_EnhMetaFile::Line(Signal sig, Number x1, Number y1, Number x2, Number y2)
{
	if (_hdc == NULL) return;
	POINT pt1 = ToPoint(x1, y1);
	POINT pt2 = ToPoint(x2, y2);
	::MoveToEx(_hdc, pt1.x, pt1.y, NULL);
	::LineTo(_hdc, pt2.x, pt2.y);
	SetCurrent(x2, y2);
}

void Device_EnhMetaFile::Rectangle(Signal sig, Number width, Number height, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(_xCur, _yCur),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	::Rectangle(_hdc, rc.left, rc.top, rc.right, rc.bottom);
}

void Device_EnhMetaFile::Ellipse(Signal sig, Number width, Number height, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(_xCur, _yCur),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	::Ellipse(_hdc, rc.left, rc.top, rc.right, rc.bottom);
}

void Device_EnhMetaFile::Pie(Signal sig, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(_xCur, _yCur),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	double radStart = degStart * 3.1415926 / 180;
	double radEnd = degEnd * 3.1415926 / 180;
	int xCenter = (rc.left + rc.right) / 2;
	int yCenter = (rc.top + rc.bottom) / 2;
	double lenBeam = 100000;
	int xStart = static_cast<int>(lenBeam * ::cos(radStart) + xCenter);
	int yStart = static_cast<int>(lenBeam * ::sin(radStart) + yCenter);
	int xEnd = xCenter + ::cos(radEnd) * lenBeam;
	int yEnd = yCenter + ::sin(radEnd) * lenBeam;
	::Pie(_hdc, rc.left, rc.top, rc.right, rc.bottom, xStart, yStart, xEnd, yEnd);
}

void Device_EnhMetaFile::Polygon(Signal sig,
					const ValueList &xs, const ValueList &ys, bool closeFlag)
{
	if (_hdc == NULL) return;
	size_t cnt = std::min(xs.size(), ys.size());
	if (cnt == 0) return;
	POINT *ptList = new POINT[cnt];
	ValueList::const_iterator xp = xs.begin();
	ValueList::const_iterator yp = ys.begin();
	size_t i = 0;
	SetCurrent((xp + cnt - 1)->GetNumber(), (yp + cnt - 1)->GetNumber());
	for (POINT *ptpDst = ptList; i < cnt; xp++, yp++, ptpDst++, i++) {
		*ptpDst = ToPoint(xp->GetNumber(), yp->GetNumber());
	}
	if (closeFlag) {
		::Polygon(_hdc, ptList, cnt);
	} else {
		::Polyline(_hdc, ptList, cnt);
	}
	delete[] ptList;
}

void Device_EnhMetaFile::Polygon(Signal sig, const ValueList &pts, bool closeFlag)
{
	if (_hdc == NULL) return;
	size_t cnt = pts.size();
	if (cnt == 0) return;
	POINT *ptList = new POINT[cnt];
	ValueList::const_iterator ptp = pts.begin();
	POINT *ptpDst = ptList;
	foreach_const (ValueList, pValue, pts) {
		if (!pValue->IsList()) {
			sig.SetError(ERR_ValueError, _T("element of point list must be a list"));
			return;
		}
		const ValueList &valList = pValue->GetList();
		if (valList.size() != 2) {
			sig.SetError(ERR_ValueError, _T("wrong format of point list"));
			return;
		}
		*ptpDst = ToPoint(valList[0].GetNumber(), valList[1].GetNumber());
		ptpDst++;
	}
	if (closeFlag) {
		::Polygon(_hdc, ptList, cnt);
	} else {
		::Polyline(_hdc, ptList, cnt);
	}
	do {
		const ValueList &valList = pts.back().GetList();
		SetCurrent(valList[0].GetNumber(), valList[1].GetNumber());
	} while (0);
	delete[] ptList;
}

void Device_EnhMetaFile::PrepareSimpleLogfont(LOGFONT *plf,
											LONG lHeight, LPCTSTR pszFaceName)
{
	plf->lfHeight = lHeight; 
	plf->lfWidth = 0;
	plf->lfEscapement = 0;		// 1/10 degree
	plf->lfOrientation = 0;		// 1/10 degree
	plf->lfWeight = FW_NORMAL;
	plf->lfItalic = FALSE;
	plf->lfUnderline = FALSE;
	plf->lfStrikeOut = FALSE;
	plf->lfCharSet = DEFAULT_CHARSET;
	plf->lfOutPrecision = OUT_DEFAULT_PRECIS;
	plf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
	plf->lfQuality = DEFAULT_QUALITY;
	plf->lfPitchAndFamily = DEFAULT_PITCH;
	::_tcscpy(plf->lfFaceName, pszFaceName);
}

void Device_EnhMetaFile::MakeRect(RECT *prc,
						const POINT &pt, int width, int height, Anchor anchor)
{
	prc->left =
		(anchor == ANCHOR_NW || anchor == ANCHOR_W || anchor == ANCHOR_SW)? pt.x :
		(anchor == ANCHOR_NE || anchor == ANCHOR_E || anchor == ANCHOR_SE)? pt.x - width + 1 :
		pt.x - width / 2;
	prc->right = prc->left + width;
	prc->bottom =
		(anchor == ANCHOR_NW || anchor == ANCHOR_N || anchor == ANCHOR_NE)? pt.y - height + 1 :
		(anchor == ANCHOR_SW || anchor == ANCHOR_S || anchor == ANCHOR_SE)? pt.y :
		pt.y - height / 2;
	prc->top = prc->bottom + height;
}

//-----------------------------------------------------------------------------
// Object_Canvas
//-----------------------------------------------------------------------------
Object_Canvas::~Object_Canvas()
{
	Device::Delete(_pDevice);
}

Object *Object_Canvas::Clone() const
{
	return new Object_Canvas(*this);
}

String Object_Canvas::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	rtn += _T("<canvas:");
	rtn += _pDevice->GetName();
	rtn += _T(">");
	return rtn;
}

// canvas.Canvas#width()
AScript_DeclareMethod(Canvas, width)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
}

AScript_ImplementMethod(Canvas, width)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	return Value(pObj->Device().GetWidth());
}

// canvas.Canvas#height()
AScript_DeclareMethod(Canvas, height)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
}

AScript_ImplementMethod(Canvas, height)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	return Value(pObj->Device().GetHeight());
}

// canvas.Canvas#setfont(height:number, weight?:symbol, facename?:string)
AScript_DeclareMethod(Canvas, setfont)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("height"), VTYPE_Number);
	DeclareArg(env, _T("family"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("style"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("weight"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("facename"), VTYPE_String, false, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Canvas, setfont)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().SetFont(sig, context.GetNumber(0),
		context.IsSymbol(1)? context.GetSymbol(1) : AScript_PrivSymbol(default),
		context.IsSymbol(2)? context.GetSymbol(2) : AScript_PrivSymbol(normal),
		context.IsSymbol(3)? context.GetSymbol(3) : AScript_PrivSymbol(normal),
		context.IsString(4)? context.GetString(4) : _T(""));
	return context.GetSelf();
}

// canvas.Canvas#settextcolor(color)
AScript_DeclareMethod(Canvas, settextcolor)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("color"), VTYPE_AnyType);
}

AScript_ImplementMethod(Canvas, settextcolor)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().SetTextColor(sig, context.GetValue(0));
	return context.GetSelf();
}

// canvas.Canvas#setpen(color, width:number, style:symbol)
AScript_DeclareMethod(Canvas, setpen)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("color"), VTYPE_AnyType);
	DeclareArg(env, _T("width"), VTYPE_Number, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("style"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Canvas, setpen)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().SetPen(sig, context.GetValue(0),
		context.IsNumber(1)? context.GetNumber(1) : 1,
		context.IsSymbol(2)? context.GetSymbol(2) : AScript_PrivSymbol(solid));
	return context.GetSelf();
}

// canvas.Canvas#setbrush(color, style:symbol)
AScript_DeclareMethod(Canvas, setbrush)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("color"), VTYPE_AnyType);
	DeclareArg(env, _T("style"), VTYPE_Symbol, false, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Canvas, setbrush)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().SetBrush(sig, context.GetValue(0),
		context.IsSymbol(1)? context.GetSymbol(1) : AScript_PrivSymbol(solid));
	return context.GetSelf();
}

// canvas.Canvas#text(text:string, width_bound?:number, height_bound?:number):[n,ne,e,se,s,sw,w,nw,c]
AScript_DeclareMethod(Canvas, text)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("text"), VTYPE_String);
	DeclareArg(env, _T("width_bound"), VTYPE_Number, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("height_bound"), VTYPE_Number, false, OCCUR_ZeroOrOnce);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, text)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Text(sig, context.GetString(0),
		context.IsNumber(1)? context.GetNumber(1) : -1,
		context.IsNumber(2)? context.GetNumber(2) : -1, GetAnchor(context.GetAttrs()));
	return context.GetSelf();
}

// canvas.Canvas#textrot(text:string, angle.number)
AScript_DeclareMethod(Canvas, textrot)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("text"), VTYPE_String);
	DeclareArg(env, _T("angle"), VTYPE_Number);
}

AScript_ImplementMethod(Canvas, textrot)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().TextRot(sig, context.GetString(0), context.GetNumber(1));
	return context.GetSelf();
}

// canvas.Canvas#moveto(x:number, y:number)
AScript_DeclareMethod(Canvas, moveto)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("x"), VTYPE_Number);
	DeclareArg(env, _T("y"), VTYPE_Number);
}

AScript_ImplementMethod(Canvas, moveto)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().MoveTo(sig, context.GetNumber(0), context.GetNumber(1));
	return context.GetSelf();
}

// canvas.Canvas#lineto(x:number, y:number):map
AScript_DeclareMethod(Canvas, lineto)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("x"), VTYPE_Number);
	DeclareArg(env, _T("y"), VTYPE_Number);
}

AScript_ImplementMethod(Canvas, lineto)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().LineTo(sig, context.GetNumber(0), context.GetNumber(1));
	return context.GetSelf();
}

// canvas.Canvas#line(x1:number, y1:number, x2:number, y2:number):map
AScript_DeclareMethod(Canvas, line)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("x1"), VTYPE_Number);
	DeclareArg(env, _T("y1"), VTYPE_Number);
	DeclareArg(env, _T("x2"), VTYPE_Number);
	DeclareArg(env, _T("y2"), VTYPE_Number);
}

AScript_ImplementMethod(Canvas, line)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Line(sig, context.GetNumber(0), context.GetNumber(1),
					context.GetNumber(2), context.GetNumber(3));
	return context.GetSelf();
}

// canvas.Canvas#rectangle(width:number, height:number):map
AScript_DeclareMethod(Canvas, rectangle)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("width"), VTYPE_Number);
	DeclareArg(env, _T("height"), VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, rectangle)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Rectangle(sig, context.GetNumber(0), context.GetNumber(1),
											GetAnchor(context.GetAttrs()));
	return context.GetSelf();
}

// canvas.Canvas#ellipse(width:number, height:number):map
AScript_DeclareMethod(Canvas, ellipse)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("width"), VTYPE_Number);
	DeclareArg(env, _T("height"), VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, ellipse)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Ellipse(sig, context.GetNumber(0), context.GetNumber(1),
											GetAnchor(context.GetAttrs()));
	return context.GetSelf();
}

// canvas.Canvas#pie(width:number, height:number, start_degree:number, end_degree:number):map
AScript_DeclareMethod(Canvas, pie)
{
	SetMode(RSLTMODE_Normal, MAP_On);
	DeclareArg(env, _T("width"), VTYPE_Number);
	DeclareArg(env, _T("height"), VTYPE_Number);
	DeclareArg(env, _T("start_degree"), VTYPE_Number);
	DeclareArg(env, _T("end_degree"), VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, pie)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Pie(sig, context.GetNumber(0), context.GetNumber(1),
		context.GetNumber(2), context.GetNumber(3), GetAnchor(context.GetAttrs()));
	return context.GetSelf();
}

// canvas.Canvas#polyline(xs[]:number, ys[]:number)
AScript_DeclareMethod(Canvas, polyline)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("xs"), VTYPE_Number, true);
	DeclareArg(env, _T("ys"), VTYPE_Number, true);
}

AScript_ImplementMethod(Canvas, polyline)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Polygon(sig, context.GetList(0), context.GetList(1), false);
	return context.GetSelf();
}

// canvas.Canvas#polylinep(pts[]:List)
AScript_DeclareMethod(Canvas, polylinep)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("pts"), VTYPE_List, true);
}

AScript_ImplementMethod(Canvas, polylinep)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Polygon(sig, context.GetList(0), false);
	return context.GetSelf();
}

// canvas.Canvas#polygon(xs[]:number, ys[]:number)
AScript_DeclareMethod(Canvas, polygon)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("xs"), VTYPE_Number, true);
	DeclareArg(env, _T("ys"), VTYPE_Number, true);
}

AScript_ImplementMethod(Canvas, polygon)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Polygon(sig, context.GetList(0), context.GetList(1), true);
	return context.GetSelf();
}

// canvas.Canvas#polygonp(pts[]:List)
AScript_DeclareMethod(Canvas, polygonp)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("pts"), VTYPE_List, true);
}

AScript_ImplementMethod(Canvas, polygonp)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Polygon(sig, context.GetList(0), true);
	return context.GetSelf();
}

// str = canvas.Canvas#close()
AScript_DeclareMethod(Canvas, close)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
}

AScript_ImplementMethod(Canvas, close)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(context);
	pObj->Device().Close();
	return Value::Null;
}

AScript_ImplementClass(Canvas)
{
	AScript_AssignMethod(Canvas, width);
	AScript_AssignMethod(Canvas, height);
	AScript_AssignMethod(Canvas, setfont);
	AScript_AssignMethod(Canvas, settextcolor);
	AScript_AssignMethod(Canvas, setpen);
	AScript_AssignMethod(Canvas, setbrush);
	AScript_AssignMethod(Canvas, text);
	AScript_AssignMethod(Canvas, textrot);
	AScript_AssignMethod(Canvas, moveto);
	AScript_AssignMethod(Canvas, lineto);
	AScript_AssignMethod(Canvas, line);
	AScript_AssignMethod(Canvas, rectangle);
	AScript_AssignMethod(Canvas, ellipse);
	AScript_AssignMethod(Canvas, pie);
	AScript_AssignMethod(Canvas, polyline);
	AScript_AssignMethod(Canvas, polylinep);
	AScript_AssignMethod(Canvas, polygon);
	AScript_AssignMethod(Canvas, polygonp);
	AScript_AssignMethod(Canvas, close);
}

// create_emf(filename:string, width:number, height:number, appname?:string, imagename?:string) {block?}
// unit of width and height is mm
AScript_DeclareFunction(create_emf)
{
	SetMode(RSLTMODE_Normal, MAP_Off);
	DeclareArg(env, _T("filename"), VTYPE_String);
	DeclareArg(env, _T("width"), VTYPE_Number);
	DeclareArg(env, _T("height"), VTYPE_Number);
	DeclareArg(env, _T("appname"), VTYPE_String, false, OCCUR_ZeroOrOnce);
	DeclareArg(env, _T("imagename"), VTYPE_String, false, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(create_emf)
{
	Device *pDevice = new Device_EnhMetaFile(sig,
		context.GetString(0), context.GetNumber(1), context.GetNumber(2),
		context.IsString(3)? context.GetString(3) : NULL,
		context.IsString(4)? context.GetString(4) : NULL);
	if (sig.IsSignalled()) {
		delete pDevice;
		return Value::Null;
	}
	return pDevice->Initialize(env, sig, GetBlockFunction(env, sig, context));
}

AScript_ModuleEntry()
{
	AScript_RealizePrivSymbol(red);
	AScript_RealizePrivSymbol(blue);
	AScript_RealizePrivSymbol(green);
	AScript_RealizePrivSymbol(cyan);
	AScript_RealizePrivSymbol(light_blue);
	AScript_RealizePrivSymbol(yellow);
	AScript_RealizePrivSymbol(black);
	AScript_RealizePrivSymbol(white);
	AScript_RealizePrivSymbol(grey);
	AScript_RealizePrivSymbol(solid);
	AScript_RealizePrivSymbol(dot);
	AScript_RealizePrivSymbol(dash);
	AScript_RealizePrivSymbol(dot_dash);
	AScript_RealizePrivSymbol(bdiagonal);
	AScript_RealizePrivSymbol(cross);
	AScript_RealizePrivSymbol(diagcross);
	AScript_RealizePrivSymbol(fdiagonal);
	AScript_RealizePrivSymbol(horizontal);
	AScript_RealizePrivSymbol(vertical);
	AScript_RealizePrivSymbol(normal);
	AScript_RealizePrivSymbol(default);
	AScript_RealizePrivSymbol(decorative);
	AScript_RealizePrivSymbol(roman);
	AScript_RealizePrivSymbol(script);
	AScript_RealizePrivSymbol(swiss);
	AScript_RealizePrivSymbol(modern);
	AScript_RealizePrivSymbol(teletype);
	AScript_RealizePrivSymbol(slant);
	AScript_RealizePrivSymbol(italic);
	AScript_RealizePrivSymbol(light);
	AScript_RealizePrivSymbol(bold);
	AScript_RealizePrivSymbol(n);
	AScript_RealizePrivSymbol(ne);
	AScript_RealizePrivSymbol(e);
	AScript_RealizePrivSymbol(se);
	AScript_RealizePrivSymbol(s);
	AScript_RealizePrivSymbol(sw);
	AScript_RealizePrivSymbol(w);
	AScript_RealizePrivSymbol(nw);
	AScript_RealizePrivSymbol(c);
	AScript_AssignFunction(create_emf);
}

AScript_EndModule(canvas)

AScript_DLLModuleEntry(canvas)
