#include "MEM_guardedalloc.h"

#include "BKE_utildefines.h"

#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_heap.h"
#include "BLI_ghash.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_array.h"

#include "bmesh.h"
#include "bmesh_operators_private.h"

#define ELE_NEW		1
#define ELE_OUT		2
#define ELE_ORIG	4

typedef struct EPathNode {
	struct EPathNode *next, *prev;
	BMVert *v;
	BMEdge *e;
} EPathNode;

typedef struct EPath {
	ListBase nodes;
	float weight;
} EPath;

typedef struct PathBase {
	BLI_mempool *nodepool, *pathpool;
} PathBase;

typedef struct EdgeData {
	int tag;
	int ftag;
} EdgeData;

#define EDGE_MARK	1
#define EDGE_VIS	2

#define FACE_NEW	1

PathBase *edge_pathbase_new(void)
{
	PathBase *pb = MEM_callocN(sizeof(PathBase), "PathBase");

	pb->nodepool = BLI_mempool_create(sizeof(EPathNode), 1, 512, 1, 0);
	pb->pathpool = BLI_mempool_create(sizeof(EPath), 1, 512, 1, 0);

	return pb;
}

void edge_pathbase_free(PathBase *pathbase)
{
	BLI_mempool_destroy(pathbase->nodepool);
	BLI_mempool_destroy(pathbase->pathpool);
	MEM_freeN(pathbase);
}

EPath *edge_copy_add_path(PathBase *pb, EPath *path, BMVert *appendv, BMEdge *e)
{
	EPath *path2;
	EPathNode *node, *node2;

	path2 = BLI_mempool_calloc(pb->pathpool);
	
	for (node=path->nodes.first; node; node=node->next) {
		node2 = BLI_mempool_calloc(pb->nodepool);
		*node2 = *node;
		BLI_addtail(&path2->nodes, node2);
	}

	node2 = BLI_mempool_calloc(pb->nodepool);
	node2->v = appendv;
	node2->e = e;

	BLI_addtail(&path2->nodes, node2);

	return path2;
}

EPath *edge_path_new(PathBase *pb, BMVert *start)
{
	EPath *path;
	EPathNode *node;

	path = BLI_mempool_calloc(pb->pathpool);
	node = BLI_mempool_calloc(pb->nodepool);

	node->v = start;
	node->e = NULL;

	BLI_addtail(&path->nodes, node);
	path->weight = 0.0f;

	return path;
}

float edge_weight_path(EPath *path, EdgeData *edata)
{
	EPathNode *node;
	float w = 0.0;

	for (node=path->nodes.first; node; node=node->next) {
		if (node->e) {
			w += edata[BMINDEX_GET(node->e)].ftag;
		}

		w += 1.0f;
	}

	return w;
}


void edge_free_path(PathBase *pathbase, EPath *path)
{
	EPathNode *node, *next;

	for (node=path->nodes.first; node; node=next) {
		next = node->next;
		BLI_mempool_free(pathbase->nodepool, node);
	}

	BLI_mempool_free(pathbase->pathpool, path);
}

