/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/*
 * JS reflection of the HTML FORM elements.
 *
 * Brendan Eich, 9/27/95
 */
#include "lm.h"
#include "lo_ele.h"
#include "netutils.h"
#include "layout.h"
#include "pa_tags.h"
#include "shist.h"

enum form_array_slot {
    FORM_ARRAY_LENGTH = -1
};

static JSPropertySpec form_array_props[] = {
    {lm_length_str, FORM_ARRAY_LENGTH,
		    JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT},
    {0}
};

extern JSClass lm_form_array_class;

PR_STATIC_CALLBACK(JSBool)
form_array_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSObjectArray *array;
    MochaDecoder *decoder;
    MWContext *context;
    jsint count, slot;
    lo_FormData *form_data;
    int32 active_layer_id;

    if (!JSVAL_IS_INT(id))
	return JS_TRUE;

    slot = JSVAL_TO_INT(id);

    array = JS_GetInstancePrivate(cx, obj, &lm_form_array_class, NULL);
    if (!array)
	return JS_TRUE;
    decoder = array->decoder;
    context = decoder->window_context;
    if (!context) return JS_TRUE;

    LO_LockLayout();
    switch (slot) {
      case FORM_ARRAY_LENGTH:
	active_layer_id = LM_GetActiveLayer(context);
	LM_SetActiveLayer(context, array->layer_id);
        count = LO_EnumerateForms(context, array->layer_id);
	LM_SetActiveLayer(context, active_layer_id);
        if (count > array->length)
	        array->length = count;
        *vp = INT_TO_JSVAL(array->length);
        break;

      default:
	if (slot < 0) {
	    /* Don't mess with user-defined or method properties. */
	    LO_UnlockLayout();
	    return JS_TRUE;
	}
        if (slot >= array->length)
	    array->length = slot + 1;
        /* NB: form IDs start at 1, not 0. */
        form_data = LO_GetFormDataByID(context, array->layer_id, slot + 1);
        if (form_data)
	    *vp = OBJECT_TO_JSVAL(LM_ReflectForm(context, form_data, NULL,
                                                 array->layer_id, 0));
        break;
    }
    LO_UnlockLayout();
    return JS_TRUE;
}

PR_STATIC_CALLBACK(void)
form_array_finalize(JSContext *cx, JSObject *obj)
{
    JSObjectArray *array;

    array = JS_GetPrivate(cx, obj);
    if (!array)
	return;
    DROP_BACK_COUNT(array->decoder);
    JS_free(cx, array);
}

JSClass lm_form_array_class = {
    "FormArray", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    form_array_getProperty, form_array_getProperty, JS_EnumerateStub,
    JS_ResolveStub, JS_ConvertStub, form_array_finalize
};

PR_STATIC_CALLBACK(JSBool)
FormArray(JSContext *cx, JSObject *obj,
	  uint argc, jsval *argv, jsval *rval)
{
    return JS_TRUE;
}

JSObject *
lm_GetFormArray(MochaDecoder *decoder, JSObject *document)
{
    JSObject *obj;
    JSContext *cx;
    JSObjectArray *array;
    JSDocument *doc;

    cx = decoder->js_context;
    doc = JS_GetPrivate(cx, document);
    if (!doc)
	return NULL;

    obj = doc->forms;
    if (obj)
	return obj;

    array = JS_malloc(cx, sizeof *array);
    if (!array)
	return NULL;
    XP_BZERO(array, sizeof *array);

    obj = JS_NewObject(cx, &lm_form_array_class, NULL, document);
    if (!obj || !JS_SetPrivate(cx, obj, array)) {
	JS_free(cx, array);
	return NULL;
    }

    if (!JS_DefineProperties(cx, obj, form_array_props))
	return NULL;

    array->decoder = HOLD_BACK_COUNT(decoder);
    array->layer_id = doc->layer_id;
    doc->forms = obj;
    return obj;
}

/*
 * Forms can be treated as arrays of their elements, so all named properties
 * have negative slot numbers < -1.
 */
enum form_slot {
    FORM_LENGTH         = -1,
    FORM_NAME           = -2,
    FORM_ELEMENTS       = -3,
    FORM_METHOD         = -4,
    FORM_ACTION         = -5,
    FORM_ENCODING       = -6,
    FORM_TARGET         = -7
};

static char form_action_str[] = "action";

