/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 * 
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

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

#include "mesh_primitives.h"
#include "topopt.h"
#include "layout.h"
#include <geogram_gfx/glup_viewer/glup_viewer.h>
#include <geogram_gfx/glup_viewer/glup_viewer_gui.h>
#include <GLFW/glfw3.h>
#include <Eigen/Dense>
#include <chrono>
#include <mutex>
#include <thread>
#include <map>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/threading.h>
#endif

////////////////////////////////////////////////////////////////////////////////
namespace {
using namespace GEO;
using namespace Eigen;
////////////////////////////////////////////////////////////////////////////////

TopoptProblem topopt;

class TopoptDemoApplication;
TopoptDemoApplication *current_app;

// -----------------------------------------------------------------------------

GLboolean mouse(float x, float y, int button, enum GlupViewerEvent event);
void one_step_topopt();
void clear_design();

// -----------------------------------------------------------------------------

class TopoptDemoApplication : public GEO::SimpleMeshApplication {
public:
	TopoptDemoApplication(int argc, char** argv)
		: SimpleMeshApplication(argc, argv, "")
		, design_need_refresh_(false)
		, edit_forces_(false)
		, edit_fixed_(false)
		, fixed_brush_size_(1)
		, closest_orig_({nullptr, 0})
		, closest_endpoint_({nullptr, 0})
		, last_button_(-1)
		, topopt_solver_(topopt)
		, time_last_iter_(std::chrono::steady_clock::now())
		, topopt_thread_please_stop_(false)
	{
		name_ = "[Float] Topopt demo";
		this->lighting_ = true;
		this->white_bg_ = true;
		this->left_pane_visible_ = true;
		this->right_pane_visible_ = true;
		this->console_visible_ = false;
		this->show_mesh_ = false;
		glup_viewer_set_screen_size(1024, 800);
		glup_viewer_set_mouse_func(::mouse);
		init_mesh();
		current_app = this;
	}

	~TopoptDemoApplication() {
		// Wait for topopt thread to finish
		topopt_thread_please_stop_ = true;
		for (std::thread &t : topopt_thread_) {
			if (t.joinable()) { t.join(); }
		}
	}

	void init_mesh() {
		mesh_gfx()->set_mesh(nullptr);
		mesh()->clear(false, false);

		// Create the mesh of regular grid
		GEO::Mesh &M = *mesh();
		Vector2i nnodes = topopt.nelems + Vector2i(1, 1);
		M.vertices.create_vertices(nnodes.prod());
		for (int idx = 0; idx < nnodes.prod(); ++idx) {
			Vector2i pos = Layout::to_grid(idx, nnodes);
			M.vertices.point(idx) = vec3(pos[0], pos[1], 0);
		}

		index_t first_quad = M.facets.create_quads(topopt.nelems.prod());
		for (index_t q = 0; q < topopt.nelems.prod(); ++q) {
			Vector2i min_corner = Layout::to_grid(q, topopt.nelems);
			Vector2i diff[4] = { {0,0}, {1,0}, {1,1}, {0,1} };
			for (index_t lv = 0; lv < 4; ++lv) {
				int v = Layout::to_index(min_corner + diff[lv], nnodes);
				M.facets.set_vertex(first_quad + q, lv, v);
			}
		}
		M.facets.connect();

		// Administrative stuff
		mesh_gfx()->set_animate(false);
		double xyzmin[3];
		double xyzmax[3];
		get_bbox(*mesh(), xyzmin, xyzmax, false);
		glup_viewer_set_region_of_interest(
			float(xyzmin[0]), float(xyzmin[1]), float(xyzmin[2]),
			float(xyzmax[0]), float(xyzmax[1]), float(xyzmax[2])
		);

		mesh_gfx()->set_mesh(mesh());

		// Init arrow mesh
		create_arrow_2d(0.5, 1, 3.5, 1.5, arrow_mesh_);
		arrow_mesh_gfx_.set_mesh(&arrow_mesh_);

		refresh_nnz_forces();
	}

	void refresh_nnz_forces() {
		nonzero_forces_.clear();
		for (int v = 0; v < mesh()->vertices.nb(); ++v) {
			vec2 f(topopt.forces(2*v), topopt.forces(2*v+1));
			if (f[0] != 0 || f[1] != 0) {
				nonzero_forces_.push_back(v);
			}
		}
	}

