// gsclib.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "stdlib.h"
#include "stdint.h"
#include "stdio.h"
#include <assert.h>

#include "gsclib.h"

struct FractalParams
{
	COLORREF colors[16];
	int expand;				// palette expansion factor: <1, 64>; 1 - use basic 16 colors only, 64 - use 16 x 64 shades
	int step;				// RGB step for subsequent expaned color
	double zr, zi;
	double xmin, xmax;
	double ymin, ymax;
};

COLORREF defaultColors[] =
{
	RGB(0, 255, 0),		// Green
	RGB(255, 255, 0),	// Yellow
	RGB(255, 0, 0),		// Red
	RGB(0, 0, 255),		// Blue
	RGB(0, 255, 255),	// Cyan
	RGB(128, 128, 128),	// Grey
	RGB(0, 128, 128),	// Aqua
	RGB(255, 0, 255),	// Magenta
	RGB(128, 128, 0),	// Brown
	RGB(128, 0, 128),	// Purple
	RGB(255, 255, 255),
	RGB(128, 0, 0),
	RGB(0, 128, 0),
	RGB(0, 0, 128),
	RGB(192, 192, 192),
	RGB(0, 0, 0)		// Black
};


void DrawFractal(HDC hdc, const RECT& rect, FractalParams *p)
{
	//double p = /*-1.25*/0.32, q = /*0.1*/0.043;
	//double p = 0.1, q = 0.43;
	//double p = p->zr;
	//double q = p->zi;

	double m = 100;
	double zoom_a = (p->xmax - p->xmin) / (rect.right - rect.left + 1);
	double zoom_b = (p->ymax - p->ymin) / (rect.bottom - rect.top + 1);

	int palette = p->expand * 16;

	COLORREF colorsExt[1024];

	for (int c = 0; c < 16; ++c)
	{
		BYTE r = GetRValue(p->colors[c]);
		BYTE g = GetGValue(p->colors[c]);
		BYTE b = GetBValue(p->colors[c]);

		colorsExt[p->expand*c + p->expand / 2] = p->colors[c];

		for (int i = 1; i < p->expand / 2 + p->expand % 2; ++i)
			colorsExt[p->expand*c + p->expand / 2 + i] = RGB(min(r + i * p->step, 255), min(g + i * p->step, 255), min(b + i * p->step, 255));
		for (int i = 1; i < p->expand / 2 + 1; ++i)
			colorsExt[p->expand*c + p->expand / 2 - i] = RGB(max((int)r - i * p->step, 0), max((int)g - i * p->step, 0), max((int)b - i * p->step, 0));
	}

	for (int j = rect.top; j <= rect.bottom; ++j)
		for (int i = rect.left; i <= rect.right; ++i)
		{
			double a = p->xmin + (i - rect.left)*zoom_a;
			double b = p->ymin + (j - rect.top)*zoom_b;
			double aN = 0, bN = 0;

			USHORT n = 0;

			while (true)
			{
				aN = a*a - b*b + p->zr/*p*/;
				bN = 2 * a*b + p->zi/*q*/;

				++n;

				a = aN;
				b = bN;

				double s = a*a + b*b;

				if (m < s)
				{
					::SetPixelV(hdc, i, j, colorsExt[n]);
					break;
				}
				if (m == s)
				{
					::SetPixelV(hdc, i, j, colorsExt[0]);
					break;
				}

				if (n >= palette - 1)
				{
					::SetPixelV(hdc, i, j, colorsExt[n]);
					break;
				}
			}
		}
}