EPath *edge_find_shortest_path(BMesh *bm, BMEdge *edge, EdgeData *edata, PathBase *pathbase)
{
	BMIter iter;
	BMEdge *e;
	GHash *gh = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "createops find shortest path");
	BMVert *v1, *v2;
	BMVert **verts = NULL;
	BLI_array_declare(verts);
	Heap *heap = BLI_heap_new();
	EPath *path = NULL, *path2;
	EPathNode *node;
	int i;

	path = edge_path_new(pathbase, edge->v1);
	BLI_heap_insert(heap, path->weight, path);
	path = NULL;

	while (BLI_heap_size(heap)) {
		if (path)
			edge_free_path(pathbase, path);
		path = BLI_heap_popmin(heap);
		v1 = ((EPathNode*)path->nodes.last)->v;
		
		if (v1 == edge->v2) {
			/*make sure this path loop doesn't already exist*/
			i = 0;
			BLI_array_empty(verts);
			for (i=0, node = path->nodes.first; node; node=node->next, i++) {
				BLI_array_growone(verts);
				verts[i] = node->v;
			}

			if (!BM_Face_Exists(bm, verts, i, NULL))
				break;
			else
				continue;
		}

		BM_ITER(e, &iter, bm, BM_EDGES_OF_VERT, v1) {
			if (e == edge || !BMO_TestFlag(bm, e, EDGE_MARK))
				continue;
			
			v2 = BM_OtherEdgeVert(e, v1);
			
			if (BLI_ghash_haskey(gh, v2))
				continue;

			BLI_ghash_insert(gh, v2, NULL);

			path2 = edge_copy_add_path(pathbase, path, v2, e);
			path2->weight = edge_weight_path(path2, edata);

			BLI_heap_insert(heap, path2->weight, path2);
		}

		if (BLI_heap_size(heap) == 0)
			path = NULL;
	}

	BLI_array_free(verts);
	BLI_heap_free(heap, NULL);
	BLI_ghash_free(gh, NULL, NULL);

	return path;
}

void bmesh_edgenet_fill_exec(BMesh *bm, BMOperator *op)
{
	BMIter iter;
	BMOIter siter;
	BMEdge *e, *edge;
	BMFace *f;
	EPath *path;
	EPathNode *node;
	EdgeData *edata;
	BMEdge **edges = NULL;
	PathBase *pathbase = edge_pathbase_new();
	BLI_array_declare(edges);
	int i;

	if (!bm->totvert || !bm->totedge)
		return;

	edata = MEM_callocN(sizeof(EdgeData)*bm->totedge, "EdgeData");
	BMO_Flag_Buffer(bm, op, "edges", EDGE_MARK, BM_EDGE);
	
	BM_ITER(f, &iter, bm, BM_FACES_OF_MESH, NULL) {
		BMO_SetFlag(bm, f, ELE_ORIG);
	}

	i = 0;
	BM_ITER(e, &iter, bm, BM_EDGES_OF_MESH, NULL) {
		BMINDEX_SET(e, i);
		
		if (!BMO_TestFlag(bm, e, EDGE_MARK)) {
			edata[i].tag = 2;
		}

		i += 1;
	}

	while (1) {
		edge = NULL;

		BMO_ITER(e, &siter, bm, op, "edges", BM_EDGE) {
			if (edata[BMINDEX_GET(e)].tag < 2) {
				edge = e;
				break;
			}
		}

		if (!edge)
			break;

		edata[BMINDEX_GET(edge)].tag += 1;

		path = edge_find_shortest_path(bm, edge, edata, pathbase);
		if (!path)
			continue;
		
		BLI_array_empty(edges);
		i = 0;
		for (node=path->nodes.first; node; node=node->next) {
			if (!node->next)
				continue;

			e = BM_Edge_Exist(node->v, node->next->v);
			
			/*this should never happen*/
			if (!e)
				break;
			
			edata[BMINDEX_GET(e)].ftag++;
			BLI_array_growone(edges);
			edges[i++] = e;
		}
		
		BLI_array_growone(edges);
		edges[i++] = edge;

		f = BM_Make_Ngon(bm, edge->v1, edge->v2, edges, i, 1);
		if (f && !BMO_TestFlag(bm, f, ELE_ORIG))
			BMO_SetFlag(bm, f, FACE_NEW);

		edge_free_path(pathbase, path);
	}

	BMO_Flag_To_Slot(bm, op, "faceout", FACE_NEW, BM_FACE);

	BLI_array_free(edges);
	edge_pathbase_free(pathbase);
	MEM_freeN(edata);
}

