
/*
 * Copyright (C) 2003, 2004 Laurent Sansonetti <lrz@gnome.org>
 *
 * This file is part of Ruby/Libgda.
 *
 * Ruby/Libgda is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Ruby/Libgda is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Ruby/Libgda; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

#include "rbgda.h"

/*
 * Class: Gda::DataModel
 * Basic data model class.
 */
VALUE cGdaDataModel;

/*
 * Method: emit_changed
 *
 * Notifies listeners of the given data model object of changes in the
 * underlying data. Listeners usually will connect themselves to the "changed"
 * signal in the Gda::DataModel class, thus being notified of any new data
 * being appended or removed from the data model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_changed(self)
    VALUE self;
{
    gda_data_model_changed(RGDA_DATAMODEL(self));
    return self;
}

/*
 * Method: emit_row_inserted(rownum)
 * rownum: a row number.
 *
 * Emits the 'row_inserted' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_row_inserted(self, rownum)
    VALUE self, rownum;
{
    gda_data_model_row_inserted(RGDA_DATAMODEL(self), 
                                FIX2INT(rownum));
    return self;
}

/*
 * Method: emit_row_updated(rownum)
 * rownum: a row number.
 *
 * Emits the 'row_updated' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_row_updated(self, rownum)
    VALUE self, rownum;
{
    gda_data_model_row_updated(RGDA_DATAMODEL(self),
                               FIX2INT(rownum));
    return self;
}

/*
 * Method: emit_row_removed(rownum)
 * rownum: a row number.
 *
 * Emits the 'row_removed' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_row_removed(self, rownum)
    VALUE self, rownum;
{
    gda_data_model_row_removed(RGDA_DATAMODEL(self),
                               FIX2INT(rownum));
    return self;
}

/*
 * Method: freeze!
 *
 * Disables notifications of changes on the data model. To re-enable
 * notifications again, you should call the Gda::DataModel#thaw method. 
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_freeze(self)
    VALUE self;
{
    gda_data_model_freeze(RGDA_DATAMODEL(self));
    return self;
}

/*
 * Method: thaw!
 *
 * Re-enables notifications of changes on the given data model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_thaw(self)
    VALUE self;
{
    gda_data_model_thaw(RGDA_DATAMODEL(self));
    return self;
}

/*
 * Method: columns
 *
 * Returns: a list of all column titles in the model, as an Array of strings.
 */
static VALUE rb_gda_datamodel_get_columns(self)
    VALUE self;
{
    GdaDataModel *datamod;
    VALUE arr;
    int i;

    arr = rb_ary_new();
    datamod = RGDA_DATAMODEL(self);
    for (i = 0; i < gda_data_model_get_n_columns(datamod); i++) {
        rb_ary_push(arr, CSTR2RVAL(gda_data_model_get_column_title(datamod, 
                                                                   i))); 
    }
    return arr;
}

/*
 * Method: each_column { |col_title| ... }
 *
 * Calls the block for each column title in the model, passing a reference
 * to a String object as parameter.
 *
 * Returns: always nil.
 */
static VALUE rb_gda_datamodel_each_column(self)
    VALUE self;
{
    rb_ary_each(rb_gda_datamodel_get_columns(self));
    return Qnil;
}

/*
 * Method: n_columns
 *
 * Returns: the number of columns in the model.
 */
static VALUE rb_gda_datamodel_get_n_columns(self)
    VALUE self;
{
    return INT2FIX(gda_data_model_get_n_columns(RGDA_DATAMODEL(self)));
}

/*
 * Method: get_column_title(colnum)
 * colnum: a column number.
 *
 * Returns: the title for the given column in the model.
 */
static VALUE rb_gda_datamodel_get_column(self, colnum)
    VALUE self, colnum;
{
    const gchar *column = gda_data_model_get_column_title(RGDA_DATAMODEL(self),  
                                                          FIX2INT(colnum));
    return column != NULL
        ? CSTR2RVAL(column) 
        : Qnil;
}

/*
 * Method: describe_column(colnum)
 * colnum: a column number.
 *
 * Queries the underlying data model implementation for a description of a
 * given column. That description is returned in the form of a 
 * Gda::FieldAttributes object, which contains all the information about the
 * given column in the data model.
 *
 * Returns: the description of the column, as a Gda::FieldAttributes object.
 */