static JSPropertySpec form_props[] = {
    {"length",          FORM_LENGTH,    JSPROP_ENUMERATE | JSPROP_READONLY},
    {"name",            FORM_NAME,      JSPROP_ENUMERATE},
    {"elements",        FORM_ELEMENTS,  JSPROP_ENUMERATE | JSPROP_READONLY},
    {"method",          FORM_METHOD,    JSPROP_ENUMERATE},
    {form_action_str,   FORM_ACTION,    JSPROP_ENUMERATE},
    {"encoding",        FORM_ENCODING,  JSPROP_ENUMERATE},
    {"target",          FORM_TARGET,    JSPROP_ENUMERATE},
    {0}
};

typedef struct JSForm {
    JSObjectArray   object_array;
    JSObject	    *form_object;
    uint32	    form_event_mask;
    int32           layer_id;
    intn            form_id;
    JSString        *name;
    PRHashTable     *form_element_map;   /* Map from element id to object */
} JSForm;

#define form_decoder	object_array.decoder
#define form_length     object_array.length

typedef struct FormMethodMap {
    char                *name;
    uint                code;
} FormMethodMap;

static FormMethodMap form_method_map[] = {
    {"get",             FORM_METHOD_GET},
    {"post",            FORM_METHOD_POST},
    {0}
};

static char *
form_method_name(uint code)
{
    FormMethodMap *mm;

    for (mm = form_method_map; mm->name; mm++)
	if (mm->code == code)
	    return mm->name;
    return "unknown";
}

static int
form_method_code(const char *name)
{
    FormMethodMap *mm;

    for (mm = form_method_map; mm->name; mm++)
	if (XP_STRCASECMP(mm->name, name) == 0)
	    return mm->code;
    return -1;
}

extern JSClass lm_form_class;

PR_STATIC_CALLBACK(JSBool)
form_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;
    jsint count;
    JSString *str;
    LO_Element **ele_list;
    LO_FormElementStruct *first_ele;
    uint first_index;
    jsint slot;

    if (!JSVAL_IS_INT(id))
	return JS_TRUE;

    slot = JSVAL_TO_INT(id);

    form = JS_GetInstancePrivate(cx, obj, &lm_form_class, NULL);
    if (!form)
	return JS_TRUE;
    context = form->form_decoder->window_context;
    if (!context) return JS_TRUE;

    LO_LockLayout();
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data)
	goto good;

    switch (slot) {
      case FORM_LENGTH:
        count = LO_EnumerateFormElements(context, form_data);
        if (count > form->form_length)
            form->form_length = count;
        *vp = INT_TO_JSVAL(form->form_length);
        goto good;

      case FORM_NAME:
	str = lm_LocalEncodingToStr(context, (char *)form_data->name);
        break;

      case FORM_ELEMENTS:
        *vp = OBJECT_TO_JSVAL(form->form_object);
        goto good;

      case FORM_METHOD:
        str = JS_NewStringCopyZ(cx, form_method_name(form_data->method));
        break;

      case FORM_ACTION:
        str = JS_NewStringCopyZ(cx, (char *)form_data->action);
        break;

      case FORM_ENCODING:
        str = JS_NewStringCopyZ(cx, (char *)form_data->encoding);
        break;

      case FORM_TARGET:
        str = JS_NewStringCopyZ(cx, (char *)form_data->window_target);
        break;

      default:
        if ((uint)slot >= (uint)form_data->form_ele_cnt) {
	    /* Don't mess with a user-defined or method property. */
	    goto good;
        }

	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	first_ele = (LO_FormElementStruct *)ele_list[0];
	first_index = (uint)first_ele->element_index;
	PA_UNLOCK(form_data->form_elements);

	*vp = OBJECT_TO_JSVAL(LM_ReflectFormElement(context, form->layer_id,
                                                    form->form_id,
                                                    first_index + slot, NULL));
        goto good;
    }

    LO_UnlockLayout();

    /* Common tail code for string-type properties. */
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;

