/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * JavaScript Debugging support - Object support
 */

#include "jsd.h"

/*
* #define JSD_TRACE 1
*/

#ifdef JSD_TRACE
#define TRACEOBJ(jsdc, jsdobj, which) _traceObj(jsdc, jsdobj, which)

static char *
_describeObj(JSDContext* jsdc, JSDObject *jsdobj)
{
    return
        JS_smprintf("%0x new'd in %s at line %d using ctor %s in %s at line %d",
                    (int)jsdobj,
                    JSD_GetObjectNewURL(jsdc, jsdobj),
                    JSD_GetObjectNewLineNumber(jsdc, jsdobj),
                    JSD_GetObjectConstructorName(jsdc, jsdobj),
                    JSD_GetObjectConstructorURL(jsdc, jsdobj),
                    JSD_GetObjectConstructorLineNumber(jsdc, jsdobj));
}

static void
_traceObj(JSDContext* jsdc, JSDObject* jsdobj, int which)
{
    char* description;

    if( !jsdobj )
        return;

    description = _describeObj(jsdc, jsdobj);

    printf("%s : %s\n",
           which == 0 ? "new  " :
           which == 1 ? "final" :
                        "ctor ",
           description);
    if(description)
        free(description);
}
#else
#define TRACEOBJ(jsdc, jsdobj, which) ((void)0)
#endif /* JSD_TRACE */

#ifdef DEBUG
void JSD_ASSERT_VALID_OBJECT(JSDObject* jsdobj)
{
    JS_ASSERT(jsdobj);
    JS_ASSERT(!JS_CLIST_IS_EMPTY(&jsdobj->links));
    JS_ASSERT(jsdobj->obj);
}
#endif


static void
_destroyJSDObject(JSDContext* jsdc, JSDObject* jsdobj)
{
    JS_ASSERT(JSD_OBJECTS_LOCKED(jsdc));

    JS_REMOVE_LINK(&jsdobj->links);
    JS_HashTableRemove(jsdc->objectsTable, jsdobj->obj);

    if(jsdobj->newURL)
        jsd_DropAtom(jsdc, jsdobj->newURL);
    if(jsdobj->ctorURL)
        jsd_DropAtom(jsdc, jsdobj->ctorURL);
    if(jsdobj->ctorName)
        jsd_DropAtom(jsdc, jsdobj->ctorName);
    free(jsdobj);
}

static JSDObject*
_createJSDObject(JSDContext* jsdc, JSContext *cx, JSObject *obj)
{
    JSDObject* jsdobj;
    JSStackFrame* fp;
    JSStackFrame* iter = NULL;
    const char* newURL;
    jsbytecode* pc;

    JS_ASSERT(JSD_OBJECTS_LOCKED(jsdc));

    jsdobj = (JSDObject*) calloc(1, sizeof(JSDObject));
    if (jsdobj)
    {
        JS_INIT_CLIST(&jsdobj->links);
        JS_APPEND_LINK(&jsdobj->links, &jsdc->objectsList);
        jsdobj->obj = obj;
        JS_HashTableAdd(jsdc->objectsTable, obj, jsdobj);

        if (jsdc->flags & JSD_DISABLE_OBJECT_TRACE)
            return jsdobj;
        
        /* walk the stack to find js frame (if any) causing creation */
        while (NULL != (fp = JS_FrameIterator(cx, &iter)))
        {
            if( !JS_IsNativeFrame(cx, fp) )
            {
                JSScript* script = JS_GetFrameScript(cx, fp);
                if( !script )
                    continue;

                newURL = JS_GetScriptFilename(cx, script);
                if( newURL )
                    jsdobj->newURL = jsd_AddAtom(jsdc, newURL);

                pc = JS_GetFramePC(cx, fp);
                if( pc )
                    jsdobj->newLineno = JS_PCToLineNumber(cx, script, pc);

                break;
            }
        }
    }
    return jsdobj;
}

void
jsd_ObjectHook(JSContext *cx, JSObject *obj, JSBool isNew, void *closure)
{
    JSDObject* jsdobj;
    JSDContext* jsdc = (JSDContext*) closure;

    if( ! jsdc || ! jsdc->inited )
        return;

    JSD_LOCK_OBJECTS(jsdc);
    if(isNew)
    {
        jsdobj = _createJSDObject(jsdc, cx, obj);
        TRACEOBJ(jsdc, jsdobj, 0);
    }
    else
    {
        jsdobj = jsd_GetJSDObjectForJSObject(jsdc, obj);
        if( jsdobj )
        {
            TRACEOBJ(jsdc, jsdobj, 1);
            _destroyJSDObject(jsdc, jsdobj);
        }
    }
    JSD_UNLOCK_OBJECTS(jsdc);
}

