#include "stdafx.h"
#include "rmgen.h"
#include "smoothelevationpainter.h"
#include "pointmap.h"

using namespace std;

SmoothElevationPainter::SmoothElevationPainter(int t, float e, int b)
{
	type = t;
	elevation = e;
	blendRadius = b;
	if(type!=SET && type!=MODIFY) {
		JS_ReportError(cx, "SmoothElevationPainter: type must be either SET or MODIFY");
	}
}

SmoothElevationPainter::~SmoothElevationPainter(void)
{
}

bool SmoothElevationPainter::checkInArea(Map* m, Area* a, int x, int y) {
	if(m->validT(x, y)) {
		return m->area[x][y] == a;
	}
	else {
		return false;
	}
}

void SmoothElevationPainter::paint(Map* m, Area* a) {
	// TODO: Use a 2D array instead of STL maps and sets for speed

	PointMap<int> saw;
	PointMap<int> dist;
	queue<Point> q;
	vector<Point>& pts = a->points;
	vector<Point> heightPts;
	PointMap<int> gotHeightPt;
	PointMap<float> newHeight;

	// get a list of all points
	for(int i=0; i<pts.size(); i++) {
		int x = pts[i].x, y = pts[i].y;
		for(int dx=-1; dx<=2; dx++) {
			for(int dy=-1; dy<=2; dy++) {
				int nx = x+dx, ny = y+dy;
				Point np(nx, ny);
				if(m->validH(nx, ny) && !gotHeightPt[np]) {
					gotHeightPt[np] = 1;
					heightPts.push_back(np);
					newHeight[np] = m->height[nx][ny];
				}
			}
		}
	}

	// push edge points
	for(int i=0; i<pts.size(); i++) {
		int x = pts[i].x, y = pts[i].y;
		for(int dx=-1; dx<=2; dx++) {
			for(int dy=-1; dy<=2; dy++) {
				int nx = x+dx, ny = y+dy;
				Point np(nx, ny);
				if(m->validH(nx, ny) 
						&& !checkInArea(m, a, nx, ny)
						&& !checkInArea(m, a, nx-1, ny)
						&& !checkInArea(m, a, nx, ny-1)
						&& !checkInArea(m, a, nx-1, ny-1)
						&& !saw[np]) {
					saw[np] = 1;
					dist[np] = 0;
					q.push(np);
				}
			}
		}
	}

	// do BFS inwards to find distances to edge
	while(!q.empty()) {
		Point p = q.front();
		q.pop();

		int d = dist[p];

		// paint if in area
		if(m->validH(p.x, p.y)
				&& (checkInArea(m, a, p.x, p.y) || checkInArea(m, a, p.x-1, p.y) 
				|| checkInArea(m, a, p.x, p.y-1) || checkInArea(m, a, p.x-1, p.y-1))) {
			if(d <= blendRadius) {
				float a = ((float)(d-1)) / blendRadius;
				if(type == SET) {
					newHeight[p] = a*elevation + (1-a)*m->height[p.x][p.y];
				}
				else {	// type == MODIFY
					newHeight[p] += a*elevation;
				}
			}
			else {	// also happens when blendRadius == 0
				if(type == SET) {
					newHeight[p] = elevation;
				}
				else {	// type == MODIFY
					newHeight[p] += elevation;
				}
			}
		}

		// enqueue neighbours
		for(int dx=-1; dx<=1; dx++) {
			for(int dy=-1; dy<=1; dy++) {
				int nx = p.x+dx, ny = p.y+dy;
				Point np(nx, ny);
				if(m->validH(nx, ny) 
						&& (checkInArea(m, a, nx, ny) || checkInArea(m, a, nx-1, ny) 
							|| checkInArea(m, a, nx, ny-1) || checkInArea(m, a, nx-1, ny-1))
						&& !saw[np]) {
					saw[np] = 1;
					dist[np] = d+1;
					q.push(np);
				}
			}
		}
	}

	// smooth everything out
	for(vector<Point>::iterator it = heightPts.begin(); it != heightPts.end(); it++) {
		Point p = *it;
		int x = p.x, y = p.y;
		if((checkInArea(m, a, x, y) || checkInArea(m, a, x-1, y) 
			|| checkInArea(m, a, x, y-1) || checkInArea(m, a, x-1, y-1))) {
			float sum = 8 * newHeight[p];
			int count = 8;
			for(int dx=-1; dx<=1; dx++) {
				for(int dy=-1; dy<=1; dy++) {
					int nx = x+dx, ny = y+dy;
					if(m->validH(nx, ny)) {
						sum += newHeight[Point(nx,ny)];//m->height[nx][ny];
						count++;
					}
				}
			}
			m->height[x][y] = sum/count;
		}
	}
	// don't smooth
	/*
	for(int i=0; i<heightPts.size(); i++) {
		int x = heightPts[i].x, y = heightPts[i].y;
		if((checkInArea(m, a, x, y) || checkInArea(m, a, x-1, y) 
			|| checkInArea(m, a, x, y-1) || checkInArea(m, a, x-1, y-1))) {
			m->height[x][y] = newHeight[p];
		}
	*/
}