/* evaluate if entire quad is a proper convex quad */
static int convex(float *v1, float *v2, float *v3, float *v4)
{
	float nor[3], nor1[3], nor2[3], vec[4][2];
	
	/* define projection, do both trias apart, quad is undefined! */
	normal_tri_v3( nor1,v1, v2, v3);
	normal_tri_v3( nor2,v1, v3, v4);
	nor[0]= ABS(nor1[0]) + ABS(nor2[0]);
	nor[1]= ABS(nor1[1]) + ABS(nor2[1]);
	nor[2]= ABS(nor1[2]) + ABS(nor2[2]);

	if(nor[2] >= nor[0] && nor[2] >= nor[1]) {
		vec[0][0]= v1[0]; vec[0][1]= v1[1];
		vec[1][0]= v2[0]; vec[1][1]= v2[1];
		vec[2][0]= v3[0]; vec[2][1]= v3[1];
		vec[3][0]= v4[0]; vec[3][1]= v4[1];
	}
	else if(nor[1] >= nor[0] && nor[1]>= nor[2]) {
		vec[0][0]= v1[0]; vec[0][1]= v1[2];
		vec[1][0]= v2[0]; vec[1][1]= v2[2];
		vec[2][0]= v3[0]; vec[2][1]= v3[2];
		vec[3][0]= v4[0]; vec[3][1]= v4[2];
	}
	else {
		vec[0][0]= v1[1]; vec[0][1]= v1[2];
		vec[1][0]= v2[1]; vec[1][1]= v2[2];
		vec[2][0]= v3[1]; vec[2][1]= v3[2];
		vec[3][0]= v4[1]; vec[3][1]= v4[2];
	}
	
	/* linetests, the 2 diagonals have to instersect to be convex */
	if( isect_line_line_v2(vec[0], vec[2], vec[1], vec[3]) > 0 ) return 1;
	return 0;
}

BMEdge *edge_next(BMesh *bm, BMEdge *e)
{
	BMIter iter;
	BMEdge *e2;
	int i;

	for (i=0; i<2; i++) {
		BM_ITER(e2, &iter, bm, BM_EDGES_OF_VERT, i?e->v2:e->v1) {
			if (BMO_TestFlag(bm, e2, EDGE_MARK) 
			    && !BMO_TestFlag(bm, e2, EDGE_VIS) && e2 != e)
			{
				return e2;
			}
		}
	}

	return NULL;
}

