/*
ChangeLog:
    * Sun Sep 29 2002 Kachalov Anton <mouse@altlinux.ru>
    - merge with new php-extension (ver. 1.66)
    * Thu Sep 25 2003 Stanislav Karchebny <berkus@users.sf.net>
    - merge with new php-extension (ver. 1.80)


   | Authors:                                                                 |
   |  Initial PHP version     by  Alex Barkov <bar@izhcom.ru>                 |
   |                          and Ramil Kalimullin <ram@izhcom.ru>            |
   |  Further PHP development by  Sergey Kartashoff <gluke@mail.ru>           |
   |  Ruby version	      by  Kachalov Anton <mouse@altlinux.ru>	      |

*/

#include "ruby.h"

#define UDMSTRSIZ               1024*5

#define UDM_FIELD_URLID		1
#define UDM_FIELD_URL		2
#define UDM_FIELD_CONTENT	3	
#define UDM_FIELD_TITLE		4
#define UDM_FIELD_KEYWORDS	5
#define UDM_FIELD_DESC		6
#define UDM_FIELD_TEXT		7
#define UDM_FIELD_SIZE		8
#define UDM_FIELD_RATING	9
#define UDM_FIELD_MODIFIED	10
#define UDM_FIELD_ORDER		11
#define UDM_FIELD_CRC		12
#define UDM_FIELD_CATEGORY	13
#define UDM_FIELD_LANG		14
#define UDM_FIELD_CHARSET	15
#define UDM_FIELD_SITEID	16
#define UDM_FIELD_POP_RANK	17
#define UDM_FIELD_ORIGINID	18

/* udm_set_agent_param constants */
#define UDM_PARAM_PAGE_SIZE		1
#define UDM_PARAM_PAGE_NUM		2
#define UDM_PARAM_SEARCH_MODE		3
#define UDM_PARAM_CACHE_MODE		4
#define UDM_PARAM_TRACK_MODE		5
#define UDM_PARAM_CHARSET		6
#define UDM_PARAM_STOPTABLE		7
#define UDM_PARAM_STOPFILE		8
#define UDM_PARAM_WEIGHT_FACTOR		9
#define UDM_PARAM_WORD_MATCH		10
#define UDM_PARAM_PHRASE_MODE		11
#define UDM_PARAM_MIN_WORD_LEN		12
#define UDM_PARAM_MAX_WORD_LEN		13
#define UDM_PARAM_ISPELL_PREFIXES	14
#define UDM_PARAM_CROSS_WORDS		15
#define UDM_PARAM_VARDIR               	16
#define UDM_PARAM_LOCAL_CHARSET         17
#define UDM_PARAM_BROWSER_CHARSET      	18
#define UDM_PARAM_HLBEG                 19
#define UDM_PARAM_HLEND                 20
#define UDM_PARAM_SYNONYM               21
#define UDM_PARAM_SEARCHD               22
#define UDM_PARAM_QSTRING               23
#define UDM_PARAM_REMOTE_ADDR           24
#define UDM_PARAM_QUERY                 25
#define UDM_PARAM_STORED                26
#define UDM_PARAM_GROUPBYSITE		27
#define UDM_PARAM_SITEID		28
#define UDM_PARAM_DETECT_CLONES		29

/* udm_add_search_limit constants */
#define UDM_LIMIT_URL		1
#define UDM_LIMIT_TAG		2
#define UDM_LIMIT_LANG		3
#define UDM_LIMIT_CAT		4
#define UDM_LIMIT_DATE		5
#define UDM_LIMIT_TYPE		6

/* word match type */
#define UDM_MATCH_WORD         0
#define UDM_MATCH_BEGIN                1
#define UDM_MATCH_SUBSTR       2
#define UDM_MATCH_END          3

/* General modes */
#define UDM_ENABLED		1
#define UDM_DISABLED		0

/* udm_get_res_param constants */
#define UDM_PARAM_NUM_ROWS	256
#define UDM_PARAM_FOUND		257
#define UDM_PARAM_WORDINFO	258
#define UDM_PARAM_SEARCHTIME	259
#define UDM_PARAM_FIRST_DOC	260
#define UDM_PARAM_LAST_DOC	261
#define UDM_PARAM_WORDINFO_ALL 262

/* udm_load_ispell_data constants */
#define UDM_ISPELL_TYPE_AFFIX	1
#define UDM_ISPELL_TYPE_SPELL	2
#define UDM_ISPELL_TYPE_DB	3
#define UDM_ISPELL_TYPE_SERVER	4

#include <udm_config.h>
#include <udmsearch.h>

#define GetHandler(obj)		(Check_Type(obj, T_DATA), (((struct mnogosearch*)DATA_PTR(obj))->Agent))
#define GetRes(obj)		(Check_Type(obj, T_DATA), (((struct mnogo_res*)DATA_PTR(obj))->Res))
#define GetAgent(obj)		(Check_Type(obj, T_DATA), (((struct mnogo_cat*)DATA_PTR(obj))->Agent))

#define NILorSTRING(obj)        (NIL_P(obj)? NULL: STR2CSTR(obj))
#define NILorINT(obj)           (NIL_P(obj)? 0: NUM2INT(obj))

struct mnogosearch {
    UDM_AGENT *Agent;
};

struct mnogo_res {
    UDM_RESULT *Res;
    char freed;
};

struct mnogo_cat {
    UDM_AGENT *Agent;
};

VALUE cMnogo, cMnogoRes, cMnogoField, cMnogoCat, cMnogoCatAtom;
char *charset = NULL;

static void free_udm_agent(struct mnogosearch *mnogo)
{
#if UDM_VERSION_ID >= 30204
    UdmEnvFree(mnogo->Agent->Conf);
    UdmAgentFree(mnogo->Agent);
#else
    UdmFreeEnv(mnogo->Agent->Conf);
    UdmFreeAgent(mnogo->Agent);
#endif
    free(mnogo);
    free(charset);
}

static void free_cat(struct mnogo_cat *cat)
{
    free(cat);
}

static void free_udm_res(struct mnogo_res *res)
{
    if (!res->freed)
#if UDM_VERSION_ID >= 30204
	UdmResultFree(res->Res);
#else
	UdmFreeResult(res->Res);
#endif
    free(res);
}

static VALUE res2obj(VALUE klass, UDM_RESULT *res)
{
    VALUE obj;
    struct mnogo_res *resp;

    obj = Data_Make_Struct(cMnogoRes, struct mnogo_res, 0, free_udm_res, resp);
    resp->Res = res;
    resp->freed = Qfalse;
    rb_obj_call_init(obj,0,NULL);
    return obj;
}

#if UDM_VERSION_ID >= 30204
static char* MyRemoveHiLightDup(const char *s){
	size_t	len=strlen(s)+1;
	char	*res=malloc(len);
	char	*d;

	for(d=res;s[0];s++){
		switch(s[0]){
			case '\2':
			case '\3':
				break;
			default:
				*d++=*s;
		}
	}
	*d='\0';
	return res;
}
#endif

/*
#if UDM_VERSION_ID >= 30204
				Env=UdmEnvInit(NULL);
				UdmVarListReplaceStr(&Env->Vars,"SyslogFacility","local7");
				UdmSetLogLevel(Env,0);
				UdmOpenLog("mnoGoSearch-php",Env,0);
   #if UDM_VERSION_ID <= 30210
				if(!memcmp(dbaddr,"searchd:",8)){
					UDM_URL	Url;
					UdmURLParse(&Url,dbaddr);
					UdmDBListAdd(&Env->sdcl,Url.hostinfo);
				}

				UdmVarListReplaceStr(&Env->Vars,"DBAddr",dbaddr);

				if(UDM_OK!=UdmDBSetAddr(Env->db,dbaddr,UDM_OPEN_MODE_READ)){
				    sprintf(Env->errstr,"Invalid DBAddr: '%s'",dbaddr);
      #if UDM_VERSION_ID <= 30207
				    Env->errcode=1;
      #endif
				    php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid DBAddr");
				    RETURN_FALSE;
				}
   #else
				UdmDBListAdd(&Env->dbl,dbaddr, UDM_OPEN_MODE_WRITE);
   #endif
				Agent=UdmAgentInit(NULL,Env,0);
#elif UDM_VERSION_ID >= 30200
				Env=UdmAllocEnv();
				Env->vars=UdmAllocVarList();
				Env->DBAddr=strdup(dbaddr);
				UdmEnvSetDBMode(Env,"single");
				Agent=UdmAllocAgent(Env,0,UDM_OPEN_MODE_READ);
#else
				Env=UdmAllocEnv();
				UdmEnvSetDBAddr(Env,dbaddr);
				Agent=UdmAllocAgent(Env,0,UDM_OPEN_MODE_READ);
#endif
*/

/* {{{ proto int udm_alloc_agent(string dbaddr [, string dbmode])
   Allocate mnoGoSearch session */
