/*-
 * Copyright (c) 1998 fjoe <fjoe@iclub.nsu.ru>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: lang.c,v 1.10 1999/04/16 15:52:24 fjoe Exp $
 */

#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

#include "merc.h"
#include "lang.h"
#include "db.h"

/*----------------------------------------------------------------------------
 * main language support functions
 */

static const char*
word_form_lookup(lang_t *l, rulecl_t *rcl, const char *word, int fnum)
{
	rule_t *rule;
	char **p;
	char *q;
	static char buf[MAX_STRING_LENGTH];

	if (!fnum || IS_NULLSTR(word))
		return word;

	/*
	 * variable part(s) of word can be specified by tildes
	 * (simple recursion)
 	 */
	if ((q = strchr(word, '~'))) {
		char buf2[MAX_STRING_LENGTH];
		char buf3[MAX_STRING_LENGTH];
		char *r;

		/* copy prefix */
		strnzncpy(buf2, sizeof(buf2), word, q-word);

		/*
		 * translate infix, translation must be done
		 * before copying the result to buf[] because buf is
		 * static
		 */
		r = strchr(q+1, '~');
		if (!r)
			r = strchr(q+1, '\0');
		strnzncpy(buf3, sizeof(buf3), q+1, *r ? r-q-1 : r-q);
		strnzcat(buf2, sizeof(buf2),
			 word_form_lookup(l, rcl, buf3, fnum));

		/* translate the rest */
		if (!*r) {
			strnzcpy(buf, sizeof(buf), buf2);
			return buf;
		}

		if (strchr(r+1, '~')) {
			strnzcpy(buf3, sizeof(buf3),
				 word_form_lookup(l, rcl, r+1, fnum));
			q = buf3;
		}
		else
			q = r+1;

		strnzcpy(buf, sizeof(buf), buf2);
		strnzcat(buf, sizeof(buf), q);

		return buf;
	}

	/*
	 * explicit rule lookup
	 */
	if ((rule = erule_lookup(rcl, word)) == NULL) {
		rule_t e;

		/*
		 * implicit rule lookup
		 */
		if ((rule = irule_find(rcl, word)) == NULL)
			return word;

		/*
		 * implicit rule found - create explicit rule and use it
		 */
		erule_create(&e, rule, word);
		rule = erule_add(rcl, &e);
		SET_BIT(rcl->flags, RULES_EXPL_CHANGED);
	}

	if ((p = varr_get(&rule->f->v, fnum)) == NULL
	||  IS_NULLSTR(*p))
		return word;

	if (rule->arg <= 0 || **p != '-')
		return *p;

	strnzcpy(buf, sizeof(buf), word);
	strnzcpy(buf + rule->arg, sizeof(buf) - rule->arg, *p + 1);
	return buf;
}

const char *word_form(const char *word, int fnum, int lang, int rulecl)
{
	lang_t *l;

	if ((rulecl < 0 || rulecl >= MAX_RULECL)
	||  (l = varr_get(&langs, lang)) == NULL)
		return word;

	switch (rulecl) {
	case RULES_GENDER:
		fnum = (fnum + SEX_MAX - 1) % SEX_MAX;
		break;

	case RULES_QTY:
		fnum %= 100;
		if (fnum > 14)
			fnum %= 10;
	}

	return word_form_lookup(l, l->rules + rulecl, word, fnum);
}

/*----------------------------------------------------------------------------
 * vform_t functions
 */
vform_t *vform_new(void)
{
	vform_t *f = calloc(1, sizeof(*f));
	f->v.nsize = sizeof(char*);
	f->v.nstep = 4;
	f->ref = 1;
	return f;
}

vform_t *vform_dup(vform_t *f)
{
	f->ref++;
	return f;
}

void vform_free(vform_t *f)
{
	int i;

	if (--f->ref)
		return;
	for (i = 0; i < f->v.nused; i++)
		free_string(VARR_GET(&f->v, i));
	varr_free(&f->v);
	free(f);
}

void vform_add(vform_t *f, int fnum, const char *s)
{
	const char **p = varr_touch(&f->v, fnum);
	if (*p)
		free_string(*p);
	*p = str_dup(s);
}

void vform_del(vform_t *f, int fnum)
{
	const char **p = varr_get(&f->v, fnum);
	if (*p)
		free_string(*p);
	*p = NULL;
}

/*----------------------------------------------------------------------------
 * rule_t functions
 */

#define rulehash(s) hashistr(s, 16, MAX_RULE_HASH)