void
jsd_Constructing(JSDContext* jsdc, JSContext *cx, JSObject *obj,
                 JSStackFrame *fp)
{
    JSDObject* jsdobj;
    JSScript* script;
    JSDScript* jsdscript;
    const char* ctorURL;
    const char* ctorName;

    JSD_LOCK_OBJECTS(jsdc);
    jsdobj = jsd_GetJSDObjectForJSObject(jsdc, obj);
    if( jsdobj && !jsdobj->ctorURL && !JS_IsNativeFrame(cx, fp) )
    {
        script = JS_GetFrameScript(cx, fp);
        if( script )
        {
            ctorURL = JS_GetScriptFilename(cx, script);
            if( ctorURL )
                jsdobj->ctorURL = jsd_AddAtom(jsdc, ctorURL);

            JSD_LOCK_SCRIPTS(jsdc);
            jsdscript = jsd_FindOrCreateJSDScript(jsdc, cx, script, fp);
            JSD_UNLOCK_SCRIPTS(jsdc);
            if( jsdscript )
            {
                ctorName = jsd_GetScriptFunctionName(jsdc, jsdscript);
                if( ctorName )
                    jsdobj->ctorName = jsd_AddAtom(jsdc, ctorName);
            }
            jsdobj->ctorLineno = JS_GetScriptBaseLineNumber(cx, script);
        }
    }
    TRACEOBJ(jsdc, jsdobj, 3);
    JSD_UNLOCK_OBJECTS(jsdc);
}

static JSHashNumber
_hash_root(const void *key)
{
    return ((JSHashNumber) key) >> 2; /* help lame MSVC1.5 on Win16 */
}

JSBool
jsd_InitObjectManager(JSDContext* jsdc)
{
    JS_INIT_CLIST(&jsdc->objectsList);
    jsdc->objectsTable = JS_NewHashTable(256, _hash_root,
                                         JS_CompareValues, JS_CompareValues,
                                         NULL, NULL);
    return (JSBool) jsdc->objectsTable;
}

void
jsd_DestroyObjectManager(JSDContext* jsdc)
{
    JSD_LOCK_OBJECTS(jsdc);
    while( !JS_CLIST_IS_EMPTY(&jsdc->objectsList) )
        _destroyJSDObject(jsdc, (JSDObject*)JS_NEXT_LINK(&jsdc->objectsList));
    JS_HashTableDestroy(jsdc->objectsTable);
    JSD_UNLOCK_OBJECTS(jsdc);
}

JSDObject*
jsd_IterateObjects(JSDContext* jsdc, JSDObject** iterp)
{
    JSDObject *jsdobj = *iterp;

    JS_ASSERT(JSD_OBJECTS_LOCKED(jsdc));

    if( !jsdobj )
        jsdobj = (JSDObject *)jsdc->objectsList.next;
    if( jsdobj == (JSDObject *)&jsdc->objectsList )
        return NULL;
    *iterp = (JSDObject*) jsdobj->links.next;
    return jsdobj;
}

JSObject*
jsd_GetWrappedObject(JSDContext* jsdc, JSDObject* jsdobj)
{
    return jsdobj->obj;
}

const char*
jsd_GetObjectNewURL(JSDContext* jsdc, JSDObject* jsdobj)
{
    if( jsdobj->newURL )
        return JSD_ATOM_TO_STRING(jsdobj->newURL);
    return NULL;
}

uintN
jsd_GetObjectNewLineNumber(JSDContext* jsdc, JSDObject* jsdobj)
{
    return jsdobj->newLineno;
}

const char*
jsd_GetObjectConstructorURL(JSDContext* jsdc, JSDObject* jsdobj)
{
    if( jsdobj->ctorURL )
        return JSD_ATOM_TO_STRING(jsdobj->ctorURL);
    return NULL;
}

uintN
jsd_GetObjectConstructorLineNumber(JSDContext* jsdc, JSDObject* jsdobj)
{
    return jsdobj->ctorLineno;
}

const char*
jsd_GetObjectConstructorName(JSDContext* jsdc, JSDObject* jsdobj)
{
    if( jsdobj->ctorName )
        return JSD_ATOM_TO_STRING(jsdobj->ctorName);
    return NULL;
}

JSDObject*
jsd_GetJSDObjectForJSObject(JSDContext* jsdc, JSObject* jsobj)
{
    JSDObject* jsdobj;

    JSD_LOCK_OBJECTS(jsdc);
    jsdobj = (JSDObject*) JS_HashTableLookup(jsdc->objectsTable, jsobj);
    JSD_UNLOCK_OBJECTS(jsdc);
    return jsdobj;
}

JSDObject*
jsd_GetObjectForValue(JSDContext* jsdc, JSDValue* jsdval)
{
    return jsd_GetJSDObjectForJSObject(jsdc, JSVAL_TO_OBJECT(jsdval->val));
}

JSDValue*
jsd_GetValueForObject(JSDContext* jsdc, JSDObject* jsdobj)
{
    return jsd_NewValue(jsdc, OBJECT_TO_JSVAL(jsdobj->obj));
}


