#include <GL/gl.h>
#include <eepp/ee.hpp>

EE::Window::Window* win = NULL;

static Float borderDiff = 0;
static Float borderWidthDiff = 0;

VertexBuffer* mVertexBuffer;
VertexBuffer* mVertexBuffers[4];
VertexBuffer* mVBO;

struct borderRadius {
	Sizef topLeft;
	Sizef topRight;
	Sizef bottomLeft;
	Sizef bottomRight;
};

struct border {
	int width;
	Color color;

	border() { width = 0; }
	border( const border& val ) {
		width = val.width;
		color = val.color;
	}
	border& operator=( const border& val ) {
		width = val.width;
		color = val.color;
		return *this;
	}
};

struct border_radiuses {
	Float top_left_x;
	Float top_left_y;

	Float top_right_x;
	Float top_right_y;

	Float bottom_right_x;
	Float bottom_right_y;

	Float bottom_left_x;
	Float bottom_left_y;

	border_radiuses() {
		top_left_x = 0;
		top_left_y = 0;
		top_right_x = 0;
		top_right_y = 0;
		bottom_right_x = 0;
		bottom_right_y = 0;
		bottom_left_x = 0;
		bottom_left_y = 0;
	}
	border_radiuses( const border_radiuses& val ) {
		top_left_x = val.top_left_x;
		top_left_y = val.top_left_y;
		top_right_x = val.top_right_x;
		top_right_y = val.top_right_y;
		bottom_right_x = val.bottom_right_x;
		bottom_right_y = val.bottom_right_y;
		bottom_left_x = val.bottom_left_x;
		bottom_left_y = val.bottom_left_y;
	}
	border_radiuses& operator=( const border_radiuses& val ) {
		top_left_x = val.top_left_x;
		top_left_y = val.top_left_y;
		top_right_x = val.top_right_x;
		top_right_y = val.top_right_y;
		bottom_right_x = val.bottom_right_x;
		bottom_right_y = val.bottom_right_y;
		bottom_left_x = val.bottom_left_x;
		bottom_left_y = val.bottom_left_y;
		return *this;
	}
};

struct borders {
	border left;
	border top;
	border right;
	border bottom;
	border_radiuses radius;

	borders() {}

	borders( const borders& val ) {
		left = val.left;
		right = val.right;
		top = val.top;
		bottom = val.bottom;
		radius = val.radius;
	}

	borders& operator=( const borders& val ) {
		left = val.left;
		right = val.right;
		top = val.top;
		bottom = val.bottom;
		radius = val.radius;
		return *this;
	}
};

void vboAddArc( VertexBuffer* vbo, Vector2f pos, Float radiW, Float radiH, double arcStartAngle,
				double arcAngleA, Color color, Float lineW, Vector2f basePos,
				bool decrease = false ) {
	Float angleShift = 1;
	double startAngle = eemin( arcStartAngle, arcAngleA );
	double endAngle = eemax( arcStartAngle, arcAngleA );
	Vector2f startPos = ( radiW > lineW ) ? pos : basePos;
	if ( decrease ) {
		for ( double i = endAngle; i >= startAngle; i -= angleShift ) {
			if ( radiW > lineW ) {
				vbo->addVertex( Vector2f( pos.x + ( radiW - lineW ) * Math::cosAng( i ),
										  pos.y + ( radiH - lineW ) * Math::sinAng( i ) ) );
			} else {
				vbo->addVertex( startPos );
			}

			vbo->addColor( color );

			vbo->addVertex( Vector2f( pos.x + radiW * Math::cosAng( i + angleShift ),
									  pos.y + radiH * Math::sinAng( i + angleShift ) ) );
			vbo->addColor( color );

			vbo->addVertex(
				Vector2f( pos.x + radiW * Math::cosAng( i ), pos.y + radiH * Math::sinAng( i ) ) );
			vbo->addColor( color );

			if ( radiW > lineW ) {
				vbo->addVertex( Vector2f( pos.x + ( radiW - lineW ) * Math::cosAng( i ),
										  pos.y + ( radiH - lineW ) * Math::sinAng( i ) ) );
			} else {
				vbo->addVertex( startPos );
			}

			vbo->addColor( color );
		}
	} else {
		for ( double i = startAngle; i <= endAngle; i += angleShift ) {
			if ( radiW > lineW ) {
				vbo->addVertex( Vector2f( pos.x + ( radiW - lineW ) * Math::cosAng( i ),
										  pos.y + ( radiH - lineW ) * Math::sinAng( i ) ) );
			} else {
				vbo->addVertex( startPos );
			}

			vbo->addColor( color );

			vbo->addVertex(
				Vector2f( pos.x + radiW * Math::cosAng( i ), pos.y + radiH * Math::sinAng( i ) ) );
			vbo->addColor( color );

			vbo->addVertex( Vector2f( pos.x + radiW * Math::cosAng( i + angleShift ),
									  pos.y + radiH * Math::sinAng( i + angleShift ) ) );
			vbo->addColor( color );

			if ( radiW > lineW ) {
				vbo->addVertex( Vector2f( pos.x + ( radiW - lineW ) * Math::cosAng( i ),
										  pos.y + ( radiH - lineW ) * Math::sinAng( i ) ) );
			} else {
				vbo->addVertex( startPos );
			}

			vbo->addColor( color );
		}
	}
}