	void refresh_densities() {
		std::lock_guard<std::mutex> lck(mtx_densities_gfx_);
		Attribute<float> densities(mesh()->facets.attributes(), "densities");
		for (int f = 0; f < mesh()->facets.nb(); ++f) {
			densities[f] = topopt.densities(f);
		}
	}

	void reset_design() {
		std::lock_guard<std::mutex> lck(mtx_densities_top_);
		topopt_solver_.resetDesign();
		refresh_densities();
	}

	void rescale_design() {
		std::lock_guard<std::mutex> lck(mtx_densities_top_);
		topopt_solver_.rescaleDesign(
			std::max(0.0, topopt.volfrac - 0.1),
			std::min(1.0, topopt.volfrac + 0.1));
		refresh_densities();
		design_need_refresh_ = false;
	}

	void one_step_topopt() {
		std::lock_guard<std::mutex> lck(mtx_densities_top_);
		topopt_solver_.iter();
		refresh_densities();
	}

	void animate_topopt() {
		#ifdef __EMSCRIPTEN__
		if (!emscripten_has_threading_support()) {
			auto time_now = std::chrono::steady_clock::now();
			auto time_span = std::chrono::duration_cast<std::chrono::milliseconds>(time_now - time_last_iter_);
			if (time_span.count() > 100) {
				one_step_topopt();
				time_last_iter_ = std::chrono::steady_clock::now();
			}
			return;
		}
		#endif
		if (topopt_thread_.empty()) {
			// Init topopt thread if needed
			topopt_thread_.emplace_back([this]() {
				while (!topopt_thread_please_stop_) {
					if (glup_viewer_is_enabled(GLUP_VIEWER_IDLE_REDRAW)) {
						this->one_step_topopt();
						std::this_thread::yield();
					}
					std::this_thread::sleep_for(std::chrono::milliseconds(10));
				}
			});
		}
	}

	virtual void init_graphics() {
		SimpleMeshApplication::init_graphics();
		glup_viewer_disable(GLUP_VIEWER_BACKGROUND);
		glup_viewer_disable(GLUP_VIEWER_3D);
		glup_viewer_add_toggle('f', &edit_forces_, "edit forces");
		glup_viewer_add_toggle('n', &edit_fixed_, "edit fixed nodes");
		glup_viewer_add_key_func('u', ::one_step_topopt, "one iteration");
		glup_viewer_add_key_func('c', ::clear_design, "clear design");

		// Attributes
		refresh_densities();
		this->set_attribute("facets.densities");
		this->attribute_min_ = 0.0;
		this->attribute_max_ = 1.0;
		this->current_colormap_texture_ = colormaps_[3].texture;
		this->show_attributes();

		// Fixed nodes
		Attribute<bool> fixed(mesh()->vertices.attributes(), "fixed");
		for (int v = 0; v < mesh()->vertices.nb(); ++v) {
			fixed[v] = (topopt.fixed(2*v) > 0) || (topopt.fixed(2*v+1) > 0);
		}
	}