static VALUE udm_alloc_agent(int argc, VALUE *argv, VALUE klass)
{
    VALUE dbaddr, dbmode;
    struct mnogosearch *mnogo;
    struct mnogo_cat *cat;
    char *da, *dm;
    UDM_ENV   * Env=NULL;
    VALUE obj;

    rb_scan_args(argc, argv, "11", &dbaddr, &dbmode);
    da = NILorSTRING(dbaddr);
    dm = NILorSTRING(dbmode);
#if UDM_VERSION_ID < 30200
    UdmInitCharset();
    Env=UdmAllocEnv();
    UdmEnvSetDBAddr(Env,da);
    if (dm)
      UdmEnvSetDBMode(Env,dm);
#elif UDM_VERSION_ID >= 30204
    Env=UdmEnvInit(NULL);
    UdmVarListReplaceStr(&Env->Vars,"SyslogFacility","local7");
    UdmSetLogLevel(Env,0);
    UdmOpenLog("mnoGoSearch-ruby",Env,0);

   #if UDM_VERSION_ID <= 30210
    if(!memcmp(da,"searchd:",8)) {
      UDM_URL	Url;
      UdmURLParse(&Url,da);
      UdmDBListAdd(&Env->sdcl,Url.hostinfo);
    }
    UdmVarListReplaceStr(&Env->Vars,"DBAddr",da);
    if(UDM_OK!=UdmDBSetAddr(Env->db,da,UDM_OPEN_MODE_READ)){
        sprintf(Env->errstr,"Invalid DBAddr: '%s'",da);
      #if UDM_VERSION_ID <= 30207
        Env->errcode=1;
      #endif
        rb_warn("udm_alloc_agent(): Invalid DBAddr");
        return Qnil;
    }
   #else
    UdmDBListAdd(&Env->dbl,da, UDM_OPEN_MODE_WRITE);
   #endif
#elif UDM_VERSION_ID >= 30200
    Env=UdmAllocEnv();
    Env->vars=UdmAllocVarList();
    Env->DBAddr=strdup(da);
    if (dm)
	UdmEnvSetDBMode(Env,dm);
    else
	UdmEnvSetDBMode(Env,"single");
#endif
    obj = Data_Make_Struct(klass,struct mnogosearch,0,free_udm_agent,mnogo);
#if UDM_VERSION_ID >= 30204
    mnogo->Agent=UdmAgentInit(NULL,Env,0);
// FIXME: allocation & free
    //printf("%s\n",UdmVarListFindStr(&mnogo->Agent->Conf->Vars,"CS",""));
    UdmVarListReplaceStr(&mnogo->Agent->Conf->Vars,"LocalCharset","iso-8859-1");
    mnogo->Agent->Conf->lcs = UdmGetCharSet("iso-8859-1");
    mnogo->Agent->Conf->bcs = UdmGetCharSet("iso-8859-1");
    //charset = strdup(mnogo->Agent->Conf->lcs->name);
    charset = strdup("iso-8859-1");
#else
    mnogo->Agent=UdmAllocAgent(Env,0,UDM_OPEN_MODE_READ);
// FIXME: allocation & free
    charset = strdup(UdmCharsetStr(mnogo->Agent->Conf->local_charset));
#endif
    rb_obj_call_init(obj,argc,argv);
    rb_iv_set(obj, "cat", Data_Make_Struct(cMnogoCat,struct mnogo_cat,0,free_cat,cat));//rb_obj_alloc(cMnogoCat));
    cat->Agent = mnogo->Agent;
    return obj;
}
/* }}} */


/* {{{ proto int udm_set_agent_param(int agent, int var, string val)
   Set mnoGoSearch agent session parameters */
static VALUE udm_set_agent_param(int argc,VALUE *argv, VALUE obj)
{
	VALUE vr, vl;
	char *vals=NULL;
	int var, vali=0;
	UDM_AGENT * Agent = GetHandler(obj);

	rb_scan_args(argc,argv,"20",&vr,&vl);
	var = NILorINT(vr);
	if (TYPE(vl) == T_FIXNUM)
	    vali = NILorINT(vl);
	else if (TYPE(vl) == T_STRING) {
	    vals = NILorSTRING(vl);
	    vali = atoi(vals);
	} else {
	    rb_warn("Params must be FixNum or String");
	    return Qfalse;
	}
	switch(var){
		case UDM_PARAM_PAGE_SIZE: 
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"ps",vals);
#else
			Agent->page_size=vali;
			if(Agent->page_size<1)Agent->page_size=20;
#endif
			break;
			
		case UDM_PARAM_PAGE_NUM: 
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"np",vals);
#else
			Agent->page_number=vali;
			if(Agent->page_number<0)Agent->page_number=0;
#endif
			break;
			
		case UDM_PARAM_SEARCH_MODE:
			switch (vali){
					case UDM_MODE_ALL:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"m","all");
#else
						Agent->search_mode=UDM_MODE_ALL;
#endif
						break;
						
					case UDM_MODE_ANY:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"m","any");
#else
						Agent->search_mode=UDM_MODE_ANY;
#endif
						break;
						
					case UDM_MODE_BOOL: 
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"m","bool");
#else
						Agent->search_mode=UDM_MODE_BOOL;
#endif
						break;

					case UDM_MODE_PHRASE:
#if UDM_VERSION_ID >= 30204
						rb_warn("Udm_Set_Agent_Param: Unknown search mode");
						return Qfalse;
#else
						Agent->search_mode=UDM_MODE_PHRASE;
#endif
						break;
						
					default:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"m","all");
#else
						Agent->search_mode=UDM_MODE_ALL;
#endif
						rb_warning("Udm_Set_Agent_Param: Unknown search mode");
						return Qfalse;
						break;
			}
			
			break;

		case UDM_PARAM_WORD_MATCH:
			switch (vali){
					case UDM_MATCH_WORD:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"wm","wrd");
#else
						Agent->word_match=UDM_MATCH_WORD;
#endif
						break;

					case UDM_MATCH_BEGIN:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"wm","beg");
#else
						Agent->word_match=UDM_MATCH_BEGIN;
#endif
						break;

					case UDM_MATCH_END:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"wm","end");
#else
						Agent->word_match=UDM_MATCH_END;
#endif
						break;

					case UDM_MATCH_SUBSTR:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"wm","sub");
#else
						Agent->word_match=UDM_MATCH_SUBSTR;
#endif
						break;
						
					default:
#if UDM_VERSION_ID >= 30204
						UdmVarListReplaceStr(&Agent->Conf->Vars,"wm","wrd");
#else
						Agent->word_match=UDM_MATCH_WORD;
#endif
						rb_warning("Udm_Set_Agent_Param: Unknown word match mode");
						return Qfalse;
						break;
			}
			
			break;
			
		case UDM_PARAM_CACHE_MODE: 
			switch (vali){
				case UDM_ENABLED:
#if UDM_VERSION_ID < 30200
					Agent->cache_mode=UDM_ENABLED;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"Cache","yes");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"Cache","yes",UDM_VARSRC_GLOBAL);
#endif
					break;
					
				case UDM_DISABLED:
#if UDM_VERSION_ID < 30200
					Agent->cache_mode=UDM_DISABLED;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"Cache","no");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"Cache","no",UDM_VARSRC_GLOBAL);
#endif
					break;
					
				default:
#if UDM_VERSION_ID < 30200
					Agent->cache_mode=UDM_DISABLED;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"Cache","no");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"Cache","no",UDM_VARSRC_GLOBAL);
#endif
					rb_warning("Udm_Set_Agent_Param: Unknown cache mode");
					return Qfalse;
					break;
			}
			
			break;
			
		case UDM_PARAM_TRACK_MODE: 
			switch (vali){
				case UDM_ENABLED:
#if UDM_VERSION_ID < 30200
					Agent->track_mode|=UDM_TRACK_QUERIES;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"TrackQuery","yes");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"TrackQuery","yes",UDM_VARSRC_GLOBAL);
#endif
					break;
					
				case UDM_DISABLED:
#if UDM_VERSION_ID < 30200
					Agent->track_mode &= ~(UDM_TRACK_QUERIES);    
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"TrackQuery","no");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"TrackQuery","no",UDM_VARSRC_GLOBAL);
#endif
					break;
					
				default:
#if UDM_VERSION_ID < 30200
					Agent->track_mode &= ~(UDM_TRACK_QUERIES);
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"TrackQuery","no");
#else
					UdmReplaceStrVar(Agent->Conf->vars,"TrackQuery","no",UDM_VARSRC_GLOBAL);
#endif
					rb_warning("Udm_Set_Agent_Param: Unknown track mode");
					return Qfalse;
					break;
			}
			
			break;
		
		case UDM_PARAM_PHRASE_MODE: 
#if UDM_VERSION_ID < 30200
			switch (vali){
				case UDM_ENABLED:
					Agent->Conf->use_phrases=UDM_ENABLED;
					break;
					
				case UDM_DISABLED:
					Agent->Conf->use_phrases=UDM_DISABLED;
					break;
					
				default:
					rb_warning("Udm_Set_Agent_Param: Unknown phrase mode");
					return Qfalse;
					break;
			}
#endif			
			break;
			
		case UDM_PARAM_ISPELL_PREFIXES: 
			switch (vali){
				case UDM_ENABLED:
#if UDM_VERSION_ID < 30200
					Agent->Conf->ispell_mode |= UDM_ISPELL_USE_PREFIXES;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"IspellUsePrefixes","1");
#else
					UdmAddIntVar(Agent->Conf->vars, "IspellUsePrefixes", 1, UDM_VARSRC_GLOBAL);
#endif
					break;
					
				case UDM_DISABLED:
#if UDM_VERSION_ID < 30200
					Agent->Conf->ispell_mode &= ~UDM_ISPELL_USE_PREFIXES;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"IspellUsePrefixes","0");
#else
					UdmAddIntVar(Agent->Conf->vars, "IspellUsePrefixes", 0, UDM_VARSRC_GLOBAL);
#endif
					break;
				
				default:
#if UDM_VERSION_ID < 30200
					Agent->Conf->ispell_mode |= UDM_ISPELL_USE_PREFIXES;
#elif UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"IspellUsePrefixes","0");
#else
					UdmAddIntVar(Agent->Conf->vars, "IspellUsePrefixes", 1, UDM_VARSRC_GLOBAL);
#endif
					rb_warn("Udm_Set_Agent_Param: Unknown ispell prefixes mode");
					return Qfalse;
					break;
			}
			
			break;

		case UDM_PARAM_CHARSET:
		case UDM_PARAM_LOCAL_CHARSET:
#if UDM_VERSION_ID < 30200
			Agent->Conf->local_charset=UdmGetCharset(vals);
			Agent->charset=Agent->Conf->local_charset;
			charset = strdup(UdmCharsetStr(Agent->Conf->local_charset));
#elif UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"LocalCharset",vals);
			{
			    const char * newcharset=UdmVarListFindStr(&Agent->Conf->Vars,"LocalCharset","iso-8859-1");
			    Agent->Conf->lcs=UdmGetCharSet(newcharset);
			    charset = strdup(newcharset);
			}
			//Agent->Conf->lcs=UdmGetCharSet(vals);
			//charset = strdup(vals);
#else
			Agent->Conf->local_charset=strdup(vals);
			charset = strdup(Agent->Conf->local_charset);
			UdmReplaceStrVar(Agent->Conf->vars,"LocalCharset",vals,UDM_VARSRC_GLOBAL);
#endif

			break;
			
#if UDM_VERSION_ID >= 30200
		case UDM_PARAM_BROWSER_CHARSET:
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"BrowserCharset",vals);
			{
			    const char * charset=UdmVarListFindStr(&Agent->Conf->Vars,"BrowserCharset","iso-8859-1");
			    Agent->Conf->bcs=UdmGetCharSet(charset);
			}
	    		//Agent->Conf->bcs=UdmGetCharSet(vals);