void batch_borders( VertexBuffer* vbo, const borders& borders, const Vector2f& pos,
					const Sizef& size ) {
	vbo->clear();

	int bdr_top = 0;
	int bdr_bottom = 0;
	int bdr_left = 0;
	int bdr_right = 0;
	Float hW = size.getWidth() * 0.5f;
	Float hH = size.getHeight() * 0.5f;

	if ( borders.top.width >= 0 ) {
		bdr_top = (int)borders.top.width;
	}
	if ( borders.bottom.width >= 0 ) {
		bdr_bottom = (int)borders.bottom.width;
	}
	if ( borders.left.width >= 0 ) {
		bdr_left = (int)borders.left.width;
	}
	if ( borders.right.width >= 0 ) {
		bdr_right = (int)borders.right.width;
	}

	// draw top border
	if ( bdr_top ) {
		Float r_left_w = eemin( hW, eemax( 0.f, borders.radius.top_left_x ) );
		Float r_right_w = eemin( hH, eemax( 0.f, borders.radius.top_right_x ) );
		Float r_left_h = eemin( hW, eemax( 0.f, borders.radius.top_left_y ) );
		Float r_right_h = eemin( hH, eemax( 0.f, borders.radius.top_right_y ) );

		if ( r_left_w ) {
			double end_angle = Math::degrees( M_PI * 3.0 / 2.0 );
			double start_angle = Math::degrees(
				M_PI * 3.0 / 2.0 - M_PI / 2.0 / ( (double)bdr_left / (double)bdr_top + 1 ) );

			vboAddArc( vbo, Vector2f( pos.x + r_left_w, pos.y + r_left_h ), r_left_w, r_left_h,
					   start_angle, end_angle, borders.top.color, bdr_top,
					   Vector2f( pos.x + bdr_left, pos.y + bdr_top ) );
		} else {
			vbo->addVertex( Vector2f( pos.x, pos.y ) );
			vbo->addColor( borders.top.color );
			vbo->addVertex( Vector2f( pos.x + bdr_left, pos.y + bdr_top ) );
			vbo->addColor( borders.top.color );
		}

		if ( r_right_w ) {
			vbo->addVertex( Vector2f( pos.x + size.getWidth() - r_right_w, pos.y ) );
			vbo->addColor( borders.top.color );

			Vector2f basePos( pos.x + size.getWidth() - bdr_right, pos.y + bdr_top );

			double start_angle = Math::degrees( M_PI * 3.0 / 2.0 );
			double end_angle = Math::degrees(
				M_PI * 3.0 / 2.0 + M_PI / 2.0 / ( (double)bdr_right / (double)bdr_top + 1 ) );

			vboAddArc( vbo, Vector2f( pos.x + size.getWidth() - r_right_w, pos.y + r_right_h ),
					   r_right_w, r_right_h, start_angle, end_angle, borders.top.color, bdr_top,
					   basePos );
		} else {
			vbo->addVertex( Vector2f( pos.x + size.getWidth(), pos.y ) );
			vbo->addColor( borders.top.color );
			vbo->addVertex( Vector2f( pos.x + size.getWidth() - bdr_right, pos.y + bdr_top ) );
			vbo->addColor( borders.top.color );
		}
	}

	// draw right border
	if ( bdr_right ) {
		Float r_top_w = eemin( hW, eemax( 0.f, borders.radius.top_right_x ) );
		Float r_bottom_w = eemin( hH, eemax( 0.f, borders.radius.bottom_right_x ) );
		Float r_top_h = eemin( hW, eemax( 0.f, borders.radius.top_right_y ) );
		Float r_bottom_h = eemin( hH, eemax( 0.f, borders.radius.bottom_right_y ) );

		if ( r_top_w ) {
			double end_angle = Math::degrees( 2 * M_PI );
			double start_angle = Math::degrees(
				2 * M_PI - M_PI / 2.0 / ( (double)bdr_top / (double)bdr_right + 1 ) );

			Vector2f basePos( pos.x + size.getWidth() - bdr_right, pos.y + bdr_top );

			vboAddArc( vbo, Vector2f( pos.x + size.getWidth() - r_top_w, pos.y + r_top_h ), r_top_w,
					   r_top_h, start_angle, end_angle, borders.right.color, bdr_right, basePos );
		} else {
			vbo->addVertex( Vector2f( pos.x + size.getWidth(), pos.y ) );
			vbo->addColor( borders.right.color );
			vbo->addVertex( Vector2f( pos.x + size.getWidth() - bdr_right, pos.y + bdr_top ) );
			vbo->addColor( borders.right.color );
		}

		if ( r_bottom_h ) {
			vbo->addVertex(
				Vector2f( pos.x + size.getWidth(), pos.y + size.getHeight() - r_bottom_h ) );
			vbo->addColor( borders.right.color );

			Vector2f basePos( pos.x + size.getWidth() - bdr_right,
							  pos.y + size.getHeight() - bdr_bottom );
			double start_angle = Math::degrees( 0 );
			double end_angle =
				Math::degrees( M_PI / 2.0 / ( (double)bdr_bottom / (double)bdr_right + 1 ) );

			vboAddArc( vbo,
					   Vector2f( pos.x + size.getWidth() - r_bottom_w,
								 pos.y + size.getHeight() - r_bottom_h ),
					   r_bottom_w, r_bottom_h, start_angle, end_angle, borders.right.color,
					   bdr_right, basePos );

		} else {
			vbo->addVertex( Vector2f( pos.x + size.getWidth(), pos.y + size.getHeight() ) );
			vbo->addColor( borders.right.color );
			vbo->addVertex( Vector2f( pos.x + size.getWidth() - bdr_right,
									  pos.y + size.getHeight() - bdr_bottom ) );
			vbo->addColor( borders.right.color );
		}
	}

	// draw bottom border
	if ( bdr_bottom ) {
		double r_left_w = eemin( hW, eemax( 0.f, borders.radius.bottom_left_x ) );
		double r_right_w = eemin( hH, eemax( 0.f, borders.radius.bottom_right_x ) );
		double r_left_h = eemin( hW, eemax( 0.f, borders.radius.bottom_left_y ) );
		double r_right_h = eemin( hH, eemax( 0.f, borders.radius.bottom_right_y ) );

		if ( r_right_w ) {
			double end_angle = Math::degrees( M_PI / 2.0 );
			double start_angle = Math::degrees(
				M_PI / 2.0 - M_PI / 2.0 / ( (double)bdr_right / (double)bdr_bottom + 1 ) );
			Vector2f basePos( pos.x + size.getWidth() - bdr_right,
							  pos.y + size.getHeight() - bdr_bottom );
			vboAddArc( vbo,
					   Vector2f( pos.x + size.getWidth() - r_right_w,
								 pos.y + size.getHeight() - r_right_h ),
					   r_right_w, r_right_h, start_angle, end_angle, borders.bottom.color,
					   bdr_bottom, basePos );
		} else {
			vbo->addVertex( Vector2f( pos.x + size.getWidth(), pos.y + size.getHeight() ) );
			vbo->addColor( borders.bottom.color );

			vbo->addVertex( Vector2f( pos.x + size.getWidth() - bdr_right,
									  pos.y + size.getHeight() - bdr_bottom ) );
			vbo->addColor( borders.bottom.color );
		}

		if ( r_left_w ) {
			double start_angle = Math::degrees( M_PI / 2.0 );
			double end_angle = Math::degrees(
				M_PI / 2.0 + M_PI / 2.0 / ( (double)bdr_left / (double)bdr_bottom + 1 ) );

			Vector2f basePos( pos.x + bdr_left, pos.y + size.getHeight() - bdr_bottom );

			Vector2f tPos( Vector2f( pos.x + r_left_w, pos.y + size.getHeight() - r_left_h ) );
			vbo->addVertex( Vector2f( tPos.x + r_left_w * Math::cosAng( start_angle ),
									  tPos.y + r_left_h * Math::sinAng( start_angle ) ) );
			vbo->addColor( borders.bottom.color );

			vboAddArc( vbo, tPos, r_left_w, r_left_h, start_angle, end_angle, borders.bottom.color,
					   bdr_bottom, basePos );
		} else {
			vbo->addVertex( Vector2f( pos.x, pos.y + size.getHeight() ) );
			vbo->addColor( borders.bottom.color );
			vbo->addVertex( Vector2f( pos.x + bdr_left, pos.y + size.getHeight() - bdr_bottom ) );
			vbo->addColor( borders.bottom.color );
		}
	}

	// draw left border
	if ( bdr_left ) {
		double r_top_w = eemin( hW, eemax( 0.f, borders.radius.top_left_x ) );
		double r_bottom_w = eemin( hH, eemax( 0.f, borders.radius.bottom_left_x ) );
		double r_top_h = eemin( hW, eemax( 0.f, borders.radius.top_left_y ) );
		double r_bottom_h = eemin( hH, eemax( 0.f, borders.radius.bottom_left_y ) );

		if ( r_bottom_w ) {
			Vector2f basePos( pos.x + bdr_left, pos.y + size.getHeight() - bdr_bottom );

			double end_angle = Math::degrees( M_PI );
			double start_angle =
				Math::degrees( M_PI - M_PI / 2.0 / ( (double)bdr_bottom / (double)bdr_left + 1 ) );

			vboAddArc( vbo, Vector2f( pos.x + r_bottom_w, pos.y + size.getHeight() - r_bottom_h ),
					   r_bottom_w, r_bottom_h, start_angle, end_angle, borders.left.color, bdr_left,
					   basePos );
		} else {
			vbo->addVertex( Vector2f( pos.x, pos.y + size.getHeight() ) );
			vbo->addColor( borders.left.color );
			vbo->addVertex( Vector2f( pos.x + bdr_left, pos.y + size.getHeight() - bdr_bottom ) );
			vbo->addColor( borders.left.color );
		}

		if ( r_top_w ) {
			double start_angle = Math::degrees( M_PI );
			double end_angle =
				Math::degrees( M_PI + M_PI / 2.0 / ( (double)bdr_top / (double)bdr_left + 1 ) );

			Vector2f basePos( pos.x + bdr_left, pos.y + bdr_top );
			Vector2f tPos( pos.x + r_top_w, pos.y + r_top_w );
			vbo->addVertex( Vector2f( tPos.x + r_top_w * Math::cosAng( start_angle ),
									  tPos.y + r_top_h * Math::sinAng( start_angle ) ) );
			vbo->addColor( borders.left.color );

			vboAddArc( vbo, tPos, r_top_w, r_top_h, start_angle, end_angle, borders.left.color,
					   bdr_left, basePos );

		} else {
			vbo->addVertex( Vector2f( pos.x, pos.y ) );
			vbo->addColor( borders.left.color );
			vbo->addVertex( Vector2f( pos.x + bdr_left, pos.y + bdr_top ) );
			vbo->addColor( borders.left.color );
		}
	}
}