	virtual void draw_scene() {
		SimpleMeshApplication::draw_scene();
		if (show_vertices_selection_) {
			mesh_gfx_.set_points_color(1.0, 0.0, 0.0);
			mesh_gfx_.set_points_size(vertices_size_);
			mesh_gfx_.set_vertices_selection("fixed");
			mesh_gfx_.draw_vertices();
			mesh_gfx_.set_vertices_selection("");
		}

		// Draw forces
		for (int v : nonzero_forces_) {
			vec2 f(topopt.forces(2*v), topopt.forces(2*v+1));
			double alpha = std::atan2(f[1], f[0]) * 180 / M_PI;
			auto pos = Layout::to_grid<vec2>(v, topopt.nelems[0]+1, topopt.nelems[1]+1);

			glupPushMatrix();
			glupTranslated(pos[0], pos[1], 0.001);
			glupRotated(alpha-90, 0, 0, 1);
			glupScaled(1, 0.2*f.length(), 1);
			arrow_mesh_gfx_.set_lighting(lighting_);
			arrow_mesh_gfx_.set_surface_color(0, 0.6f, 0);
			arrow_mesh_gfx_.set_show_mesh(false);
			arrow_mesh_gfx_.draw_surface();
			glupPopMatrix();
		}

		GLUPfloat old_point_size = glupGetPointSize();
		glupSetPointSize(vertices_size_ * 5.f);
		glupSetColor3d(GLUP_FRONT_AND_BACK_COLOR, 0, 0.4, 0);
		GLUPboolean vertex_color_was_enabled = glupIsEnabled(GLUP_VERTEX_COLORS);
		glupEnable(GLUP_VERTEX_COLORS);
		for (int v : nonzero_forces_) {
			vec2 f(topopt.forces(2*v), topopt.forces(2*v+1));
			auto pos = Layout::to_grid<vec2>(v, topopt.nelems[0]+1, topopt.nelems[1]+1);
			glupBegin(GLUP_POINTS);
			glupColor3d(0, 0, 0.4);
			glupVertex3d(pos[0], pos[1], 0.1);
			glupColor3d(0, 0.4, 0);
			glupVertex3d(pos[0]+f[0], pos[1]+f[1], 0.1);
			glupEnd();
		}
		glupSetPointSize(old_point_size);
		if (!vertex_color_was_enabled) {
			glupDisable(GLUP_VERTEX_COLORS);
		}
		if (design_need_refresh_ && last_button_ == -1) {
			rescale_design();
		}
		if (glup_viewer_is_enabled(GLUP_VIEWER_IDLE_REDRAW)) {
			animate_topopt();
		}
	}