#else
			Agent->Conf->browser_charset=strdup(vals);
			UdmReplaceStrVar(Agent->Conf->vars,"BrowserCharset",vals,UDM_VARSRC_GLOBAL);
#endif
			break;

		case UDM_PARAM_HLBEG:
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"HlBeg",vals);
#else
			UdmReplaceStrVar(Agent->Conf->vars,"HlBeg",vals,UDM_VARSRC_GLOBAL);
#endif
			break;

		case UDM_PARAM_HLEND:
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"HlEnd",vals);
#else
			UdmReplaceStrVar(Agent->Conf->vars,"HlEnd",vals,UDM_VARSRC_GLOBAL);
#endif			
			break;
			
		case UDM_PARAM_SYNONYM:
			if (UdmSynonymListLoad(Agent->Conf,vals)) {
				rb_warn("Udm_Set_Agent_Param(): %s",Agent->Conf->errstr);
				return Qfalse;
#if UDM_VERSION_ID >= 30204
			} else UdmSynonymListSort(&(Agent->Conf->Synonyms));
#else
			} else UdmSynonymListSort(&(Agent->Conf->SynList));
#endif			
			break;
			
		case UDM_PARAM_SEARCHD:
#if UDM_VERSION_ID <= 30203
			UdmSDCLientListAdd(&(Agent->Conf->sdcl),vals);
			{
				size_t i;
				for(i=0;i<Agent->Conf->sdcl.nclients;i++){
					UdmSDCLientListAdd(&Agent->sdcl,Agent->Conf->sdcl.Clients[i].addr);
				}
			}
#endif			
			break;

		case UDM_PARAM_QSTRING:
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"QUERY_STRING",vals);
#else
			UdmReplaceStrVar(Agent->Conf->vars,"QUERY_STRING",vals,UDM_VARSRC_GLOBAL);
#endif			
			break;


		case UDM_PARAM_REMOTE_ADDR:
#if UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"IP",vals);
#else
			UdmReplaceStrVar(Agent->Conf->vars,"IP",vals,UDM_VARSRC_GLOBAL);
#endif			
			break;			
#endif
		case UDM_PARAM_STOPTABLE:
#if UDM_VERSION_ID < 30200
			strcat(Agent->Conf->stop_tables," ");
			strcat(Agent->Conf->stop_tables,vals);
#endif
			break;

		case UDM_PARAM_STOPFILE: 
#if UDM_VERSION_ID >= 30204
			if (UdmStopListLoad(Agent->Conf,vals)) {
#else
			if (UdmFileLoadStopList(Agent->Conf,vals)) {
#endif
				rb_warn(Agent->Conf->errstr);
				return Qfalse;
			}
			    
			break;
			
		case UDM_PARAM_WEIGHT_FACTOR: 
#if UDM_VERSION_ID < 30200
			Agent->weight_factor=strdup(vals);
#elif UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"wf",vals);
#else
			UdmReplaceStrVar(Agent->Conf->vars,"wf",vals,UDM_VARSRC_GLOBAL);
			{
				size_t len;
	
		                len=strlen(vals);
				if((len>0)&&(len<256)){
				    const char *sec;
				    int sn;
				
				    for(sn=0;sn<256;sn++){
					    Agent->wf[sn]=0;
				    }
				
				    for(sec=vals+len-1;sec>=vals;sec--){
					    Agent->wf[len-(sec-vals)]=UdmHex2Int(*sec);
	                	    }
        			}                                                          			
			}
#endif			    
			break;
			
		case UDM_PARAM_MIN_WORD_LEN: 
#if UDM_VERSION_ID >= 30204
			Agent->Conf->WordParam.min_word_len=vali;
#else
			Agent->Conf->min_word_len=vali;
#endif
			break;
			
		case UDM_PARAM_MAX_WORD_LEN: 
#if UDM_VERSION_ID >= 30204
			Agent->Conf->WordParam.max_word_len=vali;
#else
			Agent->Conf->max_word_len=vali;
#endif
			break;
			
		case UDM_PARAM_CROSS_WORDS: 
			switch (vali){
				case UDM_ENABLED:
#if UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"CrossWords","yes");
#else
					Agent->Conf->use_crossword=UDM_ENABLED;
#endif
					break;
					
				case UDM_DISABLED:
#if UDM_VERSION_ID >= 30204
					UdmVarListReplaceStr(&Agent->Conf->Vars,"CrossWords","no");
#else
					Agent->Conf->use_crossword=UDM_DISABLED;
#endif
					break;
					
				default:
					rb_warn("Udm_Set_Agent_Param: Unknown crosswords mode");
					return Qfalse;
					break;
			}
			
			break;
#if UDM_VERSION_ID > 30112


#if UDM_VERSION_ID > 30112

		case UDM_PARAM_VARDIR:
   #if UDM_VERSION_ID < 30200
			udm_snprintf(Agent->Conf->vardir,sizeof(Agent->Conf->vardir)-1,"%s%s",vals,UDMSLASHSTR);
   #elif UDM_VERSION_ID >= 30208
			UdmVarListReplaceStr(&Agent->Conf->Vars,"Vardir",vals);
   #elif UDM_VERSION_ID >= 30204
			UdmVarListReplaceStr(&Agent->Conf->Vars,"Vardir",vals);
			snprintf(Agent->Conf->vardir,sizeof(Agent->Conf->vardir)-1,"%s%s",vals,UDMSLASHSTR);
   #else
			snprintf(Agent->Conf->vardir,sizeof(Agent->Conf->vardir)-1,"%s%s",vals,UDMSLASHSTR);
   #endif
			break;

#endif

#if UDM_VERSION_ID >= 30204
		case UDM_PARAM_QUERY:
			UdmVarListReplaceStr(&Agent->Conf->Vars,"q",vals);

			break;

		case UDM_PARAM_STORED:
			UdmVarListReplaceStr(&Agent->Conf->Vars,"StoredAddr",vals);

			break;
#endif

#endif

#if UDM_VERSION_ID >= 30207
		case UDM_PARAM_GROUPBYSITE:
			switch (vali){
				case UDM_ENABLED:
					UdmVarListReplaceStr(&Agent->Conf->Vars,"GroupBySite","yes");
					break;
					
				case UDM_DISABLED:
					UdmVarListReplaceStr(&Agent->Conf->Vars,"GroupBySite","no");

					break;
					
				default:
					rb_warn("Udm_Set_Agent_Param(): Unknown groupbysite mode");
					return Qfalse;
					break;
			}
			
			break;
			
		case UDM_PARAM_SITEID: 
			UdmVarListReplaceStr(&Agent->Conf->Vars,"site",vals);
		
			break;
 
		case UDM_PARAM_DETECT_CLONES: 
			switch (vali){
				case UDM_ENABLED:
					UdmVarListReplaceStr(&Agent->Conf->Vars,"DetectClones","yes");
					
					break;
					
				case UDM_DISABLED:
					UdmVarListReplaceStr(&Agent->Conf->Vars,"DetectClones","no");

					break;
					
				default:
					rb_warn("Udm_Set_Agent_Param(): Unknown clones mode");
					return Qfalse;
					break;
			}
			
			break;
#endif
		default:
			rb_warn("Udm_Set_Agent_Param: Unknown agent session parameter");
			return Qfalse;
			break;
	}
	return Qtrue;
}
/* }}} */


/* {{{ proto int udm_load_ispell_data(int agent, int var, string val1, string val2, int flag, [string charset])
   Load ispell data */
static VALUE udm_load_ispell_data(int argc, VALUE *argv, VALUE obj)
{
	VALUE yyvar, yyval1, yyval2, yyflag, yycharset;
	char *val1, *val2, *charset;
	int var, flag;
	UDM_AGENT * Agent = GetHandler(obj);

        rb_scan_args(argc, argv, "41", &yyvar, &yyval1, &yyval2, &yyflag, &yycharset);
	if (yycharset == Qnil)
	    charset = "us-ascii";
	else
	    charset = NILorSTRING(yycharset);
	var  = NILorINT(yyvar);
	flag = NILorINT(yyflag);
	val1 = NILorSTRING(yyval1);
	val2 = NILorSTRING(yyval2);

	switch(var){
#if UDM_VERSION_ID < 30200		
		case UDM_ISPELL_TYPE_DB: 
			Agent->Conf->ispell_mode |= UDM_ISPELL_MODE_DB;
			
			if (UdmDBImportAffixes(Agent,Agent->charset) || 
			    UdmImportDictionaryFromDB(Agent)) {
				return Qfalse;
			} 
									
			break;
			
#if UDM_VERSION_ID > 30111

		case UDM_ISPELL_TYPE_SERVER:
			Agent->Conf->ispell_mode &= ~UDM_ISPELL_MODE_DB;
			Agent->Conf->ispell_mode |=  UDM_ISPELL_MODE_SERVER;
			
			Agent->Conf->spellhost = strdup(val1);
		
			break;
			
#endif			
#endif

		case UDM_ISPELL_TYPE_AFFIX: 
#if UDM_VERSION_ID < 30200		
			Agent->Conf->ispell_mode &= ~UDM_ISPELL_MODE_DB;

#if UDM_VERSION_ID > 30111
			Agent->Conf->ispell_mode &= ~UDM_ISPELL_MODE_SERVER;
#endif
			
			if (UdmImportAffixes(Agent->Conf,val1,val2,NULL,0)) {
				rb_warn("Udm_Load_Ispell_Data(): Cannot load affix file %s", val2);
				return Qfalse;
			}
#else
			if (UdmImportAffixes(Agent->Conf,val1,charset,val2)) {
				rb_warn("Udm_Load_Ispell_Data(): Cannot load affix file %s", val2);
				return Qfalse;
			}
    
#endif
			break;
			
		case UDM_ISPELL_TYPE_SPELL: 
#if UDM_VERSION_ID < 30200				
			Agent->Conf->ispell_mode &= ~UDM_ISPELL_MODE_DB;
			
#if UDM_VERSION_ID > 30111
			Agent->Conf->ispell_mode &= ~UDM_ISPELL_MODE_SERVER;
#endif
			
			if (UdmImportDictionary(Agent->Conf,val1,val2,1,"")) {
				rb_warn("Udm_Load_Ispell_Data(): Cannot load spell file %s", val2);
				return Qfalse;
			}
#else
			if (UdmImportDictionary(Agent->Conf,val1,charset,val2,0,"")) {
				rb_warn("Udm_Load_Ispell_Data(): Cannot load spell file %s", val2);
				return Qfalse;
			}
#endif
			break;


		default:
			rb_warn("Udm_Load_Ispell_Data(): Unknown ispell type parameter");
			return Qfalse;
			break;
	}
	
	if (flag) {
#if UDM_VERSION_ID >= 30204
		if(Agent->Conf->Spells.nspell) {
			UdmSortDictionary(&Agent->Conf->Spells);
		  	UdmSortAffixes(&Agent->Conf->Affixes,&Agent->Conf->Spells);
		}
#else
		if(Agent->Conf->nspell) {
			UdmSortDictionary(Agent->Conf);
		  	UdmSortAffixes(Agent->Conf);
		}
#endif
	}
	
	return Qtrue;
}
/* }}} */

