/*************************************************************************/
/*  bit_mask.cpp                                                         */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "bit_mask.h"
#include "io/image_loader.h"

void BitMap::create(const Size2 &p_size) {

	ERR_FAIL_COND(p_size.width < 1);
	ERR_FAIL_COND(p_size.height < 1);

	width = p_size.width;
	height = p_size.height;
	bitmask.resize(((width * height) / 8) + 1);
	zeromem(bitmask.ptrw(), bitmask.size());
}

void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshold) {

	ERR_FAIL_COND(p_image.is_null() || p_image->empty());
	Ref<Image> img = p_image->duplicate();
	img->convert(Image::FORMAT_LA8);
	ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8);

	create(Size2(img->get_width(), img->get_height()));

	PoolVector<uint8_t>::Read r = img->get_data().read();
	uint8_t *w = bitmask.ptrw();

	for (int i = 0; i < width * height; i++) {

		int bbyte = i / 8;
		int bbit = i % 8;
		if (r[i * 2 + 1] / 255.0 > p_threshold) {
			w[bbyte] |= (1 << bbit);
		}
	}
}

void BitMap::set_bit_rect(const Rect2 &p_rect, bool p_value) {

	Rect2i current = Rect2i(0, 0, width, height).clip(p_rect);
	uint8_t *data = bitmask.ptrw();

	for (int i = current.position.x; i < current.position.x + current.size.x; i++) {

		for (int j = current.position.y; j < current.position.y + current.size.y; j++) {

			int ofs = width * j + i;
			int bbyte = ofs / 8;
			int bbit = ofs % 8;

			uint8_t b = data[bbyte];

			if (p_value)
				b |= (1 << bbit);
			else
				b &= ~(1 << bbit);

			data[bbyte] = b;
		}
	}
}

int BitMap::get_true_bit_count() const {

	int ds = bitmask.size();
	const uint8_t *d = bitmask.ptr();
	int c = 0;

	//fast, almot branchless version

	for (int i = 0; i < ds; i++) {

		c += (d[i] & (1 << 7)) >> 7;
		c += (d[i] & (1 << 6)) >> 6;
		c += (d[i] & (1 << 5)) >> 5;
		c += (d[i] & (1 << 4)) >> 4;
		c += (d[i] & (1 << 3)) >> 3;
		c += (d[i] & (1 << 2)) >> 2;
		c += d[i] & 1;
	}

	return c;
}

void BitMap::set_bit(const Point2 &p_pos, bool p_value) {

	int x = p_pos.x;
	int y = p_pos.y;

	ERR_FAIL_INDEX(x, width);
	ERR_FAIL_INDEX(y, height);

	int ofs = width * y + x;
	int bbyte = ofs / 8;
	int bbit = ofs % 8;

	uint8_t b = bitmask[bbyte];

	if (p_value)
		b |= (1 << bbit);
	else
		b &= ~(1 << bbit);

	bitmask[bbyte] = b;
}

bool BitMap::get_bit(const Point2 &p_pos) const {

	int x = Math::fast_ftoi(p_pos.x);
	int y = Math::fast_ftoi(p_pos.y);
	ERR_FAIL_INDEX_V(x, width, false);
	ERR_FAIL_INDEX_V(y, height, false);

	int ofs = width * y + x;
	int bbyte = ofs / 8;
	int bbit = ofs % 8;

	return (bitmask[bbyte] & (1 << bbit)) != 0;
}

Size2 BitMap::get_size() const {

	return Size2(width, height);
}

void BitMap::_set_data(const Dictionary &p_d) {

	ERR_FAIL_COND(!p_d.has("size"));
	ERR_FAIL_COND(!p_d.has("data"));

	create(p_d["size"]);
	bitmask = p_d["data"];
}