bool SaveFractalAsDIB(GSCalcArg *arg, BYTE*& dib, size_t& dibLen, const RECT& rect, FractalParams *p, uint8_t& errorCode)
{
	int width = rect.right - rect.left + 1;
	int height = rect.bottom - rect.top + 1;
	int rowBytes = 4 * width;
	dibLen = sizeof(BITMAPINFOHEADER) + height*(rowBytes + (rowBytes % 4 ? 4 - rowBytes % 4 : 0)) * sizeof(BYTE);

	if (!(dib = static_cast<BYTE*>(arg->image.memory_alloc(dibLen))))
	{
		errorCode = ERROR1_OUT_OF_MEMORY;
		return false;
	}

	BYTE *bits = dib + sizeof(BITMAPINFOHEADER);
	BITMAPINFOHEADER *header = reinterpret_cast<BITMAPINFOHEADER*>(dib);
	BITMAPINFO *bmpInfo = reinterpret_cast<BITMAPINFO*>(dib);

	::memset(header, 0, sizeof(BITMAPINFOHEADER));

	header->biSize = sizeof(BITMAPINFOHEADER);
	header->biWidth = width;
	header->biHeight = -height;
	header->biCompression = BI_RGB;
	header->biPlanes = 1;
	header->biBitCount = 32;
	header->biSizeImage = static_cast<DWORD>(dibLen) - sizeof(BITMAPINFOHEADER);

	HDC screenDC = ::GetDC(NULL);

	HDC memoryDC = ::CreateCompatibleDC(screenDC);
	HBITMAP bmpNew = ::CreateCompatibleBitmap(screenDC, width, height);

	::ReleaseDC(NULL, screenDC);

	if (!memoryDC || !bmpNew)
	{
		arg->image.memory_free(dib);
		dib = NULL;
		dibLen = 0;
		errorCode = ERROR1_OUT_OF_MEMORY;
		if (bmpNew)
			::DeleteObject(bmpNew);
		if (memoryDC)
			::DeleteDC(memoryDC);
		return false;
	}
	else
	{
		HBITMAP bmpOld = (HBITMAP)::SelectObject(memoryDC, bmpNew);

		DrawFractal(memoryDC, rect, p);

		::SelectObject(memoryDC, bmpOld);
		::GetDIBits(memoryDC, bmpNew, 0, height, bits, bmpInfo, DIB_RGB_COLORS);
		::DeleteObject(bmpNew);
		::DeleteDC(memoryDC);
		return true;
	}
}