/* {{{ proto int udm_free_ispell_data(int agent)
   Free memory allocated for ispell data */
static VALUE udm_free_ispell_data(VALUE obj)
{
	UDM_AGENT * Agent = GetHandler(obj);

#if UDM_VERSION_ID >= 30204
	UdmSpellListFree(&Agent->Conf->Spells);
	UdmAffixListFree(&Agent->Conf->Affixes);
#elif UDM_VERSION_ID > 30111
	UdmFreeIspell(Agent->Conf);
#endif
	
	return Qtrue;
}
/* }}} */

/* {{{ proto int udm_add_search_limit(int agent, int var, string val)
   Add mnoGoSearch search restrictions */
static VALUE udm_add_search_limit(int argc, VALUE* argv, VALUE obj)
{
	VALUE vr, vl;
	char *val;
	int var;
	UDM_AGENT * Agent = GetHandler(obj);

	rb_scan_args(argc,argv,"20",&vr,&vl);
	var = NILorINT(vr);
	if (TYPE(vl) == T_FIXNUM)
	    asprintf(&val,"%d",NILorINT(vl));
	else if (TYPE(vl) == T_STRING)
	    val = NILorSTRING(vl);
	else {
	    rb_warn("Params must be FixNum or String");
	    return Qfalse;
	}
	switch(var){
		case UDM_LIMIT_URL: 
#if UDM_VERSION_ID >= 30204
			UdmVarListAddStr(&Agent->Conf->Vars,"ul",val);
#else
			UdmAddURLLimit(Agent->Conf,val);
#endif
			break;
			
		case UDM_LIMIT_TAG:
#if UDM_VERSION_ID >= 30204
			UdmVarListAddStr(&Agent->Conf->Vars,"t",val);
#else
			UdmAddTagLimit(Agent->Conf,val);
#endif
			break;

		case UDM_LIMIT_LANG: 
#if UDM_VERSION_ID >= 30204
			UdmVarListAddStr(&Agent->Conf->Vars,"lang",val);
#else
			UdmAddLangLimit(Agent->Conf,val);
#endif
			break;

		case UDM_LIMIT_CAT: 
#if UDM_VERSION_ID >= 30204
			UdmVarListAddStr(&Agent->Conf->Vars,"cat",val);
#else
			UdmAddCatLimit(Agent->Conf,val);
#endif
			break;

		case UDM_LIMIT_TYPE:
#if UDM_VERSION_ID >= 30207
			UdmVarListAddStr(&Agent->Conf->Vars,"type",val);
#endif			
			break;

		case UDM_LIMIT_DATE:
#if UDM_VERSION_ID < 30200
			{
			struct udm_stl_info_t stl_info = { 0, 0, 0 };
			char *edate;

			if (val[0] == '>') {
				stl_info.type=1;
			} else if (val[0] == '<') {
				stl_info.type=-1;
			} else if((val[0]=='#')&&(edate=strchr(val,','))){
				stl_info.type=2;
				stl_info.t2=(time_t)(atol(edate+1));
			} else {
				rb_warn("Udm_Add_Search_Limit: Incorrect date limit format");
				return Qfalse;
			}
			
			stl_info.t1=(time_t)(atol(val+1));
			UdmAddTimeLimit(Agent->Conf,&stl_info);
			}
#endif
			break;
		default:
			rb_warn("Udm_Add_Search_Limit: Unknown search limit parameter");
			return Qfalse;
			break;
	}
	return Qtrue;
}
/* }}} */


/* {{{ proto int udm_clear_search_limits(int agent)
   Clear all mnoGoSearch search restrictions */

static VALUE udm_clear_search_limits(VALUE obj)
{
	UDM_AGENT * Agent = GetHandler(obj);
#if UDM_VERSION_ID >= 30204	
	int i;

	for(i=0;i<Agent->Conf->Vars.nvars;i++){
		if ((!strcasecmp("ul",Agent->Conf->Vars.Var[i].name))||
		    (!strcasecmp("cat",Agent->Conf->Vars.Var[i].name))||
		    (!strcasecmp("t",Agent->Conf->Vars.Var[i].name))||
		    (!strcasecmp("lang",Agent->Conf->Vars.Var[i].name))) {
		    UDM_FREE(Agent->Conf->Vars.Var[i].name);
		    UDM_FREE(Agent->Conf->Vars.Var[i].val);
		    Agent->Conf->Vars.nvars--;
		}
	}
#else
	UdmClearLimits(Agent->Conf);
#endif
	return Qtrue;
}
/* }}} */

#if UDM_VERSION_ID >= 30200
/* {{{ proto int udm_check_charset(int agent, string charset)
   Check if the given charset is known to mnogosearch */
static VALUE udm_check_charset(VALUE obj, VALUE charset)
{
	if (UdmGetCharSet(NILorSTRING(charset))) {
	    return Qtrue;
	} else {
	    return Qfalse;
	}	
}
/* }}} */
#endif

#if UDM_VERSION_ID >= 30203
/* {{{ proto int udm_crc32(int agent, string str)
   Return CRC32 checksum of gived string */
static VALUE udm_crc32(VALUE obj, VALUE vstr)
{
	char *str;
	int crc32;

	str = NILorSTRING(vstr);

	crc32=UdmCRC32((str),strlen(str));
	//snprintf(buf,sizeof(buf)-1,"%u",crc32);
	
	return INT2NUM(crc32);
}
/* }}} */
#endif

#if UDM_VERSION_ID == 30203

static ssize_t UdmRecvAll(int s, void *buf, size_t len, int flags) {
  size_t received = 0, r;
  char *b = buf;
  while ( (received < len) && ((r = recv(s, &b[received], len - received, flags)) >= 0 ) ) {
    received += r;
  }
  return received;
}


/* {{{ proto int udm_open_stored(int agent, string storedaddr)
   Open connection to stored  */
static VALUE udm_open_stored(VALUE obj, VALUE storedaddr)
{
	int s;
	char * addr;
	const char *hello = "F\0";
	
	UDM_AGENT * Agent = GetHandler(obj);

	addr = NILorSTRING(storedaddr);

	UdmReplaceStrVar(Agent->Conf->vars,"StoredAddr",addr,UDM_VARSRC_GLOBAL);
	Agent->Conf->stored_addr = strdup(UdmFindStrVar(Agent->Conf->vars, "StoredAddr", "localhost"));
	
	s = UdmStoreOpen(Agent->Conf);
	
	if (s >= 0) {
	    send(s, hello, 1, 0);
	    return INT2NUM(s);
	} else return Qfalse;
}
/* }}} */

/* {{{ proto int udm_close_stored(int agent, int link)
   Open connection to stored  */
static VALUE udm_close_stored(VALUE obj, VALUE link)
{
	int s;
	unsigned int rec_id = 0;
	
	UDM_AGENT * Agent = GetHandler(obj);

	s = NILorINT(link);

	send(s, &rec_id, sizeof(rec_id), 0);
	closesocket(s);
	
	return Qtrue;
}
/* }}} */

/* {{{ proto int udm_check_stored(int agent, int link, string doc_id)
   Open connection to stored  */
static VALUE udm_check_stored(int argc, VALUE* argv, VALUE obj)
{
	int s;
	unsigned int rec_id = 0;
	int store_found;
	VALUE link, doc_id;
	
	UDM_AGENT * Agent = GetHandler(obj);

	rb_scan_args(argc,argv,"20",&link,&doc_id);
	s = NILorINT(link);
	
	rec_id=NILorINT(doc_id);

	send(s, &rec_id, sizeof(rec_id), 0);
	if (UdmRecvAll(s, &store_found, sizeof(store_found), MSG_WAITALL) < 0) {
	    return INT2NUM(store_found);
	} else return Qfalse;
}
/* }}} */
#endif

#if UDM_VERSION_ID >= 30204
/* {{{ proto int udm_parse_query_string(int agent, string str)
   Parses query string, initialises variables and search limits taken from it */
static VALUE udm_parse_query_string(VALUE obj, VALUE vstr)
{
	char *str;
	UDM_AGENT * Agent = GetHandler(obj);

	str = NILorSTRING(vstr);

	UdmParseQueryString(Agent,&Agent->Conf->Vars,str);
	return Qtrue;
}
/* }}} */

/* {{{ proto int udm_make_excerpt(int agent, int res, int row)
   Perform search */
static VALUE udm_make_excerpt(int argc, VALUE* argv, VALUE obj)
{
	VALUE res,row_num;
	UDM_RESULT * Res;
	UDM_AGENT * Agent = GetHandler(obj);
	int row;

	rb_scan_args(argc,argv,"20",&res,&row_num);
	Res = GetRes(res);
	row = NILorINT(row_num);
	
	if(row<Res->num_rows){
		char		*al;
		char		*Excerpt;
		
		al = (char *)MyRemoveHiLightDup((const char *)(UdmVarListFindStr(&(Res->Doc[row].Sections), "URL", "")));
		UdmVarListReplaceInt(&(Res->Doc[row].Sections), "STORED_ID", UdmCRC32(al, strlen(al)));
		free(al);
		
		Excerpt = UdmExcerptDoc(Agent, Res, &(Res->Doc[row]), 256);
		
		if (Excerpt != NULL) {
			char *HlExcerpt = UdmHlConvert(&Res->WWList, Excerpt, Agent->Conf->lcs, Agent->Conf->bcs);
			UdmVarListReplaceInt(&(Res->Doc[row].Sections),"ST",1);
			UdmVarListReplaceStr(&(Res->Doc[row].Sections),"Body",HlExcerpt);
			UDM_FREE(HlExcerpt);
			UDM_FREE(Excerpt);
		} else {
		        UdmVarListReplaceInt(&(Res->Doc[row].Sections),"ST",0);
			return Qfalse;
		}
	}else{
		rb_warn("Udm_Make_Excerpt(): row number too large");
		return Qfalse;
	}
	
	return Qtrue;
}
/* }}} */
#endif

