
#include "ruby.h"
#include "gpc.h"

/* Macro to make the code look more consistent. */
#define CHARPTR2STR( char_ptr ) rb_str_new2( char_ptr )
#define DBL2NUM( dbl_val ) rb_float_new( dbl_val )


/* Values for the Ruby object space mapping. */
static VALUE gpc_module;
static VALUE vertex_class;
static VALUE contour_class;
static VALUE polygon_class;
static VALUE tristrip_class;



/****************************************************************************/
/* GPC helper layer.                                                        */
/****************************************************************************/

static gpc_vertex_list * create_vertex_list( VALUE contour )
{
    int idx;
    gpc_vertex_list * vertex_list;
    VALUE val_size, val_vertex, val_x, val_y;

    ID id_size = rb_intern( "size" );
    ID id_fetch = rb_intern( "fetch" );
    ID id_x = rb_intern( "x" );
    ID id_y = rb_intern( "y" );

    val_size = rb_funcall( contour, id_size, 0 );

    vertex_list = ALLOC( gpc_vertex_list );
    vertex_list->num_vertices = FIX2INT( val_size );
    vertex_list->vertex = ALLOC_N( gpc_vertex, vertex_list->num_vertices );

    for ( idx = 0; idx < vertex_list->num_vertices; idx++ )
    {
        val_vertex = rb_funcall( contour, id_fetch, 1, INT2FIX( idx ) );
        val_x = rb_funcall( val_vertex, id_x, 0 );
        val_y = rb_funcall( val_vertex, id_y, 0 );
        vertex_list->vertex[ idx ].x = NUM2DBL( val_x );
        vertex_list->vertex[ idx ].y = NUM2DBL( val_y );
    }

    return vertex_list;
}



/****************************************************************************/
/* Ruby interfacing layer.                                                  */
/****************************************************************************/

static VALUE vertex_init( VALUE self, VALUE x, VALUE y )
{
    rb_iv_set( self, "@x", x );
    rb_iv_set( self, "@y", y );
    return self;
}


static VALUE vertex_eq( VALUE self, VALUE other )
{
    ID id_eq = rb_intern( "==" );

    VALUE x1 = rb_iv_get( self, "@x" );
    VALUE x2 = rb_iv_get( other, "@x" );
    VALUE x = rb_funcall( x1, id_eq, 1, x2 );

    VALUE y1 = rb_iv_get( self, "@y" );
    VALUE y2 = rb_iv_get( other, "@y" );
    VALUE y = rb_funcall( y1, id_eq, 1, y2 );

    return ( x == Qtrue ) && ( y == Qtrue );
}


static VALUE vertex_x_set( VALUE self, VALUE x )
{
    rb_iv_set( self, "@x", x );
    return Qnil;
}


static VALUE vertex_y_set( VALUE self, VALUE y )
{
    rb_iv_set( self, "@y", y );
    return Qnil;
}


static VALUE vertex_x_get( VALUE self )
{
    return rb_iv_get( self, "@x" );
}


static VALUE vertex_y_get( VALUE self )
{
    return rb_iv_get( self, "@y" );
}


static VALUE contour_num_vertices( VALUE self )
{
    ID id_size = rb_intern( "size" );
    return rb_funcall( self, id_size, 0 );
}


static VALUE contour_vertex_get( VALUE self, VALUE index )
{
    ID id_fetch = rb_intern( "fetch" );
    return rb_funcall( self, id_fetch, 1, index );
}


static VALUE contour_vertex_add( VALUE self, VALUE x, VALUE y )
{
    ID id_new = rb_intern( "new" );
    ID id_add = rb_intern( "<<" );

    VALUE vertex = rb_funcall( vertex_class, id_new, 2, x, y );
    rb_funcall( self, id_add, 1, vertex );

    return self;
}


static VALUE polygon_add_contour( VALUE self, VALUE contour )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    gpc_add_contour( polygon, create_vertex_list( contour ), 0 );

    return self;
}


static VALUE polygon_add_hole( VALUE self, VALUE contour )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    gpc_add_contour( polygon, create_vertex_list( contour ), 1 );

    return self;
}


static VALUE polygon_num_contours( VALUE self )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    return INT2FIX( polygon->num_contours );
}


static VALUE polygon_num_vertices( VALUE self, VALUE contour_index )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    return INT2FIX( polygon->contour[ FIX2INT( contour_index ) ].num_vertices );
}