extern "C" {

// fractal(zr, zi, xmin, xmax, ymin, ymax, width, height, [colors], [expand], [step])
//
// arguments:
//			zr, zi - transformation
//			xmin, xmax, ymin, ymax - zoom/offset
//			width, height - physical bitmap size
//			[colors] - 16-item RGB array/range with basic colors (optional)
//			[expand] - expands the number of colors 2-64 times (optional)
//			[step] - RGB step to obtain subsequent expanded colors out of the basic colors (optional)
// returns:
//			a pointer to the allocated memory containing the fractal DIB bitmap;
//			to obtain a higher resulution image when printing use arg->image.dims.cx/.cy to specify the display rectangle
//			and width/height to specify the actual DIB bitmap dimensions

__declspec(dllexport) double __cdecl fractal(GSCalcArg *arg)
{
	uint8_t errorCode = 0;

	for (int i = 0; i < 8; ++i)
		if (arg->types[i] != DLL_ARGT_DOUBLE)
		{
			errorCode = ERROR1_INVALID_VALUE;
			break;
		}
		else if ((errorCode = arg->errors[i]) != 0)
			break;

	if (arg->types[11]/*...*/)
		errorCode = ERROR1_SYNTAX_ERROR;

	if (errorCode)
	{
		arg->errors[DLL_ARGC_RET] = errorCode;
		return arg->types[DLL_ARGC_RET] = DLL_ARGT_EMPTY;
	}

	struct FractalParams p = { 0 };
	RECT r = { 0, 0, 0, 0 };

	p.zr = arg->numbers[0];	// 1st DLL_ARGT_DOUBLE argument
	p.zi = arg->numbers[1];
	p.xmin = arg->numbers[2];
	p.xmax = arg->numbers[3];
	p.ymin = arg->numbers[4];
	p.ymax = arg->numbers[5];
	r.right = static_cast<int>(arg->numbers[6]);
	r.bottom = static_cast<int>(arg->numbers[7]);

	if (!errorCode && !(errorCode = arg->errors[8]))
	{
		if (arg->types[8] == DLL_ARGT_ARRAY)
		{
			if (arg->array_dims[0].cy*arg->array_dims[0].cx != 16) // 0 - 1st array/range of the 11 arguments
				errorCode = ERROR1_INVALID_VALUE;
			ArrayItem x = { 0 };
			int index = 0;
			for (x.row = 0; x.row < arg->array_dims[0].cy && index < 16 && !errorCode; ++x.row)
			{
				for (x.col = 0; x.col < arg->array_dims[0].cx && index < 16 && !errorCode; ++x.col)
				{
					arg->read_array(arg->env, 0, &x);
					if (x.type == DLL_ARGT_DOUBLE)
						p.colors[index++] = static_cast<long>(x.num);
					else if (x.type == DLL_ARGT_EMPTY)
						p.colors[index++] = ::defaultColors[index];
					else
						errorCode = ERROR1_INVALID_VALUE;
				}
			}
		}
		else if (arg->types[8] == DLL_ARGT_EMPTY)
			::memcpy(p.colors, ::defaultColors, 16 * sizeof(COLORREF));
		else
			errorCode = ERROR1_INVALID_VALUE;
	}

	if (!errorCode && !(errorCode = arg->errors[9]))
	{
		if (arg->types[9] == DLL_ARGT_DOUBLE)
		{
			p.expand = static_cast<int>(arg->numbers[8]); // 8 - 9th number of the 11 arguments
			errorCode = arg->errors[8];
		}
		else if (arg->types[9] == DLL_ARGT_EMPTY)
			p.expand = 64;
		else
			errorCode = ERROR1_INVALID_VALUE;
	}

	if (!errorCode && !(errorCode = arg->errors[10]))
	{
		if (arg->types[10] == DLL_ARGT_DOUBLE)
		{
			p.step = static_cast<int>(arg->numbers[9]); // 9 - 10th number of the 11 arguments
			errorCode = arg->errors[9];
		}
		else if (arg->types[10] == DLL_ARGT_EMPTY)
			p.step = 4;
		else
			errorCode = ERROR1_INVALID_VALUE;
	}

	assert(p.expand > 0 && p.expand <= 64 && p.xmax > p.xmin && p.ymax > p.ymin && p.step >= 1 && p.step <= 127);
	if (p.expand < 1 || p.expand > 64 || p.xmax <= p.xmin || p.ymax <= p.ymin || p.step < 1 || p.step > 127 || r.right <= 0 || r.bottom <= 0)
		errorCode = ERROR1_INVALID_NUMBER;

	if (!errorCode)
		SaveFractalAsDIB(arg, arg->image.data, arg->image.size, r, &p, errorCode);

	arg->errors[DLL_ARGC_RET] = errorCode;
	return arg->types[DLL_ARGC_RET] = DLL_ARGT_PNG;
}


// image(path, cx, cy)
//
// arguments:
//			path - a bmp, jpg, png, gif file path
//			cx/cy - display image size (optional)
// returns:
//			loads an image (bmp, jpg, png, gif) from a disk and displays it, optionally resing it
//			to the cx/cy dimensions (in screen pixels)

__declspec(dllexport) double __cdecl image(GSCalcArg *arg)
{
	uint8_t errorCode = 0;

	if (arg->types[0] != DLL_ARGT_TEXT ||
		arg->types[1] != DLL_ARGT_DOUBLE && arg->types[1] != DLL_ARGT_EMPTY ||
		arg->types[2] != DLL_ARGT_DOUBLE && arg->types[2] != DLL_ARGT_EMPTY)
		errorCode = ERROR1_INVALID_VALUE;
	if (arg->types[3]/*...*/)
		errorCode = ERROR1_SYNTAX_ERROR;

	if (!errorCode && arg->errors[0])
		errorCode = arg->errors[0];

	::strcpy_s(arg->strings[DLL_ARGC_RET], MAXINPUT, arg->strings[0]);
	arg->image.dims.cx = static_cast<uint32_t>(arg->numbers[0]);
	arg->image.dims.cy = static_cast<uint32_t>(arg->numbers[1]);

	// arg->image.data must remain NULL

	arg->errors[DLL_ARGC_RET] = errorCode;
	return arg->types[DLL_ARGC_RET] = DLL_ARGT_PNG;
}


// filter(range, column, number)
//
// arguments:
//			range - a range/array
//			column - column index (from 0 to the number of columns in 'range' - 1)
//			number - numeric value to perform equality check
// returns:
//			rows of 'range' containing 'value' in the specified column

__declspec(dllexport) double __cdecl filter(GSCalcArg *arg)
{
	uint8_t errorCode = 0;

	if (arg->types[0] != DLL_ARGT_ARRAY || arg->types[1] != DLL_ARGT_DOUBLE || arg->types[2] != DLL_ARGT_DOUBLE)
		errorCode = ERROR1_INVALID_VALUE;
	if (arg->types[3]/*...*/)
		errorCode = ERROR1_SYNTAX_ERROR;

	if (!errorCode && arg->errors[0])
		errorCode = arg->errors[0];
	if (!errorCode && arg->errors[1])
		errorCode = arg->errors[1];
	if (!errorCode && arg->errors[2])
		errorCode = arg->errors[2];

	ArrayItem x = { 0 };
	int outRow = 0;

	for (uint32_t inRow = 0; inRow < arg->array_dims[0].cy && !errorCode; ++inRow)
	{
		x.row = inRow;
		x.col = static_cast<int>(arg->numbers[0]);
		if (arg->read_array(arg->env, 0, &x) == -1)
		{
			errorCode = static_cast<BYTE>(x.err_code);
			break;
		}

		bool match = false;
		if (x.type == DLL_ARGT_DOUBLE)
			match = (x.num == arg->numbers[1] || x.err_code && x.err_code == arg->errors[2]);
		else if (x.type == DLL_ARGT_EMPTY)
			match = (arg->numbers[1] == 0);
		else
			errorCode = ERROR1_INVALID_VALUE;

		if (match)
		{
			for (x.col = 0; x.col < arg->array_dims[0].cx && !errorCode; ++x.col)
			{
				x.row = inRow;
				arg->read_array(arg->env, 0, &x);
				x.row = outRow;
				if (arg->write_array(arg->env, DLL_ARGC_RET, &x) == -1)
					errorCode = static_cast<BYTE>(x.err_code);
			}
			++outRow;
		}
	}

	arg->errors[DLL_ARGC_RET] = (!outRow ? ERROR1_NULL_VALUE : 0);
	return arg->types[DLL_ARGC_RET] = DLL_ARGT_ARRAY;

	return 0;
}


// messageIf(condition, text)
//
// arguments:
//			condition, message text
// returns:
//			if condition != 0, 'messageIf' displays 'text' as a message box after each update/recalculation

__declspec(dllexport) double __cdecl messageIf(GSCalcArg *arg)
{
	uint8_t errorCode = 0;

	if (arg->types[0] != DLL_ARGT_DOUBLE && arg->types[0] != DLL_ARGT_EMPTY || arg->types[1] != DLL_ARGT_TEXT)
		errorCode = ERROR1_INVALID_VALUE;
	if (arg->types[2]/*...*/)
		errorCode = ERROR1_SYNTAX_ERROR;

	if (!errorCode && arg->errors[0])
		errorCode = arg->errors[0];
	if (!errorCode && arg->errors[1])
		errorCode = arg->errors[1];

	if (!errorCode)
	{
		::strncpy(arg->strings[DLL_ARGC_RET], arg->strings[0], 1024);
		arg->strings[DLL_ARGC_RET][1024] = 0; // actually it's guaranteed on the entry
		return arg->types[DLL_ARGC_RET] = (arg->numbers[0] ? DLL_ARGT_MESSAGE : DLL_ARGT_TEXT);
	}
	else
	{
		arg->errors[DLL_ARGC_RET] = errorCode;
		return arg->types[DLL_ARGC_RET] = DLL_ARGT_EMPTY;
	}

	return 0;
}

}