#include <cmath>
#include "hillshading.h"

struct Constants
{
	double a1;
	double a2;
	double a3;
};

struct SubMatrix
{
	double z1;
	double z2;
	double z3;
	double z4;
	double z6;
	double z7;
	double z8;
	double z9;
};

struct Derivatives
{
	double dzdx;
	double dzdy;
};

int HillShading::_alpha = 96;
int HillShading::_blur = 3;
int HillShading::_azimuth = 315;
int HillShading::_altitude = 45;
double HillShading::_z = 0.6;
double HillShading::_l = 0.2;

static void getConstants(double azimuth, double elevation, Constants &c)
{
	double alpha = (M_PI / 180.0) * azimuth;
	double beta = (M_PI / 180.0) * elevation;

	c.a1 = sin(beta);
	c.a2 = cos(beta) * sin(alpha);
	c.a3 = cos(beta) * cos(alpha);
}

static void getDerivativesHorn(const SubMatrix &sm, double z, Derivatives &d)
{
	d.dzdx = (z * (sm.z3 + 2 * sm.z6 + sm.z9 - sm.z1 - 2 * sm.z4 - sm.z7)) / 8;
	d.dzdy = (z * (sm.z1 + 2 * sm.z2 + sm.z3 - sm.z7 - 2 * sm.z8 - sm.z9)) / 8;
}

static void getSubmatrix(int x, int y, const MatrixD &m, SubMatrix &sm)
{
	int left = x - 1;
	int right = x + 1;
	int top = y - 1;
	int bottom = y + 1;

	sm.z1 = m.at(top, left);
	sm.z2 = m.at(top, x);
	sm.z3 = m.at(top, right);
	sm.z4 = m.at(y, left);
	sm.z6 = m.at(y, right);
	sm.z7 = m.at(bottom, left);
	sm.z8 = m.at(bottom, x);
	sm.z9 = m.at(bottom, right);
}

QImage HillShading::render(const MatrixD &m, int extend)
{
	QImage img(m.w() - 2 * extend, m.h() - 2 * extend,
	  QImage::Format_ARGB32_Premultiplied);
	uchar *bits = img.bits();
	int bpl = img.bytesPerLine();

	Constants c;
	SubMatrix sm;
	Derivatives d;

	getConstants(_azimuth, _altitude, c);

	Q_ASSERT(extend > 0);

	for (int y = extend; y < m.h() - extend; y++) {
		for (int x = extend; x < m.w() - extend; x++) {
			getSubmatrix(x, y, m, sm);
			getDerivativesHorn(sm, _z, d);

			double L = (c.a1 - c.a2 * d.dzdx - c.a3 * d.dzdy)
			  / sqrt(1.0 + d.dzdx * d.dzdx + d.dzdy * d.dzdy);

			quint32 pixel;
			if (std::isnan(L))
				pixel = 0;
			else {
				L = sqrt(L * (1.0 - _l) + _l);
				quint8 val = (L < 0) ? 0 : L * _alpha;
				pixel = (_alpha - val)<<24;
			}

			*(quint32*)(bits + (y - extend) * bpl + (x - extend) * 4) = pixel;
		}
	}

	return img;
}