static VALUE rb_gda_datamodel_describe_column(self, colnum)
    VALUE self, colnum;
{
#if defined(GDA_AT_LEAST_1_3)
    GdaColumn *a;
#else
    GdaFieldAttributes *a;
#endif
   
    a = gda_data_model_describe_column(RGDA_DATAMODEL(self),
                                       FIX2INT(colnum));

    return a != NULL
        ? RGDA_FIELD_ATTRIBUTES_NEW(a)
        : Qnil;
}

/*
 * Method: get_column_pos(col_title)
 * col_title: a column title.
 *
 * Gets the position of a column on the data model, based on the column's title.
 *
 * Returns: the position of the column in the data model, or -1 if the column
 * could not be found.
 */
static VALUE rb_gda_datamodel_get_column_pos(self, col_title)
    VALUE self, col_title;
{
    return INT2FIX(gda_data_model_get_column_position(RGDA_DATAMODEL(self),
                                                      RVAL2CSTR(col_title)));
}

/*
 * Method: get_value_at(colnum, rownum)
 * colnum: a column number.
 * rownum: a row number.
 *
 * Retrieves the data stored in the given position in the data model.
 * This is the main function for accessing data in a model.
 *
 * Returns: a Ruby object according to the type of the column is successful,
 * nil otherwise (out-of-bound position, etc...).
 */
static VALUE rb_gda_datamodel_get_value_at(self, colnum, rownum)
    VALUE self, colnum, rownum;
{
    const GdaValue *val = gda_data_model_get_value_at(RGDA_DATAMODEL(self),
                                                      FIX2INT(colnum),
                                                      FIX2INT(rownum));
    return val != NULL
        ? RGDA_VALUE_NEW(val)
        : Qnil;
}

/*
 * Method: rows
 *
 * Returns: a list of all the rows in the data model, as an Array of Gda::Row
 * objects.
 */
static VALUE rb_gda_datamodel_get_rows(self)
    VALUE self;
{
    GdaDataModel *datamod;
    VALUE arr;
    int i;

    arr = rb_ary_new();
    datamod = RGDA_DATAMODEL(self);
    for (i = 0; i < gda_data_model_get_n_rows(datamod); i++) {
        rb_ary_push(arr, RGDA_ROW_NEW(gda_data_model_get_row(datamod, 
                                                             i))); 
    }
    return arr;
}

/*
 * Method: each_row { |row| ... }
 *
 * Calls the block for each row in the data model, passing a reference to a
 * Gda::Row object as parameter.
 *
 * Returns: always nil.
 */ 
static VALUE rb_gda_datamodel_each_row(self)
    VALUE self;
{
    rb_ary_each(rb_gda_datamodel_get_rows(self));
    return Qnil;
}

/*
 * Method: n_rows
 *
 * Returns: the number of rows in the data model.
 */
static VALUE rb_gda_datamodel_get_n_rows(self)
    VALUE self;
{
    return INT2FIX(gda_data_model_get_n_rows(RGDA_DATAMODEL(self)));
}

/*
 * Method: get_row(rownum)
 * rownum: a row number.
 *
 * Retrieves a given row from the data model.
 *
 * Returns: the row, as a Gda::Row object, or nil if the given row number is
 * invalid.
 */
static VALUE rb_gda_datamodel_get_row(self, row_number)
    VALUE self, row_number;
{
    const GdaRow *row = gda_data_model_get_row(RGDA_DATAMODEL(self),  
                                               FIX2INT(row_number));
    return row != NULL
        ? RGDA_ROW_NEW(row)
        : Qnil;
}

/*
 * Method: append_row(*values)
 * values: a list of Gda::Value objects. Their length must match model's
 * column count.
 *
 * Appends a row to the data model.
 *
 * Returns: the added row, as a Gda::Row object, on success, nil on errors.
 */
static VALUE rb_gda_datamodel_append_row(self, values)
    VALUE self, values;
{
#if defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_append_row(RGDA_DATAMODEL(self), 
                                                RGDA_ROW(values)));
#else
    const GdaRow *row;
    GList *list = NULL;
    int i;
    
    for (i = 0; i < RARRAY(values)->len; i++) {
        list = g_list_append(list, RGDA_VALUE(rb_ary_entry(values, i),
                                              GDA_VALUE_TYPE_NULL));
    }

    if (list == NULL) {
        rb_raise(rb_eArgError, "Wrong # of arguments (need at least 1)");
    }
    row = gda_data_model_append_row(RGDA_DATAMODEL(self), 
                                    (const GList *)list);
    return row != NULL 
        ? RGDA_ROW_NEW(row)
        : Qnil;
