MeshLib::Mesh* MeshSurfaceExtraction::getMeshSurface(
    const MeshLib::Mesh& mesh, const MathLib::Vector3& dir, double angle,
    std::string const& subsfc_node_id_backup_prop_name)
{
	if (angle < 0 || angle > 90)
	{
		ERR ("Supported angle between 0 and 90 degrees only.");
		return nullptr;
	}

	INFO ("Extracting mesh surface...");
	std::vector<MeshLib::Element*> sfc_elements;
	get2DSurfaceElements(mesh.getElements(), sfc_elements, dir, angle, mesh.getDimension());

	if (sfc_elements.empty())
		return nullptr;

	std::vector<MeshLib::Node*> sfc_nodes;
	std::vector<std::size_t> node_id_map(mesh.getNNodes());
	get2DSurfaceNodes(sfc_nodes, mesh.getNNodes(), sfc_elements, node_id_map);

	// create new elements vector with newly created nodes
	std::vector<MeshLib::Element*> new_elements;
	new_elements.reserve(sfc_elements.size());
	for (auto elem = sfc_elements.cbegin(); elem != sfc_elements.cend(); ++elem)
	{
		unsigned const n_elem_nodes ((*elem)->getNBaseNodes());
		MeshLib::Node** new_nodes = new MeshLib::Node*[n_elem_nodes];
		for (unsigned k(0); k<n_elem_nodes; k++)
			new_nodes[k] = sfc_nodes[node_id_map[(*elem)->getNode(k)->getID()]];
		if ((*elem)->getGeomType() == MeshElemType::TRIANGLE)
			new_elements.push_back(new MeshLib::Tri(new_nodes));
		else {
			assert((*elem)->getGeomType() == MeshElemType::QUAD);
			new_elements.push_back(new MeshLib::Quad(new_nodes));
		}
		delete *elem;
	}

	std::vector<std::size_t> id_map;
	if (!subsfc_node_id_backup_prop_name.empty())
	{
		id_map.reserve(sfc_nodes.size());
		for (auto node = sfc_nodes.cbegin(); node != sfc_nodes.cend(); ++node)
			id_map.push_back((*node)->getID());
	}
	MeshLib::Mesh* result (new Mesh(mesh.getName()+"-Surface", sfc_nodes, new_elements));
	// transmit the original node ids of the subsurface mesh as a property
	if (!subsfc_node_id_backup_prop_name.empty()) {
		boost::optional<MeshLib::PropertyVector<std::size_t>&> orig_node_ids(
		    result->getProperties().createNewPropertyVector<std::size_t>(
		        subsfc_node_id_backup_prop_name , MeshLib::MeshItemType::Node, 1));
		if (orig_node_ids) {
			orig_node_ids->resize(id_map.size());
			std::copy(id_map.cbegin(), id_map.cend(), orig_node_ids->begin());
		}
	}
	return result;
}
int main (int argc, char* argv[])
{
    ApplicationsLib::LogogSetup logog_setup;

    TCLAP::CmdLine cmd("The tool computes the area per node of the surface mesh"
        " and writes the information as txt and csv data.", ' ', "0.1");
    TCLAP::ValueArg<std::string> mesh_in("i", "mesh-input-file",
        "the name of the file containing the input mesh", true,
        "", "file name of input mesh");
    cmd.add(mesh_in);
    TCLAP::ValueArg<std::string> id_prop_name("", "id-prop-name",
        "the name of the property containing the id information", false,
        "OriginalSubsurfaceNodeIDs", "property name");
    cmd.add(id_prop_name);
    TCLAP::ValueArg<std::string> out_base_fname("p", "output-base-name",
        "the path and base file name the output will be written to", false,
        "", "output path and base name as one string");
    cmd.add(out_base_fname);

    cmd.parse(argc, argv);

    std::unique_ptr<MeshLib::Mesh> surface_mesh(
        MeshLib::IO::readMeshFromFile(mesh_in.getValue()));
    INFO("Mesh read: %u nodes, %u elements.", surface_mesh->getNNodes(),
         surface_mesh->getNElements());
    // ToDo check if mesh is read correct and if the mesh is a surface mesh

    // check if a node property containing the subsurface ids is available
    boost::optional<MeshLib::PropertyVector<std::size_t> const&> orig_node_ids(
        surface_mesh->getProperties().getPropertyVector<std::size_t>(
            id_prop_name.getValue()));
    // if the node property is not available generate it
    if (!orig_node_ids) {
        boost::optional<MeshLib::PropertyVector<std::size_t>&> node_ids(
            surface_mesh->getProperties().createNewPropertyVector<std::size_t>(
                id_prop_name.getValue(), MeshLib::MeshItemType::Node, 1));
        if (!node_ids) {
            ERR("Fatal error: could not create property.");
            return EXIT_FAILURE;
        }
        node_ids->resize(surface_mesh->getNNodes());
        std::iota(node_ids->begin(), node_ids->end(), 0);
        orig_node_ids = node_ids;
    }

    std::vector<double> areas(
        MeshLib::MeshSurfaceExtraction::getSurfaceAreaForNodes(*surface_mesh));

    // pack area and node id together
    std::vector<std::pair<std::size_t, double>> ids_and_areas;
    std::transform(orig_node_ids->cbegin(), orig_node_ids->cend(),
                   areas.cbegin(), std::back_inserter(ids_and_areas),
                   std::make_pair<std::size_t const&, double const&>);

    // generate file names for output
    std::string path(out_base_fname.getValue());
    if (path.empty())
        path = BaseLib::dropFileExtension(mesh_in.getValue());
    std::string const id_and_area_fname(path+".txt");
    std::string const csv_fname(path+".csv");

    writeToFile(id_and_area_fname, csv_fname, ids_and_areas,
                surface_mesh->getNodes());

    return EXIT_SUCCESS;
}