void update_borders() {
	borders borders;
	borders.top.width = 1 + borderWidthDiff;
	borders.bottom.width = 1 + borderWidthDiff;
	borders.right.width = 1 + borderWidthDiff;
	borders.left.width = 1 + borderWidthDiff;
	borders.top.color = Color::Red;
	borders.bottom.color = Color::Green;
	borders.left.color = Color::Blue;
	borders.right.color = Color::Fuchsia;
	borders.radius.top_left_x = 50 + borderDiff;
	borders.radius.top_right_x = 25 + borderDiff;
	borders.radius.bottom_left_x = 75 + borderDiff;
	borders.radius.bottom_right_x = 100 + borderDiff;
	borders.radius.top_left_y = 50 + borderDiff;
	borders.radius.top_right_y = 25 + borderDiff;
	borders.radius.bottom_left_y = 75 + borderDiff;
	borders.radius.bottom_right_y = 100 + borderDiff;
	batch_borders( mVBO, borders, Vector2f( 50, 50 ), Sizef( 400, 400 ) );
}

void mainLoop() {
	// Clear the screen buffer
	win->clear();

	// Create an instance of the primitive renderer
	Primitives p;

	// Change the color
	p.setColor( Color( 0, 255, 0, 150 ) );

	// Update the input
	win->getInput()->update();

	// Check if ESCAPE key is pressed
	if ( win->getInput()->isKeyDown( KEY_ESCAPE ) ) {
		// Close the window
		win->close();
	}

	// Draw a circle
	// p.drawCircle( win->getCenter(), 200, 60 );

	mVertexBuffer->bind();
	mVertexBuffer->draw();
	mVertexBuffer->unbind();

	for ( Uint32 i = 0; i < eeARRAY_SIZE( mVertexBuffers ); i++ ) {
		mVertexBuffers[i]->bind();
		mVertexBuffers[i]->draw();
		mVertexBuffers[i]->unbind();
	}

	if ( win->getInput()->isKeyDown( KEY_UP ) ) {
		borderDiff += win->getElapsed().asSeconds() * 40;
		update_borders();
	} else if ( win->getInput()->isKeyDown( KEY_DOWN ) ) {
		borderDiff -= win->getElapsed().asSeconds() * 40;
		update_borders();
	}

	if ( win->getInput()->isKeyDown( KEY_KP_PLUS ) ) {
		borderWidthDiff += win->getElapsed().asSeconds() * 40;
		update_borders();
	} else if ( win->getInput()->isKeyDown( KEY_KP_MINUS ) ) {
		borderWidthDiff -= win->getElapsed().asSeconds() * 40;
		update_borders();
	}

	mVBO->bind();
	mVBO->draw();
	mVBO->unbind();

	// Draw frame
	win->display();
}