/* {{{ proto int udm_find(int agent, string query)
   Perform search */
static VALUE udm_find(VALUE obj, VALUE que)
{
	UDM_RESULT * Res;
	UDM_AGENT * Agent = GetHandler(obj);
	char *query;

	query = NILorSTRING(que);

/*
#if UDM_VERSION_ID < 30200
	if ((Res=UdmFind(Agent,UdmTolower(Z_STRVAL_PP(yyquery),Agent->charset)))) {
#else
#if UDM_VERSION_ID >= 30213
	if ((Res=UdmFind(Agent))) {
#else
	if ((Res=UdmFind(Agent,Z_STRVAL_PP(yyquery)))) {
#endif
#endif
*/

#if UDM_VERSION_ID < 30200
	if ((Res=UdmFind(Agent,UdmTolower(query,Agent->charset)))) {
#elif UDM_VERSION_ID >= 30213
	UdmVarListReplaceStr(&Agent->Conf->Vars,"q",query);
	if ((Res=UdmFind(Agent))) {
#else
	if ((Res=UdmFind(Agent,query))) {
#endif
	    return res2obj(obj,Res);
	}
        return Qfalse;
}
/* }}} */


static VALUE _udm_get_res_field(UDM_RESULT* Res, int row, int field)
{
	if(row<Res->num_rows){
		switch(field){
			case UDM_FIELD_URL:
#if UDM_VERSION_ID >= 30204
			    {
				char	*al;
				al = (char *)MyRemoveHiLightDup((const char *)(UdmVarListFindStr(&(Res->Doc[row].Sections), "URL", "")));
				//al = UdmVarListFindStr(&(Res->Doc[row].Sections), "URL", "");
				UdmVarListReplaceStr(&Res->Doc[row].Sections,"URL",al);
				free(al);

				return rb_tainted_str_new2((char *)UdmVarListFindStr(&Res->Doc[row].Sections,"URL",""));
			    }
#else
				return rb_tainted_str_new2((Res->Doc[row].url)?(Res->Doc[row].url):"");
#endif
				break;
				
			case UDM_FIELD_CONTENT:	
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Content-Type",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].content_type)?(Res->Doc[row].content_type):"");
#endif
				break;
				
			case UDM_FIELD_TITLE:
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Title",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].title)?(Res->Doc[row].title):"");
#endif
				break;
				
			case UDM_FIELD_KEYWORDS:
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Meta.Keywords",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].keywords)?(Res->Doc[row].keywords):"");
#endif
				break;
				
			case UDM_FIELD_DESC:		
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Meta.Description",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].description)?(Res->Doc[row].description):"");
#endif
				break;
				
			case UDM_FIELD_TEXT:
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Body",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].text)?(Res->Doc[row].text):"");
#endif
				break;
				
			case UDM_FIELD_SIZE:
#if UDM_VERSION_ID >= 30204
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"Content-Length",0));
#else
				return INT2NUM(Res->Doc[row].size);
#endif
				break;
				
			case UDM_FIELD_URLID:
#if UDM_VERSION_ID >= 30204
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"ID",0));
#else
				return INT2NUM(Res->Doc[row].url_id);
#endif
				break;
				
			case UDM_FIELD_RATING:
#if UDM_VERSION_ID >= 30204
//				return INT2NUM((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Score",""));
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"Score",0));
#else
				return INT2NUM(Res->Doc[row].rating);
#endif
				break;
				
			case UDM_FIELD_MODIFIED:
#if UDM_VERSION_ID >= 30204
				{
				    char *mod = (char *)UdmVarListFindInt(&(Res->Doc[row].Sections),"Last-Modified",0);
				    if (mod)
					return rb_tainted_str_new2(mod);
				    else
					return Qnil;
				}
#else
				return INT2NUM(Res->Doc[row].last_mod_time);
#endif
				break;

			case UDM_FIELD_ORDER:
#if UDM_VERSION_ID >= 30204
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"Order",0));
#else
				return INT2NUM(Res->Doc[row].order);
#endif
				break;
				
			case UDM_FIELD_CRC:
#if UDM_VERSION_ID >= 30204
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"crc32",0));
#else
				return INT2NUM(Res->Doc[row].crc32);
#endif
				break;

			case UDM_FIELD_CATEGORY:		
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Category",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].category)?(Res->Doc[row].category):"");
#endif
				break;

#if UDM_VERSION_ID >= 30203		
			case UDM_FIELD_LANG:
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Content-Language",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].lang)?(Res->Doc[row].lang):"");
#endif
				break;

			case UDM_FIELD_CHARSET:
#if UDM_VERSION_ID >= 30204
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Charset",""));
#else
				return rb_tainted_str_new2((Res->Doc[row].charset)?(Res->Doc[row].charset):"");
#endif
				break;
#endif

#if UDM_VERSION_ID >= 30207
			case UDM_FIELD_SITEID:
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"Site_id",0));

				break;

			case UDM_FIELD_POP_RANK:
				return rb_tainted_str_new2((char *)UdmVarListFindStr(&(Res->Doc[row].Sections),"Pop_Rank",""));

				break;

			case UDM_FIELD_ORIGINID:
				return INT2NUM(UdmVarListFindInt(&(Res->Doc[row].Sections),"Origin-Id",0));

				break;
#endif

			default: 
				rb_warn("Udm_Get_Res_Field: Unknown mnoGoSearch field name %d\n",field);
				return Qfalse;
				break;
		}
	}
	rb_warn("Udm_Get_Res_Field: row number too large");
	return Qfalse;
}
/* }}} */

/* {{{ proto string udm_get_res_field(int res, int row, int field)
   Fetch mnoGoSearch result field */

static VALUE udm_get_res_field(int argc, VALUE* argv, VALUE obj)
{
	VALUE r, f;
	UDM_RESULT * Res = GetRes(obj);
	int row,field;

	rb_scan_args(argc,argv,"20",&r,&f);
	row = NILorINT(r);
	field = NILorINT(f);
	return _udm_get_res_field(Res, row, field);
}

static VALUE each(VALUE obj)
{
    UDM_RESULT *res = GetRes(obj);
    VALUE row;
    int i;

    for (i=0; i<res->num_rows; i++) {
	row = rb_obj_alloc(cMnogoField);
	rb_iv_set(row, "urlid", _udm_get_res_field(res, i, UDM_FIELD_URLID));
	rb_iv_set(row, "url", _udm_get_res_field(res, i, UDM_FIELD_URL));
	rb_iv_set(row, "content", _udm_get_res_field(res, i, UDM_FIELD_CONTENT));
	rb_iv_set(row, "title", _udm_get_res_field(res, i, UDM_FIELD_TITLE));
	rb_iv_set(row, "keywords", _udm_get_res_field(res, i, UDM_FIELD_KEYWORDS));
	rb_iv_set(row, "desc", _udm_get_res_field(res, i, UDM_FIELD_DESC));
	rb_iv_set(row, "text", _udm_get_res_field(res, i, UDM_FIELD_TEXT));
	rb_iv_set(row, "size", _udm_get_res_field(res, i, UDM_FIELD_SIZE));
	rb_iv_set(row, "rating", _udm_get_res_field(res, i, UDM_FIELD_RATING));
	rb_iv_set(row, "modified", _udm_get_res_field(res, i, UDM_FIELD_MODIFIED));
	rb_iv_set(row, "order", _udm_get_res_field(res, i, UDM_FIELD_ORDER));
	rb_iv_set(row, "crc", _udm_get_res_field(res, i, UDM_FIELD_CRC));
	rb_iv_set(row, "category", _udm_get_res_field(res, i, UDM_FIELD_CATEGORY));
#if UDM_VERSION_ID >= 30203
	rb_iv_set(row, "lang", _udm_get_res_field(res, i, UDM_FIELD_LANG));
	rb_iv_set(row, "charset", _udm_get_res_field(res, i, UDM_FIELD_CHARSET));
#endif
	rb_yield(row);
    }
    return obj;
}

static VALUE each2(VALUE obj, VALUE fld)
{
    UDM_RESULT *res = GetRes(obj);
    int i,field;

    field = NILorINT(fld);
    for (i=0; i<res->num_rows; i++) {
	rb_yield(_udm_get_res_field(res, i, field));
    }
    return obj;
}

/* {{{ proto string udm_get_res_param(int res, int param)
   Get mnoGoSearch result parameters */

static VALUE _udm_get_res_param(UDM_RESULT *Res, int param)
{
	switch(param){
		case UDM_PARAM_NUM_ROWS:
			return INT2NUM(Res->num_rows);
			break;
		
		case UDM_PARAM_FOUND:
			return INT2NUM(Res->total_found);
			break;
		
		case UDM_PARAM_WORDINFO: 	
#if UDM_VERSION_ID >= 30204
			{
			    int len,i;
			    for(len = i = 0; i < Res->WWList.nwords; i++) 
				len += Res->WWList.Word[i].len;
			    {	
				size_t wsize=(1+len*15)*sizeof(char);
				char *wordinfo = (char*) malloc(wsize);
				//int corder = -1, ccount = 0;
	  
				*wordinfo = '\0';
	  
				for(i = 0; i < Res->WWList.nwords; i++){
				    if ((Res->WWList.Word[i].count > 0) || (Res->WWList.Word[i].origin == UDM_WORD_ORIGIN_QUERY)) {
				    if(wordinfo[0]) strcat(wordinfo,", ");
					sprintf(UDM_STREND(wordinfo)," %s : %d", Res->WWList.Word[i].word, Res->WWList.Word[i].count);
				    } else if (Res->WWList.Word[i].origin == UDM_WORD_ORIGIN_STOP) {
					if(wordinfo[0]) strcat(wordinfo,", ");
					sprintf(UDM_STREND(wordinfo)," %s : stopword", Res->WWList.Word[i].word);
				    }
				}
				return rb_tainted_str_new2(wordinfo);
			    }
			}
#else
			return rb_tainted_str_new2((Res->wordinfo)?(Res->wordinfo):"");
#endif
			break;

#if UDM_VERSION_ID >= 30204
		case UDM_PARAM_WORDINFO_ALL: 
			{
			    int len,i;
			    for(len = i = 0; i < Res->WWList.nwords; i++) 
				len += Res->WWList.Word[i].len;
			    {	
				size_t wsize=(1+len*15)*sizeof(char);
				char *wordinfo = (char*) malloc(wsize);
				int corder = -1, ccount = 0;
	  
				*wordinfo = '\0';
				
				for(i = 0; i < Res->WWList.nwords; i++){
				    if (Res->WWList.Word[i].order != corder) {
					if(wordinfo[0]) {
					    sprintf(UDM_STREND(wordinfo)," / %d, ", ccount);
					}
					ccount = Res->WWList.Word[i].count;
					if (Res->WWList.Word[i].origin == UDM_WORD_ORIGIN_STOP) {
					    sprintf(UDM_STREND(wordinfo)," %s : stopword", Res->WWList.Word[i].word);
					} else {
					    sprintf(UDM_STREND(wordinfo)," %s : %d", Res->WWList.Word[i].word, Res->WWList.Word[i].count);
					    corder = Res->WWList.Word[i].order; 
					}
				    } else {
					ccount += Res->WWList.Word[i].count;
				    }
				}
				if (Res->WWList.nwords) {
				    sprintf(UDM_STREND(wordinfo)," / %d", ccount);
				}
				return rb_tainted_str_new2(wordinfo);
			    }
			}
			break;
#endif
			
		case UDM_PARAM_SEARCHTIME:
			return rb_float_new(((double)Res->work_time)/1000);
			break;		
			
		case UDM_PARAM_FIRST_DOC:	 	
			return INT2NUM(Res->first);
			break;

		case UDM_PARAM_LAST_DOC:	 	
			return INT2NUM(Res->last);
			break;

		default:
			rb_warn("Udm_Get_Res_Param: Unknown mnoGoSearch param name");
			return Qfalse;
			break;
	}
	return Qfalse;
}
/* }}} */