Dictionary BitMap::_get_data() const {

	Dictionary d;
	d["size"] = get_size();
	d["data"] = bitmask;
	return d;
}

Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) const {

	int stepx = 0;
	int stepy = 0;
	int prevx = 0;
	int prevy = 0;
	int startx = start.x;
	int starty = start.y;
	int curx = startx;
	int cury = starty;
	unsigned int count = 0;
	Set<Point2i> case9s;
	Set<Point2i> case6s;
	int i;
	Vector<Vector2> _points;
	do {
		int sv = 0;
		{ //square value

			/*
	     checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent
	     +---+---+
	     | 1 | 2 |
	     +---+---+
	     | 4 | 8 | <- current pixel (curx,cury)
	     +---+---+
	     */
			//NOTE: due to the way we pick points from texture, rect needs to be smaller, otherwise it goes outside 1 pixel
			Rect2i fixed_rect = Rect2i(rect.position, rect.size - Size2i(2, 2));
			Point2i tl = Point2i(curx - 1, cury - 1);
			sv += (fixed_rect.has_point(tl) && get_bit(tl)) ? 1 : 0;
			Point2i tr = Point2i(curx, cury - 1);
			sv += (fixed_rect.has_point(tr) && get_bit(tr)) ? 2 : 0;
			Point2i bl = Point2i(curx - 1, cury);
			sv += (fixed_rect.has_point(bl) && get_bit(bl)) ? 4 : 0;
			Point2i br = Point2i(curx, cury);
			sv += (fixed_rect.has_point(br) && get_bit(br)) ? 8 : 0;
			ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector2>());
		}

		switch (sv) {

			case 1:
			case 5:
			case 13:
				/* going UP with these cases:
		 1          5           13
		 +---+---+  +---+---+  +---+---+
		 | 1 |   |  | 1 |   |  | 1 |   |
		 +---+---+  +---+---+  +---+---+
		 |   |   |  | 4 |   |  | 4 | 8 |
		 +---+---+  +---+---+  +---+---+
		 */
				stepx = 0;
				stepy = -1;
				break;

			case 8:
			case 10:
			case 11:
				/* going DOWN with these cases:
		 8          10          11
		 +---+---+  +---+---+   +---+---+
		 |   |   |  |   | 2 |   | 1 | 2 |
		 +---+---+  +---+---+   +---+---+
		 |   | 8 |  |   | 8 |   |   | 8 |
		 +---+---+  +---+---+  	+---+---+
		 */
				stepx = 0;
				stepy = 1;
				break;

			case 4:
			case 12:
			case 14:
				/* going LEFT with these cases:
		 4          12          14
		 +---+---+  +---+---+   +---+---+
		 |   |   |  |   |   |   |   | 2 |
		 +---+---+  +---+---+   +---+---+
		 | 4 |   |  | 4 | 8 |   | 4 | 8 |
		 +---+---+  +---+---+  	+---+---+
		 */
				stepx = -1;
				stepy = 0;
				break;

			case 2:
			case 3:
			case 7:
				/* going RIGHT with these cases:
		 2          3           7
		 +---+---+  +---+---+   +---+---+
		 |   | 2 |  | 1 | 2 |   | 1 | 2 |
		 +---+---+  +---+---+   +---+---+
		 |   |   |  |   |   |   | 4 |   |
		 +---+---+  +---+---+  	+---+---+
		 */
				stepx = 1;
				stepy = 0;
				break;
			case 9:
				/*
		 +---+---+
		 | 1 |   |
		 +---+---+
		 |   | 8 |
		 +---+---+
		 this should normally go UP, but if we already been here, we go down
		*/
				if (case9s.has(Point2i(curx, cury))) {
					//found, so we go down, and delete from case9s;
					stepx = 0;
					stepy = 1;
					case9s.erase(Point2i(curx, cury));
				} else {
					//not found, we go up, and add to case9s;
					stepx = 0;
					stepy = -1;
					case9s.insert(Point2i(curx, cury));
				}
				break;
			case 6:
				/*
		 6
		 +---+---+
		 |   | 2 |
		 +---+---+
		 | 4 |   |
		 +---+---+
		 this normally go RIGHT, but if its coming from UP, it should go LEFT
		 */
				if (case6s.has(Point2i(curx, cury))) {
					//found, so we go down, and delete from case6s;
					stepx = -1;
					stepy = 0;
					case6s.erase(Point2i(curx, cury));
				} else {
					//not found, we go up, and add to case6s;
					stepx = 1;
					stepy = 0;
					case6s.insert(Point2i(curx, cury));
				}
				break;
			default:
				ERR_PRINT("this shouldn't happen.");
		}
		//little optimization
		// if previous direction is same as current direction,
		// then we should modify the last vec to current
		curx += stepx;
		cury += stepy;
		if (stepx == prevx && stepy == prevy) {
			_points[_points.size() - 1].x = (float)(curx - rect.position.x);
			_points[_points.size() - 1].y = (float)(cury + rect.position.y);
		} else {
			_points.push_back(Vector2((float)(curx - rect.position.x), (float)(cury + rect.position.y)));
		}

		count++;
		prevx = stepx;
		prevy = stepy;

		ERR_FAIL_COND_V(count > width * height, _points);
	} while (curx != startx || cury != starty);
	return _points;
}

static float perpendicular_distance(const Vector2 &i, const Vector2 &start, const Vector2 &end) {
	float res;
	float slope;
	float intercept;

	if (start.x == end.x) {
		res = Math::absf(i.x - end.x);
	} else if (start.y == end.y) {
		res = Math::absf(i.y - end.y);
	} else {
		slope = (end.y - start.y) / (end.x - start.x);
		intercept = start.y - (slope * start.x);
		res = Math::absf(slope * i.x - i.y + intercept) / Math::sqrt(Math::pow(slope, 2.0f) + 1.0);
	}
	return res;
}

