/*
 * call-seq:
 *  update(key, old_value, new_value, option={})
 *
 * レコードを追加する場合は old_value に nil を指定する。
 * レコードを削除する場合は new_value に nil を指定する。
 * レコードを更新する場合は old_value, new_value の両方を指定する。
 *
 * 有効な option:
 * :section [Integer] :: セクション番号
 *
 * === Argument
 * key [String/Integer] :: インデックス値
 * old_value [String/Senna::Values] :: 古いレコード
 * new_value [String/Senna::Values] :: 新しいレコード
 *
 * === Exception
 * RuntimeError :: インデックスがクローズ済み
 * RuntimeError :: key のサイズがインデックスファイルのものとあっていない
 * TypeError :: open() で key_size に Integer が指定されているのに、key が整数でない
 * TypeError :: old_value, new_value が String でも Senna::Values でもない
 */
static VALUE index_update(int argc, VALUE *argv, VALUE obj)
{
    VALUE key, oldv, newv, opt, section;
    struct index_data *data;
    sen_rc rc;
    char *_key;
    long n;
    VALUE skey;
    int key_size;
    VALUE key_type;
    sen_values *_oldv=NULL, *_newv=NULL;
    unsigned int _section=1;

    rb_scan_args(argc, argv, "31", &key, &oldv, &newv, &opt);
    Data_Get_Struct(obj, struct index_data, data);
    if (data->closed)
        rb_raise(rb_eRuntimeError, "already closed");
    key_size = NUM2INT(rb_iv_get(obj, "key_size"));
    key_type = rb_iv_get(obj, "key_type");
    if (!NIL_P(key_type)) {
        if (!rb_obj_is_kind_of(key, key_type))
            rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", rb_obj_classname(key), rb_class_name(key_type));
    }
    if (key_size == 0) {
        skey = StringValue(key);
        _key = RSTRING_PTR(skey);
    } else {
        switch(TYPE(key)) {
        case T_FIXNUM:
        case T_BIGNUM:
            if (key_size != sizeof(n))
                rb_raise(rb_eRuntimeError, "invalid key_size: %d != %d", key_size, sizeof(n));
            n = NUM2LONG(key);
            _key = (char *)&n;
            break;
        default:
            skey = StringValue(key);
            if (key_size != RSTRING_LEN(skey))
                rb_raise(rb_eRuntimeError, "invalid key_size: %d != %d", key_size, RSTRING_LEN(skey));
            _key = RSTRING_PTR(skey);
            break;
        }
    }
    if (!NIL_P(oldv)) {
        if (TYPE(oldv) == T_STRING) {
            VALUE str = oldv;
            oldv = values_new(cSennaValues);
            values_add(oldv, str, INT2FIX(0));
        } else {
            if (!rb_obj_is_kind_of(oldv, cSennaValues))
                rb_raise(rb_eTypeError, "wrong argument type %s (expected Senna::Values)", rb_obj_classname(oldv));
        }
        Data_Get_Struct(oldv, sen_values, _oldv);
    }
    if (!NIL_P(newv)) {
        if (TYPE(newv) == T_STRING) {
            VALUE str = newv;
            newv = values_new(cSennaValues);
            values_add(newv, str, INT2FIX(0));
        } else {
            if (!rb_obj_is_kind_of(newv, cSennaValues))
                rb_raise(rb_eTypeError, "wrong argument type %s (expected Senna::Values)", rb_obj_classname(newv));
        }
        Data_Get_Struct(newv, sen_values, _newv);
    }
    if (!NIL_P(opt)) {
        Check_Type(opt, T_HASH);
        section = rb_hash_aref(opt, ID2SYM(rb_intern("section")));
        _section = NIL_P(section) ? 1 : NUM2UINT(section);
    }
    rc = sen_index_update(data->indexp, _key, _section, _oldv, _newv);
    if (rc != sen_success)
        senna_error(rc, "cannot update: key=%s", _key);
    return obj;
}