void bmesh_edgenet_prepare(BMesh *bm, BMOperator *op)
{
	BMOIter siter;
	BMIter iter;
	BMEdge *e, *e2;
	BMEdge **edges1 = NULL, **edges2 = NULL, **edges;
	BLI_array_declare(edges1);
	BLI_array_declare(edges2);
	BLI_array_declare(edges);
	int ok = 1;
	int i, count;

	BMO_Flag_Buffer(bm, op, "edges", EDGE_MARK, BM_EDGE);
	
	/*validate that each edge has at most one other tagged edge in the
	  disk cycle around each of it's vertices*/
	BMO_ITER(e, &siter, bm, op, "edges", BM_EDGE) {
		for (i=0; i<2; i++) {
			count = BMO_Vert_CountEdgeFlags(bm, i?e->v2:e->v1, EDGE_MARK);
			if (count > 2) {
				ok = 0;
				break;
			}
		}

		if (!ok) break;
	}

	/*we don't have valid edge layouts, return*/
	if (!ok)
		return;


	/*find connected loops within the input edges*/
	count = 0;
	while (1) {
		BMO_ITER(e, &siter, bm, op, "edges", BM_EDGE) {
			if (!BMO_TestFlag(bm, e, EDGE_VIS)) {
				if (BMO_Vert_CountEdgeFlags(bm, e->v1, EDGE_MARK)==1)
					break;
				if (BMO_Vert_CountEdgeFlags(bm, e->v2, EDGE_MARK)==1)
					break;
			}
		}
		
		if (!e) break;
		
		if (!count)
			edges = edges1;
		else if (count==1)
			edges = edges2;
		else break;
		
		i = 0;
		while (e) {
			BMO_SetFlag(bm, e, EDGE_VIS);
			BLI_array_growone(edges);
			edges[i] = e;

			e = edge_next(bm, e);
			i++;
		}

		if (!count) {
			edges1 = edges;
			BLI_array_set_length(edges1, BLI_array_count(edges));
		} else {
			edges2 = edges;
			BLI_array_set_length(edges2, BLI_array_count(edges));
		}

		BLI_array_empty(edges);
		count++;
	}

#define EDGECON(e1, e2) (e1->v1 == e2->v1 || e1->v2 == e2->v2 || e1->v1 == e2->v2)

	if (edges1 && BLI_array_count(edges1) > 2 && EDGECON(edges1[0], edges1[BLI_array_count(edges1)-1])) {
		if (edges2 && BLI_array_count(edges2) > 2 && EDGECON(edges2[0], edges2[BLI_array_count(edges2)-1])) {
			BLI_array_free(edges1);
			BLI_array_free(edges2);
			return;
		} else {
			edges1 = edges2;
			edges2 = NULL;
		}
	}

	if (edges2 && BLI_array_count(edges2) > 2 && EDGECON(edges2[0], edges2[BLI_array_count(edges2)-1])) {
		edges2 = NULL;
	}

	/*two unconnected loops, connect them*/
	if (edges1 && edges2) {
		BMVert *v1, *v2, *v3, *v4;

		if (BLI_array_count(edges1)==1) {
			v1 = edges1[0]->v1;
			v2 = edges1[0]->v2;
		} else {
			if (BM_Vert_In_Edge(edges1[1], edges1[0]->v1))
				v1 = edges1[0]->v2;
			else v1 = edges1[0]->v1;

			i = BLI_array_count(edges1)-1;
			if (BM_Vert_In_Edge(edges1[i-1], edges1[i]->v1))
				v2 = edges1[i]->v2;
			else v2 = edges1[i]->v1;
		}

		if (BLI_array_count(edges2)==1) {
			v3 = edges2[0]->v1;
			v4 = edges2[0]->v2;
		} else {
			if (BM_Vert_In_Edge(edges2[1], edges2[0]->v1))
				v3 = edges2[0]->v2;
			else v3 = edges2[0]->v1;

			i = BLI_array_count(edges2)-1;
			if (BM_Vert_In_Edge(edges2[i-1], edges2[i]->v1))
				v4 = edges2[i]->v2;
			else v4 = edges2[i]->v1;
		}

		if (len_v3v3(v1->co, v3->co) > len_v3v3(v1->co, v4->co)) {
			BMVert *v;
			v = v3;
			v3 = v4;
			v4 = v;
		}

		e = BM_Make_Edge(bm, v1, v3, NULL, 1);
		BMO_SetFlag(bm, e, ELE_NEW);
		e = BM_Make_Edge(bm, v2, v4, NULL, 1);
		BMO_SetFlag(bm, e, ELE_NEW);
	} else if (edges1) {
		BMVert *v1, *v2;
		
		if (BLI_array_count(edges1) > 1) {
			if (BM_Vert_In_Edge(edges1[1], edges1[0]->v1))
				v1 = edges1[0]->v2;
			else v1 = edges1[0]->v1;

			i = BLI_array_count(edges1)-1;
			if (BM_Vert_In_Edge(edges1[i-1], edges1[i]->v1))
				v2 = edges1[i]->v2;
			else v2 = edges1[i]->v1;

			e = BM_Make_Edge(bm, v1, v2, NULL, 1);
			BMO_SetFlag(bm, e, ELE_NEW);
		}
	}
	
	BMO_Flag_To_Slot(bm, op, "edgeout", ELE_NEW, BM_EDGE);

	BLI_array_free(edges1);
	BLI_array_free(edges2);

#undef EDGECON
}