good:
    LO_UnlockLayout();
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
form_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;
    const char *value;
    jsint slot;

    form = JS_GetInstancePrivate(cx, obj, &lm_form_class, NULL);
    if (!form)
	return JS_TRUE;
    context = form->form_decoder->window_context;
    if (!context)
	return JS_TRUE;

    if (!JSVAL_IS_INT(id))
	return JS_TRUE;

    slot = JSVAL_TO_INT(id);

    LO_LockLayout();
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data)
	goto good;

    if (!JSVAL_IS_STRING(*vp) &&
	!JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) {
	goto bad;
    }

    value = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
    switch (slot) {
      case FORM_METHOD:
	form_data->method = form_method_code(value);
	break;

      case FORM_ACTION:
	value = lm_CheckURL(cx, value, JS_TRUE);
	if (!value)
	    goto bad;
	if (!lm_SaveParamString(cx, &form_data->action, value))
	    goto bad;
	XP_FREE((char *)value);
	break;

      case FORM_ENCODING:
	if (!lm_SaveParamString(cx, &form_data->encoding, value))
	    goto bad;
	break;

      case FORM_TARGET:
	if (!lm_CheckWindowName(cx, value))
	    goto bad;
	if (!lm_SaveParamString(cx, &form_data->window_target, value))
	    goto bad;
	break;
    }

    LO_UnlockLayout();
    return form_getProperty(cx, obj, id, vp);
good:
    LO_UnlockLayout();
    return JS_TRUE;
bad:
    LO_UnlockLayout();
    return JS_FALSE;

}

PR_STATIC_CALLBACK(JSBool)
form_list_properties(JSContext *cx, JSObject *obj)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;

    form = JS_GetPrivate(cx, obj);
    if (!form)
	return JS_TRUE;
    context = form->form_decoder->window_context;
    if (!context)
	return JS_TRUE;

    LO_LockLayout();
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data) {
	LO_UnlockLayout();
	return JS_TRUE;
    }
    /* XXX should return FALSE on reflection error */
    (void) LO_EnumerateFormElements(context, form_data);
    LO_UnlockLayout();
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
form_resolve_name(JSContext *cx, JSObject *obj, jsval id)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;
    LO_Element **ele_list;
    LO_FormElementStruct *form_ele;
    lo_FormElementMinimalData *min_data;
    int32 i;
    const char * name;

    if (!JSVAL_IS_STRING(id))
	return JS_TRUE;

    name = JS_GetStringBytes(JSVAL_TO_STRING(id));

    form = JS_GetPrivate(cx, obj);
    if (!form)
	return JS_TRUE;
    context = form->form_decoder->window_context;
    if (!context)
	return JS_TRUE;

    LO_LockLayout();
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data) {
	LO_UnlockLayout();
	return JS_TRUE;
    }

    PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
    for (i = 0; i < form_data->form_ele_cnt; i++) {
	if (ele_list[i]->type != LO_FORM_ELE)
	    continue;
	form_ele = (LO_FormElementStruct *)ele_list[i];
	if (!form_ele->element_data)
	    continue;
	min_data = &form_ele->element_data->ele_minimal;
	if (min_data->name && XP_STRCMP((char *)min_data->name, name) == 0)
	    (void) LM_ReflectFormElement(context, form->layer_id,
                                         form->form_id,
					 form_ele->element_index, NULL);
    }

    PA_UNLOCK(form_data->form_elements);
    LO_UnlockLayout();
    return JS_TRUE;
}

PR_STATIC_CALLBACK(void)
form_finalize(JSContext *cx, JSObject *obj)
{
    JSForm *form;
    MochaDecoder *decoder;
    MWContext *context;
    lo_FormData *form_data;

    form = JS_GetPrivate(cx, obj);
    if (!form)
	return;
    decoder = form->form_decoder;
    context = decoder->window_context;
    if (context) {
	LO_LockLayout();
	form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
	if (form_data && form_data->mocha_object == obj)
	    form_data->mocha_object = NULL;
	LO_UnlockLayout();
    }
    DROP_BACK_COUNT(decoder);
    if (form->form_element_map)
        PR_HashTableDestroy(form->form_element_map);
    JS_free(cx, form);
}

JSClass lm_form_class = {
    "Form", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub, form_getProperty, form_setProperty,
    form_list_properties, form_resolve_name, JS_ConvertStub, form_finalize
};

PR_STATIC_CALLBACK(JSBool)
Form(JSContext *cx, JSObject *obj,
     uint argc, jsval *argv, jsval *rval)
{
    return JS_TRUE;
}

