#include "GridWarpSurface.h"

namespace ofx {
namespace piMapper {

GridWarpSurface::GridWarpSurface(){
	_gridCols = 2;
	_gridRows = 3;
	createGridMesh();
}

void GridWarpSurface::draw(){
	if(source->getTexture() == 0){
		return;
	}
	
	if(!source->getTexture()->isAllocated()){
		return;
	}
	
	source->getTexture()->bind();
	mesh.draw();
	source->getTexture()->unbind();
}

void GridWarpSurface::moveBy(ofVec2f v){
	vector <ofVec3f> & vertices = getVertices();
	
	for(int i = 0; i < vertices.size(); i++){
		vertices[i] += v;
	}
	
	setMoved(true);
	ofNotifyEvent(verticesChangedEvent, mesh.getVertices(), this);
}

int GridWarpSurface::getType(){
	return SurfaceType::GRID_WARP_SURFACE;
}

int GridWarpSurface::getGridRows(){
	return _gridRows;
}

int GridWarpSurface::getGridCols(){
	return _gridCols;
}

int GridWarpSurface::setGridRows(int r){
	_gridRows = r;
	createGridMesh();
}

int GridWarpSurface::setGridCols(int c){
	_gridCols = c;
	createGridMesh();
}

bool GridWarpSurface::hitTest(ofVec2f p){
	ofPolyline pl;
	int vertsPerCol = _gridRows + 1;
	int vertsPerRow = _gridCols + 1;
	
	for(int iy = 0; iy < _gridRows; ++iy){
		for(int ix = 0; ix < _gridCols; ++ix){
			int a = (iy * vertsPerRow) + ix;
			int b = (iy * vertsPerRow) + ix + 1;
			int c = ((iy + 1) * vertsPerRow) + ix + 1;
			int d = ((iy + 1) * vertsPerRow) + ix;
			
			pl.clear();
			pl.addVertex(mesh.getVertex(a));
			pl.addVertex(mesh.getVertex(b));
			pl.addVertex(mesh.getVertex(c));
			pl.addVertex(mesh.getVertex(d));
			pl.close();
			
			if(pl.inside(p)){
				return true;
			}
		}
	}
	
	return false;
}

ofPolyline GridWarpSurface::getHitArea(){
	ofPolyline pl;
	int vertsPerCol = _gridRows + 1;
	int vertsPerRow = _gridCols + 1;
	
	// Get the top border
	for(int ix = 0; ix <= _gridCols; ++ix){
		pl.addVertex(mesh.getVertex(ix));
	}
	
	// Get right border from top down
	for(int iy = 1; iy <= _gridRows; ++iy){
		int i = iy * vertsPerRow + vertsPerRow - 1;
		pl.addVertex(mesh.getVertex(i));
	}
	
	// Get bottom border from right to left
	for(int ix = _gridCols; ix >= 0; --ix){
		int i = _gridRows * vertsPerRow + vertsPerRow - 2;
		pl.addVertex(mesh.getVertex(i));
	}
	
	// Get left border from bottom to top
	for(int iy = _gridRows; iy > 0; --iy){
		int i = iy * vertsPerRow;
		pl.addVertex(mesh.getVertex(i));
	}
	
	pl.close();
	return pl;
}

ofPolyline GridWarpSurface::getTextureHitArea(){
	ofPolyline line;
	vector <ofVec2f> & texCoords = mesh.getTexCoords();
	ofVec2f textureSize = ofVec2f(source->getTexture()->getWidth(), source->getTexture()->getHeight());
	
	int vertsPerRow = _gridCols + 1;
	int vertsPerCol = _gridRows + 1;
	
	int a = 0;
	int b = _gridCols;
	int c = (_gridRows * vertsPerRow) + (vertsPerRow - 1);
	int d = (_gridRows * vertsPerRow);
	
	line.addVertex(ofPoint(texCoords[a] * textureSize));
	line.addVertex(ofPoint(texCoords[b] * textureSize));
	line.addVertex(ofPoint(texCoords[c] * textureSize));
	line.addVertex(ofPoint(texCoords[d] * textureSize));
	line.close();

	return line;
}

void GridWarpSurface::setVertex(int index, ofVec2f p){
	if(index >= mesh.getVertices().size()){
		throw runtime_error("Vertex with provided index does not exist");
	}
	
	mesh.setVertex(index, p);
	ofVec3f v = mesh.getVertex(index);
	ofNotifyEvent(vertexChangedEvent, v, this);
}

void GridWarpSurface::setVertices(vector<ofVec2f> v){
	if(v.size() != mesh.getVertices().size()){
		throw runtime_error("Wrong number of vertices");
	}
	
	for(int i = 0; i < v.size(); ++i){
		mesh.setVertex(i, v[i]);
	}
	
	ofNotifyEvent(verticesChangedEvent, mesh.getVertices(), this);
}

void GridWarpSurface::setTexCoord(int index, ofVec2f t){
	if(index >= mesh.getVertices().size()){
		throw runtime_error("Texture coordinate with provided index does not exist");
	}
	mesh.setTexCoord(index, t);
}

void GridWarpSurface::setTexCoords(vector<ofVec2f> t){
	if(t.size() != mesh.getVertices().size()){
		throw runtime_error("Wrong number of texture coordinates");
	}
	for(int i = 0; i < t.size(); ++i){
		mesh.setTexCoord(i, t[i]);
	}
}


vector <ofVec3f> & GridWarpSurface::getVertices(){
	return mesh.getVertices();
}

vector <ofVec2f> & GridWarpSurface::getTexCoords(){
	return mesh.getTexCoords();
}

void GridWarpSurface::createGridMesh(){
	mesh.clear();
	
	float margin = 100.0f;
	float surfaceWidth = (float)ofGetWidth() - margin * 2.0f;
	float surfaceHeight = (float)ofGetHeight() - margin * 2.0f;
	float vertexDistanceX = surfaceWidth / (float)_gridCols;
	float vertexDistanceY = surfaceHeight / (float)_gridRows;
	
	// Add vertices for each col and row
	for(int iy = 0; iy <= _gridRows; ++iy){
		for(int ix = 0; ix <= _gridCols; ++ix){
			mesh.addVertex(
				ofVec2f(
					margin + (vertexDistanceX * (float)ix),
					margin + (vertexDistanceY * (float)iy) ));
		}
	}
	
	int vertsPerCol = _gridRows + 1;
	int vertsPerRow = _gridCols + 1;
	
	// Form triangles for all grid cols and rows
	for(int iy = 0; iy < _gridRows; ++iy){
		for(int ix = 0; ix < _gridCols; ++ix){
			int a = (iy * vertsPerRow) + ix;
			int b = (iy * vertsPerRow) + ix + 1;
			int c = ((iy + 1) * vertsPerRow) + ix + 1;
			int d = ((iy + 1) * vertsPerRow) + ix;
			mesh.addTriangle(a, b, c);
			mesh.addTriangle(a, c, d);
		}
	}
	
	// Add texture coordinates for each of the vertices
	for(int iy = 0; iy <= _gridRows; ++iy){
		for(int ix = 0; ix <= _gridCols; ++ix){
			float xc = (ix == 0) ? 0.0f : (float)ix / (float)_gridCols;
			float yc = (iy == 0) ? 0.0f : (float)iy / (float)_gridRows;
			mesh.addTexCoord(ofVec2f(xc, yc));
		}
	}
	
	ofNotifyEvent(verticesChangedEvent, mesh.getVertices(), this);
}



} // namespace piMapper
} // namespace ofx