/*this is essentially new fkey*/
void bmesh_contextual_create_exec(BMesh *bm, BMOperator *op)
{
	BMOperator op2;
	BMOIter oiter;
	BMIter iter;
	BMHeader *h;
	BMVert *v, *verts[4];
	BMEdge *e;
	BMFace *f;
	int totv=0, tote=0, totf=0, amount;

	/*count number of each element type we were passed*/
	BMO_ITER(h, &oiter, bm, op, "geom", BM_VERT|BM_EDGE|BM_FACE) {
		switch (h->type) {
			case BM_VERT: totv++; break;
			case BM_EDGE: tote++; break;
			case BM_FACE: totf++; break;
		}

		BMO_SetFlag(bm, h, ELE_NEW);
	}
	
	/*call edgenet create*/
	/*  call edgenet prepare op so additional face creation cases work*/
	BMO_InitOpf(bm, &op2, "edgenet_prepare edges=%fe", ELE_NEW);
	BMO_Exec_Op(bm, &op2);
	BMO_Flag_Buffer(bm, &op2, "edgeout", ELE_NEW, BM_EDGE);
	BMO_Finish_Op(bm, &op2);

	BMO_InitOpf(bm, &op2, "edgenet_fill edges=%fe", ELE_NEW);
	BMO_Exec_Op(bm, &op2);

	/*return if edge net create did something*/
	if (BMO_CountSlotBuf(bm, &op2, "faceout")) {
		BMO_CopySlot(&op2, op, "faceout", "faceout");
		BMO_Finish_Op(bm, &op2);
		return;
	}

	BMO_Finish_Op(bm, &op2);
	
	/*now call dissolve faces*/
	BMO_InitOpf(bm, &op2, "dissolvefaces faces=%ff", ELE_NEW);
	BMO_Exec_Op(bm, &op2);
	
	/*if we dissolved anything, then return.*/
	if (BMO_CountSlotBuf(bm, &op2, "regionout")) {
		BMO_CopySlot(&op2, op, "regionout", "faceout");
		BMO_Finish_Op(bm, &op2);
		return;
	}

	BMO_Finish_Op(bm, &op2);

	/*now, count how many verts we have*/
	amount = 0;
	BM_ITER(v, &iter, bm, BM_VERTS_OF_MESH, NULL) {
		if (BMO_TestFlag(bm, v, ELE_NEW)) {
			verts[amount] = v;
			amount++;

			if (amount > 4) break;
		}
	}

	if (amount == 2) {
		/*create edge*/
		e = BM_Make_Edge(bm, verts[0], verts[1], NULL, 1);
		BMO_SetFlag(bm, e, ELE_OUT);		
	} else if (amount == 3) {
		/*create triangle*/
		BM_Make_QuadTri(bm, verts[0], verts[1], verts[2], NULL, NULL, 1);
	} else if (amount == 4) {
		f = NULL;

		/* the order of vertices can be anything, 6 cases to check */
		if( convex(verts[0]->co, verts[1]->co, verts[2]->co, verts[3]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[1], verts[2], verts[3], NULL, 1);
		}
		else if( convex(verts[0]->co, verts[2]->co, verts[3]->co, verts[1]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[2], verts[3], verts[1], NULL, 1);
		}
		else if( convex(verts[0]->co, verts[2]->co, verts[1]->co, verts[3]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[2], verts[1], verts[3], NULL, 1);
		}
		else if( convex(verts[0]->co, verts[1]->co, verts[3]->co, verts[2]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[1], verts[3], verts[2], NULL, 1);
		}
		else if( convex(verts[0]->co, verts[3]->co, verts[2]->co, verts[1]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[3], verts[2], verts[1], NULL, 1);
		}
		else if( convex(verts[0]->co, verts[3]->co, verts[1]->co, verts[2]->co) ) {
			f= BM_Make_QuadTri(bm, verts[0], verts[3], verts[1], verts[2], NULL, 1);
		}
		else {
			printf("cannot find nice quad from concave set of vertices\n");
		}

		if (f) BMO_SetFlag(bm, f, ELE_OUT);
	}
}