static JSBool
form_native_prolog(JSContext *cx, JSObject *obj, jsval *argv,
		   JSForm **formp, lo_FormData **form_datap)
{
    JSForm *form;
    MochaDecoder *decoder;
    lo_FormData *form_data;

    if (!JS_InstanceOf(cx, obj, &lm_form_class, argv))
        return JS_FALSE;
    form = JS_GetPrivate(cx, obj);
    if (!form) {
	*formp = NULL;
	*form_datap = NULL;
	return JS_TRUE;
    }
    decoder = form->form_decoder;
    form_data = decoder->window_context
		? LO_GetFormDataByID(decoder->window_context,
                                     form->layer_id,
                                     form->form_id)
		: 0;
    *formp = form;
    *form_datap = form_data;
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
form_reset(JSContext *cx, JSObject *obj,
	   uint argc, jsval *argv, jsval *rval)
{
    JSForm *form;
    lo_FormData *form_data;
    LO_Element **ele_list;

    if (!form_native_prolog(cx, obj, argv, &form, &form_data))
        return JS_FALSE;
    if (form_data && form_data->form_ele_cnt > 0) {
	/* There is no form LO_Element; use the first thing in the form. */
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	ET_lo_ResetForm(form->form_decoder->window_context, ele_list[0]);
	PA_UNLOCK(form_data->form_elements);
    }
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
form_submit(JSContext *cx, JSObject *obj,
	    uint argc, jsval *argv, jsval *rval)
{
    JSForm *form;
    lo_FormData *form_data;
    JSBool ok;
    MochaDecoder *decoder;
    MWContext *context;
    LO_Element **ele_list, *element;

    if (!form_native_prolog(cx, obj, argv, &form, &form_data))
        return JS_FALSE;
    if (!form_data)
        return JS_TRUE;

    switch (NET_URL_Type((const char *) form_data->action)) {
      case MAILTO_TYPE_URL:
      case NEWS_TYPE_URL:
	/* only OK if we are a signed script */
	ok = lm_CanAccessTarget(cx, JSTARGET_UNIVERSAL_SEND_MAIL);
	break;
      default:
	ok = JS_TRUE;
	break;
    }

    if (!ok) {
	/* XXX silently fail this mailto: or news: form post. */
	return JS_TRUE;
    }

    if (form_data->form_ele_cnt > 0) {
	decoder = form->form_decoder;
	context = decoder->window_context;
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	element = ele_list[0];
	PA_UNLOCK(form_data->form_elements);
	ET_fe_SubmitInputElement(context, element);

    }

    return JS_TRUE;
}

static JSFunctionSpec form_methods[] = {
    {"reset",	form_reset,	0},
    {"submit",	form_submit,	0},
    {0}
};

static JSBool
form_event(MWContext *context, LO_Element *element, JSEvent *event, char *method)
{
    lo_FormData *form_data;
    JSObject *obj;
    JSForm *form;
    JSBool ok;
    jsval rval;

    if (element->type != LO_FORM_ELE &&
	(element->type != LO_IMAGE || !element->lo_image.image_attr)) {
        LO_UnlockLayout();
	return JS_TRUE;
    }
    form_data = LO_GetFormDataByID(context,
				   (element->type == LO_IMAGE)
                                   ? element->lo_image.image_attr->layer_id
                                   : element->lo_form.layer_id,
				   (element->type == LO_IMAGE)
				   ? element->lo_image.image_attr->form_id
				   : element->lo_form.form_id);
    if (!form_data || !(obj = form_data->mocha_object)) {
        LO_UnlockLayout();
	return JS_TRUE;
    }

    
    LO_UnlockLayout();

    form = JS_GetPrivate(context->mocha_context, obj);
    if (!form) 
	return JS_TRUE;

    if (form->form_event_mask & event->type)
	return JS_TRUE;

    form->form_event_mask |= event->type;
    ok = lm_SendEvent(context, obj, event, &rval);
    form->form_event_mask &= ~event->type;

    if (ok && rval == JSVAL_FALSE)
	return JS_FALSE;
    return JS_TRUE;
}

JSBool
lm_SendOnReset(MWContext *context, JSEvent *event, LO_Element *element)
{
    return form_event(context, element, event, lm_onReset_str);
}

JSBool
lm_SendOnSubmit(MWContext *context, JSEvent *event, LO_Element *element)
{
    return form_event(context, element, event, lm_onSubmit_str);
}

#ifdef DEBUG_brendan
static char *
form_name(lo_FormData *form_data)
{
    static char buf[20];

    if (form_data->name)
	return (char *)form_data->name;
    PR_snprintf(buf, sizeof buf, "$form%d", form_data->id - 1);
    return buf;
}
#endif

JSObject *
LM_ReflectForm(MWContext *context, lo_FormData *form_data, PA_Tag * tag,
               int32 layer_id, uint index)
{
    JSObject *obj, *array_obj, *prototype, *document;
    MochaDecoder *decoder;
    JSContext *cx;
    JSForm *form;
    JSBool ok;
    lo_TopState *top_state;
    PRHashTable *map;

    /* if we got passed an index get the form data by index and don't
     *   trust the lo_FormData that was passed in
     */
    if (index)
	form_data = LO_GetFormDataByID(context, layer_id, index);

    if (!form_data)
	return NULL;

    obj = form_data->mocha_object;
    if (obj)
        return obj;

    decoder = LM_GetMochaDecoder(context);
    if (!decoder)
        return NULL;
    cx = decoder->js_context;

    top_state = lo_GetMochaTopState(context);
    if (top_state->resize_reload) {
        map = lm_GetIdToObjectMap(decoder);

        if (map)
            obj = (JSObject *)PR_HashTableLookup(map,
                             LM_GET_MAPPING_KEY(LM_FORMS, layer_id, index));
        if (obj) {
            form_data->mocha_object = obj;
            goto out;
        }
    }

    /* Get the document object that will hold this form */
    document = lm_GetDocumentFromLayerId(decoder, layer_id);
    if (!document)
        goto out;

    array_obj = lm_GetFormArray(decoder, document);
    if (!array_obj)
	goto out;

    prototype = decoder->form_prototype;
    if (!prototype) {
        prototype = JS_InitClass(cx, decoder->window_object,
				 decoder->event_receiver_prototype,
				 &lm_form_class,
				 Form, 0, form_props, form_methods,
				 NULL, NULL);
        if (!prototype)
	    goto out;
        decoder->form_prototype = prototype;
    }

    form = JS_malloc(cx, sizeof *form);
    if (!form)
	goto out;
    XP_BZERO(form, sizeof *form);

    obj = JS_NewObject(cx, &lm_form_class, prototype, document);
    if (!obj || !JS_SetPrivate(cx, obj, form)) {
        JS_free(cx, form);
        obj = NULL;
        goto out;
    }
    if (form_data->name) {
        ok = JS_DefineProperty(cx, document,
			       (char *) form_data->name,
			       OBJECT_TO_JSVAL(obj),
			       NULL, NULL,
			       JSPROP_ENUMERATE | JSPROP_READONLY);
        if (!ok) {
	    obj = NULL;
	    goto out;
        }
    }

    /* put it in the form array */
    if (!lm_AddObjectToArray(cx, array_obj, (char *) form_data->name,
			     index - 1, obj)) {
	obj = NULL;
	goto out;
    }

    /* Put it in the index to object hash table */
    map = lm_GetIdToObjectMap(decoder);
    if (map)
        PR_HashTableAdd(map, LM_GET_MAPPING_KEY(LM_FORMS, layer_id, index),
                        obj);

    form->form_decoder = HOLD_BACK_COUNT(decoder);
    form->form_element_map = NULL;
    form->form_object = obj;
    form->form_id = form_data->id;
    form->layer_id = layer_id;
    form_data->mocha_object = obj;

    /* see if there are any other attributes that we should be
     *  adding to this object
     */
    if(tag) {
        PA_Block onreset, onsubmit, id;

	/* don't hold the layout lock across compiles */
	LO_UnlockLayout();

        onreset  = lo_FetchParamValue(context, tag, PARAM_ONRESET);
        onsubmit = lo_FetchParamValue(context, tag, PARAM_ONSUBMIT);
        id = lo_FetchParamValue(context, tag, PARAM_ID);

	ok = JS_TRUE;
	if (onsubmit) {
	    ok = lm_CompileEventHandler(decoder, id, tag->data,
				        tag->newline_count, obj,
				        PARAM_ONSUBMIT, onsubmit);
	    PA_FREE(onsubmit);
	}
	if (onreset) {
	    ok &= lm_CompileEventHandler(decoder, id, tag->data,
					 tag->newline_count, obj,
				         PARAM_ONRESET, onreset);
	    PA_FREE(onreset);
	}
	if (!ok)
	    obj = NULL;
	if (id)
	    PA_FREE(id);
        
	LO_LockLayout();
    }

out:
    LM_PutMochaDecoder(decoder);
    return obj;
}

JSObject *
lm_GetFormObjectByID(MWContext * context, int32 layer_id, uint form_id)
{
    lo_FormData *form_data;

    form_data = LO_GetFormDataByID(context, layer_id, form_id);
    if (!form_data)
	return NULL;
    return form_data->mocha_object;
}

LO_FormElementStruct *
lm_GetFormElementByIndex(JSContext * cx, JSObject *form_obj, int32 index)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;

    if (!form_obj)
	return NULL;
    form = JS_GetPrivate(cx, form_obj);
    if (!form)
	return NULL;
    context = form->form_decoder->window_context;
    if (!context)
	return NULL;
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data)
	return NULL;
    return LO_GetFormElementByIndex(form_data, index);
}

