/*
 *  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 <geogram_gfx/gui/simple_application.h>
#include <geogram/delaunay/delaunay_2d.h>
#include <geogram/numerics/predicates.h>

namespace {
    using namespace GEO;

    typedef vector<vec2> Polygon;

    /*******************************************************************/
    
    // http://astronomy.swin.edu.au/~pbourke/geometry/polyarea/
    double signed_area(const Polygon& P) {
        double result = 0 ;
        for(unsigned int i=0; i<P.size(); i++) {
            unsigned int j = (i+1) % P.size() ;
            const vec2& t1 = P[i] ;
            const vec2& t2 = P[j] ;
            result += t1.x * t2.y - t2.x * t1.y ;
        }
        result /= 2.0 ;
        return result ;
    }

    // http://astronomy.swin.edu.au/~pbourke/geometry/polyarea/
    vec2 centroid(const Polygon& P) {
        geo_assert(P.size() > 0) ;

        double A = signed_area(P) ;

        if(::fabs(A) < 1e-30) {
            return P[0] ;
        }

        double x = 0.0 ;
        double y = 0.0 ;
        for(unsigned int i=0; i<P.size(); i++) {
            unsigned int j = (i+1) % P.size() ;
            const vec2& t1 = P[i] ;
            const vec2& t2 = P[j] ;
            double d = (t1.x * t2.y - t2.x * t1.y) ;
            x += (t1.x + t2.x) * d ;
            y += (t1.y + t2.y) * d ;
        }
        
        return vec2(
            x / (6.0 * A),
            y / (6.0 * A)
        ) ;
    }
    
    static inline Sign point_is_in_half_plane(
        const vec2& p, const vec2& q1, const vec2& q2
    ) {
        return PCK::orient_2d(q1, q2, p);
    }

    static inline bool intersect_segments(
        const vec2& p1, const vec2& p2,
        const vec2& q1, const vec2& q2,
        vec2& result
    ) {

        vec2 Vp = p2 - p1;
        vec2 Vq = q2 - q1;
        vec2 pq = q1 - p1;
        
        double a =  Vp.x;
        double b = -Vq.x;
        double c =  Vp.y;
        double d = -Vq.y;
        
        double delta = a*d-b*c;
        if(delta == 0.0) {
            return false ;
        }
            
        double tp = (d * pq.x -b * pq.y) / delta;
        
        result = vec2(
            (1.0 - tp) * p1.x + tp * p2.x,
            (1.0 - tp) * p1.y + tp * p2.y
        );
        
        return true;
    }
    
    void clip_polygon_by_half_plane(
        const Polygon& P, 
        const vec2& q1,
        const vec2& q2,
        Polygon& result
    ) {
        result.clear() ;
        
        if(P.size() == 0) {
            return ;
        }

        if(P.size() == 1) {
            if(point_is_in_half_plane(P[0], q1, q2)) {
                result.push_back(P[0]) ;
            }
            return ;
        }

        vec2 prev_p = P[P.size() - 1] ;
        Sign prev_status = point_is_in_half_plane(
            prev_p, q1, q2
        );
        
        for(unsigned int i=0; i<P.size(); i++) {
            vec2 p = P[i] ;
            Sign status = point_is_in_half_plane(
                p, q1, q2
            );
            if(
                status != prev_status &&
                status != ZERO &&
                prev_status != ZERO
            ) {
                vec2 intersect ;
                if(intersect_segments(prev_p, p, q1, q2, intersect)) {
                    result.push_back(intersect) ;
                }
            }

            switch(status) {
            case NEGATIVE:
                break ;
            case ZERO:
                result.push_back(p) ;
                break ;
            case POSITIVE:
                result.push_back(p) ;
                break ;
            }
            
            prev_p = p ;
            prev_status = status ;
        }
    }
    
    void convex_clip_polygon(
        const Polygon& P, const Polygon& clip, Polygon& result
    ) {
        Polygon tmp1 = P ;
        Polygon tmp2 ;
        Polygon* src = &tmp1 ;
        Polygon* dst = &tmp2 ;
        for(unsigned int i=0; i<clip.size(); i++) {
            unsigned int j = ((i+1) % clip.size()) ;
            const vec2& p1 = clip[i] ;
            const vec2& p2 = clip[j] ;
            clip_polygon_by_half_plane(*src, p1, p2, *dst);
	    std::swap(src, dst) ;
        }
        result = *src ;
    }

    /*******************************************************************/