/* reverse order (otherwise word_del will not work) */
static int cmprule(const void *p1, const void *p2)
{
	return -str_cmp(((rule_t*) p1)->name, ((rule_t*) p2)->name);
}

void rule_init(rule_t *r)
{
	r->name = NULL;
	r->arg = 0;
	r->f = vform_new();
}

void rule_clear(rule_t *r)
{
	free_string(r->name);
	vform_free(r->f);
	r->name = NULL;
}

void erule_create(rule_t *expl, rule_t *impl, const char *word)
{
	rule_init(expl);
	expl->name = str_dup(word);
	expl->arg = strlen(word) + impl->arg;
	expl->f = vform_dup(impl->f);
}

/*----------------------------------------------------------------------------
 * implicit rules operations
 */
rule_t *irule_add(rulecl_t *rcl, rule_t *r)
{
	rule_t *rnew = varr_enew(&rcl->impl);
	*rnew = *r;
	return rnew;
}

rule_t *irule_insert(rulecl_t *rcl, size_t num, rule_t *r)
{
	rule_t *rnew;
	if (num > rcl->impl.nused)
		num = rcl->impl.nused;
	rnew = varr_insert(&rcl->impl, num);
	*rnew = *r;
	return rnew;
}

void irule_del(rulecl_t *rcl, rule_t *r)
{
	rule_clear(r);
	varr_del(&rcl->impl, r);
}

rule_t *irule_lookup(rulecl_t *rcl, const char *num)
{
	char *q;
	size_t i;

	if (IS_NULLSTR(num))
		return NULL;

	i = strtoul(num, &q, 0);
	if (*q || i >= rcl->impl.nused)
		return NULL;

	return VARR_GET(&rcl->impl, i);
}

rule_t *irule_find(rulecl_t *rcl, const char *word)
{
	int i;

	for (i = 0; i < rcl->impl.nused; i++) {
		rule_t *r = VARR_GET(&rcl->impl, i);
		if (r->name[0] == '-'
		&&  !strchr(word, ' ')
		&&  !str_suffix(r->name+1, word))
			return r;
	}

	return NULL;
}

/*----------------------------------------------------------------------------
 * explicit rules operations
 */
rule_t *erule_add(rulecl_t *rcl, rule_t *r)
{
	rule_t *rnew;
	varr *v;

	if (IS_NULLSTR(r->name))
		return NULL;

	v = rcl->expl + rulehash(r->name);
	if (varr_bsearch(v, r, cmprule))
		return NULL;

	rnew = varr_enew(v);
	*rnew = *r;
	varr_qsort(v, cmprule);
	return varr_bsearch(v, r, cmprule);
}

void erule_del(rulecl_t *rcl, rule_t *r)
{
	varr *v;

	v = rcl->expl + rulehash(r->name);
	rule_clear(r);
	varr_qsort(v, cmprule);
	v->nused--;
}

rule_t *erule_lookup(rulecl_t *rcl, const char *name)
{
	if (IS_NULLSTR(name))
		return NULL;
	return varr_bsearch(rcl->expl + rulehash(name), &name, cmprule);
}

/*----------------------------------------------------------------------------
 * rulecl_t functions
 */
static void rulecl_init(lang_t *l, int rulecl)
{
	int i;
	rulecl_t *rcl = l->rules + rulecl;

	rcl->rulecl = rulecl;
	for (i = 0; i < MAX_RULE_HASH; i++) {
		rcl->expl[i].nsize = sizeof(rule_t);
		rcl->expl[i].nstep = 4;
	}
	rcl->impl.nsize = sizeof(rule_t);
	rcl->impl.nstep = 4;
}

/*----------------------------------------------------------------------------
 * lang_t functions
 */
varr langs = { sizeof(lang_t), 2 };

lang_t *lang_new(void)
{
	int i;
	lang_t *l = varr_enew(&langs);

	l->slang_of = -1;
	l->vnum = langs.nused-1;

	for (i = 0; i < MAX_RULECL; i++)
		rulecl_init(l, i);

	return l;
}

int lang_lookup(const char *name)
{
	return lang_nlookup(name, strlen(name));
}

int lang_nlookup(const char *name, size_t len)
{
	int lang;

	if (IS_NULLSTR(name))
		return -1;

	for (lang = 0; lang < langs.nused; lang++) {
		lang_t *l = VARR_GET(&langs, lang);
		if (str_ncmp(l->name, name, len) == 0)
			return lang;
	}

	if (fBootDb)
		db_error("lang_lookup", "%s: unknown language", name);
	return -1;
}