PRIVATE JSBool
lm_normalize_element_index(lo_FormData *form_data, uint *index)
{
    LO_Element **ele_list;
    LO_FormElementStruct *first_ele;
    uint first_index;

    /* XXX confine this to laymocha.c where LO_GetFormByElementIndex() lives */
    if (form_data->form_ele_cnt == 0) {
	*index = 0;
    } else {
	ele_list = (LO_Element **) form_data->form_elements;
	first_ele = (LO_FormElementStruct *)ele_list[0];
	first_index = (uint)first_ele->element_index;
	XP_ASSERT(*index >= first_index);
	if (*index < first_index)
	    return JS_FALSE;
	*index -= first_index;
    }

    return JS_TRUE;
}

JSBool
lm_AddFormElement(JSContext *cx, JSObject *form_obj, JSObject *ele_obj,
		  char *name, uint index)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;

    form = JS_GetPrivate(cx, form_obj);
    if (!form)
	return JS_TRUE;
    context = form->form_decoder->window_context;
    if (!context)
	return JS_FALSE;
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data)
	return JS_FALSE;

    if (!lm_normalize_element_index(form_data, &index))
        return JS_FALSE;

    if (!form->form_element_map)
        form->form_element_map = PR_NewHashTable(LM_FORM_ELEMENT_MAP_SIZE,
                                                 lm_KeyHash,
                                                 PR_CompareValues,
                                                 PR_CompareValues,
                                                 NULL, NULL);
    if (!form->form_element_map)
        return JS_FALSE;

    PR_HashTableAdd(form->form_element_map, (void *)index, ele_obj);

    /* put it in the form elememt array */
    return (lm_AddObjectToArray(cx, form_obj, name, index, ele_obj));

}