#define c1 0.35 
#define c2 0.5 
#define c3 1.0 

    double color_table[12][3] = {
	{c3, c2, c2},
	{c2, c3, c2},
	{c2, c2, c3},
	{c2, c3, c3},
	{c3, c2, c3},
	{c3, c3, c2},
	
	{c1, c2, c2},
	{c2, c1, c2},
	{c2, c2, c1},
	{c2, c1, c1},
	{c1, c2, c1},
	{c1, c1, c2}
    };

    int random_color_index_ = 0 ;


    float white[4] = {
	0.8f, 0.8f, 0.3f, 1.0f
    };
    
    void glup_random_color() {
	glupColor3d(
	    color_table[random_color_index_][0], 
	    color_table[random_color_index_][1], 
	    color_table[random_color_index_][2]
	    );
	random_color_index_ = (random_color_index_ + 1) % 12 ;
    }
    
    void glup_randomize_colors(int index) {
	random_color_index_ = (index % 12) ;
    }

    void glup_random_color_from_index(int index) {
	if(index >= 0) {
	    glup_randomize_colors(index) ;
	    glup_random_color() ;
	} else {
	    glupColor4fv(white) ;
	}
    }
    
    /*******************************************************************/
    
    class Delaunay2dApplication : public SimpleApplication {
    public:
	Delaunay2dApplication() : SimpleApplication("Delaunay2d") {
	    console_visible_ = false;
	    viewer_properties_visible_ = false;
	    delaunay_ = new Delaunay2d(); 
	    border_shape_ = index_t(-1);
	    set_border_shape(0);
	    create_random_points(3);
	    point_size_ = 20;
	    edit_ = true;
	    show_Voronoi_cells_ = true;
	    show_Delaunay_triangles_ = true;
	    show_Voronoi_edges_ = true;
	    show_points_ = true;
	    show_border_ = true;
	    picked_point_ = index_t(-1);
	    last_button_ = index_t(-1);
	    add_key_toggle("F10", &edit_);
	    set_2d();
	    start_animation();
	}

    protected:
	/**
	 * \brief Updates the Delaunay triangulation with the current
	 *  vector of points.
	 */
	void update_Delaunay() {
	    delaunay_->set_vertices(points_.size(), &points_.data()->x);
	}

	/**
	 * \brief Creates random points.
	 * \details Points are created uniformly in the [0,1]x[0,1] 
	 *  square
	 * \param[in] nb the number of points to create.
	 */
	void create_random_points(index_t nb) {
	    for(index_t i=0; i<nb; ++i) {
		points_.push_back(
		    vec2(0.25, 0.25) + 
		    vec2(
			Numeric::random_float64()*0.5,
			Numeric::random_float64()*0.5
		    )
		);
	    }
	    update_Delaunay();
	}

	/**
	 * \brief Gets the index of the point that matches a given
	 *  point.
	 * \details The current GLUP point size is taken into account
	 *  to determine the tolerance.
	 * \param[in] p a const reference coordinate to a world-space
	 *  point.
	 * \param[in] get_nearest if true, gets the nearest point, else
	 *  only gets the point that matches p (up to current value of
	 *  point size when reprojected on the screen)
	 * \return the index of the point that corresponds to p if it
	 *  exists, or index_t(-1) if no such point exists.
	 */
	index_t get_picked_point(const vec2& p, bool get_nearest = false) {
	    if(points_.size() == 0) {
		return index_t(-1);
	    }
	    double dist_so_far = Numeric::max_float64();
	    index_t nearest = index_t(-1);
	    for(index_t i=0; i<points_.size(); ++i) {
		double dist = distance2(p, points_[i]);
		if(dist < dist_so_far) {
		    nearest = i;
		    dist_so_far = dist;
		}
	    }
	    if(!get_nearest) {
		vec3 q(points_[nearest].x, points_[nearest].y, 0.0);
		vec3 p_scr = project(vec3(p.x, p.y, 0.0));
		vec3 q_scr = project(vec3(q.x, q.y, 0.0));
		if(distance2(p_scr,q_scr) > 2.0 * double(glupGetPointSize())) {
		    nearest = index_t(-1);
		}
	    }
	    return nearest;
	}
	
	void set_border_as_polygon(index_t nb_sides) {
	    border_.clear();
	    for(index_t i=0; i<nb_sides; ++i) {
		double alpha = double(i) * 2.0 * M_PI / double(nb_sides);
		double s = sin(alpha);
		double c = cos(alpha);
		border_.push_back( vec2(0.5*(c + 1.0), 0.5*(s + 1.0)) );
	    }
	}

	void set_border_shape(index_t shape) {
	    if(border_shape_ == shape) {
		return;
	    }
	    border_shape_ = shape;
	    switch(shape) {
		case 1:
		    set_border_as_polygon(3);
		    break;
		case 2:
		    set_border_as_polygon(5);
		    break;
		case 3:
		    set_border_as_polygon(100);
		    break;
		default:
		    border_.clear();            
		    border_.push_back(vec2(0.0, 0.0));
		    border_.push_back(vec2(1.0, 0.0));
		    border_.push_back(vec2(1.0, 1.0));
		    border_.push_back(vec2(0.0, 1.0));        
		    break;
	    }
	}

	
	/**
	 * \brief Draws the border of the domain.
	 */
	void draw_border() {
	    glupSetColor3f(GLUP_FRONT_AND_BACK_COLOR, 0.0, 0.0, 0.0);
	    glupSetMeshWidth(4);
	    glupBegin(GLUP_LINES);
	    for(index_t i=0; i<border_.size(); ++i) {
		glupVertex(border_[i]);
		glupVertex(border_[(i+1)%border_.size()]);
	    }
	    glupEnd();
	}

	/**
	 * \brief Draws the points.
	 */
	void draw_points() {
	    glupEnable(GLUP_LIGHTING);
	    glupSetPointSize(GLfloat(point_size_));
	    glupDisable(GLUP_VERTEX_COLORS);
	    glupSetColor3f(GLUP_FRONT_AND_BACK_COLOR, 0.0f, 1.0f, 1.0f);
	    glupBegin(GLUP_POINTS);
	    for(index_t i=0; i<points_.size(); ++i) {
		glupVertex(points_[i]);
	    }
	    glupEnd();
	    glupDisable(GLUP_LIGHTING);       
	}
	
	/**
	 * \brief Draws the Delaunay triangles.
	 */
	void draw_Delaunay_triangles() {
	    glupSetColor3f(GLUP_FRONT_AND_BACK_COLOR, 0.7f, 0.7f, 0.7f);
	    glupSetMeshWidth(1);
	    glupBegin(GLUP_LINES);
	    for(index_t c=0; c<delaunay_->nb_cells(); ++c) {
		const signed_index_t* cell = delaunay_->cell_to_v() + 3*c;
		for(index_t e=0; e<3; ++e) {
		    signed_index_t v1 = cell[e];
		    signed_index_t v2 = cell[(e+1)%3];
		    glupVertex2dv(delaunay_->vertex_ptr(index_t(v1)));
		    glupVertex2dv(delaunay_->vertex_ptr(index_t(v2)));
		}
	    }
	    glupEnd();
	}
	


	/**
	 * \brief Gets the circumcenter of a triangle.
	 * \param[in] t the index of the triangle, in 0..delaunay->nb_cells()-1
	 * \return the circumcenter of triangle \p t
	 */
	vec2 circumcenter(index_t t) {
	    signed_index_t v1 = delaunay_->cell_to_v()[3*t];
	    signed_index_t v2 = delaunay_->cell_to_v()[3*t+1];
	    signed_index_t v3 = delaunay_->cell_to_v()[3*t+2];
	    vec2 p1(delaunay_->vertex_ptr(index_t(v1)));
	    vec2 p2(delaunay_->vertex_ptr(index_t(v2)));
	    vec2 p3(delaunay_->vertex_ptr(index_t(v3)));
	    return Geom::triangle_circumcenter(p1,p2,p3);
	}

	/**
	 * \brief Gets an infinite vertex in the direction normal to an
	 *  edge on the boundary of the convex hull.
	 * \param[in] t the index of the triangle, in 0..delaunay->nb_cells()-1
	 * \param[in] e the local index of the edge, in {0,1,2}
	 * \return a point located far away along the direction normal to the
	 *  edge \p e of triangle \p t
	 */
	vec2 infinite_vertex(index_t t, index_t e) {
	    index_t lv1 = (e+1)%3;
	    index_t lv2 = (e+2)%3;
	    index_t v1 = index_t(delaunay_->cell_to_v()[3*t+lv1]);
	    index_t v2 = index_t(delaunay_->cell_to_v()[3*t+lv2]);
	    vec2 p1(delaunay_->vertex_ptr(v1));
	    vec2 p2(delaunay_->vertex_ptr(v2));
	    vec2 n = normalize(p2-p1);
	    n = vec2(n.y, -n.x);
	    return 0.5*(p1+p2)+100000.0*n;
	}

	/**
	 * \brief Draw the Voronoi edges.
	 */
	void draw_Voronoi_edges() {
	    glupSetColor3f(GLUP_FRONT_AND_BACK_COLOR, 0.3f, 0.3f, 0.3f);
	    glupSetMeshWidth(2);
	    glupBegin(GLUP_LINES);
	    for(index_t t=0; t<delaunay_->nb_cells(); ++t) {
		vec2 cc = circumcenter(t);
		for(index_t e=0; e<3; ++e) {
		    signed_index_t t2 = delaunay_->cell_to_cell()[3*t+e];
		    if(t2 == -1) {
			glupVertex(cc);
			glupVertex(infinite_vertex(t,e));
		    } else if(t2 >signed_index_t(t)) {
			glupVertex(cc);
			glupVertex(circumcenter(index_t(t2)));
		    }
		}
	    }
	    glupEnd();
	}

	/**
	 * \brief Finds the local index of a vertex in a triangle.
	 * \details Throws an assertion failure if the triangle \p t is
	 *  not incident to vertex \p v
	 * \param[in] t the triangle, in 0..delaunay->nb_cells()-1
	 * \param[in] v the vertex, in 0..delaunay->nb_vertices()-1
	 * \return the local index of v in t, in {0,1,2}
	 */
	index_t find_vertex(index_t t, index_t v) {
	    for(index_t lv=0; lv<3; ++lv) {
		if(index_t(delaunay_->cell_to_v()[3*t+lv]) == v) {
		    return lv;
		}
	    }
	    geo_assert_not_reached;
	}


	/**
	 * \brief Gets a Voronoi cell of a vertex
	 * \details The vertex is specified by a triangle and a local index in
	 *  the triangle
	 * \param[in] t0 the triangle
	 * \param[in] lv the local index of the vertex in triangle \p t0
	 * \param[out] cell a reference to the Voronoi cell
	 */
	void get_Voronoi_cell(index_t t0, index_t lv, Polygon& cell) {
	    cell.resize(0);
	    index_t v = index_t(delaunay_->cell_to_v()[3*t0+lv]);
	    bool on_border = false;
	    index_t t = t0;
	    
	    // First, we turn around the vertex v. To do that, we compute
	    // lv, the local index of v in the current triangle. Following
	    // the standard numerotation of a triangle, edge number lv is
	    // not incident to vertex v. The two other edges (lv+1)%3 and
	    // (lv+2)%3 of the triangle are indicent to vertex v. By always
	    // traversing (lv+1)%3, we turn around vertex v.
	    do {
		index_t e = (lv+1)%3;
		signed_index_t neigh_t = delaunay_->cell_to_cell()[3*t+e];
		if(neigh_t == -1) {
		    on_border = true;
		    break;
		}
		cell.push_back(circumcenter(t));
		t = index_t(neigh_t);
		lv = find_vertex(t,v);
	    } while(t != t0);
	    
        
	    // If one traversed edge is on the border of the convex hull, then
	    // we empty the cell, and start turing around the vertex in
	    // the other direction, i.e. by traversing this time
	    // edge (lv+2)%3 until we reach the other edge on the border of
	    // the convex hull that is incident to v.
	    if(on_border) {
		cell.resize(0);
		cell.push_back(infinite_vertex(t,(lv + 1)%3));
		for(;;) {
		    cell.push_back(circumcenter(t));                
		    index_t e = (lv+2)%3;
		    signed_index_t neigh_t = delaunay_->cell_to_cell()[3*t+e];
		    if(neigh_t == -1) {
			cell.push_back(infinite_vertex(t, e));
			break;
		    }
		    t = index_t(neigh_t);
		    lv = find_vertex(t,v);
		}
	    }
	    
	    Polygon clipped;
	    convex_clip_polygon(cell, border_, clipped);
	    cell.swap(clipped);
	}

	/**
	 * \brief Draws the Voronoi cells as filled polygons with
	 *  random colors.
	 */
	void draw_Voronoi_cells() {
	    v_visited_.assign(points_.size(), false);
	    Polygon cell;
	    glupEnable(GLUP_VERTEX_COLORS);
	    glupBegin(GLUP_TRIANGLES);        
	    for(index_t t=0; t<delaunay_->nb_cells(); ++t) {
		for(index_t lv=0; lv<3; ++lv) {
		    index_t v = index_t(delaunay_->cell_to_v()[3*t+lv]);
		    if(!v_visited_[v]) {
			glup_random_color_from_index(int(v));
			v_visited_[v] = true;
			get_Voronoi_cell(t,lv,cell);
			for(index_t i=1; i+1<cell.size(); ++i) {
			    glupVertex(cell[0]);
			    glupVertex(cell[i]);
			    glupVertex(cell[i+1]);
			}
		    }
		}
	    }
	    glupEnd();
	    glupDisable(GLUP_VERTEX_COLORS);        
	}
	
	/**
	 * \brief Draws all the elements of the Delaunay triangulation / 
	 *  Voronoi diagram.
	 * \details Specified as glup_viewer_set_display_func() callback.
	 */
	void draw_scene() override {
	    glupDisable(GLUP_LIGHTING);
	    if(show_Voronoi_cells_) {
		draw_Voronoi_cells();
	    }
	    if(show_Delaunay_triangles_) {
		draw_Delaunay_triangles();
	    }
	    if(show_Voronoi_edges_) {
		draw_Voronoi_edges();
	    }
	    if(show_border_) {
		draw_border();
	    }
	    glupEnable(GLUP_LIGHTING);
	    if(show_points_) {
		draw_points();
	    }
	    if(animate()) {
		Lloyd_relaxation();
	    }
	}
	
	void Lloyd_relaxation() {
	    v_visited_.assign(delaunay_->nb_vertices(),false);
	    new_points_.resize(points_.size());
	    Polygon cell;
	    for(index_t t=0; t<delaunay_->nb_cells(); ++t) {
		for(index_t lv=0; lv<3; ++lv) {
		    index_t v = index_t(delaunay_->cell_to_v()[3*t+lv]);
		    if(!v_visited_[v]) {
			v_visited_[v] = true;
			get_Voronoi_cell(t,lv,cell);
			if(cell.size() > 0) {
			    new_points_[v] = centroid(cell);
			} else {
			    new_points_[v] = points_[v];
			}
		    }
		}
	    }
	    std::swap(points_, new_points_);
	    update_Delaunay();
	}

	void draw_object_properties() override {
	    SimpleApplication::draw_object_properties();	    
	    if(ImGui::Button(
		   (icon_UTF8("home") + " Home [H]").c_str(), ImVec2(-1.0, 0.0))
	    ) {
		home();
	    }
	    ImGui::Checkbox("Edit", &edit_);

	    ImGui::Separator();        
	    if(ImGui::Button("Relax.")) {
		Lloyd_relaxation();
	    }
	    ImGui::SameLine();
	    ImGui::Checkbox("animate",animate_ptr());
	    if(ImGui::Button("reset points", ImVec2(-1.0f,0.0f))) {
		points_.clear();
		create_random_points(3);
		update_Delaunay();
	    }
	    ImGui::Text("Bndry");
	    ImGui::SameLine();
	    int new_border_shape = int(border_shape_);
	    ImGui::Combo(
		"##", &new_border_shape,
		"square\0triangle\0pentagon\0circle\0\0"
	    );
	    set_border_shape(index_t(new_border_shape));

	    ImGui::Separator();
	    ImGui::Checkbox("cells", &show_Voronoi_cells_);
	    ImGui::SameLine();
	    ImGui::Checkbox("edges", &show_Voronoi_edges_);
	    
	    ImGui::Checkbox("trgls", &show_Delaunay_triangles_);
	    ImGui::SameLine();	    
	    ImGui::Checkbox("points", &show_points_);
	}

	void mouse_button_callback(
	    int button, int action, int mods, int source
	) override {
	    geo_argused(source);

	    // Hide object and viewer properties if in phone
	    // mode and user clicked elsewhere.
	    if(phone_screen_ &&
	       !ImGui::GetIO().WantCaptureMouse &&
	       get_height() >= get_width()
	    ) {
		if(!props_pinned_) {
		    object_properties_visible_ = false;
		    viewer_properties_visible_ = false;
		}
	    }

	    if(!edit_) {
		SimpleApplication::mouse_button_callback(
		    button, action, mods, source
		);
		return;
	    }
	    
	    if(action != EVENT_ACTION_DOWN) {
		last_button_ = index_t(-1);
		return;
	    }

	    last_button_ = index_t(button);

	    if(animate()) {
		switch(button) {
		    case 0: {
			points_.push_back(mouse_point_);
			picked_point_ = points_.size() - 1;                    
		    } break;
		    case 1: {
			picked_point_ = get_picked_point(mouse_point_,true); 
			if(points_.size() > 3) {
			    points_.erase(points_.begin() + int(picked_point_));
			}
		    } break;
		}
	    } else {
		picked_point_ = get_picked_point(mouse_point_);
		switch(button) {
		    case 0: {
			if(picked_point_ == index_t(-1)) {
			    points_.push_back(mouse_point_);
			    picked_point_ = points_.size() - 1;
			}
		    } break;
		    case 1: {
			if(points_.size() > 3) {
			    if(picked_point_ != index_t(-1)) {
				points_.erase(
				    points_.begin() + int(picked_point_)
				);
			    }
			}
			picked_point_ = index_t(-1);
		    } break;
		}
	    }
	    update_Delaunay();
	}

	void cursor_pos_callback(double x, double y, int source) override {
	    geo_argused(source);
	    if(!edit_) {
		SimpleApplication::cursor_pos_callback(x,y,source);
		return;
	    }

	    // For retina displays: x and y are in 'window pixels',
	    // and GLUP project / unproject expect 'framebuffer pixels'.
	    double sx =
		double(get_frame_buffer_width()) / double(get_width());
	    
	    double sy =
		double(get_frame_buffer_height()) / double(get_height());
	    
	    mouse_point_ = unproject_2d(
		vec2(sx*x, sy*(double(get_height()) - y))
	    );
	    if(animate()) {
		if(last_button_ == 0) {
		    points_.push_back(mouse_point_);
		    picked_point_ = points_.size() - 1;
		    update_Delaunay();
		} else if(last_button_ == 1) {
		    picked_point_ = get_picked_point(mouse_point_,true); 
		    if(points_.size() > 3) {
			points_.erase(points_.begin() + int(picked_point_));
		    }
		    update_Delaunay();		    
		}
	    } else {
		if(picked_point_ != index_t(-1) && (last_button_ == 0)) {
		    points_[picked_point_] = mouse_point_;
		    update_Delaunay();
		}
	    }
	}

    private:
	vector<vec2> points_;
	vector<vec2> new_points_;
	vector<bool> v_visited_;
	Delaunay_var delaunay_;
	Polygon border_;
	index_t border_shape_;
	GLint point_size_; /**< The size of all displayed points. */

	bool edit_;
	bool show_Voronoi_cells_;
	bool show_Delaunay_triangles_;
	bool show_Voronoi_edges_;
	bool show_points_;
	bool show_border_;

	index_t picked_point_;
	index_t last_button_;
	vec2 mouse_point_;
    };
    
}

int main(int argc, char** argv) {
    Delaunay2dApplication app;
    app.start(argc, argv);
    return 0;
}