static VALUE udm_get_res_param(VALUE obj,VALUE par)
{
    int param = NILorINT(par);
    UDM_RESULT *Res = GetRes(obj);
    return _udm_get_res_param(Res, param);
}

static VALUE xml(VALUE obj)
{
    UDM_RESULT *res = GetRes(obj);
    VALUE row = rb_str_new2("");
    int i;

    rb_str_cat(row, "<?xml version=\"1.0\" encoding=\"",30);
    rb_str_append(row, rb_str_new2(charset));
    //rb_str_cat2(row, "\">\n<?xml-stylesheet href=\"http://localhost/a/test.xsl\" type=\"text/xsl\"?>\n<mnogosearch>\n");
    rb_str_cat(row, "\"?>\n<mnogosearch>\n",18);
    rb_str_cat2(row, "<rows>");
    rb_str_append(row, rb_String(_udm_get_res_param(res,UDM_PARAM_NUM_ROWS)));
    rb_str_cat2(row, "</rows>\n<found>");
    rb_str_append(row, rb_String(_udm_get_res_param(res,UDM_PARAM_FOUND)));
    rb_str_cat2(row, "</found>\n<wordinfo>");
    rb_str_append(row, _udm_get_res_param(res,UDM_PARAM_WORDINFO));
    rb_str_cat2(row, "</wordinfo>\n<time>");
    rb_str_append(row, rb_String(_udm_get_res_param(res,UDM_PARAM_SEARCHTIME)));
    rb_str_cat2(row, "</time>\n<first>");
    rb_str_append(row, rb_String(_udm_get_res_param(res,UDM_PARAM_FIRST_DOC)));
    rb_str_cat2(row, "</first>\n<last>");
    rb_str_append(row, rb_String(_udm_get_res_param(res,UDM_PARAM_LAST_DOC)));
    rb_str_cat2(row, "</last>\n");
    for (i=0; i<res->num_rows; i++) {
// FIXME: use rb_str_cat() instead rb_str_cat2()
	rb_str_cat2(row, "<res id=\"");
	rb_str_append(row, rb_String(_udm_get_res_field(res, i, UDM_FIELD_URLID)));
	rb_str_cat2(row, "\">\n\t<url>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_URL));
	rb_str_cat2(row, "</url>\n\t<content>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_CONTENT));
	rb_str_cat2(row, "</content>\n\t<title>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_TITLE));
	rb_str_cat2(row, "</title>\n\t<keywords>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_KEYWORDS));
	rb_str_cat2(row, "</keywords>\n\t<desc>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_DESC));
	rb_str_cat2(row, "</desc>\n\t<text>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_TEXT));
	rb_str_cat2(row, "</text>\n\t<size>");
	rb_str_append(row, rb_String(_udm_get_res_field(res, i, UDM_FIELD_SIZE)));
	rb_str_cat2(row, "</size>\n\t<rating>");
	rb_str_append(row, rb_String(_udm_get_res_field(res, i, UDM_FIELD_RATING)));
	rb_str_cat2(row, "</rating>\n\t<modified>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_MODIFIED));
	rb_str_cat2(row, "</modified>\n\t<order>");
	rb_str_append(row, rb_String(_udm_get_res_field(res, i, UDM_FIELD_ORDER)));
	rb_str_cat2(row, "</order>\n\t<crc>");
	rb_str_append(row, rb_String(_udm_get_res_field(res, i, UDM_FIELD_CRC)));
	rb_str_cat2(row, "</crc>\n\t<category>");
	//rb_str_cat2(row, "<category>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_CATEGORY));
	rb_str_cat2(row, "</category>\n");
#if UDM_VERSION_ID >= 30203
	rb_str_cat2(row, "\t<lang>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_LANG));
	rb_str_cat2(row, "</lang>\n\t<charset>");
	rb_str_append(row, _udm_get_res_field(res, i, UDM_FIELD_CHARSET));
	rb_str_cat2(row, "</charset>\n");
#endif
	rb_str_cat2(row, "</res>\n");
    }
    rb_str_cat2(row, "</mnogosearch>\n");
    return row;
}

/* {{{ proto int udm_errno(int agent)
   Get mnoGoSearch error number */

static VALUE udm_errno(VALUE obj)
{
	UDM_AGENT * Agent = GetHandler(obj);
#if UDM_VERSION_ID >= 30204
	return INT2NUM(UdmEnvErrCode(Agent->Conf));
#else
	return INT2NUM(UdmDBErrorCode(Agent->db));
#endif
}
/* }}} */


/* {{{ proto string udm_error(int agent)
   Get mnoGoSearch error message */

static VALUE udm_error(VALUE obj)
{
	UDM_AGENT *Agent = GetHandler(obj);
#if UDM_VERSION_ID >= 30204
	return rb_tainted_str_new2((UdmEnvErrMsg(Agent->Conf))?(UdmEnvErrMsg(Agent->Conf)):"");
#else
	return rb_tainted_str_new2((UdmDBErrorMsg(Agent->db))?(UdmDBErrorMsg(Agent->db)):"");
#endif
}
/* }}} */

/* {{{ proto int udm_api_version()
   Get mnoGoSearch API version */
static VALUE udm_api_version(VALUE obj)
{
	return INT2NUM(UDM_VERSION_ID);
}
/* }}} */

/* {{{ proto array udm_cat_list(int agent, string category)
   Get mnoGoSearch categories list with the same root */
static VALUE udm_cat_list(int argc, VALUE* argv, VALUE obj)
{
	VALUE category;
	VALUE ret, row;
	UDM_AGENT * Agent = GetAgent(obj);
	char *cat;
#if UDM_VERSION_ID >= 30204	
	UDM_CATEGORY C;
#else	
	UDM_CATEGORY *c=NULL;
#endif
	char *buf=NULL;

	rb_scan_args(argc,argv,"01",&category);
	cat = NILorSTRING(category);

#if UDM_VERSION_ID >= 30204
	bzero(&C,sizeof(C));
	if (cat)
	    strncpy(C.addr,cat,sizeof(C.addr)-1);
#if UDM_VERSION_ID <= 30210
   if(!UdmCatAction(Agent,&C,UDM_CAT_ACTION_LIST,Agent->Conf->db)){
#else
	if(UDM_OK == UdmCatAction(Agent,&C,UDM_CAT_ACTION_LIST)){
#endif
#else
	if((c=UdmCatList(Agent,cat))){
#endif
		ret = rb_ary_new();

		if (!(buf=calloc(1,UDMSTRSIZ+1))) {
			return Qnil;
		}
		
#if UDM_VERSION_ID >= 30204
		{
		    int i;
		    if (C.ncategories) {
			for(i=0;i<C.ncategories;i++){			
			    snprintf(buf, UDMSTRSIZ, "%s%s",C.Category[i].link[0]?"@ ":"", C.Category[i].name);
			    row = rb_obj_alloc(cMnogoCatAtom);
			    rb_iv_set(row, "link", rb_tainted_str_new2(C.Category[i].link[0]?C.Category[i].link:C.Category[i].path));
			    rb_iv_set(row, "name", rb_tainted_str_new2(buf));
			    rb_ary_push(ret, row);
			}
			free(C.Category);
		    } else {
			return Qnil;
		    }
		}
#else
		while(c->rec_id){			
			snprintf(buf, UDMSTRSIZ, "%s%s",c->link[0]?"@ ":"", c->name);
			row = rb_obj_alloc(cMnogoCatAtom);
			rb_iv_set(row, "link", rb_tainted_str_new2(c->link[0]?c->link:c->path));
			rb_iv_set(row, "name", rb_tainted_str_new2(buf));
			rb_ary_push(ret, row);
			c++;
		}
#endif		
		free(buf);
	} else {
		return Qnil;
	}
	return ret;
}
/* }}} */

/* {{{ proto array udm_cat_path(int agent, string category)
   Get mnoGoSearch categories path from the root to the given catgory */
static VALUE udm_cat_path(VALUE obj, VALUE category)
{
	VALUE ret, row;
	UDM_AGENT * Agent = GetAgent(obj);
	char *cat;
#if UDM_VERSION_ID >= 30204	
	UDM_CATEGORY C;
#else	
	UDM_CATEGORY *c=NULL;
#endif
	char *buf=NULL;

	cat = NILorSTRING(category);

#if UDM_VERSION_ID >= 30204
	bzero(&C,sizeof(C));
	strncpy(C.addr,cat,sizeof(C.addr)-1);
#if UDM_VERSION_ID <= 30210
	if(!UdmCatAction(Agent,&C,UDM_CAT_ACTION_PATH,Agent->Conf->db)){
#else
	if(UDM_OK == UdmCatAction(Agent,&C,UDM_CAT_ACTION_PATH)){
#endif
#else
	if((c=UdmCatPath(Agent,cat))){
#endif
		ret = rb_ary_new();

		if (!(buf=calloc(1,UDMSTRSIZ+1))) {
			return Qnil;
		}

#if UDM_VERSION_ID >= 30204
		{
		    int i;
		    if (C.ncategories) {
			for(i=0;i<C.ncategories;i++){
			    snprintf(buf, UDMSTRSIZ, "%s%s",C.Category[i].link[0]?"@ ":"", C.Category[i].name);
			    row = rb_obj_alloc(cMnogoCatAtom);
			    rb_iv_set(row, "link", rb_tainted_str_new2(C.Category[i].link[0]?C.Category[i].link:C.Category[i].path));
			    rb_iv_set(row, "name", rb_tainted_str_new2(buf));
			    rb_ary_push(ret, row);
			}
			free(C.Category);
		    } else {
			return Qnil;
		    }
		}
#else
		while(c->rec_id){			
			snprintf(buf, UDMSTRSIZ, "%s%s",c->link[0]?"@ ":"", c->name);				 
// FIXME:
			row = rb_obj_alloc(cMnogoCatAtom);
			rb_iv_set(row, "link", rb_tainted_str_new2(c->link[0]?c->link:c->path));
			rb_iv_set(row, "name", rb_tainted_str_new2(buf));
			rb_ary_push(ret, row);
//			rb_ary_push(ret, rb_tainted_str_new2(c->link[0]?c->link:c->path));
//			rb_ary_push(ret, rb_tainted_str_new2(buf));
			c++;
		}
#endif		
		free(buf);
	} else {
		return Qfalse;
	}
	return ret;
}
/* }}} */

#if UDM_VERSION_ID > 30110
/* {{{ proto int udm_get_doc_count(int agent)
   Get total number of documents in database */

static VALUE udm_get_doc_count(VALUE obj)
{
	UDM_AGENT * Agent = GetHandler(obj);
#if UDM_VERSION_ID >= 30204
#if UDM_VERSION_ID <= 30210
	if (!Agent->doccount) UdmURLAction(Agent,NULL,UDM_URL_ACTION_DOCCOUNT,Agent->Conf->db);
#else
	if (!Agent->doccount) UdmURLAction(Agent,NULL,UDM_URL_ACTION_DOCCOUNT);
#endif
	return INT2NUM(Agent->doccount);
#else
	return INT2NUM(UdmGetDocCount(Agent));
#endif
}
/* }}} */
#endif

static VALUE initialize(int argc, VALUE* argv, VALUE obj)
{
    return obj;
}

static VALUE udm_cat(VALUE obj)
{
    return rb_iv_get(obj, "cat");
}

#define DefineMnogoFieldMemberMethod(m)\
static VALUE field_##m(VALUE obj)\
{return rb_iv_get(obj, #m);}

DefineMnogoFieldMemberMethod(urlid)
DefineMnogoFieldMemberMethod(url)
DefineMnogoFieldMemberMethod(content)
DefineMnogoFieldMemberMethod(title)
DefineMnogoFieldMemberMethod(keywords)
DefineMnogoFieldMemberMethod(desc)
DefineMnogoFieldMemberMethod(text)
DefineMnogoFieldMemberMethod(size)
DefineMnogoFieldMemberMethod(rating)
DefineMnogoFieldMemberMethod(modified)
DefineMnogoFieldMemberMethod(order)
DefineMnogoFieldMemberMethod(crc)
DefineMnogoFieldMemberMethod(category)
#if UDM_VERSION_ID >= 30203
DefineMnogoFieldMemberMethod(lang)
DefineMnogoFieldMemberMethod(charset)
#endif
#if UDM_VERSION_ID >= 30207
DefineMnogoFieldMemberMethod(siteid)
#endif

DefineMnogoFieldMemberMethod(name)
DefineMnogoFieldMemberMethod(link)

void Init_mnogo(void)
{
    cMnogo = rb_define_class("Mnogo", rb_cObject);
    cMnogoCat = rb_define_class_under(cMnogo,"MnogoCat", rb_cObject);
    cMnogoCatAtom = rb_define_class_under(cMnogoCat,"MnogoCatAtom", rb_cObject);
    cMnogoRes = rb_define_class_under(cMnogo,"MnogoRes", rb_cObject);
    cMnogoField = rb_define_class_under(cMnogoRes,"MnogoField", rb_cObject);

    /* Class methods */
    rb_define_singleton_method(cMnogo, "new", udm_alloc_agent, -1);

    /* */
    rb_define_method(cMnogo, "set_param", udm_set_agent_param, -1);
    rb_define_method(cMnogo, "version", udm_api_version, 0);

    rb_define_method(cMnogo, "ispell_load", udm_load_ispell_data, -1);
    rb_define_method(cMnogo, "ispell_free", udm_free_ispell_data, 0);

    rb_define_method(cMnogo, "add_search_limit", udm_add_search_limit, -1);
    rb_define_method(cMnogo, "clear_search_limits", udm_clear_search_limits, 0);

    rb_define_method(cMnogo, "error", udm_error, 0);
    rb_define_method(cMnogo, "errno", udm_errno, 0);

    rb_define_method(cMnogo, "find", udm_find, 1);

    rb_define_method(cMnogo, "initialize", initialize, -1);

#if UDM_VERSION_ID > 30110
    rb_define_method(cMnogo, "doc_count", udm_get_doc_count, 0);
#endif

#if UDM_VERSION_ID >= 30200			
    rb_define_method(cMnogo, "check_charset", udm_check_charset, 1);
#if UDM_VERSION_ID == 30203
    rb_define_method(cMnogo, "open_stored", udm_open_stored, 1);
    rb_define_method(cMnogo, "check_stored", udm_check_stored, -1);
    rb_define_method(cMnogo, "close_stored", udm_close_stored, 1);
#endif
#if UDM_VERSION_ID >= 30203
    rb_define_method(cMnogo, "crc32", udm_crc32, 1);
#endif
#if UDM_VERSION_ID >= 30204
    rb_define_method(cMnogo, "parse_query_string", udm_parse_query_string, 1);
    rb_define_method(cMnogo, "make_excerpt", udm_make_excerpt, -1);
#endif
#endif

    /* Methods for Result class */
    rb_define_method(cMnogoRes, "getparam", udm_get_res_param, 1);
    rb_define_method(cMnogoRes, "getfield", udm_get_res_field, -1);
    rb_define_method(cMnogoRes, "xml", xml, 0);
    rb_define_method(cMnogoRes, "each", each, 0);
    rb_define_method(cMnogoRes, "eachp", each2, 1);
/*
    rb_define_method(cMnogoRes, "rows", field_rows, 0);
    rb_define_method(cMnogoRes, "found", field_found, 0);
    rb_define_method(cMnogoRes, "wordinfo", field_wordinfo, 0);
*/
    rb_define_method(cMnogo, "cat", udm_cat, 0);
    rb_define_method(cMnogoCat, "list", udm_cat_list, -1);
    rb_define_method(cMnogoCat, "path", udm_cat_path, 1);

    rb_define_method(cMnogoField, "urlid", field_urlid, 0);
    rb_define_method(cMnogoField, "url", field_url, 0);
    rb_define_method(cMnogoField, "content", field_content, 0);
    rb_define_method(cMnogoField, "title", field_title, 0);
    rb_define_method(cMnogoField, "keywords", field_keywords, 0);
    rb_define_method(cMnogoField, "desc", field_desc, 0);
    rb_define_method(cMnogoField, "text", field_text, 0);
    rb_define_method(cMnogoField, "size", field_size, 0);
    rb_define_method(cMnogoField, "rating", field_rating, 0);
    rb_define_method(cMnogoField, "modified", field_modified, 0);
    rb_define_method(cMnogoField, "order", field_order, 0);
    rb_define_method(cMnogoField, "crc", field_crc, 0);
    rb_define_method(cMnogoField, "category", field_category, 0);
#if UDM_VERSION_ID >= 30203
    rb_define_method(cMnogoField, "lang", field_lang, 0);
    rb_define_method(cMnogoField, "charset", field_charset, 0);
#endif
#if UDM_VERSION_ID >= 30207
    rb_define_method(cMnogoField, "siteid", field_siteid, 0);
#endif

    rb_define_method(cMnogoCatAtom, "name", field_name, 0);
    rb_define_method(cMnogoCatAtom, "link", field_link, 0);

    rb_define_const(cMnogo, "UDM_FIELD_URLID",	INT2NUM(UDM_FIELD_URLID));
    rb_define_const(cMnogo, "UDM_FIELD_URL",	INT2NUM(UDM_FIELD_URL));
    rb_define_const(cMnogo, "UDM_FIELD_CONTENT",	INT2NUM(UDM_FIELD_CONTENT));
    rb_define_const(cMnogo, "UDM_FIELD_TITLE",	INT2NUM(UDM_FIELD_TITLE));
    rb_define_const(cMnogo, "UDM_FIELD_KEYWORDS",	INT2NUM(UDM_FIELD_KEYWORDS));
    rb_define_const(cMnogo, "UDM_FIELD_DESC",	INT2NUM(UDM_FIELD_DESC));
    rb_define_const(cMnogo, "UDM_FIELD_DESCRIPTION",INT2NUM(UDM_FIELD_DESC));
    rb_define_const(cMnogo, "UDM_FIELD_TEXT",	INT2NUM(UDM_FIELD_TEXT));
    rb_define_const(cMnogo, "UDM_FIELD_SIZE",	INT2NUM(UDM_FIELD_SIZE));
    rb_define_const(cMnogo, "UDM_FIELD_RATING",	INT2NUM(UDM_FIELD_RATING));
    rb_define_const(cMnogo, "UDM_FIELD_SCORE",	INT2NUM(UDM_FIELD_RATING));
    rb_define_const(cMnogo, "UDM_FIELD_MODIFIED",	INT2NUM(UDM_FIELD_MODIFIED));
    rb_define_const(cMnogo, "UDM_FIELD_ORDER",	INT2NUM(UDM_FIELD_ORDER));
    rb_define_const(cMnogo, "UDM_FIELD_CRC",	INT2NUM(UDM_FIELD_CRC));
    rb_define_const(cMnogo, "UDM_FIELD_CATEGORY",	INT2NUM(UDM_FIELD_CATEGORY));
    rb_define_const(cMnogo, "UDM_FIELD_LANG",	INT2NUM(UDM_FIELD_LANG));
    rb_define_const(cMnogo, "UDM_FIELD_CHARSET",	INT2NUM(UDM_FIELD_CHARSET));
    rb_define_const(cMnogo, "UDM_FIELD_SITEID",	INT2NUM(UDM_FIELD_SITEID));
    rb_define_const(cMnogo, "UDM_FIELD_POP_RANK",INT2NUM(UDM_FIELD_POP_RANK));
    rb_define_const(cMnogo, "UDM_FIELD_ORIGINID",INT2NUM(UDM_FIELD_ORIGINID));

    /* udm_set_agent_param constants */
    rb_define_const(cMnogo, "UDM_PARAM_PAGE_SIZE",	INT2NUM(UDM_PARAM_PAGE_SIZE));
    rb_define_const(cMnogo, "UDM_PARAM_PAGE_NUM",	INT2NUM(UDM_PARAM_PAGE_NUM));

    rb_define_const(cMnogo, "UDM_PARAM_SEARCH_MODE",INT2NUM(UDM_PARAM_SEARCH_MODE));	
    rb_define_const(cMnogo, "UDM_PARAM_CACHE_MODE",	INT2NUM(UDM_PARAM_CACHE_MODE));	
    rb_define_const(cMnogo, "UDM_PARAM_TRACK_MODE",	INT2NUM(UDM_PARAM_TRACK_MODE));	
    rb_define_const(cMnogo, "UDM_PARAM_PHRASE_MODE",INT2NUM(UDM_PARAM_PHRASE_MODE));	
	
    rb_define_const(cMnogo, "UDM_PARAM_CHARSET",	INT2NUM(UDM_PARAM_CHARSET));	
    rb_define_const(cMnogo, "UDM_PARAM_LOCAL_CHARSET",	INT2NUM(UDM_PARAM_LOCAL_CHARSET));	
    rb_define_const(cMnogo, "UDM_PARAM_BROWSER_CHARSET",	INT2NUM(UDM_PARAM_BROWSER_CHARSET));	
	
    rb_define_const(cMnogo, "UDM_PARAM_STOPTABLE",	INT2NUM(UDM_PARAM_STOPTABLE));	
    rb_define_const(cMnogo, "UDM_PARAM_STOP_TABLE",	INT2NUM(UDM_PARAM_STOPTABLE));	
    rb_define_const(cMnogo, "UDM_PARAM_STOPFILE",	INT2NUM(UDM_PARAM_STOPFILE));	
    rb_define_const(cMnogo, "UDM_PARAM_STOP_FILE",	INT2NUM(UDM_PARAM_STOPFILE));	
	
    rb_define_const(cMnogo, "UDM_PARAM_WEIGHT_FACTOR",INT2NUM(UDM_PARAM_WEIGHT_FACTOR));
    rb_define_const(cMnogo, "UDM_PARAM_WORD_MATCH",  INT2NUM(UDM_PARAM_WORD_MATCH));	
	
    rb_define_const(cMnogo, "UDM_PARAM_MAX_WORD_LEN",INT2NUM(UDM_PARAM_MAX_WORD_LEN));	
    rb_define_const(cMnogo, "UDM_PARAM_MAX_WORDLEN", INT2NUM(UDM_PARAM_MAX_WORD_LEN));	
    rb_define_const(cMnogo, "UDM_PARAM_MIN_WORD_LEN",INT2NUM(UDM_PARAM_MIN_WORD_LEN));	
    rb_define_const(cMnogo, "UDM_PARAM_MIN_WORDLEN", INT2NUM(UDM_PARAM_MIN_WORD_LEN));	

    rb_define_const(cMnogo, "UDM_PARAM_ISPELL_PREFIXES",INT2NUM(UDM_PARAM_ISPELL_PREFIXES));	
    rb_define_const(cMnogo, "UDM_PARAM_ISPELL_PREFIX",INT2NUM(UDM_PARAM_ISPELL_PREFIXES));	
    rb_define_const(cMnogo, "UDM_PARAM_PREFIXES",	INT2NUM(UDM_PARAM_ISPELL_PREFIXES));	
    rb_define_const(cMnogo, "UDM_PARAM_PREFIX",	INT2NUM(UDM_PARAM_ISPELL_PREFIXES));

    rb_define_const(cMnogo, "UDM_PARAM_CROSS_WORDS",INT2NUM(UDM_PARAM_CROSS_WORDS));
    rb_define_const(cMnogo, "UDM_PARAM_CROSSWORDS",	INT2NUM(UDM_PARAM_CROSS_WORDS));	

    rb_define_const(cMnogo, "UDM_PARAM_VARDIR", INT2NUM(UDM_PARAM_VARDIR));
    rb_define_const(cMnogo, "UDM_PARAM_DATADIR", INT2NUM(UDM_PARAM_VARDIR));

    rb_define_const(cMnogo, "UDM_PARAM_HLBEG", INT2NUM(UDM_PARAM_HLBEG));
    rb_define_const(cMnogo, "UDM_PARAM_HLEND", INT2NUM(UDM_PARAM_HLEND));

    rb_define_const(cMnogo, "UDM_PARAM_SYNONYM", INT2NUM(UDM_PARAM_SYNONYM));
    rb_define_const(cMnogo, "UDM_PARAM_SEARCHD", INT2NUM(UDM_PARAM_SEARCHD));
    rb_define_const(cMnogo, "UDM_PARAM_STORED", INT2NUM(UDM_PARAM_STORED));
    rb_define_const(cMnogo, "UDM_PARAM_GROUPBYSITE", INT2NUM(UDM_PARAM_GROUPBYSITE));

    rb_define_const(cMnogo, "UDM_PARAM_QSTRING", INT2NUM(UDM_PARAM_QSTRING));
    rb_define_const(cMnogo, "UDM_PARAM_REMOTE_ADDR", INT2NUM(UDM_PARAM_REMOTE_ADDR));
    rb_define_const(cMnogo, "UDM_PARAM_QUERY", INT2NUM(UDM_PARAM_QUERY));
    rb_define_const(cMnogo, "UDM_PARAM_SITEID", INT2NUM(UDM_PARAM_SITEID));
    rb_define_const(cMnogo, "UDM_PARAM_DETECT_CLONES", INT2NUM(UDM_PARAM_DETECT_CLONES));

    /* udm_add_search_limit constants */
    rb_define_const(cMnogo, "UDM_LIMIT_CAT",	INT2NUM(UDM_LIMIT_CAT));
    rb_define_const(cMnogo, "UDM_LIMIT_URL",	INT2NUM(UDM_LIMIT_URL));
    rb_define_const(cMnogo, "UDM_LIMIT_TAG",	INT2NUM(UDM_LIMIT_TAG));
    rb_define_const(cMnogo, "UDM_LIMIT_LANG",	INT2NUM(UDM_LIMIT_LANG));
    rb_define_const(cMnogo, "UDM_LIMIT_DATE",	INT2NUM(UDM_LIMIT_DATE));
    rb_define_const(cMnogo, "UDM_LIMIT_TYPE",	INT2NUM(UDM_LIMIT_TYPE));

    /* udm_get_res_param constants */
    rb_define_const(cMnogo, "UDM_PARAM_FOUND",	INT2NUM(UDM_PARAM_FOUND));
    rb_define_const(cMnogo, "UDM_PARAM_NUM_ROWS",	INT2NUM(UDM_PARAM_NUM_ROWS));
    rb_define_const(cMnogo, "UDM_PARAM_WORDINFO",	INT2NUM(UDM_PARAM_WORDINFO));
    rb_define_const(cMnogo, "UDM_PARAM_WORDINFO_ALL",INT2NUM(UDM_PARAM_WORDINFO_ALL));
    rb_define_const(cMnogo, "UDM_PARAM_WORD_INFO",	INT2NUM(UDM_PARAM_WORDINFO));
    rb_define_const(cMnogo, "UDM_PARAM_SEARCHTIME",	INT2NUM(UDM_PARAM_SEARCHTIME));
    rb_define_const(cMnogo, "UDM_PARAM_SEARCH_TIME",INT2NUM(UDM_PARAM_SEARCHTIME));
    rb_define_const(cMnogo, "UDM_PARAM_FIRST_DOC",	INT2NUM(UDM_PARAM_FIRST_DOC));
    rb_define_const(cMnogo, "UDM_PARAM_LAST_DOC",	INT2NUM(UDM_PARAM_LAST_DOC));

    /* search modes */
    rb_define_const(cMnogo, "UDM_MODE_ALL",		INT2NUM(UDM_MODE_ALL));
    rb_define_const(cMnogo, "UDM_MODE_ANY",		INT2NUM(UDM_MODE_ANY));
    rb_define_const(cMnogo, "UDM_MODE_BOOL",	INT2NUM(UDM_MODE_BOOL));
    rb_define_const(cMnogo, "UDM_MODE_PHRASE",	INT2NUM(UDM_MODE_PHRASE));

    /* general params */
    rb_define_const(cMnogo, "UDM_ENABLED",	INT2NUM(UDM_ENABLED));
    rb_define_const(cMnogo, "UDM_DISABLED",	INT2NUM(UDM_DISABLED));

    /* ispell type params */
    rb_define_const(cMnogo, "UDM_ISPELL_TYPE_AFFIX",INT2NUM(UDM_ISPELL_TYPE_AFFIX));
    rb_define_const(cMnogo, "UDM_ISPELL_TYPE_SPELL",INT2NUM(UDM_ISPELL_TYPE_SPELL));
    rb_define_const(cMnogo, "UDM_ISPELL_TYPE_DB",	INT2NUM(UDM_ISPELL_TYPE_DB));
    rb_define_const(cMnogo, "UDM_ISPELL_TYPE_SERVER",INT2NUM(UDM_ISPELL_TYPE_SERVER));

    /* word match mode params */
    rb_define_const(cMnogo, "UDM_MATCH_WORD",	INT2NUM(UDM_MATCH_WORD));
    rb_define_const(cMnogo, "UDM_MATCH_BEGIN",	INT2NUM(UDM_MATCH_BEGIN));
    rb_define_const(cMnogo, "UDM_MATCH_SUBSTR",	INT2NUM(UDM_MATCH_SUBSTR));
    rb_define_const(cMnogo, "UDM_MATCH_END",	INT2NUM(UDM_MATCH_END));
}