#endif
}

/*
 * Method: update_row(row)
 * row: a Gda::Row object.
 *
 * Updates a row data model. This results in the underlying database row's
 * values being changed.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_datamodel_update_row(self, row)
    VALUE self, row;
{
    return CBOOL2RVAL(gda_data_model_update_row(RGDA_DATAMODEL(self),
                                                RGDA_ROW(row)));
}

/*
 * Method: remove_row(row)
 * row: a Gda::Row object.
 *
 * Removes a row from the data model. This results in the underlying database
 * row being removed in the database.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_datamodel_remove_row(self, row)
    VALUE self, row;
{
    return CBOOL2RVAL(gda_data_model_remove_row(RGDA_DATAMODEL(self),
                                                RGDA_ROW(row)));
}

#if defined(GDA_AT_LEAST_1_1)
/*
 * Method: emit_column_inserted(colnum)
 * colnum: a column number.
 *
 * Emits the 'column_inserted' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_column_inserted(self, colnum)
    VALUE self, colnum;
{
    gda_data_model_column_inserted(RGDA_DATAMODEL(self), 
                                   FIX2INT(colnum));
    return self;
}

/*
 * Method: emit_column_updated(colnum)
 * colnum: a column number.
 *
 * Emits the 'column_updated' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_column_updated(self, colnum)
    VALUE self, colnum;
{
    gda_data_model_column_updated(RGDA_DATAMODEL(self),
                                  FIX2INT(colnum));
    return self;
}

/*
 * Method: emit_column_removed(colnum)
 * colnum: a column number.
 *
 * Emits the 'column_removed' and 'changed' signals on the model.
 *
 * Returns: self.
 */
static VALUE rb_gda_datamodel_column_removed(self, colnum)
    VALUE self, colnum;
{
    gda_data_model_column_removed(RGDA_DATAMODEL(self),
                                  FIX2INT(colnum));
    return self;
}

/*
 * Method: append_column(attributes)
 * attributes: a Gda::FieldAttributes object describing the column to add.
 *
 * Appends a column to the data model.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_datamodel_append_column(self, column)
    VALUE self, column;
{
    return CBOOL2RVAL(gda_data_model_append_column(RGDA_DATAMODEL(self),
                                                   RGDA_FIELD_ATTRIBUTES(column)));
}

/*
 * Method: update_column(column_id, attributes)
 * column_id: the number of the column to be updated.
 * attributes: a Gda::FieldAttributes object describing the column to update.
 *
 * Updates an existing column in the data model.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_datamodel_update_column(self, column_id, column)
    VALUE self, column_id, column;
{
    return CBOOL2RVAL(gda_data_model_update_column(RGDA_DATAMODEL(self),
                                                   FIX2INT(column_id),
                                                   RGDA_FIELD_ATTRIBUTES(column)));
}

/*
 * Method: remove_column(column_id)
 * column_id: the number of the column to be updated.
 *
 * Removes an existing column in the data model.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_datamodel_remove_column(self, column_id)
    VALUE self, column_id;
{
    return CBOOL2RVAL(gda_data_model_remove_column(RGDA_DATAMODEL(self),
                                                   FIX2INT(column_id)));
}
#endif /* GDA_AT_LEAST_1_1 */

/*
 * Method: updatable?
 *
 * Returns: true if the model can be updated, false otherwise.
 */
static VALUE rb_gda_datamodel_is_updatable(self)
    VALUE self;
{
#if defined(GDA_AT_LEAST_1_1) || defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_is_updatable(RGDA_DATAMODEL(self)));
#else
    return CBOOL2RVAL(gda_data_model_is_editable(RGDA_DATAMODEL(self)));
#endif
}

/*
 * Method: has_changed?
 *
 * Checks whether this data model is in updating mode or not. Updating
 * mode is set to true when Gda::DataModel#begin_update has been
 * called successfully, and is not set back to false until either
 * Gda::DataModel#cancel_update or Gda::DataModel#end_update have been called.
     
 * Returns: true if updating mode, false otherwise.
 */
static VALUE rb_gda_datamodel_has_changed(self)
    VALUE self;
{
#if defined(GDA_AT_LEAST_1_1) || defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_has_changed(RGDA_DATAMODEL(self)));
#else
    return CBOOL2RVAL(gda_data_model_is_editing(RGDA_DATAMODEL(self)));
#endif
}