// EE_MAIN_FUNC is needed by some platforms to be able to find the real application main
EE_MAIN_FUNC int main( int argc, char* argv[] ) {
	// Create a new window with vsync enabled
	win = Engine::instance()->createWindow( WindowSettings( 960, 640, "eepp - Empty Window" ),
											ContextSettings( true ) );

	// Check if created
	if ( win->isOpen() ) {
		// Set window background color
		win->setClearColor( RGB( 50, 50, 50 ) );
		// Set the MainLoop function and run it
		// This is the application loop, it will loop until the window is closed.
		// This is only a requirement if you want to support Emscripten builds ( WebGL + Canvas ).
		// This is the same as, except for Emscripten.
		// while ( win->isRunning() )
		// {
		//		MainLoop();
		// }
		mVertexBuffer =
			VertexBuffer::NewVertexArray( VERTEX_FLAGS_PRIMITIVE, PRIMITIVE_TRIANGLE_STRIP );
		// vboAddArc( mVertexBuffer, win->getCenter(), 200, 200, 0, 360, Color::Black, 64 );
		for ( Uint32 i = 0; i < eeARRAY_SIZE( mVertexBuffers ); i++ ) {
			mVertexBuffers[i] =
				VertexBuffer::NewVertexArray( VERTEX_FLAGS_PRIMITIVE, PRIMITIVE_TRIANGLE_STRIP );
		}
		/*vboAddArc( mVertexBuffers[0], win->getCenter(), 200, 200, 135, 135 + 90, Color::Fuchsia,
				   32 );
		vboAddArc( mVertexBuffers[1], win->getCenter(), 200, 200, 225, 225 + 90, Color::Red, 32 );
		vboAddArc( mVertexBuffers[2], win->getCenter(), 200, 200, -45, 45, Color::Green, 32 );
		vboAddArc( mVertexBuffers[3], win->getCenter(), 200, 200, 45, 45 + 90, Color::Blue, 32 );*/

		mVBO = VertexBuffer::NewVertexArray( VERTEX_FLAGS_PRIMITIVE, PRIMITIVE_TRIANGLE_STRIP );
		update_borders();

		win->runMainLoop( &mainLoop );
	}

	eeSAFE_DELETE( mVBO );
	eeSAFE_DELETE( mVertexBuffer );
	for ( Uint32 i = 0; i < eeARRAY_SIZE( mVertexBuffers ); i++ )
		eeSAFE_DELETE( mVertexBuffers[i] );

	// Destroy the engine instance. Destroys all the windows and engine singletons.
	Engine::destroySingleton();

	// If was compiled in debug mode it will print the memory manager report
	MemoryManager::showResults();

	return EXIT_SUCCESS;
}