static VALUE polygon_is_hole( VALUE self, VALUE contour_index )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    if ( polygon->hole[ FIX2INT( contour_index ) ] ) return Qtrue;
    
    return Qfalse;
}


static VALUE polygon_get_x( VALUE self, VALUE contour_index, VALUE vertex_index )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    return DBL2NUM( polygon->contour[ FIX2INT( contour_index ) ].vertex[ FIX2INT( vertex_index ) ].x );
}


static VALUE polygon_get_y( VALUE self, VALUE contour_index, VALUE vertex_index )
{
    gpc_polygon * polygon;

    Data_Get_Struct( self, gpc_polygon, polygon );
    return DBL2NUM( polygon->contour[ FIX2INT( contour_index ) ].vertex[ FIX2INT( vertex_index ) ].y );
}


static VALUE polygon_get_contour( VALUE self, VALUE contour_index )
{
    int idx;
    gpc_polygon * polygon;
    gpc_vertex_list * contour;
    VALUE result, x, y;

    ID id_new = rb_intern( "new" );
    ID id_add = rb_intern( "add" );

    Data_Get_Struct( self, gpc_polygon, polygon );
    contour = &( polygon->contour[ FIX2INT( contour_index ) ] );

    result = rb_funcall( contour_class, id_new, 0 );
    for ( idx = 0; idx < contour->num_vertices; idx++ )
    {
        x = DBL2NUM( contour->vertex[ idx ].x );
        y = DBL2NUM( contour->vertex[ idx ].y );
        rb_funcall( result, id_add, 2, x, y );
    }

    return result;
}


static VALUE polygon_clip( VALUE self, VALUE op, VALUE other )
{
    gpc_polygon * self_gpc;
    gpc_polygon * other_gpc;
    gpc_polygon * result_gpc;

    ID id_new = rb_intern( "new" );

    VALUE result = rb_funcall( polygon_class, id_new, 0 );
   
    Data_Get_Struct( self, gpc_polygon, self_gpc );
    Data_Get_Struct( other, gpc_polygon, other_gpc );
    Data_Get_Struct( result, gpc_polygon, result_gpc );
    gpc_polygon_clip( FIX2INT( op ), self_gpc, other_gpc, result_gpc );

    return result;
}


static VALUE polygon_triangulate( VALUE self, VALUE op, VALUE other )
{
    gpc_polygon * self_gpc;
    gpc_tristrip * result_gpc;

    ID id_new = rb_intern( "new" );

    VALUE result = rb_funcall( tristrip_class, id_new, 0 );
   
    Data_Get_Struct( self, gpc_polygon, self_gpc );
    Data_Get_Struct( result, gpc_tristrip, result_gpc );
    gpc_polygon_to_tristrip( self_gpc, result_gpc );

    return result;
}


static VALUE polygon_init( VALUE self )
{
    return self;
}


static void polygon_free( void * ptr )
{
    gpc_free_polygon( ptr );
}


static VALUE polygon_new( VALUE class )
{
    gpc_polygon * ptr;

    ptr = ALLOC( gpc_polygon );
    ptr->num_contours = 0;
    ptr->hole = NULL;
    ptr->contour = NULL;

    return Data_Wrap_Struct( class, 0, polygon_free, ptr );
}


static VALUE tristrip_init( VALUE self )
{
    return self;
}


static void tristrip_free( void * ptr )
{
    gpc_free_tristrip( ptr );
}


static VALUE tristrip_new( VALUE class )
{
    gpc_tristrip * ptr;

    ptr = ALLOC( gpc_tristrip );
    ptr->num_strips = 0;
    ptr->strip = NULL;

    return Data_Wrap_Struct( class, 0, tristrip_free, ptr );
}


static VALUE tristrip_num_strips( VALUE self )
{
    gpc_tristrip * tristrip;

    Data_Get_Struct( self, gpc_tristrip, tristrip );
    return INT2FIX( tristrip->num_strips );
}


static VALUE tristrip_num_vertices( VALUE self, VALUE strip_index )
{
    gpc_tristrip * tristrip;

    Data_Get_Struct( self, gpc_tristrip, tristrip );
    return INT2FIX( tristrip->strip[ FIX2INT( strip_index ) ].num_vertices );
}


static VALUE tristrip_get_x( VALUE self, VALUE strip_index, VALUE vertex_index )
{
    gpc_tristrip * tristrip;

    Data_Get_Struct( self, gpc_tristrip, tristrip );
    return DBL2NUM( tristrip->strip[ FIX2INT( strip_index ) ].vertex[ FIX2INT( vertex_index ) ].x );
}