	virtual void draw_left_pane() {
		int w, h;
		glup_viewer_get_screen_size(&w, &h);
		if(status_bar_->active()) { h -= (STATUS_HEIGHT()+1); }
		if(console_visible_) { h -= (CONSOLE_HEIGHT()+1); }
		h -= MENU_HEIGHT();
		if(Command::current() != nullptr) { h /= 2; }

		ImGui::SetNextWindowPos(ImVec2(0.0f, float(MENU_HEIGHT())), ImGuiSetCond_Always);
		ImGui::SetNextWindowSize(ImVec2(float(PANE_WIDTH()), float(h)), ImGuiSetCond_Always);

		ImGui::Begin("Topopt", &left_pane_visible_, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
		ImGui::Text("Initial design");
		ImGui::Checkbox("Edit forces [f]", &edit_forces_);
		ImGui::Checkbox("Edit fixed [n]", &edit_fixed_);
		if (edit_fixed_) {
			fixed_brush_size_ = std::round(2.0 * fixed_brush_size_) / 2.0;
			ImGui::SliderInt("sz.", &fixed_brush_size_, 1, 10);
		}
		ImGui::Text("Volume fraction");
		ImGui::SliderFloat("vol.", &topopt.volfrac, 0.f, 1.f, "%.1f");
		ImGui::Text("Filter radius");
		ImGui::SliderFloat("rad.", &topopt.radius, 1.f, 10.f, "%.1f");
		ImGui::Text("Filter type");
		std::map<FilterType, std::string> names = {
			{FilterType::NoFilter, "No Filter"},
			{FilterType::Densities, "Densities"},
			{FilterType::Sensitivies, "Sensitivies"}
		};
		if (ImGui::Button((names[topopt.filter] + "##FilterType").c_str(), ImVec2(-1, 0))) {
			ImGui::OpenPopup("##FilterType");
		}
		if(ImGui::BeginPopup("##FilterType")) {
			FilterType filter_type[3] = {FilterType::NoFilter, FilterType::Densities, FilterType::Sensitivies};
			std::string filter_name[3] = {"No filter", "Densities", "Sensitivies"};
			for (int i : {0, 1, 2}) {
				if (ImGui::Button(filter_name[i].c_str())) {
					topopt.filter = filter_type[i];
					ImGui::CloseCurrentPopup();
				}
			}
			ImGui::EndPopup();
		}

		ImGui::Separator();
		ImGui::Text("Design update");
		if (ImGui::Button("One iteration [u]", ImVec2(-1, 0))) {
			one_step_topopt();
		}
		ImGui::Checkbox("animate [a]", (bool*)glup_viewer_is_enabled_ptr(GLUP_VIEWER_IDLE_REDRAW));

		ImGui::Separator();
		if (ImGui::Button("Reset design [c]", ImVec2(-1, 0))) {
			reset_design();
		}

		ImGui::End();
	}

	virtual void draw_console() {
		this->console_visible_ = false;
	}

	virtual void draw_object_properties() {
		ImGui::Checkbox("attributes", &show_attributes_);
		if(show_attributes_) {
			if(attribute_min_ == 0.0f && attribute_max_ == 0.0f) {
				autorange();
			}
			if(ImGui::Button(
				   (attribute_ + "##Attribute").c_str(), ImVec2(-1,0))
			) {
				ImGui::OpenPopup("##Attributes");
			}
			if(ImGui::BeginPopup("##Attributes")) {
				std::vector<std::string> attributes;
				String::split_string(attribute_names(), ';', attributes);
				for(index_t i=0; i<attributes.size(); ++i) {
					if(ImGui::Button(attributes[i].c_str())) {
						set_attribute(attributes[i]);
						ImGui::CloseCurrentPopup();
					}
				}
				ImGui::EndPopup();
			}
			ImGui::InputFloat("min",&attribute_min_);
			ImGui::InputFloat("max",&attribute_max_);
			if(ImGui::ImageButton(
				   convert_to_ImTextureID(current_colormap_texture_),
				   ImVec2(115.0f*scaling(),8.0f*scaling()))
			) {
				ImGui::OpenPopup("##Colormap");
			}
			if(ImGui::BeginPopup("##Colormap")) {
				for(index_t i=0; i<colormaps_.size(); ++i) {
					if(ImGui::ImageButton(
						   convert_to_ImTextureID(colormaps_[i].texture),
						   ImVec2(100.0f*scaling(),8.0f*scaling()))
					) {
						current_colormap_texture_ = colormaps_[i].texture;
						ImGui::CloseCurrentPopup();
					}
				}
				ImGui::EndPopup();
			}
		}

		if(mesh_.vertices.dimension() >= 6) {
			ImGui::Separator();
			ImGui::Checkbox(
				"Animate [a]",
				(bool*)glup_viewer_is_enabled_ptr(GLUP_VIEWER_IDLE_REDRAW)
			);
			ImGui::SliderFloat("spd.", &anim_speed_, 1.0f, 10.0f, "%.1f");
			ImGui::SliderFloat("t.", &anim_time_, 0.0f, 1.0f, "%.2f");
		}

		ImGui::Separator();
		ImGui::Checkbox("Fixed nodes", &show_vertices_selection_);
		ImGui::SliderFloat("sz.", &vertices_size_, 0.1f, 5.0f, "%.1f");

		if(mesh_.facets.nb() != 0) {
			ImGui::Separator();
			ImGui::Checkbox("Surface [S]", &show_surface_);
			if(show_surface_) {
				ImGui::Checkbox("colors [c]", &show_surface_colors_);
				ImGui::Checkbox("mesh [m]", &show_mesh_);
				ImGui::Checkbox("borders [B]", &show_surface_borders_);
			}
		}

		if(mesh_.cells.nb() != 0) {
			ImGui::Separator();
			ImGui::Checkbox("Volume [V]", &show_volume_);
			if(show_volume_) {
				ImGui::SliderFloat(
					"shrk.", &cells_shrink_, 0.0f, 1.0f, "%.2f"
				);
				if(!mesh_.cells.are_simplices()) {
					ImGui::Checkbox("colored cells [C]", &show_colored_cells_);
					ImGui::Checkbox("hexes [j]", &show_hexes_);
				}
			}
		}
	}

	GLboolean mouse(float x, float y, int button, enum GlupViewerEvent event) {
		GLdouble xyz[3];
		GLboolean bkgnd;
		glup_viewer_get_picked_point(xyz, &bkgnd);
		vec2 p(xyz[0], xyz[1]);

		switch(event) {
		case GLUP_VIEWER_DOWN:
			last_button_ = button;
			break;
		case GLUP_VIEWER_UP:
			last_button_ = -1;
			break;
		case GLUP_VIEWER_MOVE:
			break;
		}

		if (edit_fixed_ && (last_button_ == GLFW_MOUSE_BUTTON_1 || last_button_ == GLFW_MOUSE_BUTTON_2)) {
			int x(std::round(p[0]));
			int y(std::round(p[1]));

			bool new_state = (last_button_ == GLFW_MOUSE_BUTTON_1);

			if (x >= 0 && y >= 0 && x <= topopt.nelems[0]+1 && y <= topopt.nelems[1]+1) {
				Attribute<bool> fixed(mesh()->vertices.attributes(), "fixed");
				Vector2i min_corner = Vector2i(x, y) - (fixed_brush_size_-1) * Vector2i::Ones();
				Vector2i max_corner = Vector2i(x, y) + (fixed_brush_size_-1) * Vector2i::Ones();
				min_corner = min_corner.cwiseMax(0);
				max_corner = max_corner.cwiseMin(topopt.nelems);
				for (int xx = min_corner[0]; xx <= max_corner[0]; ++xx) {
					for (int yy = min_corner[1]; yy <= max_corner[1]; ++yy) {
						if (Vector2i(xx-x, yy-y).squaredNorm() <= fixed_brush_size_) {
							int v = Layout::to_index(xx, yy, topopt.nelems[0]+1, topopt.nelems[1]+1);
							fixed[v] = new_state;
							topopt.fixed.segment<2>(2*v).setConstant(new_state ? 1.0 : 0.0);
							design_need_refresh_ = true;
						}
					}
				}
			}

			return GL_TRUE;
		}

		if (edit_forces_) {
			//////////////////////
			// Helper functions //
			//////////////////////

			auto sq_dist_force = [&](int v) {
				Vector2d a = Layout::to_grid<Vector2i>(v, topopt.nelems + Vector2i(1, 1)).cast<double>();
				Vector2d b = a + topopt.forces.segment<2>(2*v);
				double dist_a = (a - Vector2d(p[0], p[1])).squaredNorm();
				double dist_b = (b - Vector2d(p[0], p[1])).squaredNorm();
				return std::make_pair(dist_a, dist_b);
			};

			auto closest_grid_node_index = [&]() {
				Vector2d node(p[0], p[1]);
				node = node.unaryExpr([] (double x) { return std::round(x); });
				node = node.cwiseMax(0).cwiseMin(topopt.nelems.cast<double>());
				return Layout::to_index(node[0], node[1], topopt.nelems[0]+1, topopt.nelems[1]+1);
			};

			auto has_nonzero_forces = [&](int i) {
				for (int j : nonzero_forces_) { if (j == i) { return true; } }
				return false;
			};

			auto delta_from_grid_node = [&](int v) {
				Vector2d diff(p[0], p[1]);
				diff -= Layout::to_grid<Vector2i>(v, topopt.nelems + Vector2i(1, 1)).cast<double>();
				double radius = diff.norm();
				double alpha = std::atan2(diff[1], diff[0]);
				alpha = std::round(32.0 * alpha / (2.0 * M_PI)) / 32.0 * 2.0 * M_PI;
				diff = radius * Vector2d(std::cos(alpha), std::sin(alpha));
				return diff;
			};

			// Find closest node with nnz external force
			double max_active_dist = 0.1 * topopt.nelems.maxCoeff();
			if (event == GLUP_VIEWER_DOWN) {
				closest_orig_ = {nullptr, std::numeric_limits<double>::max()};
				closest_endpoint_ = {nullptr, std::numeric_limits<double>::max()};
				for (int &v : nonzero_forces_) {
					auto dist = sq_dist_force(v);
					if (dist.first < closest_orig_.dist) {
						closest_orig_.index = &v;
						closest_orig_.dist = dist.first;
					}
					if (dist.second < closest_endpoint_.dist) {
						closest_endpoint_.index = &v;
						closest_endpoint_.dist = dist.second;
					}
				}
			}

			if (last_button_ == GLFW_MOUSE_BUTTON_1) {
				// Edit force
				if (closest_orig_.index && closest_endpoint_.index && closest_orig_.dist < closest_endpoint_.dist) {
					if (closest_orig_.index && closest_orig_.dist < max_active_dist) {
						int &v = *closest_orig_.index;
						int i = closest_grid_node_index();
						if (!has_nonzero_forces(i)) {
							topopt.forces.segment<2>(2*i) = topopt.forces.segment<2>(2*v);
							topopt.forces.segment<2>(2*v).setZero();
							v = i;
							design_need_refresh_ = true;
						}
					}
				} else {
					if (closest_endpoint_.index && closest_endpoint_.dist < max_active_dist) {
						int v = *closest_endpoint_.index;
						topopt.forces.segment<2>(2*v) = delta_from_grid_node(v);
						design_need_refresh_ = true;
					}
				}
			} else if (last_button_ == GLFW_MOUSE_BUTTON_2) {
				// Add new force
				if (event == GLUP_VIEWER_DOWN) {
					int i = closest_grid_node_index();
					if (!has_nonzero_forces(i)) {
						topopt.forces.segment<2>(2*i) = delta_from_grid_node(i);
						refresh_nnz_forces();
						for (int &v : nonzero_forces_) { if (v == i) { closest_endpoint_.index = &v; } }
						closest_endpoint_.dist = 0;
						design_need_refresh_ = true;
					}
				} else {
					if (closest_endpoint_.index && closest_endpoint_.dist < max_active_dist) {
						int v = *closest_endpoint_.index;
						topopt.forces.segment<2>(2*v) = delta_from_grid_node(v);
						design_need_refresh_ = true;
					}
				}
			} else if (last_button_ == GLFW_MOUSE_BUTTON_3) {
				// Delete force
				if (event == GLUP_VIEWER_DOWN) {
					if (closest_orig_.index && closest_orig_.dist < max_active_dist) {
						int v = *closest_orig_.index;
						topopt.forces.segment<2>(2*v).setZero();
						refresh_nnz_forces();
						design_need_refresh_ = true;
					}
				}
			}

			return GL_TRUE;
		}

		if (event == GLUP_VIEWER_DOWN) {
			last_down_x_ = x;
			last_down_y_ = y;
		}

		return GL_FALSE;
	}

private:
	bool design_need_refresh_;
	bool edit_forces_;
	bool edit_fixed_;
	int fixed_brush_size_;
	std::vector<int> nonzero_forces_;

	// closest force endpoints
	struct { int *index; double dist; } closest_orig_, closest_endpoint_;

	int last_button_;
	float last_down_x_, last_down_y_;

	Mesh arrow_mesh_;
	MeshGfx arrow_mesh_gfx_;

	TopoptSolver topopt_solver_;
	std::chrono::steady_clock::time_point time_last_iter_;
	std::mutex mtx_densities_top_;
	std::mutex mtx_densities_gfx_;
	std::vector<std::thread> topopt_thread_;
	bool topopt_thread_please_stop_;
};

// -----------------------------------------------------------------------------

GLboolean mouse(float x, float y, int button, enum GlupViewerEvent event) {
	return current_app->mouse(x, y, button, event);
}

void one_step_topopt() {
	current_app->one_step_topopt();
};

void clear_design() {
	current_app->reset_design();
};

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

} // anonymous namespace

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

int main(int argc, char** argv) {
	// Init topopt struct
	int nelx = 100;
	int nely = 50;
	topopt.nelems = Eigen::Vector2i(nelx, nely);
	topopt.densities.resize(nelx*nely);
	topopt.forces.resize(2*(nelx+1)*(nely+1));
	topopt.fixed.resize(2*(nelx+1)*(nely+1));
	topopt.volfrac = 0.4;
	// Densities
	topopt.densities.setConstant(topopt.volfrac);
	// External forces
	topopt.forces.setZero();
	topopt.forces(2*Layout::to_index(nelx, nely/2, nelx+1, nely+1) + 1) = -3;
	// Fixed nodes/dofs
	topopt.fixed.setZero();
	for (int y = 0; y <= nely; ++y) {
		topopt.fixed.segment<2>(2*Layout::to_index(0, y, nelx+1, nely+1)).setConstant(1);
	}

	TopoptDemoApplication app(argc, argv);
	app.start();

	return 0;
}