static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) {
	if (v.size() < 3)
		return v;

	int index = -1;
	float dist = 0;
	//not looping first and last point
	for (size_t i = 1, size = v.size(); i < size - 1; ++i) {
		float cdist = perpendicular_distance(v[i], v[0], v[v.size() - 1]);
		if (cdist > dist) {
			dist = cdist;
			index = static_cast<int>(i);
		}
	}
	if (dist > optimization) {

		Vector<Vector2> left, right;
		left.resize(index);
		for (int i = 0; i < index; i++) {
			left[i] = v[i];
		}
		right.resize(v.size() - index);
		for (int i = 0; i < right.size(); i++) {
			right[i] = v[index + i];
		}
		Vector<Vector2> r1 = rdp(left, optimization);
		Vector<Vector2> r2 = rdp(right, optimization);

		int middle = r1.size();
		r1.resize(r1.size() + r2.size());
		for (int i = 0; i < r2.size(); i++) {
			r1[middle + i] = r2[i];
		}
		return r1;
	} else {
		Vector<Vector2> ret;
		ret.push_back(v[0]);
		ret.push_back(v[v.size() - 1]);
		return ret;
	}
}

static Vector<Vector2> reduce(const Vector<Vector2> &points, const Rect2i &rect, float epsilon) {
	int size = points.size();
	// if there are less than 3 points, then we have nothing
	ERR_FAIL_COND_V(size < 3, Vector<Vector2>());
	// if there are less than 9 points (but more than 3), then we don't need to reduce it
	if (size < 9) {
		return points;
	}

	float maxEp = MIN(rect.size.width, rect.size.height);
	float ep = CLAMP(epsilon, 0.0, maxEp / 2);
	Vector<Vector2> result = rdp(points, ep);

	Vector2 last = result[result.size() - 1];

	if (last.y > result[0].y && last.distance_to(result[0]) < ep * 0.5f) {
		result[0].y = last.y;
		result.resize(result.size() - 1);
	}
	return result;
}

static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_pos, const Rect2i &rect) {

	for (int i = p_pos.x - 1; i <= p_pos.x + 1; i++) {
		for (int j = p_pos.y - 1; j <= p_pos.y + 1; j++) {

			if (i < rect.position.x || i >= rect.position.x + rect.size.x)
				continue;
			if (j < rect.position.y || j >= rect.position.y + rect.size.y)
				continue;

			if (p_map->get_bit(Vector2(i, j)))
				continue;

			else if (p_src->get_bit(Vector2(i, j))) {
				p_map->set_bit(Vector2(i, j), true);
				fill_bits(p_src, p_map, Point2i(i, j), rect);
			}
		}
	}
}
Vector<Vector<Vector2> > BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon) const {

	Rect2i r = Rect2i(0, 0, width, height).clip(p_rect);

	print_line("Rect: " + r);
	Point2i from;
	Ref<BitMap> fill;
	fill.instance();
	fill->create(get_size());

	Vector<Vector<Vector2> > polygons;
	for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
		for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
			if (!fill->get_bit(Point2(j, i)) && get_bit(Point2(j, i))) {

				Vector<Vector2> polygon = _march_square(r, Point2i(j, i));
				print_line("pre reduce: " + itos(polygon.size()));
				polygon = reduce(polygon, r, p_epsilon);
				print_line("post reduce: " + itos(polygon.size()));
				polygons.push_back(polygon);
				fill_bits(this, fill, Point2i(j, i), r);
			}
		}
	}

	return polygons;
}

void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) {

	Rect2i r = Rect2i(0, 0, width, height).clip(p_rect);

	Ref<BitMap> copy;
	copy.instance();
	copy->create(get_size());
	copy->bitmask = bitmask;

	for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
		for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
			if (copy->get_bit(Point2(j, i)))
				continue;

			bool found = false;

			for (int y = i - p_pixels; y <= i + p_pixels; y++) {
				for (int x = j - p_pixels; x <= j + p_pixels; x++) {

					if (x < p_rect.position.x || x >= p_rect.position.x + p_rect.size.x)
						continue;
					if (y < p_rect.position.y || y >= p_rect.position.y + p_rect.size.y)
						continue;

					float d = Point2(j, i).distance_to(Point2(x, y)) - CMP_EPSILON;
					if (d > p_pixels)
						continue;

					if (copy->get_bit(Point2(x, y))) {
						found = true;
						break;
					}
				}
				if (found)
					break;
			}

			if (found) {
				set_bit(Point2(j, i), true);
			}
		}
	}
}

void BitMap::_bind_methods() {

	ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create);
	ClassDB::bind_method(D_METHOD("create_from_image_alpha", "image", "threshold"), &BitMap::create_from_image_alpha, DEFVAL(0.1));

	ClassDB::bind_method(D_METHOD("set_bit", "position", "bit"), &BitMap::set_bit);
	ClassDB::bind_method(D_METHOD("get_bit", "position"), &BitMap::get_bit);

	ClassDB::bind_method(D_METHOD("set_bit_rect", "p_rect", "bit"), &BitMap::set_bit_rect);
	ClassDB::bind_method(D_METHOD("get_true_bit_count"), &BitMap::get_true_bit_count);

	ClassDB::bind_method(D_METHOD("get_size"), &BitMap::get_size);

	ClassDB::bind_method(D_METHOD("_set_data"), &BitMap::_set_data);
	ClassDB::bind_method(D_METHOD("_get_data"), &BitMap::_get_data);

	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}

BitMap::BitMap() {

	width = 0;
	height = 0;
}

//////////////////////////////////////