static VALUE tristrip_get_y( VALUE self, VALUE strip_index, VALUE vertex_index )
{
    gpc_tristrip * tristrip;

    Data_Get_Struct( self, gpc_tristrip, tristrip );
    return DBL2NUM( tristrip->strip[ FIX2INT( strip_index ) ].vertex[ FIX2INT( vertex_index ) ].y );
}


static VALUE tristrip_get_contour( VALUE self, VALUE strip_index )
{
    int idx;
    gpc_tristrip * tristrip;
    gpc_vertex_list * strip;
    VALUE result, x, y;

    ID id_new = rb_intern( "new" );
    ID id_add = rb_intern( "add" );

    Data_Get_Struct( self, gpc_tristrip, tristrip );
    strip = &( tristrip->strip[ FIX2INT( strip_index ) ] );

    result = rb_funcall( contour_class, id_new, 0 );
    for ( idx = 0; idx < strip->num_vertices; idx++ )
    {
        x = DBL2NUM( strip->vertex[ idx ].x );
        y = DBL2NUM( strip->vertex[ idx ].y );
        rb_funcall( result, id_add, 2, x, y );
    }

    return result;
}


/****************************************************************************/
/* Ruby interfacing hook.                                                   */
/****************************************************************************/

void Init_GPC()
    {
    gpc_module = rb_define_module( "GPC" );
    rb_define_const( gpc_module, "GPC_VERSION", CHARPTR2STR( GPC_VERSION ) );
    rb_define_const( gpc_module, "GPC_DIFF", INT2FIX( GPC_DIFF ) );
    rb_define_const( gpc_module, "GPC_INT", INT2FIX( GPC_INT ) );
    rb_define_const( gpc_module, "GPC_XOR", INT2FIX( GPC_XOR ) );
    rb_define_const( gpc_module, "GPC_UNION", INT2FIX( GPC_UNION ) );

    vertex_class = rb_define_class_under( gpc_module, "Vertex", rb_cObject );
    rb_define_method( vertex_class, "initialize", vertex_init, 2 );
    rb_define_method( vertex_class, "==", vertex_eq, 1 );
    rb_define_method( vertex_class, "x=", vertex_x_set, 1 );
    rb_define_method( vertex_class, "y=", vertex_y_set, 1 );
    rb_define_method( vertex_class, "x", vertex_x_get, 0 );
    rb_define_method( vertex_class, "y", vertex_y_get, 0 );

    contour_class = rb_define_class_under( gpc_module, "Contour", rb_cArray );
    rb_define_method( contour_class, "num_vertices", contour_num_vertices, 0 );
    rb_define_method( contour_class, "vertex", contour_vertex_get, 1 );
    rb_define_method( contour_class, "add", contour_vertex_add, 2 );

    polygon_class = rb_define_class_under( gpc_module, "Polygon", rb_cObject );
    rb_define_singleton_method( polygon_class, "new", polygon_new, 0 );
    rb_define_method( polygon_class, "add_contour", polygon_add_contour, 1 );
    rb_define_method( polygon_class, "add_hole", polygon_add_hole, 1 );
    rb_define_method( polygon_class, "num_contours", polygon_num_contours, 0 );
    rb_define_method( polygon_class, "num_vertices", polygon_num_vertices, 1 );
    rb_define_method( polygon_class, "is_hole?", polygon_is_hole, 1 );
    rb_define_method( polygon_class, "get_x", polygon_get_x, 2 );
    rb_define_method( polygon_class, "get_y", polygon_get_y, 2 );
    rb_define_method( polygon_class, "get_contour", polygon_get_contour, 1 );
    rb_define_method( polygon_class, "clip", polygon_clip, 2 );
    rb_define_method( polygon_class, "triangulate", polygon_triangulate, 0 );

    tristrip_class = rb_define_class_under( gpc_module, "TriStrip", rb_cObject );
    rb_define_singleton_method( tristrip_class, "new", tristrip_new, 0 );
    rb_define_method( tristrip_class, "initialize", tristrip_init, 0 );
    rb_define_method( tristrip_class, "num_strips", tristrip_num_strips, 0 );
    rb_define_method( tristrip_class, "num_vertices", tristrip_num_vertices, 1 );
    rb_define_method( tristrip_class, "get_x", tristrip_get_x, 2 );
    rb_define_method( tristrip_class, "get_y", tristrip_get_y, 2 );
    rb_define_method( tristrip_class, "get_strip", tristrip_get_contour, 1 );
}