JSObject *
lm_GetFormElementFromMapping(JSContext *cx, JSObject *form_obj, uint32 index)
{
    JSForm *form;
    MWContext *context;
    lo_FormData *form_data;

    form = JS_GetPrivate(cx, form_obj);
    if (!form || !form->form_element_map)
        return NULL;

    context = form->form_decoder->window_context;
    if (!context)
	return NULL;
    form_data = LO_GetFormDataByID(context, form->layer_id, form->form_id);
    if (!form_data)
	return NULL;

    /*
     * This converts element_id to index within this form. Need to do
     * this here, because it also happens when we put it into the hash
     * table.
     */
    if (!lm_normalize_element_index(form_data, (uint *)&index))
        return NULL;

    return (JSObject *)PR_HashTableLookup(form->form_element_map,
                                          (void *)index);
}


JSBool
lm_ReflectRadioButtonArray(MWContext *context, int32 layer_id, intn form_id,
                           const char *name, PA_Tag * tag)
{
    lo_FormData *form_data;
    JSBool ok;
    LO_Element **ele_list;
    int32 i, element_index;
    LO_FormElementStruct *form_ele;
    LO_FormElementData *data;

    form_data = LO_GetFormDataByID(context, layer_id, form_id);
    if (!form_data)
        return JS_FALSE;
    ok = JS_TRUE;
    for (i = 0; i < form_data->form_ele_cnt; i++) {
	/*
	 * Resample since the reflect call may release the layout
	 *   lock and the element list might get reallocated
	 */
        ele_list = (LO_Element **) form_data->form_elements;
        form_ele = (LO_FormElementStruct *)ele_list[i];

	/*
	 * If both the current element name and the passed in name are
	 *   non-NULL they must be equal.  If name is NULL reflect all
	 *   radio buttons
	 */
	data = form_ele->element_data;
        if (data &&
            ((data->ele_minimal.name && name) ?
	     !XP_STRCMP((char *)data->ele_minimal.name, name) :
	     data->type == FORM_TYPE_RADIO) &&
            form_ele->mocha_object == NULL) {

	    element_index = form_ele->element_index;
	    ok = (JSBool)
		 (LM_ReflectFormElement(context, layer_id, form_id,
					element_index,
					(tag->lo_data == (void *)element_index)
					? tag : NULL)
		  != NULL);
            if (!ok)
                break;
        }
    }
    return ok;
}