/*
 * Method: begin_update
 *
 * Starts update of this data model. This method should be the
 * first called when modifying the data model.
 *
 * Returns: true on success, false if there was an error.
 */
static VALUE rb_gda_datamodel_begin_update(self)
    VALUE self;
{
#if defined(GDA_AT_LEAST_1_1) || defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_begin_update(RGDA_DATAMODEL(self)));
#else
    return CBOOL2RVAL(gda_data_model_begin_edit(RGDA_DATAMODEL(self)));
#endif
}

/*
 * Method: cancel_update
 *
 * Cancels update of this data model. This means that all changes
 * will be discarded, and the old data put back in the model.
 *
 * Returns: true on success, false if there was an error.
 */
static VALUE rb_gda_datamodel_cancel_update(self)
    VALUE self;
{
#if defined(GDA_AT_LEAST_1_1) || defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_cancel_update(RGDA_DATAMODEL(self)));
#else
    return CBOOL2RVAL(gda_data_model_cancel_edit(RGDA_DATAMODEL(self)));
#endif
}

/*
 * Method: end_update
 *
 * Approves all modifications and send them to the underlying
 * data source/store.
 *
 * Returns: true on success, false if there was an error.
 */
static VALUE rb_gda_datamodel_end_update(self)
    VALUE self;
{
#if defined(GDA_AT_LEAST_1_1)
    return CBOOL2RVAL(gda_data_model_end_update(RGDA_DATAMODEL(self)));
#else 
#  if defined(GDA_AT_LEAST_1_3)
    return CBOOL2RVAL(gda_data_model_commit_update(RGDA_DATAMODEL(self)));
#  else
    return CBOOL2RVAL(gda_data_model_end_edit(RGDA_DATAMODEL(self)));
#  endif
#endif
}

#if !defined(GDA_AT_LEAST_1_3)
/*
 * Method: to_comma_separated
 *
 * Converts the given model into a comma-separated series of rows.
 *
 * Returns: the string representation of the model.
 */
static VALUE rb_gda_datamodel_to_comma_separated(self)
    VALUE self;
{
    return CSTR2RVAL(gda_data_model_to_comma_separated(RGDA_DATAMODEL(self)));
}

/*
 * Method: to_tab_separated
 *
 * Converts the given model into a tab-separated series of rows.
 *
 * Returns: the string representation of the model.
 */
static VALUE rb_gda_datamodel_to_tab_separated(self)
    VALUE self;
{
    return CSTR2RVAL(gda_data_model_to_tab_separated(RGDA_DATAMODEL(self)));
}

/*
 * Method: to_xml(standalone=false)
 *
 * Converts the given model into a XML representation.
 *
 * Returns: the string representation of the model.
 */
static VALUE rb_gda_datamodel_to_xml(argc, argv, self)
    int argc;
    VALUE *argv, self;
{
    VALUE standalone;

    rb_scan_args(argc, argv, "01", &standalone);
    
    return CSTR2RVAL(gda_data_model_to_xml(RGDA_DATAMODEL(self), 
                                           NIL_P(standalone) ? FALSE : RVAL2CBOOL(standalone)));
}
#endif

/*
 * Method: command_text
 *
 * Gets the text of command that generated this data model.
 *
 * Returns: a string with the command issued.
 */
static VALUE rb_gda_datamodel_get_command_text(self)
    VALUE self;
{
    return CSTR2RVAL(gda_data_model_get_command_text(RGDA_DATAMODEL(self)));
}

/*
 * Method: set_command_text(text)
 * text: the command text.
 *
 * Sets the command text of this data model.
 *
 * Returns: self.
 */ 
static VALUE rb_gda_datamodel_set_command_text(self, text)
    VALUE self, text;
{
    gda_data_model_set_command_text(RGDA_DATAMODEL(self),
                                    RVAL2CSTR(text));
    return self;
}

/*
 * Method: command_type
 *
 * Returns: the command type of this data model (see Gda::Command::Type).
 */
static VALUE rb_gda_datamodel_get_command_type(self)
    VALUE self;
{
    return GENUM2RVAL(gda_data_model_get_command_type(RGDA_DATAMODEL(self)),
                      GDA_TYPE_COMMAND_TYPE);
}

/*
 * Method: set_command_type(type)
 * type: the command type (see Gda::Command::Type).
 *
 * Sets the command type of this data model.
 *
 * Returns: self.
 */ 
static VALUE rb_gda_datamodel_set_command_type(self, type)
    VALUE self, type;
{
    gda_data_model_set_command_type(RGDA_DATAMODEL(self),
                                    RVAL2GENUM(type, GDA_TYPE_COMMAND_TYPE));
    return self;
}

void Init_gda_datamodel(void) {
    VALUE c = G_DEF_CLASS(GDA_TYPE_DATA_MODEL, "DataModel", mGda);

    rb_define_method(c, "emit_changed",      rb_gda_datamodel_changed,      0); 
    rb_define_method(c, "emit_row_inserted", rb_gda_datamodel_row_inserted, 1); 
    rb_define_method(c, "emit_row_updated",  rb_gda_datamodel_row_updated,  1); 
    rb_define_method(c, "emit_row_removed",  rb_gda_datamodel_row_removed,  1); 
    rb_define_method(c, "freeze!", rb_gda_datamodel_freeze, 0);
    rb_define_method(c, "thaw!",   rb_gda_datamodel_thaw,   0);

    rb_define_method(c, "columns",          rb_gda_datamodel_get_columns,   0);
    rb_define_method(c, "each_column",      rb_gda_datamodel_each_column,   0);
    rb_define_method(c, "n_columns",        rb_gda_datamodel_get_n_columns, 0);
    rb_define_method(c, "get_column_title", rb_gda_datamodel_get_column,    1);

    rb_define_method(c, "describe_column",     rb_gda_datamodel_describe_column, 1);
    rb_define_method(c, "get_column_position", rb_gda_datamodel_get_column_pos,  1);
    rb_define_method(c, "get_value_at", rb_gda_datamodel_get_value_at, 2);
    
    rb_define_method(c, "rows",     rb_gda_datamodel_get_rows,   0);
    rb_define_method(c, "each_row", rb_gda_datamodel_each_row,   0);
    rb_define_method(c, "n_rows",   rb_gda_datamodel_get_n_rows, 0);
    rb_define_method(c, "get_row",  rb_gda_datamodel_get_row,    1);

    rb_define_method(c, "append_row", rb_gda_datamodel_append_row, -2);
    rb_define_method(c, "remove_row", rb_gda_datamodel_remove_row,  1);
    rb_define_method(c, "update_row", rb_gda_datamodel_update_row,  1);
   
    rb_define_method(c, "updatable?", rb_gda_datamodel_is_updatable, 0);
    rb_define_method(c, "changed?",  rb_gda_datamodel_has_changed,  0);
    
    rb_define_method(c, "begin_update",  rb_gda_datamodel_begin_update,  0);
    rb_define_method(c, "cancel_update", rb_gda_datamodel_cancel_update, 0);
    rb_define_method(c, "end_update",    rb_gda_datamodel_end_update,    0);

#if !defined(GDA_AT_LEAST_1_3)
    rb_define_method(c, "to_comma_separated", rb_gda_datamodel_to_comma_separated, 0);
    rb_define_method(c, "to_tab_separated",   rb_gda_datamodel_to_tab_separated,   0);
    rb_define_method(c, "to_xml",             rb_gda_datamodel_to_xml,            -1);
#endif

/*  TODO:
xmlNodePtr  gda_data_model_to_xml_node      (GdaDataModel *model,
                                             const gchar *name);
gboolean    gda_data_model_add_data_from_xml_node
                                            (GdaDataModel *model,
                                             xmlNodePtr node);
*/

    rb_define_method(c, "command_text", rb_gda_datamodel_get_command_text, 0);
    rb_define_method(c, "set_command_text", 
                     rb_gda_datamodel_set_command_text, 1);

    rb_define_method(c, "command_type", rb_gda_datamodel_get_command_type, 0);
    rb_define_method(c, "set_command_type", 
                     rb_gda_datamodel_set_command_type, 1);

#if defined(GDA_AT_LEAST_1_1)
    rb_define_method(c, "emit_column_inserted", rb_gda_datamodel_column_inserted, 1); 
    rb_define_method(c, "emit_column_updated",  rb_gda_datamodel_column_updated,  1); 
    rb_define_method(c, "emit_column_removed",  rb_gda_datamodel_column_removed,  1); 
    rb_define_method(c, "append_column", rb_gda_datamodel_append_column, 1);
    rb_define_method(c, "update_column", rb_gda_datamodel_update_column, 2);
    rb_define_method(c, "remove_column", rb_gda_datamodel_remove_column, 1);
#endif
    
    G_DEF_SETTERS(c);

    cGdaDataModel = c;
}

